diff --git a/.github/workflows/backendtests.yml b/.github/workflows/backendtests.yml new file mode 100644 index 000000000..bc50f0ad4 --- /dev/null +++ b/.github/workflows/backendtests.yml @@ -0,0 +1,613 @@ +name: Backend Live Tests +on: [pull_request, workflow_dispatch] +concurrency: + group: duplicati-ci-livetests + cancel-in-progress: false +jobs: + check_secrets: + runs-on: ubuntu-latest + outputs: + s3_secrets_available: "${{ steps.check_s3.outputs.available }}" + dropbox_secrets_available: "${{ steps.check_dropbox.outputs.available }}" + azure_secrets_available: "${{ steps.check_azure.outputs.available }}" + testcontainers_secrets_available: "${{ steps.check_testcontainers.outputs.available }}" + google_secrets_available: "${{ steps.check_google.outputs.available }}" + pcloud_secrets_available: "${{ steps.check_pcloud.outputs.available }}" + b2_secrets_available: "${{ steps.check_b2.outputs.available }}" + box_secrets_available: "${{ steps.check_box.outputs.available }}" + filen_secrets_available: "${{ steps.check_filen.outputs.available }}" + steps: + - id: check_pcloud + name: Check pCloud secrets + shell: bash + run: | + echo "Starting pCloud secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_PCLOUD_SERVER }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_PCLOUD_TOKEN }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_PCLOUD_FOLDER }}" ]]; then + echo "All pCloud secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some pCloud secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "pCloud check completed" + - id: check_s3 + name: Check S3 secrets + shell: bash + run: | + echo "Starting S3 secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_S3_BUCKETNAME }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_S3_KEY }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_S3_REGION }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_S3_SECRET }}" ]]; then + echo "All S3 secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some S3 secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "S3 check completed" + - id: check_dropbox + name: Check Dropbox secrets + shell: bash + run: | + echo "Starting Dropbox secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_DROPBOX_FOLDER }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_DROPBOX_TOKEN }}" ]]; then + echo "All Dropbox secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some Dropbox secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Dropbox check completed" + - id: check_azure + name: Check Azure secrets + shell: bash + run: | + echo "Starting Azure secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_AZURE_ACCOUNTNAME }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_AZURE_ACCESSKEY }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_AZURE_CONTAINERNAME }}" ]]; then + echo "All Azure secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some Azure secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Azure check completed" + - id: check_testcontainers + name: Check TestContainers secret + shell: bash + run: | + echo "Starting TestContainers secrets check..." + if [[ -n "${{ secrets.TC_CLOUD_TOKEN }}" ]]; then + echo "All TestContainers secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing TestContainers token" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Testcontainers check completed" + + - id: check_google + name: Check Google secrets + shell: bash + run: | + echo "Starting Google secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_GOOGLEDRIVE_TOKEN }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_GOOGLEDRIVE_FOLDER }}" ]]; then + echo "All Google secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some Google secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Google check completed" + + - id: check_b2 + name: Check B2 secrets + shell: bash + run: | + echo "Starting B2 secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_B2_BUCKET }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_B2_FOLDER }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_B2_USERNAME }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_B2_PASSWORD }}" ]]; then + echo "All B2 secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some B2 secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "B2 check completed" + - id: check_box + name: Check Box.com secrets + shell: bash + run: | + echo "Starting Box.com secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_BOX_FOLDER }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_BOX_AUTHID }}" ]]; then + echo "All Box.com secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some Box.com secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Box.com check completed" + - id: check_filen + name: Check Filen.io secrets + shell: bash + run: | + echo "Starting Filen.io secrets check..." + if [[ -n "${{ secrets.TESTCREDENTIAL_FILEN_FOLDER }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_FILEN_USERNAME }}" ]] && \ + [[ -n "${{ secrets.TESTCREDENTIAL_FILEN_PASSWORD }}" ]]; then + echo "All Filen.io secrets found" + echo "available=true" >> $GITHUB_OUTPUT + else + echo "Missing some Filen.io secrets" + echo "available=false" >> $GITHUB_OUTPUT + fi + echo "Filen.io check completed" + - name: Debug Output + shell: bash + run: | + echo "S3 Available: ${{ steps.check_s3.outputs.available }}" + echo "Dropbox Available: ${{ steps.check_dropbox.outputs.available }}" + echo "TestContainers Available: ${{ steps.check_testcontainers.outputs.available }}" + echo "Google Available: ${{ steps.check_google.outputs.available }}" + echo "pCloud Available: ${{ steps.check_pcloud.outputs.available }}" + echo "Azure Available: ${{ steps.check_azure.outputs.available }}" + echo "B2 Available: ${{ steps.check_b2.outputs.available }}" + echo "Box.com Available: ${{ steps.check_box.outputs.available }}" + echo "Filen.io Available: ${{ steps.check_filen.outputs.available }}" + test_ftp: + needs: check_secrets + if: needs.check_secrets.outputs.testcontainers_secrets_available == 'true' + name: FTP Tests + runs-on: "${{ matrix.os }}" + strategy: + fail-fast: false + max-parallel: 1 + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Setup Testcontainers Cloud Client + uses: atomicjar/testcontainers-cloud-setup-action@v1 + with: + token: "${{ secrets.TC_CLOUD_TOKEN }}" + - name: Run FTP tests + env: + TC_CLOUD_TOKEN: "${{ secrets.TC_CLOUD_TOKEN }}" + run: >- + dotnet test --no-build --filter="ClassName~FtpTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + + test_pcloud: + needs: check_secrets + if: needs.check_secrets.outputs.pcloud_secrets_available == 'true' + name: pCloud Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run pCloud tests + env: + TESTCREDENTIAL_PCLOUD_SERVER: "${{ secrets.TESTCREDENTIAL_PCLOUD_SERVER }}" + TESTCREDENTIAL_PCLOUD_TOKEN: "${{ secrets.TESTCREDENTIAL_PCLOUD_TOKEN }}" + TESTCREDENTIAL_PCLOUD_FOLDER: "${{ secrets.TESTCREDENTIAL_PCLOUD_FOLDER }}" + run: >- + dotnet test --no-build --filter="ClassName~pCloudTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + + test_s3: + needs: check_secrets + if: needs.check_secrets.outputs.s3_secrets_available == 'true' + name: S3 Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run S3 tests + env: + TC_CLOUD_TOKEN: "${{ secrets.TC_CLOUD_TOKEN }}" + TESTCREDENTIAL_S3_BUCKETNAME: "${{ secrets.TESTCREDENTIAL_S3_BUCKETNAME }}" + TESTCREDENTIAL_S3_KEY: "${{ secrets.TESTCREDENTIAL_S3_KEY }}" + TESTCREDENTIAL_S3_REGION: "${{ secrets.TESTCREDENTIAL_S3_REGION }}" + TESTCREDENTIAL_S3_SECRET: "${{ secrets.TESTCREDENTIAL_S3_SECRET }}" + run: >- + dotnet test --no-build --filter="ClassName~S3Tests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_azure: + needs: check_secrets + if: needs.check_secrets.outputs.azure_secrets_available == 'true' + name: Azure Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run Azure tests + env: + READ_WRITE_TIMEOUT_SECONDS: 18000 + TESTCREDENTIAL_AZURE_ACCOUNTNAME: "${{ secrets.TESTCREDENTIAL_AZURE_ACCOUNTNAME }}" + TESTCREDENTIAL_AZURE_ACCESSKEY: "${{ secrets.TESTCREDENTIAL_AZURE_ACCESSKEY }}" + TESTCREDENTIAL_AZURE_CONTAINERNAME: "${{ secrets.TESTCREDENTIAL_AZURE_CONTAINERNAME }}" + run: >- + dotnet test --no-build --filter="ClassName~AzureTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_webdav: + needs: check_secrets + if: needs.check_secrets.outputs.testcontainers_secrets_available == 'true' + name: Webdav Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Setup Testcontainers Cloud Client + uses: atomicjar/testcontainers-cloud-setup-action@v1 + with: + token: "${{ secrets.TC_CLOUD_TOKEN }}" + - name: Run Webdav tests + env: + TC_CLOUD_TOKEN: "${{ secrets.TC_CLOUD_TOKEN }}" + run: >- + dotnet test --no-build --filter="ClassName~WebDavTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_dropbox: + needs: check_secrets + if: needs.check_secrets.outputs.dropbox_secrets_available == 'true' + name: DropBox Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run Dropbox tests + env: + TC_CLOUD_TOKEN: "${{ secrets.TC_CLOUD_TOKEN }}" + TESTCREDENTIAL_DROPBOX_FOLDER: "${{ secrets.TESTCREDENTIAL_DROPBOX_FOLDER }}" + TESTCREDENTIAL_DROPBOX_TOKEN: "${{ secrets.TESTCREDENTIAL_DROPBOX_TOKEN }}" + run: >- + dotnet test --no-build --filter="ClassName~DropBoxTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_ssh: + needs: check_secrets + if: needs.check_secrets.outputs.testcontainers_secrets_available == 'true' + name: SSH Tests + runs-on: "${{ matrix.os }}" + strategy: + fail-fast: false + max-parallel: 1 + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Setup Testcontainers Cloud Client + uses: atomicjar/testcontainers-cloud-setup-action@v1 + with: + token: "${{ secrets.TC_CLOUD_TOKEN }}" + - name: Run SSH tests + env: + TC_CLOUD_TOKEN: "${{ secrets.TC_CLOUD_TOKEN }}" + run: >- + dotnet test --no-build --filter="ClassName~SshTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_googledrive: + needs: check_secrets + if: needs.check_secrets.outputs.google_secrets_available == 'true' + name: Google Drive Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run Googledrive tests + env: + TESTCREDENTIAL_GOOGLEDRIVE_TOKEN: "${{ secrets.TESTCREDENTIAL_GOOGLEDRIVE_TOKEN }}" + TESTCREDENTIAL_GOOGLEDRIVE_FOLDER: "${{ secrets.TESTCREDENTIAL_GOOGLEDRIVE_FOLDER }}" + run: >- + dotnet test --no-build --filter="ClassName~GoogleDriveTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_cifs: + needs: check_secrets + if: needs.check_secrets.outputs.testcontainers_secrets_available == 'true' + name: CIFS Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run CIFS tests + run: >- + dotnet test --no-build --filter="ClassName~CIFSTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_b2: + needs: check_secrets + if: needs.check_secrets.outputs.b2_secrets_available == 'true' + name: Backblaze B2 Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run B2 tests + env: + TESTCREDENTIAL_B2_BUCKET: "${{ secrets.TESTCREDENTIAL_B2_BUCKET }}" + TESTCREDENTIAL_B2_FOLDER: "${{ secrets.TESTCREDENTIAL_B2_FOLDER }}" + TESTCREDENTIAL_B2_USERNAME: "${{ secrets.TESTCREDENTIAL_B2_USERNAME }}" + TESTCREDENTIAL_B2_PASSWORD: "${{ secrets.TESTCREDENTIAL_B2_PASSWORD }}" + run: >- + dotnet test --no-build --filter="ClassName~B2Tests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_box: + needs: check_secrets + if: needs.check_secrets.outputs.box_secrets_available == 'true' + name: Box.com Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run Box.com tests + env: + TESTCREDENTIAL_BOX_FOLDER: "${{ secrets.TESTCREDENTIAL_BOX_FOLDER }}" + TESTCREDENTIAL_BOX_AUTHID: "${{ secrets.TESTCREDENTIAL_BOX_AUTHID }}" + run: >- + dotnet test --no-build --filter="ClassName~Box.BoxTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + test_filen: + needs: check_secrets + if: needs.check_secrets.outputs.filen_secrets_available == 'true' + name: Filen.io Tests + runs-on: "${{ matrix.os }}" + strategy: + max-parallel: 1 + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + - macos-latest + steps: + - name: Set up .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + - name: Checkout source + uses: actions/checkout@v4 + - name: Restore NuGet dependencies + run: >- + dotnet restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Build project + run: >- + dotnet build --no-restore + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln + - name: Run Filen.io tests + env: + TESTCREDENTIAL_FILEN_FOLDER: "${{ secrets.TESTCREDENTIAL_FILEN_FOLDER }}" + TESTCREDENTIAL_FILEN_USERNAME: "${{ secrets.TESTCREDENTIAL_FILEN_USERNAME }}" + TESTCREDENTIAL_FILEN_PASSWORD: "${{ secrets.TESTCREDENTIAL_FILEN_PASSWORD }}" + run: >- + dotnet test --no-build --filter="ClassName~Filen.FilenTests" + --logger:"console;verbosity=detailed" + LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fae5c3ea5..0f0d4b5ed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,6 @@ name: Tests -on: [push, pull_request] +on: [pull_request, workflow_dispatch] jobs: unit_tests: @@ -29,9 +29,10 @@ jobs: - name: Run unit tests run: dotnet test --no-build --verbosity minimal Duplicati.sln - selenium: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Selenium - run: pipeline/selenium/test.sh + # Disabled, as a new test needs to be written for the new UI + # selenium: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - name: Selenium + # run: pipeline/selenium/test.sh diff --git a/.gitignore b/.gitignore index 377e8b38d..769557ef3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ changelog-news.txt .vs/ /publish/ ReleaseBuilder/build-temp/ -ReleaseBuilder/Resources/Windows/binfiles.wxs +ReleaseBuilder/Resources/Windows/TrayIcon/binfiles.wxs +ReleaseBuilder/Resources/Windows/Agent/binfiles.wxs diff --git a/.vscode/launch.json b/.vscode/launch.json index dc6546db2..bed893786 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,108 +1,126 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch TrayIcon executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.GUI.TrayIcon/bin/Debug/net8.0/Duplicati.GUI.TrayIcon", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch Server executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.Server/bin/Debug/net8.0/Duplicati.Server", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch CommandLine executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine/bin/Debug/net8.0/Duplicati.CommandLine", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch AutoUpdater executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.AutoUpdater/bin/Debug/net8.0/Duplicati.CommandLine.AutoUpdater", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch RecoveryTool executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.RecoveryTool/bin/Debug/net8.0/Duplicati.CommandLine.RecoveryTool", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch ServerUtil executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.ServerUtil/bin/Debug/net8.0/Duplicati.CommandLine.ServerUtil", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch BackendTool executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.BackendTool/bin/Debug/net8.0/Duplicati.CommandLine.BackendTool", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch Agent executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.Agent/bin/Debug/net8.0/Duplicati.Agent", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - { - "name": "Launch BackendTester executable", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.BackendTester/bin/Debug/net8.0/Duplicati.CommandLine.BackendTester", - "args": [], - "cwd": "${workspaceFolder}", - "stopAtEntry": false, - "console": "internalConsole" - }, - - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch TrayIcon executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.GUI.TrayIcon/bin/Debug/net8.0/Duplicati.GUI.TrayIcon", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch Server executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.Server/bin/Debug/net8.0/Duplicati.Server", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch CommandLine executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine/bin/Debug/net8.0/Duplicati.CommandLine", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch AutoUpdater executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.AutoUpdater/bin/Debug/net8.0/Duplicati.CommandLine.AutoUpdater", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch RecoveryTool executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.RecoveryTool/bin/Debug/net8.0/Duplicati.CommandLine.RecoveryTool", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch ServerUtil executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.ServerUtil/bin/Debug/net8.0/Duplicati.CommandLine.ServerUtil", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch BackendTool executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.BackendTool/bin/Debug/net8.0/Duplicati.CommandLine.BackendTool", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch Agent executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.Agent/bin/Debug/net8.0/Duplicati.Agent", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch BackendTester executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.BackendTester/bin/Debug/net8.0/Duplicati.CommandLine.BackendTester", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch SecretProvider executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.SecretTool/bin/Debug/net8.0/Duplicati.CommandLine.SecretTool", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + }, + { + "name": "Launch SourceTool executable", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Executables/net8/Duplicati.CommandLine.SourceTool/bin/Debug/net8.0/Duplicati.CommandLine.SourceTool", + "args": [] + } + ] +} diff --git a/BuildTools/LicenseUpdater/.vscode/launch.json b/BuildTools/LicenseUpdater/.vscode/launch.json new file mode 100644 index 000000000..43bc2782c --- /dev/null +++ b/BuildTools/LicenseUpdater/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "dotnet: build", + "program": "${workspaceFolder}/bin/Debug/net9.0/LicenseUpdater.dll", + "args": ["../.."], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "internalConsole" + } + + ] +} \ No newline at end of file diff --git a/BuildTools/LicenseUpdater/.vscode/tasks.json b/BuildTools/LicenseUpdater/.vscode/tasks.json new file mode 100644 index 000000000..b1c79f68e --- /dev/null +++ b/BuildTools/LicenseUpdater/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "dotnet", + "task": "build", + "group": "build", + "problemMatcher": [], + "label": "dotnet: build" + } + ] +} \ No newline at end of file diff --git a/BuildTools/LicenseUpdater/Fragments.cs b/BuildTools/LicenseUpdater/Fragments.cs index f53a635de..a0e334314 100755 --- a/BuildTools/LicenseUpdater/Fragments.cs +++ b/BuildTools/LicenseUpdater/Fragments.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -54,9 +54,15 @@ DEALINGS IN THE SOFTWARE.".Replace("YYYY", DateTime.UtcNow.Year.ToString()); public static Regex CS_MIT_MATCH = new Regex(@"(?//\s+Copyright \(C\) \d{4}, The Duplicati Team.+(?:copyright notice and this permission notice).+IN THE SOFTWARE\.)", RE_OPTS); + public static Regex CSPROJ_COPYRIGHT_MATCH = new Regex(@"(.*?)<\/Copyright>"); + public static string GetLicenseTextWithPrefixedLines(string linePrefix = "// ") => string.Join("\n", Fragments.NEW_LICENSE.Split("\n").Select(x => linePrefix + x).Append(string.Empty)); + private static readonly string LicenseTextWithPrefixedLines = GetLicenseTextWithPrefixedLines(); + + public static readonly string CopyrightText = $"Copyright © {DateTime.UtcNow.Year.ToString()} Team Duplicati, MIT license"; + public static bool MatchAndReplace(ref string data) { foreach (var m in new[] { CS_REGION_MATCH, CS_MIT_MATCH }) @@ -65,11 +71,16 @@ DEALINGS IN THE SOFTWARE.".Replace("YYYY", DateTime.UtcNow.Year.ToString()); if (match.Success && match.Groups["license"].Success) { var g = match.Groups["license"]; + var newLicense = LicenseTextWithPrefixedLines; + + if (data[(g.Index + g.Length)..].StartsWith('\n') || data[(g.Index + g.Length)..].StartsWith("\r\n")) + newLicense = newLicense.TrimEnd(); + data = string.Join( string.Empty, data.Substring(0, g.Index), - GetLicenseTextWithPrefixedLines(), + newLicense, data.Substring(g.Index + g.Length) ); diff --git a/BuildTools/LicenseUpdater/LicenseUpdater.csproj b/BuildTools/LicenseUpdater/LicenseUpdater.csproj index cc6ad9d95..0b3177572 100755 --- a/BuildTools/LicenseUpdater/LicenseUpdater.csproj +++ b/BuildTools/LicenseUpdater/LicenseUpdater.csproj @@ -6,7 +6,7 @@ license_upgrader enable enable - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/BuildTools/LicenseUpdater/Program.cs b/BuildTools/LicenseUpdater/Program.cs index 97603905a..c69cfbaaa 100755 --- a/BuildTools/LicenseUpdater/Program.cs +++ b/BuildTools/LicenseUpdater/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -30,7 +30,8 @@ if (!Directory.Exists(startpath)) throw new Exception($"Start path not found: {startpath}"); var target_extensions = new[] { - ".cs" + ".cs", + ".csproj" // ".html", // ".js", // ".css" @@ -72,6 +73,15 @@ foreach (var file in candidates) continue; } + if (string.Equals(Path.GetExtension(file), ".csproj", StringComparison.OrdinalIgnoreCase)) + { + data = Fragments.CSPROJ_COPYRIGHT_MATCH.Replace(data, $"{Fragments.CopyrightText}"); + File.WriteAllText(file, data); + // Console.WriteLine($"{file} updated!"); + continue; + } + + Console.WriteLine($"{file} skipped, no match"); } diff --git a/Duplicati.sln b/Duplicati.sln index 5237d5a54..63d105b35 100644 --- a/Duplicati.sln +++ b/Duplicati.sln @@ -154,6 +154,36 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Agent", "Executab EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.RemoteControl", "Duplicati\Library\RemoteControl\Duplicati.Library.RemoteControl.csproj", "{BB3A6E17-FC2E-42E3-B697-10D0972AFE81}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SecretProvider", "Duplicati\Library\SecretProvider\Duplicati.Library.SecretProvider.csproj", "{37A0B1B9-32F3-47C8-86E9-C10507C4ED4C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SecretTool", "Duplicati\CommandLine\SecretTool\Duplicati.CommandLine.SecretTool.csproj", "{D6417FD3-1645-4AB4-9F42-A1EC4A262B37}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SecretTool", "Executables\net8\Duplicati.CommandLine.SecretTool\Duplicati.CommandLine.SecretTool.csproj", "{E1D32EF7-368E-4148-9367-B328E78C871B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.pCloud", "Duplicati\Library\Backend\pCloud\Duplicati.Library.Backend.pCloud.csproj", "{CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Crashlog", "Duplicati\Library\Crashlog\Duplicati.Library.Crashlog.csproj", "{8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.SMB", "Duplicati\Library\Backend\SMB\Duplicati.Library.Backend.SMB.csproj", "{836E0557-B40C-4DC7-9A2A-5C062F9ACC6B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SourceProviders", "Duplicati\Library\SourceProviders\Duplicati.Library.SourceProviders.csproj", "{8A2C7A9F-3EC8-4DE5-A9FA-9E4BCF955EF3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceProviders", "SourceProviders", "{424CEF73-4984-430E-9C8B-E61CC0F22074}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.SourceProvider.Builtin", "Duplicati\Library\SourceProvider\Builtin\Duplicati.Library.SourceProvider.Builtin.csproj", "{6D9742A7-6F32-4571-8276-79716168B525}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{15388C37-9218-4818-972E-738EEA8F1602}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteSynchronization", "Tools\RemoteSynchronization\RemoteSynchronization.csproj", "{D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SyncTool", "Executables\net8\Duplicati.CommandLine.SyncTool\Duplicati.CommandLine.SyncTool.csproj", "{48D32674-0FE9-4407-B102-4AE46D93595F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SourceTool", "Duplicati\CommandLine\SourceTool\Duplicati.CommandLine.SourceTool.csproj", "{2873BCE2-2EA6-419D-BFFA-693FDB34926E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.SourceTool", "Executables\net8\Duplicati.CommandLine.SourceTool\Duplicati.CommandLine.SourceTool.csproj", "{4003BF90-6681-4155-9D06-639ED23FE3AB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Filen", "Duplicati\Library\Backend\Filen\Duplicati.Library.Backend.Filen.csproj", "{BD7F4E52-1898-4C4C-8722-FFE87B06718B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -400,6 +430,10 @@ Global {C1D4D665-23A3-4216-9CD1-D67AE9AAAA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU {C1D4D665-23A3-4216-9CD1-D67AE9AAAA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU {C1D4D665-23A3-4216-9CD1-D67AE9AAAA4C}.Release|Any CPU.Build.0 = Release|Any CPU + {4EB3DABC-D412-4C12-8876-41A1427A389E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EB3DABC-D412-4C12-8876-41A1427A389E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EB3DABC-D412-4C12-8876-41A1427A389E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EB3DABC-D412-4C12-8876-41A1427A389E}.Release|Any CPU.Build.0 = Release|Any CPU {FE6FD36C-E171-4599-8D55-62DA579C0864}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE6FD36C-E171-4599-8D55-62DA579C0864}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE6FD36C-E171-4599-8D55-62DA579C0864}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -432,6 +466,58 @@ Global {BB3A6E17-FC2E-42E3-B697-10D0972AFE81}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB3A6E17-FC2E-42E3-B697-10D0972AFE81}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB3A6E17-FC2E-42E3-B697-10D0972AFE81}.Release|Any CPU.Build.0 = Release|Any CPU + {37A0B1B9-32F3-47C8-86E9-C10507C4ED4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37A0B1B9-32F3-47C8-86E9-C10507C4ED4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37A0B1B9-32F3-47C8-86E9-C10507C4ED4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37A0B1B9-32F3-47C8-86E9-C10507C4ED4C}.Release|Any CPU.Build.0 = Release|Any CPU + {D6417FD3-1645-4AB4-9F42-A1EC4A262B37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6417FD3-1645-4AB4-9F42-A1EC4A262B37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D6417FD3-1645-4AB4-9F42-A1EC4A262B37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6417FD3-1645-4AB4-9F42-A1EC4A262B37}.Release|Any CPU.Build.0 = Release|Any CPU + {E1D32EF7-368E-4148-9367-B328E78C871B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1D32EF7-368E-4148-9367-B328E78C871B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1D32EF7-368E-4148-9367-B328E78C871B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1D32EF7-368E-4148-9367-B328E78C871B}.Release|Any CPU.Build.0 = Release|Any CPU + {CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE}.Release|Any CPU.Build.0 = Release|Any CPU + {8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332}.Release|Any CPU.Build.0 = Release|Any CPU + {836E0557-B40C-4DC7-9A2A-5C062F9ACC6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {836E0557-B40C-4DC7-9A2A-5C062F9ACC6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {836E0557-B40C-4DC7-9A2A-5C062F9ACC6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {836E0557-B40C-4DC7-9A2A-5C062F9ACC6B}.Release|Any CPU.Build.0 = Release|Any CPU + {8A2C7A9F-3EC8-4DE5-A9FA-9E4BCF955EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A2C7A9F-3EC8-4DE5-A9FA-9E4BCF955EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A2C7A9F-3EC8-4DE5-A9FA-9E4BCF955EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A2C7A9F-3EC8-4DE5-A9FA-9E4BCF955EF3}.Release|Any CPU.Build.0 = Release|Any CPU + {6D9742A7-6F32-4571-8276-79716168B525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D9742A7-6F32-4571-8276-79716168B525}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D9742A7-6F32-4571-8276-79716168B525}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D9742A7-6F32-4571-8276-79716168B525}.Release|Any CPU.Build.0 = Release|Any CPU + {D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D}.Release|Any CPU.Build.0 = Release|Any CPU + {48D32674-0FE9-4407-B102-4AE46D93595F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48D32674-0FE9-4407-B102-4AE46D93595F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48D32674-0FE9-4407-B102-4AE46D93595F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48D32674-0FE9-4407-B102-4AE46D93595F}.Release|Any CPU.Build.0 = Release|Any CPU + {2873BCE2-2EA6-419D-BFFA-693FDB34926E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2873BCE2-2EA6-419D-BFFA-693FDB34926E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2873BCE2-2EA6-419D-BFFA-693FDB34926E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2873BCE2-2EA6-419D-BFFA-693FDB34926E}.Release|Any CPU.Build.0 = Release|Any CPU + {4003BF90-6681-4155-9D06-639ED23FE3AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4003BF90-6681-4155-9D06-639ED23FE3AB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4003BF90-6681-4155-9D06-639ED23FE3AB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4003BF90-6681-4155-9D06-639ED23FE3AB}.Release|Any CPU.Build.0 = Release|Any CPU + {BD7F4E52-1898-4C4C-8722-FFE87B06718B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD7F4E52-1898-4C4C-8722-FFE87B06718B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD7F4E52-1898-4C4C-8722-FFE87B06718B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD7F4E52-1898-4C4C-8722-FFE87B06718B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -487,6 +573,17 @@ Global {3476A88B-4123-45F4-AC96-700B747367EB} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B} {0C1AD03C-89D8-4C47-82FF-25D5470055B4} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B} {A2957269-C11F-44CE-B355-CC9BA342295D} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} + {D6417FD3-1645-4AB4-9F42-A1EC4A262B37} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B} + {E1D32EF7-368E-4148-9367-B328E78C871B} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} + {CA1A0EE8-DFFF-42AF-B15D-EF538357A2DE} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} + {8ACA2736-3C69-4FA5-BCF9-5EDF50CAF332} = {566EBBDA-19A4-4056-A615-D901D57D2439} + {836E0557-B40C-4DC7-9A2A-5C062F9ACC6B} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} + {6D9742A7-6F32-4571-8276-79716168B525} = {424CEF73-4984-430E-9C8B-E61CC0F22074} + {D3A7E41E-279D-4E0A-A2DB-5E8002E02B6D} = {15388C37-9218-4818-972E-738EEA8F1602} + {48D32674-0FE9-4407-B102-4AE46D93595F} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} + {2873BCE2-2EA6-419D-BFFA-693FDB34926E} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B} + {4003BF90-6681-4155-9D06-639ED23FE3AB} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3} + {BD7F4E52-1898-4C4C-8722-FFE87B06718B} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8B40BAFE-D862-4397-9495-8F5EAF5CE80C} diff --git a/Duplicati/Agent/ConsoleLogDestination.cs b/Duplicati/Agent/ConsoleLogDestination.cs index 57566d891..42f15866e 100644 --- a/Duplicati/Agent/ConsoleLogDestination.cs +++ b/Duplicati/Agent/ConsoleLogDestination.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Agent/Duplicati.Agent.csproj b/Duplicati/Agent/Duplicati.Agent.csproj index a00d5580a..10139ce6b 100644 --- a/Duplicati/Agent/Duplicati.Agent.csproj +++ b/Duplicati/Agent/Duplicati.Agent.csproj @@ -5,7 +5,7 @@ enable enable Duplicati.Agent.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license Duplicati.Agent diff --git a/Duplicati/Agent/Program.cs b/Duplicati/Agent/Program.cs index 18ce5e398..c02ecbdd0 100644 --- a/Duplicati/Agent/Program.cs +++ b/Duplicati/Agent/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,15 +20,21 @@ // DEALINGS IN THE SOFTWARE. using System.CommandLine; +using System.CommandLine.Builder; using System.CommandLine.NamingConventionBinder; +using System.CommandLine.Parsing; using System.Reactive.Linq; using System.Security.Cryptography; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Encryption; +using Duplicati.Library.Interface; using Duplicati.Library.Logging; using Duplicati.Library.Main; using Duplicati.Library.RemoteControl; using Duplicati.Library.RestAPI; +using Duplicati.Library.Utility; using Duplicati.Server; -using Duplicati.WebserverCore.Abstractions; +using Duplicati.WebserverCore.Services; using Microsoft.Extensions.DependencyInjection; namespace Duplicati.Agent; @@ -40,20 +46,16 @@ public static class Program /// private static readonly string LogTag = Log.LogTagFromType(typeof(Program)); - /// - /// Create a random pre-shared key for the agent - /// - private static string? PreSharedKey = null; - - /// /// Commandline arguments for the agent /// - /// The server URL to connect to + /// The server URL to connect to /// The file to use for the agent settings + /// The passphrase for the agent settings file /// The interface to listen on for the webserver /// The port to listen on for the webserver /// The password for the webserver, random if none supplied + /// The encryption key for the database settings /// The Windows event log to write to /// Disable database encryption /// Reset the JWT configuration @@ -62,12 +64,19 @@ public static class Program /// Disable signin tokens for the webserver /// Enable ping-pong keepalive /// Disable the pre-shared key + /// Keep the webserver password + /// The secret provider to use + /// The secret provider cache level + /// The secret provider pattern private sealed record CommandLineArguments( - string RegistrationUrl, + string AgentRegistrationUrl, FileInfo AgentSettingsFile, + string? AgentSettingsFilePassphrase, + bool AgentRegisterOnly, string WebserviceListenInterface, string WebservicePort, string WebservicePassword, + string? SettingsEncryptionKey, string WindowsEventLog, bool DisableDbEncryption, bool WebserviceResetJwtConfig, @@ -75,7 +84,11 @@ public static class Program bool WebserviceApiOnly, bool WebserviceDisableSigninTokens, bool PingPongKeepalive, - bool DisablePreSharedKey + bool DisablePreSharedKey, + bool KeepWebservicePassword, + string? SecretProvider, + SecretProviderHelper.CachingLevel SecretProviderCache, + string SecretProviderPattern ); [STAThread] @@ -85,56 +98,191 @@ public static class Program var runcmd = new Command("run", "Runs the agent") { - new Option("registration-url", description: "The server URL to connect to", getDefaultValue: () => RegisterForRemote.DefaultRegisterationUrl), - new Option("agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)), - new Option("webservice-listen-interface", description: "The interface to listen on for the webserver", getDefaultValue: () => "loopback"), - new Option("webservice-port", description: "The port to listen on for the webserver", getDefaultValue: () => "8210"), - new Option("webservice-password", description: "The password for the webserver, random if none supplied", getDefaultValue: () => null), - new Option("windows-eventlog", description: "The Windows event log to write to", getDefaultValue: () => "Duplicati"), - new Option("disable-db-encryption", description: "Disable database encryption", getDefaultValue: () => false), - new Option("webservice-reset-jwt-config", description: "Reset the JWT configuration", getDefaultValue: () => true), - new Option("webservice-allowed-hostnames", description: "The allowed hostnames for the webserver", getDefaultValue: () => "127.0.0.1"), - new Option("webservice-api-only", description: "Only allow API access to the webserver", getDefaultValue: () => true), - new Option("webservice-disable-signin-tokens", description: "Disable signin tokens for the webserver", getDefaultValue: () => true), - new Option("ping-pong-keepalive", description: "Enable ping-pong keepalive", getDefaultValue: () => false) + new Option("--agent-registration-url", description: "The server URL to connect to", getDefaultValue: () => RegisterForRemote.DefaultRegisterationUrl), + new Option("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)), + new Option("--agent-settings-file-passphrase", description: "The passphrase for the agent settings file", getDefaultValue: () => null), + new Option("--agent-register-only", description: "Only register the agent, then exit", getDefaultValue: () => false), + new Option("--webservice-listen-interface", description: "The interface to listen on for the webserver", getDefaultValue: () => "loopback"), + new Option("--webservice-port", description: "The port to listen on for the webserver", getDefaultValue: () => "8210"), + new Option("--webservice-password", description: "The password for the webserver, not set, or set to \"random\", a random value is used.", getDefaultValue: () => null), + new Option("--settings-encryption-key", description: "The encryption key for the database settings", getDefaultValue: () => null), + new Option("--windows-eventlog", description: "The Windows event log to use as source.", getDefaultValue: () => ""), + new Option("--disable-db-encryption", description: "Disable database encryption", getDefaultValue: () => false), + new Option("--webservice-reset-jwt-config", description: "Reset the JWT configuration", getDefaultValue: () => true), + new Option("--webservice-allowed-hostnames", description: "The allowed hostnames for the webserver", getDefaultValue: () => "127.0.0.1"), + new Option("--webservice-api-only", description: "Only allow API access to the webserver", getDefaultValue: () => true), + new Option("--webservice-disable-signin-tokens", description: "Disable signin tokens for the webserver", getDefaultValue: () => true), + new Option("--ping-pong-keepalive", description: "Enable ping-pong keepalive", getDefaultValue: () => false), + new Option("--disable-pre-shared-key", description: "Disable the pre-shared key that prevents outside access to the webserver", getDefaultValue: () => false), + new Option("--keep-webservice-password", description: "Disables the random password assigned to the webserver on startup if no password is provided", getDefaultValue: () => false), + new Option("--secret-provider", description: "The secret provider to use", getDefaultValue: () => null), + new Option("--secret-provider-cache", description: "The secret provider cache level", getDefaultValue: () => SecretProviderHelper.CachingLevel.None), + new Option("--secret-provider-pattern", description: "The secret provider pattern", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN), + new Option($"--{DataFolderManager.PORTABLE_MODE_OPTION}", description: "Use portable mode for locating the database and storing configuration", getDefaultValue: () => DataFolderManager.PORTABLE_MODE), + new Option($"--{DataFolderManager.SERVER_DATAFOLDER_OPTION}", description: "The datafolder to use for locating the database and storing configuration", getDefaultValue: () => new DirectoryInfo(DataFolderManager.DATAFOLDER)), }; - runcmd.Handler = CommandHandler.Create(x => RunAgent(x, args)); + runcmd.Handler = CommandHandler.Create(RunAgent); - return new RootCommand("Duplicati Agent") + var clearCommand = new Command("clear", "Clears the agent settings") { - runcmd - }.InvokeAsync(args); + new Option("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)), + }; + clearCommand.Handler = CommandHandler.Create(ClearAgentSettings); + var registerCommand = new Command("register", "Registers the agent and exits") + { + new Argument("agent-registration-url", description: "The server URL to connect to", getDefaultValue: () => RegisterForRemote.DefaultRegisterationUrl) { Arity = ArgumentArity.ZeroOrOne }, + new Option("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)), + new Option("--agent-settings-file-passphrase", description: "The passphrase for the agent settings file", getDefaultValue: () => null), + new Option("--secret-provider", description: "The secret provider to use", getDefaultValue: () => null), + new Option("--secret-provider-pattern", description: "The secret provider pattern", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN), + }; + registerCommand.Handler = CommandHandler.Create(RegisterAgent); + + var rootcmd = new RootCommand("Duplicati Agent") + { + runcmd, + clearCommand, + registerCommand + }; + // rootcmd.Handler = CommandHandler.Create(RunAgent); + + return new CommandLineBuilder(rootcmd) + .UseDefaults() + .UseExceptionHandler((ex, context) => + { + if (ex is UserInformationException userInformationException) + { + Console.WriteLine("ErrorID: {0}", userInformationException.HelpID); + Console.WriteLine("Message: {0}", userInformationException.Message); + context.ExitCode = 2; + } + else + { + Console.WriteLine("Exception: " + ex); + context.ExitCode = 1; + } + }) + .UseAdditionalHelpAliases() + .Build() + .InvokeAsync(args); } + /// + /// Clears any local agent settings + /// + /// The file to clear + /// The exit code + private static Task ClearAgentSettings(FileInfo agentSettingsFile) + { + if (File.Exists(agentSettingsFile.FullName)) + File.Delete(agentSettingsFile.FullName); + return Task.FromResult(0); + } + + /// + /// Registers the agent with the remote server + /// + /// The URL to register the agent with + /// The file to save the settings to + /// The passphrase for the settings file + /// The secret provider to use + /// The secret provider pattern + /// + private static Task RegisterAgent(string agentRegistrationUrl, FileInfo agentSettingsFile, string? agentSettingsFilePassphrase, string? secretProvider, string secretProviderPattern) + => RunAgent(new CommandLineArguments( + AgentRegistrationUrl: string.IsNullOrWhiteSpace(agentRegistrationUrl) + ? RegisterForRemote.DefaultRegisterationUrl + : agentRegistrationUrl, + AgentSettingsFile: agentSettingsFile, + AgentSettingsFilePassphrase: agentSettingsFilePassphrase, + AgentRegisterOnly: true, + WebserviceListenInterface: "loopback", + WebservicePort: "8210", + WebservicePassword: null!, + SettingsEncryptionKey: null, + WindowsEventLog: "", + DisableDbEncryption: false, + WebserviceResetJwtConfig: false, + WebserviceAllowedHostnames: "", + WebserviceApiOnly: true, + WebserviceDisableSigninTokens: true, + PingPongKeepalive: false, + DisablePreSharedKey: false, + KeepWebservicePassword: false, + SecretProvider: secretProvider, + SecretProviderCache: SecretProviderHelper.CachingLevel.None, + SecretProviderPattern: secretProviderPattern + )); + /// /// Runs the agent /// /// The configuration from the commandline - /// All the arguments from the commandline /// The exit code - private static async Task RunAgent(CommandLineArguments agentConfig, string[] args) + private static async Task RunAgent(CommandLineArguments agentConfig) { - // Prevent access to the webserver interface from anything but the agent - if (string.IsNullOrWhiteSpace(agentConfig.WebservicePassword)) - agentConfig = agentConfig with { WebservicePassword = System.Security.Cryptography.RandomNumberGenerator.GetHexString(128) }; + // Read the environment variable for the encryption key + if (string.IsNullOrWhiteSpace(agentConfig.SettingsEncryptionKey)) + agentConfig = agentConfig with { SettingsEncryptionKey = Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME) }; + + // Apply the secret provider, if present + if (!string.IsNullOrWhiteSpace(agentConfig.SecretProvider)) + { + // We can set any string properties on the agent configuration + var agentProps = agentConfig.GetType().GetProperties() + .Where(x => x.CanRead && x.CanWrite) + .Where(x => !x.Name.StartsWith(nameof(CommandLineArguments.SecretProvider))) + .Where(x => x.PropertyType == typeof(string) || Nullable.GetUnderlyingType(x.PropertyType) == typeof(string)) + .ToDictionary(x => x.Name, x => x); + + // Grab potential options from the agent configuration + var options = agentProps.Select(x => (x.Key, Value: x.Value.GetValue(agentConfig)?.ToString())) + .Where(x => x.Value != null) + .ToDictionary(x => x.Key, x => x.Value); + + // Patch with the secret provider options + options["secret-provider"] = agentConfig.SecretProvider; + options["secret-provider-cache"] = agentConfig.SecretProviderCache.ToString(); + options["secret-provider-pattern"] = agentConfig.SecretProviderPattern; + + FIXMEGlobal.SecretProvider = await SecretProviderHelper.ApplySecretProviderAsync([], [], options, TempFolder.SystemTempPath, null, CancellationToken.None); + + // Apply the secret provider to the agent configuration + foreach (var prop in agentProps) + if (options.TryGetValue(prop.Key, out string? value)) + prop.Value.SetValue(agentConfig, value); + } + + if (agentConfig.KeepWebservicePassword) + { + // Check for conflicting options, require no password + if (!string.IsNullOrWhiteSpace(agentConfig.WebservicePassword)) + throw new UserInformationException("Cannot use --keep-webservice-password with a provided password", "KeepWebservicePasswordWithPassword"); + } + else + { + // Prevent access to the webserver interface from anything but the agent + if (string.Equals("random", agentConfig.WebservicePassword, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(agentConfig.WebservicePassword)) + agentConfig = agentConfig with { WebservicePassword = System.Security.Cryptography.RandomNumberGenerator.GetHexString(128) }; + } // Set the pre-shared key for the agent if (!agentConfig.DisablePreSharedKey) - WebserverCore.Middlewares.PreSharedKeyFilter.PreSharedKey = PreSharedKey = RandomNumberGenerator.GetHexString(128); + WebserverCore.Middlewares.PreSharedKeyFilter.PreSharedKey = RandomNumberGenerator.GetHexString(128); using var cts = new CancellationTokenSource(); var target = new ControllerMultiLogTarget(new ConsoleLogDestination(), LogMessageType.Information, null); using (Log.StartScope(target)) { - if (OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindows() && !string.IsNullOrWhiteSpace(agentConfig.WindowsEventLog)) { if (!WindowsEventLogSource.SourceExists(agentConfig.WindowsEventLog)) { - Console.WriteLine("The Windows event log source does not exist, creating it"); + Console.WriteLine($"The Windows event log {agentConfig.WindowsEventLog} does not exist, creating it"); try { WindowsEventLogSource.CreateEventSource(agentConfig.WindowsEventLog); } - catch (Exception ex) { Console.WriteLine("Failed to create the Windows event log source: {0}", ex.Message); } + catch (Exception ex) { Console.WriteLine($"Failed to create the Windows event log {agentConfig.WindowsEventLog}: {ex.Message}"); } } if (WindowsEventLogSource.SourceExists(agentConfig.WindowsEventLog)) @@ -143,18 +291,26 @@ public static class Program Log.WriteMessage(LogMessageType.Information, LogTag, "AgentStarting", "Starting agent"); - var settings = await Register(agentConfig.RegistrationUrl, agentConfig.AgentSettingsFile.FullName, cts.Token); + var settings = await Register(agentConfig.AgentRegistrationUrl, agentConfig.AgentSettingsFile.FullName, agentConfig.AgentSettingsFilePassphrase, cts.Token); + + if (agentConfig.AgentRegisterOnly) + { + Log.WriteMessage(LogMessageType.Information, LogTag, "ClientRegistered", "Client registered, exiting"); + return 0; + } var t = await Task.WhenAny( - StartLocalServer(agentConfig, settings, args, cts.Token), + Task.Run(() => StartLocalServer(agentConfig, settings, cts.Token)), KeepRemoteConnection.Start( settings.ServerUrl, settings.JWT, settings.CertificateUrl, settings.ServerCertificates, cts.Token, - ReKey, - (m) => OnMessage(m, agentConfig) + OnConnect, + m => ReKey(m, agentConfig), + OnControl, + OnMessage ) ); @@ -164,43 +320,48 @@ public static class Program } } - private static async Task OnMessage(KeepRemoteConnection.CommandMessage message, CommandLineArguments agentConfig) + private static Task> OnConnect(Dictionary metadata) + => FIXMEGlobal.Provider.GetRequiredService().OnConnect(metadata); + + private static Task OnControl(KeepRemoteConnection.ControlMessage message) + => FIXMEGlobal.Provider.GetRequiredService().OnControl(message); + + private static Task OnMessage(KeepRemoteConnection.CommandMessage message) + => FIXMEGlobal.Provider.GetRequiredService().OnMessage(message); + + + private static Task ReKey(ClaimedClientData keydata, CommandLineArguments agentConfig) { - Log.WriteMessage(LogMessageType.Information, LogTag, "OnMessage", "Received message: {0}", message); - - var provider = FIXMEGlobal.Provider.GetRequiredService(); - var token = provider.CreateAccessToken("agent", provider.TemporaryFamilyId, TimeSpan.FromMinutes(2)); - using var httpClient = FIXMEGlobal.Provider.GetRequiredService().CreateClient(); - httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - httpClient.BaseAddress = new Uri($"http://127.0.0.1:{agentConfig.WebservicePort}"); - - if (!string.IsNullOrWhiteSpace(WebserverCore.Middlewares.PreSharedKeyFilter.PreSharedKey)) - httpClient.DefaultRequestHeaders.Add(WebserverCore.Middlewares.PreSharedKeyFilter.HeaderName, PreSharedKey); - - await message.Handle(httpClient); - } - - private static Task ReKey(ClaimedClientData keydata) - { - Log.WriteMessage(LogMessageType.Information, LogTag, "ReKey", "Rekeying the settings"); - var settings = Settings.Load(); + // ReKey is handled here because we store the config outside of the database + Log.WriteMessage(LogMessageType.Verbose, LogTag, "ReKey", "Rekeying the settings"); + var settings = Settings.Load(agentConfig.AgentSettingsFile.FullName, agentConfig.AgentSettingsFilePassphrase); if (!string.IsNullOrWhiteSpace(keydata.JWT) && settings.JWT != keydata.JWT) settings = settings with { JWT = keydata.JWT }; if (keydata.ServerCertificates != null && keydata.ServerCertificates.Any()) settings = settings with { ServerCertificates = keydata.ServerCertificates }; - if (!string.IsNullOrWhiteSpace(keydata.LocalEncryptionKey) && settings.SettingsEncryptionKey != keydata.LocalEncryptionKey) + // Only allow re-keying if the settings encryption key is not set + if (!FIXMEGlobal.SettingsEncryptionKeyProvidedExternally) { - // Log.WriteMessage(LogMessageType.Information, LogTag, "ReKey", "Changing the local settings encryption key"); - // TODO: Implement changing the database encryption key - // FIXMEGlobal.Provider.GetRequiredService().ChangeDbKey(keydata.LocalEncryptionKey); - // settings = settings with { SettingsEncryptionKey = keydata.LocalEncryptionKey }; + if (!string.IsNullOrWhiteSpace(keydata.LocalEncryptionKey) && settings.SettingsEncryptionKey != keydata.LocalEncryptionKey) + { + // Log.WriteMessage(LogMessageType.Verbose, LogTag, "ReKey", "Changing the local settings encryption key"); + // TODO: Implement changing the database encryption key + // FIXMEGlobal.Provider.GetRequiredService().ChangeDbKey(keydata.LocalEncryptionKey); + // settings = settings with { SettingsEncryptionKey = keydata.LocalEncryptionKey }; + } } settings.Save(); return Task.CompletedTask; } + /// + /// Runs the server, restarting on crashes + /// + /// The commandline arguments passed to the server + /// The cancellation token to use for the process + /// An awaitable task private static async Task RunServer(string[] args, CancellationToken cancellationToken) { cancellationToken.Register(() => @@ -237,11 +398,12 @@ public static class Program /// /// The URL to register the machine with /// The file to save the settings to + /// The passphrase for the settings file /// The cancellation token to use for the process /// The settings for the machine - private static async Task Register(string registrationUrl, string settingsFile, CancellationToken cancellationToken) + private static async Task Register(string registrationUrl, string settingsFile, string? settingsPasshrase, CancellationToken cancellationToken) { - var settings = Settings.Load(settingsFile); + var settings = Settings.Load(settingsFile, settingsPasshrase); if (string.IsNullOrWhiteSpace(settings.JWT)) { Log.WriteMessage(LogMessageType.Information, LogTag, "ClientNoJWT", "No JWT found in settings, starting in registration mode"); @@ -269,7 +431,8 @@ public static class Program JWT = claimedClientData.JWT, ServerUrl = claimedClientData.ServerUrl, SettingsEncryptionKey = claimedClientData.LocalEncryptionKey, - ServerCertificates = claimedClientData.ServerCertificates + ServerCertificates = claimedClientData.ServerCertificates, + CertificateUrl = claimedClientData.CertificateUrl }; settings.Save(); @@ -284,29 +447,48 @@ public static class Program /// /// The agent configuration /// The settings for the agent - /// The commandline arguments /// The cancellation token that stops the server /// An awaitable task - private static async Task StartLocalServer(CommandLineArguments agentConfig, Settings settings, string[] args, CancellationToken cancellationToken) + private static async Task StartLocalServer(CommandLineArguments agentConfig, Settings settings, CancellationToken cancellationToken) { // TODO: Look into pipes for Kestrel to prevent network access - // Lock down the instance, reset tokens and password - args = args.Concat([ - $"--webservice-listen-interface={agentConfig.WebserviceListenInterface}", - $"--webservice-password={agentConfig.WebservicePassword}", - OperatingSystem.IsWindows() ? $"--windows-eventlog={agentConfig.WindowsEventLog}" : "", - $"--webservice-port={agentConfig.WebservicePort}", - $"--webservice-reset-jwt-config={agentConfig.WebserviceResetJwtConfig}", - $"--webservice-allowed-hostnames={agentConfig.WebserviceAllowedHostnames}", - $"--webservice-api-only={agentConfig.WebserviceApiOnly}", - $"--webservice-disable-signin-tokens={agentConfig.WebserviceDisableSigninTokens}" - ]) - .Where(x => !string.IsNullOrWhiteSpace(x)) - .ToArray(); + string? settingsEncryptionKey = null; - if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SETTINGS_ENCRYPTION_KEY"))) - Environment.SetEnvironmentVariable("SETTINGS_ENCRYPTION_KEY", settings.SettingsEncryptionKey); + // Prefer the local key for the settings encryption + if (!string.IsNullOrWhiteSpace(agentConfig.SettingsEncryptionKey)) + settingsEncryptionKey = agentConfig.SettingsEncryptionKey; + // Otherwise use server provided key + else if (!string.IsNullOrWhiteSpace(settings.SettingsEncryptionKey)) + settingsEncryptionKey = settings.SettingsEncryptionKey; + + // Helper method to support empty arguments + static string EncodeOption(string option, string? value) + { + if (string.IsNullOrWhiteSpace(value)) + return ""; + + return $"{option}={value}"; + } + + // Lock down the instance, reset tokens and password, + // and forward relevant settings from agent to the webserver + var args = new[] { + EncodeOption("--webservice-listen-interface", agentConfig.WebserviceListenInterface), + EncodeOption("--webservice-password", agentConfig.WebservicePassword), + EncodeOption("--webservice-port", agentConfig.WebservicePort), + EncodeOption("--webservice-reset-jwt-config", agentConfig.WebserviceResetJwtConfig.ToString()), + EncodeOption("--webservice-allowed-hostnames", agentConfig.WebserviceAllowedHostnames), + EncodeOption("--webservice-api-only", agentConfig.WebserviceApiOnly.ToString()), + EncodeOption("--webservice-disable-signin-tokens", agentConfig.WebserviceDisableSigninTokens.ToString()), + EncodeOption("--disable-db-encryption", agentConfig.DisableDbEncryption.ToString()), + EncodeOption("--settings-encryption-key", settingsEncryptionKey), + } + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToArray(); + + // Set the global origin + FIXMEGlobal.Origin = "Agent"; // Start the server await RunServer(args, cancellationToken); diff --git a/Duplicati/Agent/Settings.cs b/Duplicati/Agent/Settings.cs index adf1c94b4..079fd626f 100644 --- a/Duplicati/Agent/Settings.cs +++ b/Duplicati/Agent/Settings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,7 +21,9 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; +using System.Text.Json.Serialization; using Duplicati.Library.Encryption; +using Duplicati.Library.Interface; using Duplicati.Library.Main; using Duplicati.Library.RemoteControl; @@ -35,13 +37,15 @@ namespace Duplicati.Agent; /// The URL of the server /// The encryption key for the local settings /// The server certificates +/// The encryption key to use for agent settings public sealed record Settings( string Filename, string JWT, string ServerUrl, string CertificateUrl, string? SettingsEncryptionKey, - IEnumerable ServerCertificates + IEnumerable ServerCertificates, + EncryptedFieldHelper.KeyInstance? Key ) { /// @@ -58,23 +62,50 @@ public sealed record Settings( /// private static readonly EncryptedFieldHelper.KeyInstance? DefaultKey = null; + /// + /// Shared JSON serialization options + /// + private static readonly JsonSerializerOptions JsonOptions = new() + { + WriteIndented = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }, + PropertyNameCaseInsensitive = true + }; + /// /// Loads the settings from a file /// /// The path to the file /// The loaded settings - public static Settings Load(string? path = null) + public static Settings Load(string path, string? key) { - path ??= DefaultSettingsFile; - var tmp = JsonSerializer.Deserialize(File.ReadAllText(path)) - ?? new Settings(path, string.Empty, string.Empty, string.Empty, string.Empty, Array.Empty()); + var keyInstance = string.IsNullOrWhiteSpace(key) + ? DefaultKey + : EncryptedFieldHelper.KeyInstance.CreateKey(key); - return tmp with + var tmp = (File.Exists(path) ? JsonSerializer.Deserialize(File.ReadAllText(path), JsonOptions) : null) + ?? new Settings(path, string.Empty, string.Empty, string.Empty, string.Empty, Array.Empty(), null); + + try { - Filename = path, - JWT = EncryptedFieldHelper.Decrypt(tmp.JWT, DefaultKey), - SettingsEncryptionKey = EncryptedFieldHelper.Decrypt(tmp.SettingsEncryptionKey, DefaultKey) - }; + return tmp with + { + Filename = path, + JWT = EncryptedFieldHelper.Decrypt(tmp.JWT, keyInstance), + SettingsEncryptionKey = EncryptedFieldHelper.Decrypt(tmp.SettingsEncryptionKey, keyInstance), + Key = keyInstance + }; + } + catch (SettingsEncryptionKeyMismatchException sek) + { + throw new UserInformationException("Invalid settings key provided", "InvalidAgentSettingsKey", sek); + } + catch (SettingsEncryptionKeyMissingException sek) + { + throw new UserInformationException("Settings file is encrypted but key is missing", "AgentSettingsKeyMissing", sek); + } } /// @@ -82,16 +113,16 @@ public sealed record Settings( /// public void Save() { - var tmp = DefaultKey != null && !DefaultKey.IsBlacklisted - ? this - : this with + var tmp = this with { Key = null, Filename = null! }; + tmp = Key == null || Key.IsBlacklisted + ? tmp + : tmp with { - Filename = null!, - JWT = EncryptFieldIfPossible(JWT), - SettingsEncryptionKey = EncryptFieldIfPossible(SettingsEncryptionKey) + JWT = EncryptFieldIfPossible(JWT, Key), + SettingsEncryptionKey = EncryptFieldIfPossible(SettingsEncryptionKey, Key) }; - File.WriteAllText(Filename, JsonSerializer.Serialize(tmp)); + File.WriteAllText(Filename, JsonSerializer.Serialize(tmp, JsonOptions)); } /// @@ -100,15 +131,15 @@ public sealed record Settings( /// The value to encrypt /// The encrypted value [return: NotNullIfNotNull("value")] - private static string? EncryptFieldIfPossible(string? value) + private static string? EncryptFieldIfPossible(string? value, EncryptedFieldHelper.KeyInstance? key) { - if (DefaultKey == null || DefaultKey.IsBlacklisted) + if (key == null || key.IsBlacklisted) return value; if (value == null) return null; if (string.IsNullOrWhiteSpace(value)) return string.Empty; - return EncryptedFieldHelper.Encrypt(value, DefaultKey); + return EncryptedFieldHelper.Encrypt(value, key); } /// @@ -117,10 +148,8 @@ public sealed record Settings( /// The path to the settings file private static string GetSettingsPath() { - var folder = DatabaseLocator.GetDefaultStorageFolderWithDebugSupport(DefaultSettingsFilename); - if (!Directory.Exists(folder)) - Directory.CreateDirectory(folder); - + // Ideally, this should use DataFolderManager.DATAFOLDER, but we cannot due to backwards compatibility + var folder = Library.AutoUpdater.DataFolderLocator.GetDefaultStorageFolder(DefaultSettingsFilename, true); return Path.Combine(folder, DefaultSettingsFilename); } } diff --git a/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj index f71a978e1..3cb37241b 100644 --- a/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj +++ b/Duplicati/CommandLine/BackendTester/Duplicati.CommandLine.BackendTester.csproj @@ -4,7 +4,7 @@ net8.0 A backend debugging tool for Duplicati Duplicati.CommandLine.BackendTester.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs b/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs index 448d86e0c..820746465 100644 --- a/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs +++ b/Duplicati/CommandLine/BackendTester/NonSeekableStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/CommandLine/BackendTester/Program.cs b/Duplicati/CommandLine/BackendTester/Program.cs index aa5a21bc9..f76392dc1 100644 --- a/Duplicati/CommandLine/BackendTester/Program.cs +++ b/Duplicati/CommandLine/BackendTester/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,24 +21,20 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using Duplicati.Library.Interface; using System.Linq; using System.Globalization; using System.Threading; using Duplicati.Library.Utility; +using Duplicati.StreamUtil; +using System.Threading.Tasks; namespace Duplicati.CommandLine.BackendTester { public class Program { - /// - /// Used to maintain a reference to initialized system settings. - /// -#pragma warning disable CS0414 // The private field `Duplicati.CommandLine.BackendTester.Program.SystemSettings' is assigned but its value is never used - private static IDisposable SystemSettings; -#pragma warning restore CS0414 // The private field `Duplicati.CommandLine.BackendTester.Program.SystemSettings' is assigned but its value is never used - class TempFile { public readonly string remotefilename; @@ -88,12 +84,12 @@ namespace Duplicati.CommandLine.BackendTester List args = new List(_args); Dictionary options = Library.Utility.CommandLineParser.ExtractOptions(args); - if (args.Count != 1 || String.Equals(args[0], "help", StringComparison.OrdinalIgnoreCase) || args[0] == "?") + if (args.Count != 1 || HelpOptionExtensions.IsArgumentAnyHelpString(args)) { Console.WriteLine("Usage: ://:@"); Console.WriteLine("Example: ftp://user:pass@server/folder"); Console.WriteLine(); - Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); + Console.WriteLine($"Supported backends: {string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)}"); Console.WriteLine(); List lines = new List(); @@ -109,7 +105,7 @@ namespace Duplicati.CommandLine.BackendTester if (options.ContainsKey("tempdir") && !string.IsNullOrEmpty(options["tempdir"])) Library.Utility.SystemContextSettings.DefaultTempPath = options["tempdir"]; - SystemSettings = Duplicati.Library.Utility.SystemContextSettings.StartSession(); + using var SystemSettings = Duplicati.Library.Utility.SystemContextSettings.StartSession(); if (!options.ContainsKey("auth_password") && !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("AUTH_PASSWORD"))) options["auth_password"] = System.Environment.GetEnvironmentVariable("AUTH_PASSWORD"); @@ -123,16 +119,17 @@ namespace Duplicati.CommandLine.BackendTester for (int i = 0; i < reruns; i++) { - Console.WriteLine("Starting run no {0}", i); + Console.WriteLine($"{DateTime.Now.ToShortDateString()} {DateTime.Now.ToLongTimeString()}"); + Console.WriteLine($"{LogTimeStamp}Starting run no {i}"); if (!Run(args, options, i == 0)) return 1; } - Console.WriteLine("Unittest complete!"); + Console.WriteLine($"{LogTimeStamp}Unittest complete!"); return 0; } catch (Exception ex) { - Console.WriteLine("Unittest failed: " + ex); + Console.WriteLine($"{LogTimeStamp}Unittest failed: {ex}"); } return 1; @@ -140,32 +137,31 @@ namespace Duplicati.CommandLine.BackendTester static bool Run(List args, Dictionary options, bool first) { - Library.Interface.IBackend backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); + bool failAfterFinished = false; + var backend = Library.DynamicLoader.BackendLoader.GetBackend(args[0], options); if (backend == null) { Console.WriteLine("Unsupported backend"); Console.WriteLine(); - Console.WriteLine("Supported backends: " + string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)); + Console.WriteLine($"Supported backends: {string.Join(",", Duplicati.Library.DynamicLoader.BackendLoader.Keys)}"); return false; } - string allowedChars = ValidFilenameChars; + var allowedChars = ValidFilenameChars; if (options.ContainsKey("extended-chars")) { allowedChars += String.IsNullOrEmpty(options["extended-chars"]) ? ExtendedChars : options["extended-chars"]; } - bool autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); + var autoCreateFolders = Library.Utility.Utility.ParseBoolOption(options, "auto-create-folder"); - string disabledModulesValue; - string enabledModulesValue; - options.TryGetValue("enable-module", out enabledModulesValue); - options.TryGetValue("disable-module", out disabledModulesValue); - string[] enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower(CultureInfo.InvariantCulture).Split(','); - string[] disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower(CultureInfo.InvariantCulture).Split(','); + options.TryGetValue("enable-module", out var enabledModulesValue); + options.TryGetValue("disable-module", out var disabledModulesValue); + var enabledModules = enabledModulesValue == null ? new string[0] : enabledModulesValue.Trim().ToLower(CultureInfo.InvariantCulture).Split(','); + var disabledModules = disabledModulesValue == null ? new string[0] : disabledModulesValue.Trim().ToLower(CultureInfo.InvariantCulture).Split(','); - List loadedModules = new List(); - foreach (Library.Interface.IGenericModule m in Library.DynamicLoader.GenericLoader.Modules) + var loadedModules = new List(); + foreach (var m in Library.DynamicLoader.GenericLoader.Modules) if (!disabledModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase) && (m.LoadAsDefault || enabledModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase))) { m.Configure(options); @@ -178,7 +174,7 @@ namespace Duplicati.CommandLine.BackendTester try { backend.TestAsync(CancellationToken.None).Await(); - curlist = backend.List(); + curlist = backend.ListAsync(CancellationToken.None).ToBlockingEnumerable().ToList(); } catch (FolderMissingException) { @@ -187,11 +183,11 @@ namespace Duplicati.CommandLine.BackendTester try { backend.CreateFolderAsync(CancellationToken.None).Await(); - curlist = backend.List(); + curlist = backend.ListAsync(CancellationToken.None).ToBlockingEnumerable().ToList(); } catch (Exception ex) { - Console.WriteLine("Autocreate folder failed with message: " + ex.Message); + Console.WriteLine($"{LogTimeStamp}Autocreate folder failed with message: {ex.Message}"); } } @@ -205,49 +201,74 @@ namespace Duplicati.CommandLine.BackendTester if (Library.Utility.Utility.ParseBoolOption(options, "auto-clean") && first) if (Library.Utility.Utility.ParseBoolOption(options, "force")) { - Console.WriteLine("Auto clean, removing file: {0}", fe.Name); + Console.WriteLine($"{LogTimeStamp}Auto clean, removing file: {fe.Name}"); backend.DeleteAsync(fe.Name, CancellationToken.None).Await(); continue; } else Console.WriteLine("Specify the --force flag to actually delete files"); - Console.WriteLine("*** Remote folder is not empty, aborting"); + var fileCount = curlist.Where(x => !x.IsFolder).Count(); + var filenames = curlist.Where(x => !x.IsFolder).Select(x => x.Name).Take(10).ToList(); + Console.WriteLine($"{LogTimeStamp}*** Remote folder contains {fileCount} file(s), aborting"); + Console.WriteLine($"{LogTimeStamp}*** First {filenames.Count} file(s): {Environment.NewLine}{string.Join(Environment.NewLine, filenames)}"); + if (fileCount > filenames.Count) + Console.WriteLine($"{LogTimeStamp}*** ... and {fileCount - filenames.Count} more file(s)"); return false; } - int number_of_files = 10; - int min_file_size = 1024; - int max_file_size = 1024 * 1024 * 50; - int min_filename_size = 5; - int max_filename_size = 80; - bool disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); - bool skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); - bool trimFilenameSpaces = Library.Utility.Utility.ParseBoolOption(options, "trim-filename-spaces"); + var number_of_files = 10; + var min_file_size = 1024; + var max_file_size = 1024 * 1024 * 50; + var min_filename_size = 5; + var max_filename_size = 80; + var disableStreaming = Library.Utility.Utility.ParseBoolOption(options, "disable-streaming-transfers"); + var skipOverwriteTest = Library.Utility.Utility.ParseBoolOption(options, "skip-overwrite-test"); + var trimFilenameSpaces = Library.Utility.Utility.ParseBoolOption(options, "trim-filename-spaces"); + var waitAfterUpload = TimeSpan.Zero; + var waitAfterDelete = TimeSpan.Zero; - long throttleUpload = 0; - if (options.TryGetValue("throttle-upload", out string throttleUploadString)) + var throttleUpload = 0L; + if (options.TryGetValue("throttle-upload", out var throttleUploadString)) { if (!(backend is IStreamingBackend) || disableStreaming) { - Console.WriteLine("Warning: Throttling is only supported in this tool on streaming backends"); + Console.WriteLine($"{LogTimeStamp}Warning: Throttling is only supported in this tool on streaming backends"); } throttleUpload = Duplicati.Library.Utility.Sizeparser.ParseSize(throttleUploadString, "kb"); } - long throttleDownload = 0; - if (options.TryGetValue("throttle-download", out string throttleDownloadString)) + var throttleDownload = 0L; + if (options.TryGetValue("throttle-download", out var throttleDownloadString)) { if (!(backend is IStreamingBackend) || disableStreaming) { - Console.WriteLine("Warning: Throttling is only supported in this tool on streaming backends"); + Console.WriteLine($"{LogTimeStamp}Warning: Throttling is only supported in this tool on streaming backends"); } throttleDownload = Duplicati.Library.Utility.Sizeparser.ParseSize(throttleDownloadString, "kb"); } + int readWriteTimeout = (Environment.GetEnvironmentVariable("READ_WRITE_TIMEOUT_SECONDS") is { } timeout + && int.TryParse(timeout, out var seconds) + && seconds == -1) + ? Timeout.Infinite + : Environment.GetEnvironmentVariable("READ_WRITE_TIMEOUT_SECONDS") is { } timeoutRetry + && int.TryParse(timeoutRetry, out var secondsRetry) + ? (int)TimeSpan.FromSeconds(secondsRetry).TotalMilliseconds + : (int)TimeSpan.FromSeconds(60).TotalMilliseconds; + + Console.WriteLine($"{LogTimeStamp}Read Write Timeout set to {(readWriteTimeout == Timeout.Infinite ? "infinite" : readWriteTimeout + " ms")}"); + + // Allow overriding the timeout for the backend here, even if timeouts are disabled + if (options.TryGetValue("read-write-timeout", out var readWriteTimeoutString)) + readWriteTimeout = (int)Timeparser.ParseTimeSpan(readWriteTimeoutString).TotalMilliseconds; + + if (readWriteTimeout <= 0) + readWriteTimeout = Timeout.Infinite; + if (options.ContainsKey("number-of-files")) number_of_files = int.Parse(options["number-of-files"]); if (options.ContainsKey("min-file-size")) @@ -260,46 +281,51 @@ namespace Duplicati.CommandLine.BackendTester if (options.ContainsKey("max-filename-length")) max_filename_size = int.Parse(options["max-filename-length"]); - Random rnd = new Random(); - System.Security.Cryptography.SHA256 sha = System.Security.Cryptography.SHA256.Create(); + if (options.ContainsKey("wait-after-upload")) + waitAfterUpload = Timeparser.ParseTimeSpan(options["wait-after-upload"]); + if (options.ContainsKey("wait-after-delete")) + waitAfterDelete = Timeparser.ParseTimeSpan(options["wait-after-delete"]); + + var rnd = new Random(); + var sha = System.Security.Cryptography.SHA256.Create(); //Create random files - using (Library.Utility.TempFolder tf = new Duplicati.Library.Utility.TempFolder()) + using (var tf = new Duplicati.Library.Utility.TempFolder()) { - List files = new List(); + var files = new List(); for (int i = 0; i < number_of_files; i++) { - string filename = CreateRandomRemoteFileName(min_filename_size, max_filename_size, allowedChars, trimFilenameSpaces, rnd); + var filename = CreateRandomRemoteFileName(min_filename_size, max_filename_size, allowedChars, trimFilenameSpaces, rnd); - string localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); + var localfilename = CreateRandomFile(tf, i, min_file_size, max_file_size, rnd); //Calculate local hash and length - using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (var fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) files.Add(new TempFile(filename, localfilename, sha.ComputeHash(fs), fs.Length)); } byte[] dummyFileHash = null; if (!skipOverwriteTest) { - Console.WriteLine("Uploading wrong files ..."); - using (Library.Utility.TempFile dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) + Console.WriteLine($"{LogTimeStamp}Uploading wrong files ..."); + using (var dummy = Library.Utility.TempFile.WrapExistingFile(CreateRandomFile(tf, files.Count, 1024, 2048, rnd))) { - using (System.IO.FileStream fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (var fs = new System.IO.FileStream(dummy, System.IO.FileMode.Open, System.IO.FileAccess.Read)) dummyFileHash = sha.ComputeHash(fs); //Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards //We upload entry 0 twice just to try to freak any internal cache list - Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming, throttleUpload); - Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming, throttleUpload); - Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming, throttleUpload); + Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming, throttleUpload, readWriteTimeout); + Uploadfile(dummy, 0, files[0].remotefilename, backend, disableStreaming, throttleUpload, readWriteTimeout); + Uploadfile(dummy, files.Count - 1, files[files.Count - 1].remotefilename, backend, disableStreaming, throttleUpload, readWriteTimeout); } } - Console.WriteLine("Uploading files ..."); + Console.WriteLine($"{LogTimeStamp}Uploading files ..."); for (int i = 0; i < files.Count; i++) - Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming, throttleUpload); + Uploadfile(files[i].localfilename, i, files[i].remotefilename, backend, disableStreaming, throttleUpload, readWriteTimeout); TempFile originalRenamedFile = null; string renamedFileNewName = null; @@ -311,65 +337,77 @@ namespace Duplicati.CommandLine.BackendTester renamedFileNewName = CreateRandomRemoteFileName(min_filename_size, max_filename_size, allowedChars, trimFilenameSpaces, rnd); - Console.WriteLine("Renaming file {0} from {1} to {2}", renameIndex, originalRenamedFile.remotefilename, renamedFileNewName); + Console.WriteLine($"{LogTimeStamp}Renaming file {renameIndex} from {originalRenamedFile.remotefilename} to {renamedFileNewName}"); renameEnabledBackend.RenameAsync(originalRenamedFile.remotefilename, renamedFileNewName, CancellationToken.None).Await(); files[renameIndex] = new TempFile(renamedFileNewName, originalRenamedFile.localfilename, originalRenamedFile.hash, originalRenamedFile.length); } - Console.WriteLine("Verifying file list ..."); + if (waitAfterUpload > TimeSpan.Zero) + { + Console.WriteLine($"{LogTimeStamp}Waiting {waitAfterUpload} after upload"); + Thread.Sleep(waitAfterUpload); + } - curlist = backend.List(); - foreach (Library.Interface.IFileEntry fe in curlist) + Console.WriteLine($"{LogTimeStamp}Verifying file list ..."); + + curlist = backend.ListAsync(CancellationToken.None).ToBlockingEnumerable().ToList(); + foreach (var fe in curlist) if (!fe.IsFolder) { bool found = false; - foreach (TempFile tx in files) + for (var i = 0; i < files.Count; i++) + { + var tx = files[i]; if (tx.remotefilename == fe.Name) { if (tx.found) - Console.WriteLine("*** File with name {0} was found more than once", tx.remotefilename); + Console.WriteLine($"{LogTimeStamp}*** File {i}) with name {tx.remotefilename} was found more than once"); found = true; tx.found = true; if (fe.Size > 0 && tx.length != fe.Size) - Console.WriteLine("*** File with name {0} has size {1} but the size was reported as {2}", tx.remotefilename, tx.length, fe.Size); + Console.WriteLine($"{LogTimeStamp}*** File {i} with name {tx.remotefilename} has size {tx.length} but the size was reported as {fe.Size}"); break; } + } if (!found) if (originalRenamedFile != null && renamedFileNewName != null && originalRenamedFile.remotefilename == fe.Name) { - Console.WriteLine("*** File with name {0} was found on server but was supposed to have been renamed to {1}!", fe.Name, renamedFileNewName); + Console.WriteLine($"{LogTimeStamp}*** File with name {fe.Name} was found on server but was supposed to have been renamed to {renamedFileNewName}!"); } else { - Console.WriteLine("*** File with name {0} was found on server but not uploaded!", fe.Name); + Console.WriteLine($"{LogTimeStamp}*** File with name {fe.Name} was found on server but not uploaded!"); } } - foreach (TempFile tx in files) - if (!tx.found) - Console.WriteLine("*** File with name {0} was uploaded but not found afterwards", tx.remotefilename); + for (var i = 0; i < files.Count; i++) + if (!files[i].found) + Console.WriteLine($"{LogTimeStamp}*** File {i} with name {files[i].remotefilename} was uploaded but not found afterwards"); - Console.WriteLine("Downloading files"); + Console.WriteLine($"{LogTimeStamp}Downloading files"); for (int i = 0; i < files.Count; i++) { - using (Duplicati.Library.Utility.TempFile cf = new Duplicati.Library.Utility.TempFile()) + using (var cf = new Duplicati.Library.Utility.TempFile()) { Exception e = null; - Console.Write("Downloading file {0} ... ", i); + Console.Write($"{LogTimeStamp}Downloading file {i} ... "); + + var sw = Stopwatch.StartNew(); try { if (backend is IStreamingBackend streamingBackend && !disableStreaming) { - using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) - using (Library.Utility.ThrottledStream ts = new Library.Utility.ThrottledStream(fs, throttleDownload, throttleDownload)) - using (NonSeekableStream nss = new NonSeekableStream(ts)) - streamingBackend.GetAsync(files[i].remotefilename, nss, CancellationToken.None).Await(); + using (var fs = new System.IO.FileStream(cf, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None)) + using (var timeoutStream = new TimeoutObservingStream(fs) { WriteTimeout = readWriteTimeout }) + using (var ts = new Library.Utility.ThrottledStream(timeoutStream, throttleDownload, throttleDownload)) + using (var nss = new NonSeekableStream(ts)) + streamingBackend.GetAsync(files[i].remotefilename, nss, timeoutStream.TimeoutToken).Await(); } else backend.GetAsync(files[i].remotefilename, cf, CancellationToken.None).Await(); @@ -382,72 +420,89 @@ namespace Duplicati.CommandLine.BackendTester } if (e != null) - Console.WriteLine("failed\n*** Error: {0}", e); + { + failAfterFinished = true; + Console.WriteLine($"{LogTimeStamp}failed\n*** Error: {e} after {sw.ElapsedMilliseconds} ms"); + } else - Console.WriteLine("done"); + Console.WriteLine($" done in {sw.ElapsedMilliseconds} ms"); - Console.Write("Checking hash ... "); + Console.Write($"{LogTimeStamp}Checking hash ... "); - using (System.IO.FileStream fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + using (var fs = new System.IO.FileStream(cf, System.IO.FileMode.Open, System.IO.FileAccess.Read)) if (Convert.ToBase64String(sha.ComputeHash(fs)) != Convert.ToBase64String(files[i].hash)) { if (dummyFileHash != null && Convert.ToBase64String(sha.ComputeHash(fs)) == Convert.ToBase64String(dummyFileHash)) - Console.WriteLine("failed\n*** Downloaded file was the dummy file"); + Console.WriteLine($"{LogTimeStamp}failed\n*** Downloaded file was the dummy file"); // Should this be failed? else - Console.WriteLine("failed\n*** Downloaded file was corrupt"); + { + failAfterFinished = true; + Console.WriteLine($"{LogTimeStamp}failed\n*** Downloaded file was corrupt"); + } } else Console.WriteLine("done"); } } - Console.WriteLine("Deleting files..."); + Console.WriteLine($"{LogTimeStamp}Deleting files..."); - foreach (TempFile tx in files) - try { backend.DeleteAsync(tx.remotefilename, CancellationToken.None).Await(); } + for (int i = 0; i < files.Count; i++) + try + { + Console.WriteLine($"{LogTimeStamp}Deleting file {i}"); + backend.DeleteAsync(files[i].remotefilename, CancellationToken.None).Await(); + } catch (Exception ex) { - Console.WriteLine("*** Failed to delete file {0}, message: {1}", tx.remotefilename, ex); + failAfterFinished = true; + Console.WriteLine($"{LogTimeStamp}*** Failed to delete file {files[i].remotefilename}, message: {ex}"); } - curlist = backend.List(); - foreach (Library.Interface.IFileEntry fe in curlist) + if (waitAfterDelete > TimeSpan.Zero) + { + Console.WriteLine($"{LogTimeStamp}Waiting {waitAfterDelete} after delete"); + Thread.Sleep(waitAfterDelete); + } + + curlist = backend.ListAsync(CancellationToken.None).ToBlockingEnumerable().ToList(); + foreach (var fe in curlist) if (!fe.IsFolder) { - Console.WriteLine("*** Remote folder contains {0} after cleanup", fe.Name); + Console.WriteLine($"{LogTimeStamp}*** Remote folder contains {fe.Name} after cleanup"); } // Test some error cases - Console.WriteLine("Checking retrieval of non-existent file..."); - bool caughtExpectedException = false; + Console.WriteLine($"{LogTimeStamp}Checking retrieval of non-existent file..."); + var caughtExpectedException = false; try { - using (Duplicati.Library.Utility.TempFile tempFile = new Duplicati.Library.Utility.TempFile()) + using (var tempFile = new Duplicati.Library.Utility.TempFile()) { - backend.GetAsync(string.Format("NonExistentFile-{0}", Guid.NewGuid()), tempFile.Name, CancellationToken.None).Await(); + backend.GetAsync($"NonExistentFile-{Guid.NewGuid()}", tempFile.Name, CancellationToken.None).Await(); } } catch (FileMissingException) { - Console.WriteLine("Caught expected FileMissingException"); + Console.WriteLine($"{LogTimeStamp}Caught expected FileMissingException"); caughtExpectedException = true; } catch (Exception ex) { - Console.WriteLine("*** Retrieval of non-existent file failed: {0}", ex); + Console.WriteLine($"{LogTimeStamp}*** Retrieval of non-existent file failed: {ex}"); } if (!caughtExpectedException) { - Console.WriteLine("*** Retrieval of non-existent file should have failed with FileMissingException"); + failAfterFinished = true; + Console.WriteLine($"{LogTimeStamp}*** Retrieval of non-existent file should have failed with FileMissingException"); } } // Test quota retrieval - IQuotaEnabledBackend quotaEnabledBackend = backend as IQuotaEnabledBackend; - if (quotaEnabledBackend != null) + if (backend is IQuotaEnabledBackend quotaEnabledBackend) { - Console.WriteLine("Checking quota..."); + Console.WriteLine($"{LogTimeStamp}Checking quota..."); IQuotaInfo quota = null; bool noException; try @@ -457,7 +512,7 @@ namespace Duplicati.CommandLine.BackendTester } catch (Exception ex) { - Console.WriteLine("*** Checking quota information failed: {0}", ex); + Console.WriteLine($"{LogTimeStamp}*** Checking quota information failed: {ex}"); noException = false; } @@ -465,18 +520,18 @@ namespace Duplicati.CommandLine.BackendTester { if (quota != null) { - Console.WriteLine("Free Space: {0}", Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace)); - Console.WriteLine("Total Space: {0}", Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace)); + Console.WriteLine($"{LogTimeStamp}Free Space: {Library.Utility.Utility.FormatSizeString(quota.FreeQuotaSpace)}"); + Console.WriteLine($"{LogTimeStamp}Total Space: {Library.Utility.Utility.FormatSizeString(quota.TotalQuotaSpace)}"); } else { - Console.WriteLine("Unable to retrieve quota information"); + Console.WriteLine($"{LogTimeStamp}Unable to retrieve quota information"); } } } // Test DNSName lookup - Console.WriteLine("Checking DNS names used by this backend..."); + Console.WriteLine($"{LogTimeStamp}Checking DNS names used by this backend..."); try { var dnsNames = backend.GetDNSNamesAsync(CancellationToken.None).Await(); @@ -489,37 +544,40 @@ namespace Duplicati.CommandLine.BackendTester } else { - Console.WriteLine("No DNS names reported"); + Console.WriteLine($"{LogTimeStamp}No DNS names reported"); } } catch (Exception ex) { - Console.WriteLine("*** Checking DNSName failed: {0}", ex); + Console.WriteLine($"{LogTimeStamp}*** Checking DNSName failed: {ex}"); } } finally { - foreach (Library.Interface.IGenericModule m in loadedModules) + foreach (var m in loadedModules) if (m is IDisposable disposable) disposable.Dispose(); } - return true; + return !failAfterFinished; } - private static void Uploadfile(string localfilename, int i, string remotefilename, IBackend backend, bool disableStreaming, long throttle) + private static void Uploadfile(string localfilename, int i, string remotefilename, IBackend backend, bool disableStreaming, long throttle, int readWriteTimeout) { - Console.Write("Uploading file {0}, {1} ... ", i, Duplicati.Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(localfilename).Length)); + Console.Write($"{LogTimeStamp}Uploading file {i}, {Duplicati.Library.Utility.Utility.FormatSizeString(new System.IO.FileInfo(localfilename).Length)} ... "); Exception e = null; + var sw = Stopwatch.StartNew(); + try { if (backend is IStreamingBackend streamingBackend && !disableStreaming) { - using (System.IO.FileStream fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) - using (Library.Utility.ThrottledStream ts = new Library.Utility.ThrottledStream(fs, throttle, throttle)) - using (NonSeekableStream nss = new NonSeekableStream(ts)) - streamingBackend.PutAsync(remotefilename, nss, CancellationToken.None).Await(); + using (var fs = new System.IO.FileStream(localfilename, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read)) + using (var timeoutStream = new TimeoutObservingStream(fs) { ReadTimeout = readWriteTimeout }) + using (var ts = new Library.Utility.ThrottledStream(timeoutStream, throttle, throttle)) + using (var nss = new NonSeekableStream(ts)) + streamingBackend.PutAsync(remotefilename, nss, timeoutStream.TimeoutToken).Await(); } else backend.PutAsync(remotefilename, localfilename, CancellationToken.None).Await(); @@ -533,16 +591,16 @@ namespace Duplicati.CommandLine.BackendTester if (e != null) { - Console.WriteLine("Failed to upload file {0}, error message: {1}, remote name: {2}", i, e, remotefilename); + Console.WriteLine($"{LogTimeStamp}Failed to upload file {i}, error message: {e}, remote name: {remotefilename} after {sw.ElapsedMilliseconds} ms"); while (e.InnerException != null) { e = e.InnerException; - Console.WriteLine(" Inner exception: {0}", e); + Console.WriteLine($"{LogTimeStamp} Inner exception: {e}"); } } else { - Console.WriteLine(" done!"); + Console.WriteLine($" done! in {sw.ElapsedMilliseconds} ms"); } } @@ -562,7 +620,7 @@ namespace Duplicati.CommandLine.BackendTester private static string CreateRandomFile(Library.Utility.TempFolder tf, int i, int min_file_size, int max_file_size, Random rnd) { - Console.Write("Generating file {0}", i); + Console.Write($"{LogTimeStamp}Generating file {i}"); string filename = System.IO.Path.Combine(tf, i.ToString()); using (System.IO.FileStream fs = new System.IO.FileStream(filename, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write)) { @@ -570,7 +628,7 @@ namespace Duplicati.CommandLine.BackendTester byte[] buf = new byte[1024]; int size = rnd.Next(min_file_size, max_file_size); - Console.WriteLine(" ({0})", Duplicati.Library.Utility.Utility.FormatSizeString(size)); + Console.WriteLine($" ({Duplicati.Library.Utility.Utility.FormatSizeString(size)})"); while (size > 0) { @@ -583,6 +641,8 @@ namespace Duplicati.CommandLine.BackendTester return filename; } + private static string LogTimeStamp => $"[{DateTime.Now:HH:mm:ss fff}] "; + public static IList SupportedCommands { get @@ -601,6 +661,8 @@ namespace Duplicati.CommandLine.BackendTester new CommandLineArgument("skip-overwrite-test", CommandLineArgument.ArgumentType.Boolean, "Bypass the overwrite test", "A value that indicates if dummy files should be uploaded prior to uploading the real files", "false"), new CommandLineArgument("auto-clean", CommandLineArgument.ArgumentType.Boolean, "Remove any files found in target folder", "A value that indicates if all files in the target folder should be deleted before starting the first test", "false"), new CommandLineArgument("force", CommandLineArgument.ArgumentType.Boolean, "Activate file deletion", "A value that indicates if existing files should really be deleted when using auto-clean", "false"), + new CommandLineArgument("wait-after-upload", CommandLineArgument.ArgumentType.Timespan, "Wait after all uploads", "A value that indicates how long to wait after all files are uploaded, to account for the backends eventual consistency", "0s"), + new CommandLineArgument("wait-after-delete", CommandLineArgument.ArgumentType.Timespan, "Wait after all deletes", "A value that indicates how long to wait after each delete operation, to account for the backends eventual consistency", "0s"), }); } } diff --git a/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj b/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj index 93d68ae44..3b086f31f 100644 --- a/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj +++ b/Duplicati/CommandLine/BackendTool/Duplicati.CommandLine.BackendTool.csproj @@ -3,7 +3,7 @@ net8.0 Duplicati.CommandLine.BackendTool.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/CommandLine/BackendTool/Program.cs b/Duplicati/CommandLine/BackendTool/Program.cs index 5af128c64..071f27037 100644 --- a/Duplicati/CommandLine/BackendTool/Program.cs +++ b/Duplicati/CommandLine/BackendTool/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,6 +26,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace Duplicati.CommandLine.BackendTool { @@ -74,7 +75,7 @@ namespace Duplicati.CommandLine.BackendTool } - if (args.Count < 2 || String.Equals(args[0], "help", StringComparison.OrdinalIgnoreCase) || args[0] == "?" || command == null) + if (args.Count < 2 || HelpOptionExtensions.IsArgumentAnyHelpString(args) || command == null) { if (command == null && args.Count >= 2) { @@ -124,7 +125,7 @@ namespace Duplicati.CommandLine.BackendTool throw new UserInformationException(string.Format("too many arguments: {0}", string.Join(",", args)), "BackendToolTooManyArguments"); Console.WriteLine("{0}\t{1}\t{2}\t{3}", "Name", "Dir/File", "LastChange", "Size"); - foreach (var e in backend.List()) + foreach (var e in backend.ListAsync(CancellationToken.None).ToBlockingEnumerable()) Console.WriteLine("{0}\t{1}\t{2}\t{3}", e.Name, e.IsFolder ? "Dir" : "File", e.LastModification, e.Size < 0 ? "" : Library.Utility.Utility.FormatSizeString(e.Size)); return 0; diff --git a/Duplicati/CommandLine/CLI/Commands.cs b/Duplicati/CommandLine/CLI/Commands.cs index f56f1982a..9aa3a8e99 100644 --- a/Duplicati/CommandLine/CLI/Commands.cs +++ b/Duplicati/CommandLine/CLI/Commands.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -163,7 +163,7 @@ namespace Duplicati.CommandLine options.TryGetValue("dbpath", out dbpath); if (string.IsNullOrEmpty(dbpath)) { - dbpath = Library.Main.DatabaseLocator.GetDatabasePath(backend, new Duplicati.Library.Main.Options(options), false, true); + dbpath = Library.Main.CLIDatabaseLocator.GetDatabasePathForCLI(backend, new Library.Main.Options(options), false, true); if (dbpath != null) options["dbpath"] = dbpath; } @@ -366,7 +366,7 @@ namespace Duplicati.CommandLine options.TryGetValue("dbpath", out dbpath); if (string.IsNullOrEmpty(dbpath)) { - dbpath = Library.Main.DatabaseLocator.GetDatabasePath(backend, new Duplicati.Library.Main.Options(options), false, true); + dbpath = Library.Main.CLIDatabaseLocator.GetDatabasePathForCLI(backend, new Duplicati.Library.Main.Options(options), false, true); if (dbpath != null) options["dbpath"] = dbpath; } @@ -622,7 +622,7 @@ namespace Duplicati.CommandLine switch (phase) { case Duplicati.Library.Main.OperationPhase.Backup_ProcessingFiles: - output.MessageEvent("Scanning local files ..."); + output.MessageEvent("Scanning source files ..."); periodicOutput.SetReady(); break; case Duplicati.Library.Main.OperationPhase.Backup_Finalize: @@ -668,6 +668,7 @@ namespace Duplicati.CommandLine if (parsedStats.KnownFileCount > 0) { output.MessageEvent(string.Format(" Remote files: {0}", parsedStats.KnownFileCount)); + output.MessageEvent(string.Format(" Remote filesets: {0}", parsedStats.KnownFilesets)); output.MessageEvent(string.Format(" Remote size: {0}", Library.Utility.Utility.FormatSizeString(parsedStats.KnownFileSize))); } @@ -820,7 +821,7 @@ namespace Duplicati.CommandLine if (string.IsNullOrEmpty(dbpath)) { if (args.Count > 0) - dbpath = Library.Main.DatabaseLocator.GetDatabasePath(args[0], new Duplicati.Library.Main.Options(options), false, true); + dbpath = Library.Main.CLIDatabaseLocator.GetDatabasePathForCLI(args[0], new Duplicati.Library.Main.Options(options), false, true); if (dbpath == null) { @@ -866,7 +867,7 @@ namespace Duplicati.CommandLine options.TryGetValue("dbpath", out dbpath); if (string.IsNullOrEmpty(dbpath)) { - dbpath = Library.Main.DatabaseLocator.GetDatabasePath(args[0], new Duplicati.Library.Main.Options(options), false, true); + dbpath = Library.Main.CLIDatabaseLocator.GetDatabasePathForCLI(args[0], new Duplicati.Library.Main.Options(options), false, true); if (dbpath != null) options["dbpath"] = dbpath; } diff --git a/Duplicati/CommandLine/CLI/ConsoleOutput.cs b/Duplicati/CommandLine/CLI/ConsoleOutput.cs index cd08a49da..edf295945 100644 --- a/Duplicati/CommandLine/CLI/ConsoleOutput.cs +++ b/Duplicati/CommandLine/CLI/ConsoleOutput.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/CommandLine/CLI/Duplicati.CommandLine.csproj b/Duplicati/CommandLine/CLI/Duplicati.CommandLine.csproj index 96f680beb..c1f473949 100644 --- a/Duplicati/CommandLine/CLI/Duplicati.CommandLine.csproj +++ b/Duplicati/CommandLine/CLI/Duplicati.CommandLine.csproj @@ -5,7 +5,7 @@ A commandline version of Duplicati Duplicati.CommandLine.Implementation TrayWarning.ico - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -22,6 +22,7 @@ + diff --git a/Duplicati/CommandLine/CLI/Help.cs b/Duplicati/CommandLine/CLI/Help.cs index 9cced02ee..2500a5495 100644 --- a/Duplicati/CommandLine/CLI/Help.cs +++ b/Duplicati/CommandLine/CLI/Help.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,6 +26,7 @@ using System.IO; using System.Linq; using System.Text; using Duplicati.Library.AutoUpdater; +using Duplicati.Library.DynamicLoader; using FilterGroup = Duplicati.Library.Utility.FilterGroup; namespace Duplicati.CommandLine @@ -109,14 +110,13 @@ namespace Duplicati.CommandLine topic = "include"; } - if (_document.ContainsKey(topic)) { string tp = _document[topic]; Library.Main.Options opts = new Library.Main.Options(new Dictionary()); tp = tp.Replace("%CLI_EXE%", PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine)); - tp = tp.Replace("%VERSION%", License.VersionNumbers.Version); + tp = tp.Replace("%VERSION%", License.VersionNumbers.VERSION_NAME); tp = tp.Replace("%BACKENDS%", string.Join(", ", Library.DynamicLoader.BackendLoader.Keys)); tp = tp.Replace("%APP_PATH%", Path.Combine(UpdaterManager.INSTALLATIONDIR, PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.CommandLine))); tp = tp.Replace("%PATH_SEPARATOR%", System.IO.Path.PathSeparator.ToString()); @@ -260,6 +260,18 @@ namespace Duplicati.CommandLine } } + if (tp.Contains("%SECRETPROVIDERS%")) + { + var lines = new List(); + foreach (var module in SecretProviderLoader.Keys) + { + var metadata = SecretProviderLoader.GetProviderMetadata(module); + lines.Add($"- {module}: {metadata.DisplayName}"); + } + + tp = tp.Replace("%SECRETPROVIDERS%", string.Join(Environment.NewLine, lines.ToArray())); + } + if (NAMEDOPTION_REGEX.IsMatch(tp)) tp = NAMEDOPTION_REGEX.Replace(tp, new Matcher().MathEvaluator); @@ -300,6 +312,14 @@ namespace Duplicati.CommandLine break; } + if (lines.Count == 0) + foreach (Duplicati.Library.Interface.ISecretProvider mod in SecretProviderLoader.Modules) + if (string.Equals(mod.Key, topic, StringComparison.OrdinalIgnoreCase)) + { + PrintSecretProvider(mod, lines); + break; + } + if (lines.Count == 0) PrintArgumentIfFound(new Matcher().Values, topic, lines); @@ -395,6 +415,18 @@ namespace Duplicati.CommandLine lines.Add(""); } + private static void PrintSecretProvider(Duplicati.Library.Interface.ISecretProvider mod, List lines) + { + lines.Add($"{mod.Key}: {mod.Description}"); + if (mod.SupportedCommands != null && mod.SupportedCommands.Count > 0) + { + lines.Add(" " + Strings.Program.SupportedOptionsHeader); + foreach (Library.Interface.ICommandLineArgument arg in mod.SupportedCommands) + Library.Interface.CommandLineArgument.PrintArgument(lines, arg, " "); + } + lines.Add(""); + } + private static void PrintFormatted(TextWriter outwriter, IEnumerable lines) { int windowWidth = 80; @@ -432,11 +464,15 @@ namespace Duplicati.CommandLine len -= leadingSpaces.Length; if (len < c.Length) { - int ix = c.LastIndexOf(" ", len, StringComparison.Ordinal); + int ix = c.LastIndexOf(' ', len); if (ix > 0) len = ix; } + var lfix = c.IndexOf('\n'); + if (lfix >= 0 && lfix < len) + len = lfix + 1; + outwriter.WriteLine(leadingSpaces + c.Substring(0, len).Trim()); c = c.Remove(0, len); if (extraIndent) diff --git a/Duplicati/CommandLine/CLI/Program.cs b/Duplicati/CommandLine/CLI/Program.cs index 1b77c2acf..22b707851 100644 --- a/Duplicati/CommandLine/CLI/Program.cs +++ b/Duplicati/CommandLine/CLI/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,11 +26,14 @@ using Duplicati.Library.Localization.Short; using System.IO; using Duplicati.Library.Interface; using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Crashlog; +using Duplicati.Library.Utility; namespace Duplicati.CommandLine { public class Program { + private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); public static bool FROM_COMMANDLINE = false; @@ -46,14 +49,19 @@ namespace Duplicati.CommandLine FROM_COMMANDLINE = true; try { + CrashlogHelper.OnUnobservedTaskException += LogUnobservedTaskExceptions; return RunCommandLine(Console.Out, Console.Error, c => { }, args); } finally { Library.UsageReporter.Reporter.ShutDown(); + CrashlogHelper.OnUnobservedTaskException -= LogUnobservedTaskExceptions; } } + private static void LogUnobservedTaskExceptions(Exception ex) => Console.Error.WriteLine("UnobservedTaskException: {0}", ex); + + private static Dictionary, List, Dictionary, Library.Utility.IFilter, int>> CommandMap { get @@ -157,7 +165,7 @@ namespace Duplicati.CommandLine } // Probe for "help" to avoid extra processing - if (cargs.Count == 0 || (string.Equals(cargs[0], "help", StringComparison.OrdinalIgnoreCase))) + if (cargs.Count == 0 || HelpOptionExtensions.IsArgumentAnyHelpString(cargs)) { return Commands.Help(outwriter, setup, cargs, options, filter); } @@ -175,6 +183,10 @@ namespace Duplicati.CommandLine } } + // Don't pass these options into the controller + options.Remove(Library.AutoUpdater.DataFolderManager.SERVER_DATAFOLDER_OPTION); + options.Remove(Library.AutoUpdater.DataFolderManager.PORTABLE_MODE_OPTION); + if (!options.ContainsKey("passphrase")) if (!string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("PASSPHRASE"))) options["passphrase"] = System.Environment.GetEnvironmentVariable("PASSPHRASE"); @@ -277,20 +289,16 @@ namespace Duplicati.CommandLine } } - public static IList SupportedOptions - { - get - { - return new List(new Library.Interface.ICommandLineArgument[] { - new Library.Interface.CommandLineArgument("parameters-file", Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong2, "", new string[] {"parameter-file", "parameterfile"}), - new Library.Interface.CommandLineArgument("include", Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.IncludeShort, Strings.Program.IncludeLong), - new Library.Interface.CommandLineArgument("exclude", Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.ExcludeShort, Strings.Program.ExcludeLong), - new Library.Interface.CommandLineArgument("control-files", Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.ControlFilesOptionShort, Strings.Program.ControlFilesOptionLong, "false"), - new Library.Interface.CommandLineArgument("quiet-console", Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.QuietConsoleOptionShort, Strings.Program.QuietConsoleOptionLong, "false"), - new Library.Interface.CommandLineArgument("auto-update", Library.Interface.CommandLineArgument.ArgumentType.Boolean, LC.L("Toggle automatic updates"), LC.L("Set this option if you prefer to have the commandline version automatically update"), "false"), - }); - } - } + public static IList SupportedOptions => [ + new CommandLineArgument("parameters-file", CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong2, "", new string[] {"parameter-file", "parameterfile"}), + new CommandLineArgument("include", CommandLineArgument.ArgumentType.String, Strings.Program.IncludeShort, Strings.Program.IncludeLong), + new CommandLineArgument("exclude", CommandLineArgument.ArgumentType.String, Strings.Program.ExcludeShort, Strings.Program.ExcludeLong), + new CommandLineArgument("control-files", CommandLineArgument.ArgumentType.Boolean, Strings.Program.ControlFilesOptionShort, Strings.Program.ControlFilesOptionLong, "false"), + new CommandLineArgument("quiet-console", CommandLineArgument.ArgumentType.Boolean, Strings.Program.QuietConsoleOptionShort, Strings.Program.QuietConsoleOptionLong, "false"), + new CommandLineArgument("auto-update", CommandLineArgument.ArgumentType.Boolean, Strings.Program.AutoUpdateOptionShort, Strings.Program.AutoUpdateOptionLong, "false"), + new CommandLineArgument(DataFolderManager.PORTABLE_MODE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortableModeOptionShort, Strings.Program.PortableModeOptionLong, DataFolderManager.PORTABLE_MODE.ToString().ToLowerInvariant()), + new CommandLineArgument(DataFolderManager.SERVER_DATAFOLDER_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.DataFolderOptionShort, Strings.Program.DataFolderOptionLong, DataFolderManager.DATAFOLDER), + ]; private static bool ReadOptionsFromFile(TextWriter outwriter, string filename, ref Library.Utility.IFilter filter, List cargs, Dictionary options) { diff --git a/Duplicati/CommandLine/CLI/Strings.cs b/Duplicati/CommandLine/CLI/Strings.cs index 3e65ab22e..c63e347aa 100644 --- a/Duplicati/CommandLine/CLI/Strings.cs +++ b/Duplicati/CommandLine/CLI/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -54,6 +54,12 @@ namespace Duplicati.CommandLine.Strings public static string QuietConsoleOptionLong { get { return LC.L(@"If this option is set, progress reports and other messages that would normally go to the console will be redirected to the log."); } } public static string QuietConsoleOptionShort { get { return LC.L(@"Disable console output"); } } public static string SkippingSourceArgumentsOnNonBackupOperation { get { return @"The --source argument was specified in the parameter file, but the current operation is not a backup operation, so the argument is ignored."; } } + public static string AutoUpdateOptionShort { get { return LC.L(@"Toggle automatic updates"); } } + public static string AutoUpdateOptionLong { get { return LC.L(@"Set this option if you prefer to have the commandline version automatically update"); } } + public static string PortableModeOptionShort { get { return LC.L(@"Use portable mode"); } } + public static string PortableModeOptionLong { get { return LC.L(@"If this option is set, the configuration files will be stored in a data subfolder of the Duplicati installation folder. This is useful for running from a USB stick or other portable media."); } } + public static string DataFolderOptionShort { get { return LC.L(@"Data folder"); } } + public static string DataFolderOptionLong { get { return LC.L(@"The folder where data is stored. This is the folder where the database and other files are stored."); } } // ReSharper disable once UnusedMember.Global // This is a placeholder message that is intended to be used with the code diff --git a/Duplicati/CommandLine/CLI/help.txt b/Duplicati/CommandLine/CLI/help.txt index 8b4bea26b..75c8c4de6 100644 --- a/Duplicati/CommandLine/CLI/help.txt +++ b/Duplicati/CommandLine/CLI/help.txt @@ -1,9 +1,9 @@ -# This file is the help for the Duplicati Command Line Client. It shows what +# This file is the help for the Duplicati Command Line Client. It shows what # commands the CLI understands and what the outputs are. Sometimes we used # placeholders for options. In that case descriptions are taken directly from -# the modules. Sometimes we described options here. The reason for that is, -# that we found the new description much better but did not want to create a -# need for translation as many options are described in the UI as well and +# the modules. Sometimes we described options here. The reason for that is, +# that we found the new description much better but did not want to create a +# need for translation as many options are described in the UI as well and # they are translated there. # ============================================================================= > duplicati.commandline.exe @@ -18,6 +18,7 @@ See %CLI_EXE% help for more information. Modules: %ENCRYPTIONMODULES%, %COMPRESSIONMODULES%, %GENERICMODULES% Formats: date, time, size, decimal, encryption, compression Advanced: mail, advanced, returncodes, filter, filter-groups, /// The settings to use for the connection /// Whether to obtain a refresh token + /// Console messages interceptor /// The connection - public static async Task Connect(Settings settings, bool obtainRefreshToken = false) + public static async Task Connect(Settings settings, bool obtainRefreshToken = false, OutputInterceptor? console = null) { - Console.WriteLine($"Connecting to {settings.HostUrl}..."); + + if (console != null) + console.AppendConsoleMessage($"Connecting to {settings.HostUrl}..."); + else + Console.WriteLine($"Connecting to {settings.HostUrl}..."); var trustedCertificateHashes = new HashSet(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrWhiteSpace(settings.AcceptedHostCertificate)) - trustedCertificateHashes.UnionWith(settings.AcceptedHostCertificate.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); + trustedCertificateHashes.UnionWith(settings.AcceptedHostCertificate.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); // Configure the client for requests - var client = new HttpClient(new HttpClientHandler() + var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = settings.Insecure || trustedCertificateHashes.Contains("*") ? HttpClientHandler.DangerousAcceptAnyServerCertificateValidator @@ -123,7 +163,7 @@ public class Connection if (cert == null) return false; return trustedCertificateHashes.Contains(cert.GetCertHashString()); - }), + }) }) { BaseAddress = new Uri(settings.HostUrl + "api/v1/") @@ -140,36 +180,42 @@ public class Connection if (string.IsNullOrWhiteSpace(refreshToken)) throw new InvalidOperationException("Failed to get refresh token"); - (settings with { RefreshToken = refreshToken }).Save(); + (settings with { RefreshToken = refreshToken }).Save(console); return CreateConnectionWithClient(client, accessToken); } } catch (Exception ex) { - Console.WriteLine($"Failed to use refresh token: {ex.Message}"); + if (console != null) + console.AppendExceptionMessage($"Failed to use refresh token: {ex.Message}"); + else + Console.WriteLine($"Failed to use refresh token: {ex.Message}"); } // If we can read the server database, try to create a signin token try { var opts = new Dictionary(); - if (!string.IsNullOrWhiteSpace(settings.ServerDatafolder)) - opts.Add("server-datafolder", settings.ServerDatafolder); - - if (File.Exists(Path.Combine(Server.Program.GetDataFolderPath(opts), Server.Program.SERVER_DATABASE_FILENAME))) + if (settings.Key != null) + opts["settings-encryption-key"] = settings.Key.Key; + if (File.Exists(Path.Combine(DataFolderManager.DATAFOLDER, DataFolderManager.SERVER_DATABASE_FILENAME))) { string? cfg = null; - using (var connection = Duplicati.Server.Program.GetDatabaseConnection(opts, true)) + using (var connection = Server.Program.GetDatabaseConnection(opts, true)) { cfg = connection.ApplicationSettings.JWTConfig; if (settings.HostUrl.Scheme == "https" && connection.ApplicationSettings.ServerSSLCertificate != null && trustedCertificateHashes.Count == 0) - trustedCertificateHashes.Add(connection.ApplicationSettings.ServerSSLCertificate.GetCertHashString()); + { + var selfSignedCertHash = connection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString(); + if (!string.IsNullOrWhiteSpace(selfSignedCertHash)) + trustedCertificateHashes.Add(selfSignedCertHash); + } } if (!string.IsNullOrWhiteSpace(cfg)) { - var signinjwt = new WebserverCore.Middlewares.JWTTokenProvider( - JsonSerializer.Deserialize(cfg) + var signinjwt = new JWTTokenProvider( + JsonSerializer.Deserialize(cfg) ?? throw new InvalidOperationException("Failed to deserialize JWTConfig") ).CreateSigninToken("server-cli"); @@ -179,19 +225,25 @@ public class Connection throw new InvalidOperationException("Failed to get access token"); if (!string.IsNullOrWhiteSpace(refreshToken)) - (settings with { RefreshToken = refreshToken }).Save(); + (settings with { RefreshToken = refreshToken }).Save(console); return CreateConnectionWithClient(client, accessToken); } } - else if (!string.IsNullOrWhiteSpace(settings.ServerDatafolder)) + else if (!string.IsNullOrWhiteSpace(DataFolderManager.DATAFOLDER)) { - Console.WriteLine($"No database found in {settings.ServerDatafolder}"); + if (console != null) + console.AppendConsoleMessage($"No database found in {DataFolderManager.DATAFOLDER}"); + else + Console.WriteLine($"No database found in {DataFolderManager.DATAFOLDER}"); } } catch (Exception ex) { - Console.WriteLine($"Failed to obtain a signin token: {ex.Message}"); + if (console != null) + console.AppendConsoleMessage($"Failed to obtain a signin token: {ex.Message}"); + else + Console.WriteLine($"Failed to obtain a signin token: {ex.Message}"); } // Otherwise, we need a password to log in @@ -209,7 +261,7 @@ public class Connection throw new InvalidOperationException("Failed to get access token"); if (!string.IsNullOrWhiteSpace(refreshToken)) - (settings with { RefreshToken = refreshToken }).Save(); + (settings with { RefreshToken = refreshToken }).Save(console); return CreateConnectionWithClient(client, accessToken); } @@ -239,9 +291,9 @@ public class Connection /// The password to use /// Whether to obtain a refresh token /// The access and refresh tokens - private static Task<(string? AccessToken, string? RefreshToken)> LoginWithPassword(HttpClient client, string password, bool obtainRefreshToken) + private static Task<(string AccessToken, string? RefreshToken)> LoginWithPassword(HttpClient client, string password, bool obtainRefreshToken) => ParseAuthResponse( - client.PostAsync($"auth/login", JsonContent.Create(new { Password = password, RememberMe = obtainRefreshToken })) + client.PostAsync("auth/login", JsonContent.Create(new { Password = password, RememberMe = obtainRefreshToken })) ); /// @@ -250,10 +302,10 @@ public class Connection /// The HTTP client /// The refresh token to use /// The access and refresh tokens - private static Task<(string? AccessToken, string? RefreshToken)> LoginWithRefreshToken(HttpClient client, string refreshToken) + private static Task<(string AccessToken, string? RefreshToken)> LoginWithRefreshToken(HttpClient client, string refreshToken) => ParseAuthResponse(client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "auth/refresh") { - Headers = { { "Cookie", $"RefreshToken_{client.BaseAddress!.Port}={refreshToken}" } }, + Headers = { { "Cookie", $"RefreshToken_{client.BaseAddress!.Port}={refreshToken}" } } })); @@ -262,7 +314,7 @@ public class Connection /// /// The response to parse /// The access and refresh tokens - private static async Task<(string? AccessToken, string? RefreshToken)> ParseAuthResponse(Task responseTask) + private static async Task<(string AccessToken, string? RefreshToken)> ParseAuthResponse(Task responseTask) { var response = await responseTask; await EnsureSuccessStatusCodeWithParsing(response); @@ -273,7 +325,7 @@ public class Connection throw new InvalidOperationException("Failed to get access token"); response.Headers.TryGetValues("Set-Cookie", out var cookies); - var refreshToken = cookies?.SelectMany(c => c.Split(';')).FirstOrDefault(c => c.StartsWith($"RefreshToken_"))?.Split('=', 2)[1]; + var refreshToken = cookies?.SelectMany(c => c.Split(';')).FirstOrDefault(c => c.StartsWith("RefreshToken_"))?.Split('=', 2)[1]; return (accessToken, refreshToken); } @@ -296,7 +348,7 @@ public class Connection /// The task public async Task Resume() { - var response = await client.PostAsync($"serverstate/resume", null); + var response = await client.PostAsync("serverstate/resume", null); await EnsureSuccessStatusCodeWithParsing(response); } @@ -341,6 +393,54 @@ public class Connection await EnsureSuccessStatusCodeWithParsing(response); } + /// + /// Gets the server state + /// + /// The server state + public async Task GetServerState() + { + var response = await client.GetAsync("serverstate"); + await EnsureSuccessStatusCodeWithParsing(response); + return await response.Content.ReadFromJsonAsync() + ?? throw new InvalidDataException("Failed to parse server response"); + } + + /// + /// Runs a backup + /// + /// The ID of the backup + /// The task + public async Task WaitForBackup(string backupId, TimeSpan delay, Action statusMessage) + { + var state = await GetServerState(); + + if (!state.SchedulerQueueIds.Any(x => x.Item2 == backupId) && state.ActiveTask?.Item2 != backupId) + throw new UserReportedException("Backup is not queued or running"); + + var hasStarted = state.ActiveTask?.Item2 == backupId; + + while (true) + { + await Task.Delay(delay); + state = await GetServerState(); + + if (state.ActiveTask?.Item2 == backupId) + { + statusMessage("Backup is running ..."); + hasStarted = true; + continue; + } + + if (!hasStarted && state.SchedulerQueueIds.Any(x => x.Item2 == backupId)) + { + statusMessage("Backup is queued ..."); + continue; + } + + break; + } + } + /// /// Lists the active tasks /// @@ -367,7 +467,7 @@ public class Connection StopLevel.AfterCurrentFile => "stopaftercurrentfile", StopLevel.StopNow => "stopnow", StopLevel.Abort => "abort", - _ => throw new ArgumentOutOfRangeException(nameof(level)), + _ => throw new ArgumentOutOfRangeException(nameof(level)) }; var response = await client.PostAsync($"task/{Uri.EscapeDataString(taskId)}/{levelString}", null); await EnsureSuccessStatusCodeWithParsing(response); @@ -377,15 +477,16 @@ public class Connection /// Logs out of the server /// /// The settings to use + /// /// The task - public async Task Logout(Settings settings) + public async Task Logout(Settings settings, OutputInterceptor output) { var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Post, "auth/refresh/logout") { Headers = { { "Cookie", $"RefreshToken_{client.BaseAddress!.Port}={settings.RefreshToken}" } } }); await EnsureSuccessStatusCodeWithParsing(response); - (settings with { RefreshToken = null }).Save(); + (settings with { RefreshToken = null }).Save(output); } /// @@ -449,6 +550,17 @@ public class Connection return await response.Content.ReadAsStreamAsync(); } + /// + /// Creates a forever token + /// + /// The token + public async Task CreateForeverToken() + { + var (accessToken, _) = await ParseAuthResponse(client.PostAsync("auth/issue-forever-token", null)); + return accessToken; + } + + /// /// The server error structure for JSON deserialization /// diff --git a/Duplicati/CommandLine/ServerUtil/Duplicati.CommandLine.ServerUtil.csproj b/Duplicati/CommandLine/ServerUtil/Duplicati.CommandLine.ServerUtil.csproj index cebbdee7a..4f4f608da 100644 --- a/Duplicati/CommandLine/ServerUtil/Duplicati.CommandLine.ServerUtil.csproj +++ b/Duplicati/CommandLine/ServerUtil/Duplicati.CommandLine.ServerUtil.csproj @@ -5,7 +5,7 @@ enable enable Duplicati.CommandLine.ServerUtil.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license Duplicati.CommandLine.ServerUtil diff --git a/Duplicati/CommandLine/ServerUtil/HelperMethods.cs b/Duplicati/CommandLine/ServerUtil/HelperMethods.cs index fe9e66b8f..54f2edfc3 100644 --- a/Duplicati/CommandLine/ServerUtil/HelperMethods.cs +++ b/Duplicati/CommandLine/ServerUtil/HelperMethods.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.CommandLine.ServerUtil; /// diff --git a/Duplicati/CommandLine/ServerUtil/OutputInterceptor.cs b/Duplicati/CommandLine/ServerUtil/OutputInterceptor.cs new file mode 100644 index 000000000..69edad11c --- /dev/null +++ b/Duplicati/CommandLine/ServerUtil/OutputInterceptor.cs @@ -0,0 +1,118 @@ +using System.CommandLine.Binding; +using System.Dynamic; +using Duplicati.Library.Backend; +using Newtonsoft.Json; + +namespace Duplicati.CommandLine.ServerUtil; + +/// +/// Manages console output, optionally serializing it to JSON. +/// +/// +/// This class captures command execution details, messages, and exceptions, providing flexibility to either output them to the console +/// or serialize them into a JSON format based on the parameter. +/// +public sealed class OutputInterceptor(bool jsonOutput, BindingContext bindingContext) +{ + private readonly DateTimeOffset _timestamp = DateTimeOffset.Now; + private readonly List _commandMessages = []; + private readonly List _exceptionMessages = []; + private string? _command; + private bool _success; + private readonly Dictionary _extendedProperties = []; + public bool JsonOutputMode { get; } = jsonOutput; + public int ExitCode { get; set; } + + /// + /// Gets the binding context associated with this interceptor. + /// + public BindingContext BindingContext { get; } = bindingContext ?? throw new ArgumentNullException(nameof(bindingContext)); + + /// + /// Sets the command string to be intercepted and tracked. + /// + /// The command string to set. Must not be null. + /// Thrown when is null. + public void SetCommand(string command) + { + _command = command ?? throw new ArgumentNullException(nameof(command)); + } + + /// + /// Sets the result of the command execution. + /// + /// A value indicating whether the ** business rule ** was successful. On exception by definition it will be false. + public void SetResult(bool success) + { + _success = success; + } + + /// + /// Appends an exception message to the interceptor. + /// + /// The exception message to append. Ignored if null or empty. + /// + /// If JSON output is enabled, the message is stored in a list; otherwise, it is written to the console. + /// + public void AppendExceptionMessage(string? message) + { + if (string.IsNullOrEmpty(message)) return; + + if (JsonOutputMode) + { + _exceptionMessages.Add(message); + } + else + { + Console.WriteLine(message); + } + } + + public void AppendCustomObject(string keyName, object? customObject) + { + _extendedProperties.Add(keyName, customObject); + } + + /// + /// Appends a console message to the interceptor. + /// + /// The console message to append. Ignored if null or empty. + /// + /// If JSON output is enabled, the message is stored in a list; otherwise, it is written to the console. + /// + public void AppendConsoleMessage(string? message) + { + if (message == null) return; + + if (JsonOutputMode && !string.IsNullOrEmpty(message)) + _commandMessages.Add(message); + else + Console.WriteLine(message); + } + + /// + /// Serializes the intercepted data into a JSON string if JSON output is enabled. + /// + /// + /// A JSON string containing the intercepted data, or null if JSON output is disabled. + /// + /// + /// The serialized result includes the timestamp, command, success status, messages, and exceptions in a structured format. + /// + public string? GetSerializedResult() + { + if (!JsonOutputMode) return null; + + dynamic result = new ExpandoObject(); + result.Timestamp = _timestamp.ToString("O"); + result.UnixTimestamp = _timestamp.ToUnixTimeSeconds(); + result.Command = _command; + result.Success = _success; + result.ExitCode = ExitCode; + result.Messages = _commandMessages.AsReadOnly(); + result.Exceptions = _exceptionMessages.AsReadOnly(); + foreach (var kvp in _extendedProperties) ((IDictionary)result)[kvp.Key] = kvp.Value ?? string.Empty; + + return JsonConvert.SerializeObject(result, Formatting.Indented); + } +} \ No newline at end of file diff --git a/Duplicati/CommandLine/ServerUtil/OutputInterceptorBinder.cs b/Duplicati/CommandLine/ServerUtil/OutputInterceptorBinder.cs new file mode 100644 index 000000000..96d0aced7 --- /dev/null +++ b/Duplicati/CommandLine/ServerUtil/OutputInterceptorBinder.cs @@ -0,0 +1,62 @@ +using System.CommandLine.Binding; +using System.CommandLine.Parsing; + +namespace Duplicati.CommandLine.ServerUtil; + +/// +/// An abstract binder class for managing a singleton instance of . +/// +/// +/// This class ensures that only one instance of is associated with a given . +/// +public abstract class OutputInterceptorBinder : BinderBase +{ + private static OutputInterceptor? _instance; + + /// + /// Gets the current instance of . + /// + /// Thrown when the instance has not been initialized. + public static OutputInterceptor? Instance => _instance; + + /// + /// Retrieves or creates a instance for the specified binding context. + /// + /// The binding context to associate with the interceptor. Must not be null. + /// The existing or newly created instance. + /// Thrown when is null. + public static OutputInterceptor GetConsoleInterceptor(BindingContext bindingContext) + { + ArgumentNullException.ThrowIfNull(bindingContext, nameof(bindingContext)); + + if (_instance is not null && ReferenceEquals(_instance.BindingContext, bindingContext)) + { + return _instance; + } + + _instance = CreateInterceptor(bindingContext); + return _instance; + } + + /// + /// Gets the bound value for the specified binding context. + /// + /// The binding context to retrieve the interceptor for. + /// The associated instance. + protected override OutputInterceptor GetBoundValue(BindingContext bindingContext) + { + return GetConsoleInterceptor(bindingContext); + } + + /// + /// Creates a new instance with the specified binding context. + /// + /// The binding context to initialize the interceptor with. + /// A new instance. + private static OutputInterceptor CreateInterceptor(BindingContext bindingContext) + { + var interceptor = new OutputInterceptor(bindingContext.ParseResult.Tokens.Any(x => x is { Type: TokenType.Option, Value: "--json" }), bindingContext); + interceptor.SetCommand(bindingContext.ParseResult.CommandResult.Command.Name); + return interceptor; + } +} \ No newline at end of file diff --git a/Duplicati/CommandLine/ServerUtil/Program.cs b/Duplicati/CommandLine/ServerUtil/Program.cs index 1e17bb8ab..c879e228f 100644 --- a/Duplicati/CommandLine/ServerUtil/Program.cs +++ b/Duplicati/CommandLine/ServerUtil/Program.cs @@ -1,7 +1,30 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System.CommandLine; using System.CommandLine.Builder; using System.CommandLine.Parsing; using Duplicati.CommandLine.ServerUtil.Commands; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Utility; namespace Duplicati.CommandLine.ServerUtil; @@ -15,9 +38,9 @@ public static class Program /// /// /// The return code - public static Task Main(string[] args) + public static async Task Main(string[] args) { - Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.ServerUtil); + PreloadSettingsLoader.ConfigurePreloadSettings(ref args, PackageHelper.NamedExecutable.ServerUtil); var rootCmd = new RootCommand("Server CLI tool for Duplicati") { @@ -31,34 +54,47 @@ public static class Program Import.Create(), Export.Create(), Health.Create(), + IssueForeverToken.Create(), + ServerStatus.Create() }; rootCmd = SettingsBinder.AddGlobalOptions(rootCmd); - return new CommandLineBuilder(rootCmd) + return await new CommandLineBuilder(rootCmd) .UseDefaults() .UseExceptionHandler((ex, context) => { + OutputInterceptorBinder.Instance?.SetResult(false); + if (ex is UserReportedException ure) { - Console.WriteLine(ure.Message); + OutputInterceptorBinder.Instance?.AppendExceptionMessage(ure.Message); context.ExitCode = 2; } else { - Console.WriteLine(ex.ToString()); + OutputInterceptorBinder.Instance?.AppendExceptionMessage(ex.ToString()); context.ExitCode = 1; } + + if (OutputInterceptorBinder.Instance != null) + { + OutputInterceptorBinder.Instance.ExitCode = context.ExitCode; + Console.WriteLine(OutputInterceptorBinder.Instance.GetSerializedResult()); + } }) .AddMiddleware(async (context, next) => { // Inject settings with custom binder - if (context.ParseResult.CommandResult?.Command is Command cmd) + if (context.ParseResult.CommandResult.Command is { } cmd) context.BindingContext.AddService(_ => SettingsBinder.GetSettings(context.BindingContext)); - + + context.BindingContext.AddService(_ => OutputInterceptorBinder.GetConsoleInterceptor(context.BindingContext)); + await next(context); + Console.WriteLine(OutputInterceptorBinder.Instance?.GetSerializedResult()); }) - .Build() - .InvokeAsync(args); + .UseAdditionalHelpAliases() + .Build().InvokeAsync(args); } -} +} \ No newline at end of file diff --git a/Duplicati/CommandLine/ServerUtil/Settings.cs b/Duplicati/CommandLine/ServerUtil/Settings.cs index 3d44a2018..971b517e8 100644 --- a/Duplicati/CommandLine/ServerUtil/Settings.cs +++ b/Duplicati/CommandLine/ServerUtil/Settings.cs @@ -1,6 +1,33 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System.Text.Json; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.DynamicLoader; using Duplicati.Library.Encryption; +using Duplicati.Library.Interface; using Duplicati.Library.Main; +using Duplicati.Library.Utility; +using Uri = System.Uri; +using Utility = Duplicati.Library.Utility.Utility; namespace Duplicati.CommandLine.ServerUtil; @@ -10,17 +37,21 @@ namespace Duplicati.CommandLine.ServerUtil; /// The commandline password /// The saved refresh token /// The host url to connect to -/// The server datafolder for password-free connections /// The settings file where data is loaded/saved /// Whether to disable TLS/SSL certificate trust check +/// The encryption key to use for the settings file +/// The secret provider to use for reading secrets +/// The pattern to use for the secret provider /// The SHA1 hash of the host certificate to accept public sealed record Settings( string? Password, string? RefreshToken, Uri HostUrl, - string? ServerDatafolder, string SettingsFile, bool Insecure, + EncryptedFieldHelper.KeyInstance? Key, + ISecretProvider? SecretProvider, + string SecretProviderPattern, string? AcceptedHostCertificate ) { @@ -35,69 +66,125 @@ public sealed record Settings( Uri HostUrl, string? ServerDatafolder ); - private static string GetDefaultStorageFolder(string filename) - { - var folder = DatabaseLocator.GetDefaultStorageFolderWithDebugSupport(filename); - if (!Directory.Exists(folder)) - Directory.CreateDirectory(folder); - return folder; - } + /// + /// Gets the default data folder for the settings file + /// + /// The filename to use + /// The default storage folder + private static string GetDefaultStorageFolder(string filename) + // Ideally, this should use DataFolderManager.DATAFOLDER, but we cannot due to backwards compatibility + => DataFolderLocator.GetDefaultStorageFolder(filename, true); /// /// Loads the settings from the settings file /// /// The password to use /// The host URL to use - /// The server data folder to use /// The settings file to use /// Whether to disable TLS/SSL certificate trust check + /// The encryption key to use + /// The secret provider to use + /// The secret provider cache level to use + /// The secret provider pattern to use /// The SHA1 hash of the host certificate to accept /// The loaded settings - public static Settings Load(string? password, Uri? hostUrl, string? serverDataFolder, string settingsFile, bool insecure, string? acceptedHostCertificate) + public static Settings Load(string? password, Uri? hostUrl, string settingsFile, bool insecure, string? settingsPassphrase, string? secretProvider, SecretProviderHelper.CachingLevel secretProviderCache, string secretProviderPattern, string? acceptedHostCertificate) { - hostUrl ??= new Uri("http://localhost:8200"); - if (string.IsNullOrWhiteSpace(serverDataFolder)) - serverDataFolder = GetDefaultStorageFolder("Duplicati-server.sqlite"); + hostUrl ??= new Uri($"http://{Utility.IpVersionCompatibleLoopback}:8200"); + + ISecretProvider? secretInstance = null; + if (!string.IsNullOrWhiteSpace(secretProvider)) + { + var secretProviderInstance = SecretProviderLoader.CreateInstance(secretProvider); + + // Map into expected structure + var opts = new Dictionary + { + { "secret-provider", secretProvider }, + { "secret-provider-pattern", secretProviderPattern }, + { "secret-provider-cache", secretProviderCache.ToString() }, + { "password", password }, + { "settings-encryption-key", settingsPassphrase } + }; + + var args = new[] { hostUrl }; + secretInstance = SecretProviderHelper.ApplySecretProviderAsync(args, [], opts, TempFolder.SystemTempPath, null, CancellationToken.None).Await(); + + // Read back transformed values + hostUrl = args[0]; + password = opts["password"]; + settingsPassphrase = opts["settings-encryption-key"]; + } if (!string.IsNullOrWhiteSpace(settingsFile) && !Path.IsPathRooted(settingsFile)) settingsFile = Path.Combine(GetDefaultStorageFolder(settingsFile), settingsFile); - var persistedSettings = LoadSettings(settingsFile) + var key = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(settingsPassphrase); + var persistedSettings = LoadSettings(settingsFile, key) .FirstOrDefault(x => x.HostUrl == hostUrl); return new Settings( password, persistedSettings?.RefreshToken, hostUrl, - serverDataFolder, settingsFile, insecure, + key, + secretInstance, + secretProviderPattern, acceptedHostCertificate ); } + /// + /// Replaces secrets inside arguments and options + /// + /// The arguments to replace + /// The options to replace + /// The task to await + public Task ReplaceSecrets(Dictionary options) + { + if (SecretProvider == null) + return Task.CompletedTask; + + return SecretProviderHelper.ReplaceSecretsAsync(SecretProvider, [], [], options, SecretProviderPattern, CancellationToken.None); + } + /// /// Saves the settings to the settings file /// - public void Save() + public void Save(OutputInterceptor? output = null) { + var thisKey = Key; if (!string.IsNullOrWhiteSpace(RefreshToken)) { - if (!EncryptedFieldHelper.HasValidDefaultKey) - Console.WriteLine("Warning: The encryption key is missing, saving login token without encryption"); - else if (EncryptedFieldHelper.IsDefaultKeyBlacklisted) - Console.WriteLine("Warning: The current encryption key is blacklisted and cannot be used, saving login token without encryption"); + if (Key == null) + { + if (output != null) + output.AppendConsoleMessage("Warning: The encryption key is missing, saving login token without encryption"); + else + Console.WriteLine("Warning: The encryption key is missing, saving login token without encryption"); + } + else if (Key?.IsBlacklisted ?? false) + { + if (output != null) + output.AppendConsoleMessage("Warning: The current encryption key is blacklisted and cannot be used, saving login token without encryption"); + else + Console.WriteLine("Warning: The current encryption key is blacklisted and cannot be used, saving login token without encryption"); + thisKey = null; + } + } - File.WriteAllText(SettingsFile, JsonSerializer.Serialize(LoadSettings(SettingsFile) + File.WriteAllText(SettingsFile, JsonSerializer.Serialize(LoadSettings(SettingsFile, thisKey) .Where(x => x.HostUrl != HostUrl) - .Append(new PersistedSettings(RefreshToken, HostUrl, ServerDatafolder)) + .Append(new PersistedSettings(RefreshToken, HostUrl, DataFolderManager.DATAFOLDER)) .Select(x => x with { - RefreshToken = string.IsNullOrWhiteSpace(x.RefreshToken) || EncryptedFieldHelper.IsDefaultKeyBlacklisted || !EncryptedFieldHelper.HasValidDefaultKey + RefreshToken = string.IsNullOrWhiteSpace(x.RefreshToken) || thisKey == null ? x.RefreshToken - : EncryptedFieldHelper.Encrypt(x.RefreshToken) + : EncryptedFieldHelper.Encrypt(x.RefreshToken, thisKey) }) )); } @@ -110,17 +197,27 @@ public sealed record Settings( { return Connection.Connect(this); } + + /// + /// Gets a connection to the server + /// + /// The connection + public Task GetConnection(OutputInterceptor output) + { + return Connection.Connect(this, false, output); + } /// /// Loads the settings from the settings file /// /// The filename to load + /// The encryption key to use /// The loaded settings - private static List LoadSettings(string filename) + private static List LoadSettings(string filename, EncryptedFieldHelper.KeyInstance? key) { if (File.Exists(filename)) return (JsonSerializer.Deserialize>(File.ReadAllText(filename)) ?? []) - .Select(x => x with { RefreshToken = EncryptedFieldHelper.Decrypt(x.RefreshToken) }) + .Select(x => x with { RefreshToken = EncryptedFieldHelper.Decrypt(x.RefreshToken, key) }) .ToList(); return []; diff --git a/Duplicati/CommandLine/ServerUtil/SettingsBinder.cs b/Duplicati/CommandLine/ServerUtil/SettingsBinder.cs index 583e0609a..b0e40d5d0 100644 --- a/Duplicati/CommandLine/ServerUtil/SettingsBinder.cs +++ b/Duplicati/CommandLine/ServerUtil/SettingsBinder.cs @@ -1,5 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System.CommandLine; using System.CommandLine.Binding; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Encryption; +using Duplicati.Library.Main; +using Uri = System.Uri; +using Utility = Duplicati.Library.Utility.Utility; namespace Duplicati.CommandLine.ServerUtil; @@ -15,11 +41,15 @@ public class SettingsBinder : BinderBase /// /// The host URL option. /// - public static readonly Option hostUrlOption = new Option("--hosturl", description: "The host URL to use", getDefaultValue: () => new Uri("http://localhost:8200")); + public static readonly Option hostUrlOption = new Option("--hosturl", description: "The host URL to use", getDefaultValue: () => new Uri($"http://{Utility.IpVersionCompatibleLoopback}:8200")); /// /// The server datafolder option. /// - public static readonly Option serverDatafolderOption = new Option("--server-datafolder", description: "The server datafolder to use for locating the database", getDefaultValue: () => null); + public static readonly Option serverDatafolderOption = new Option($"--{DataFolderManager.SERVER_DATAFOLDER_OPTION}", description: "The server datafolder to use for locating the database and storing configuration", getDefaultValue: () => new DirectoryInfo(DataFolderManager.DATAFOLDER)); + /// + /// The server portable mode option. + /// + public static readonly Option portableModeOption = new Option($"--{DataFolderManager.PORTABLE_MODE_OPTION}", description: "Use portable mode for locating the database and storing configuration", getDefaultValue: () => DataFolderManager.PORTABLE_MODE); /// /// The settings file option. /// @@ -31,10 +61,34 @@ public class SettingsBinder : BinderBase public static readonly Option insecureOption = new Option("--insecure", description: "Accepts any TLS/SSL certificate (dangerous)", getDefaultValue: () => false); /// + /// The settings encryption key option. + /// + public static readonly Option settingsEncryptionKeyOption = new Option("--settings-encryption-key", description: $"The encryption key to use for the settings file. Can also be supplied with environment variable {EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME}", getDefaultValue: () => null); + + /// + /// The secret provider option. + /// + public static readonly Option secretProviderOption = new Option("--secret-provider", description: "The secret provider to use for reading secrets", getDefaultValue: () => null); + + /// + /// The secret provider cache option. + /// + public static readonly Option secretProviderCacheOption = new Option("--secret-provider-cache", description: "The secret provider cache to use for reading secrets", getDefaultValue: () => SecretProviderHelper.CachingLevel.None); + + /// + /// The secret provider pattern option. + /// + public static readonly Option secretProviderPatternOption = new Option("--secret-provider-pattern", description: "The pattern to use for the secret provider", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN); /// The accepted host certificate option. /// public static readonly Option acceptedHostCertificateOption = new Option("--host-cert", description: "The SHA1 hash of the host certificate to accept. Use * for any certificate, same as --insecure (dangerous)", getDefaultValue: () => string.Empty); + /// + /// Option to wrap stdout as a json. + /// + public static readonly Option jsonOutputOption = + new Option("--json", description: "Wraps stdout as a json", getDefaultValue: () => false); + /// /// Adds global options to the root command. /// @@ -45,9 +99,15 @@ public class SettingsBinder : BinderBase rootCommand.AddGlobalOption(passwordOption); rootCommand.AddGlobalOption(hostUrlOption); rootCommand.AddGlobalOption(serverDatafolderOption); + rootCommand.AddGlobalOption(portableModeOption); rootCommand.AddGlobalOption(settingsFileOption); rootCommand.AddGlobalOption(insecureOption); + rootCommand.AddGlobalOption(settingsEncryptionKeyOption); + rootCommand.AddGlobalOption(secretProviderOption); + rootCommand.AddGlobalOption(secretProviderCacheOption); + rootCommand.AddGlobalOption(secretProviderPatternOption); rootCommand.AddGlobalOption(acceptedHostCertificateOption); + rootCommand.AddGlobalOption(jsonOutputOption); return rootCommand; } @@ -60,9 +120,12 @@ public class SettingsBinder : BinderBase Settings.Load( bindingContext.ParseResult.GetValueForOption(passwordOption), bindingContext.ParseResult.GetValueForOption(hostUrlOption), - bindingContext.ParseResult.GetValueForOption(serverDatafolderOption)?.FullName, bindingContext.ParseResult.GetValueForOption(settingsFileOption)?.FullName ?? "settings.json", bindingContext.ParseResult.GetValueForOption(insecureOption), + bindingContext.ParseResult.GetValueForOption(settingsEncryptionKeyOption) ?? Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME), + bindingContext.ParseResult.GetValueForOption(secretProviderOption), + bindingContext.ParseResult.GetValueForOption(secretProviderCacheOption), + bindingContext.ParseResult.GetValueForOption(secretProviderPatternOption) ?? SecretProviderHelper.DEFAULT_PATTERN, bindingContext.ParseResult.GetValueForOption(acceptedHostCertificateOption) ); diff --git a/Duplicati/CommandLine/ServerUtil/UserReportedException.cs b/Duplicati/CommandLine/ServerUtil/UserReportedException.cs index d9ff02f81..f6d746aa8 100644 --- a/Duplicati/CommandLine/ServerUtil/UserReportedException.cs +++ b/Duplicati/CommandLine/ServerUtil/UserReportedException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.CommandLine.ServerUtil; /// diff --git a/Duplicati/CommandLine/SourceTool/CommandExtensions.cs b/Duplicati/CommandLine/SourceTool/CommandExtensions.cs new file mode 100644 index 000000000..019672143 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/CommandExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.CommandLine; +using System.CommandLine.Invocation; + +namespace Duplicati.CommandLine.SourceTool; + +/// +/// Extensions for . +/// +public static class CommandExtensions +{ + /// + /// Adds the missing WithHandler method to . + /// + /// The command to add the handler to. + /// The handler to add. + /// The command with the handler added. + public static Command WithHandler(this Command command, ICommandHandler handler) + { + command.Handler = handler; + return command; + } +} diff --git a/Duplicati/CommandLine/SourceTool/Commands/Download.cs b/Duplicati/CommandLine/SourceTool/Commands/Download.cs new file mode 100644 index 000000000..0297a2ae3 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/Commands/Download.cs @@ -0,0 +1,110 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Duplicati.CommandLine.SourceTool.Commands; + +/// +/// The download command +/// +public static class Download +{ + /// + /// Creates the download command + /// + /// The download command + public static Command Create() => + new Command("download", "Downloads all files from the remote") + { + new Argument("url", "The source URL") { + Arity = ArgumentArity.ExactlyOne + }, + new Option("--destination", description: "The destination folder", getDefaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory())), + new Option("--max-depth", description: "The maximum depth to visit", getDefaultValue: () => 0), + new Option("--max-size", description: "The maximum filesize to download", getDefaultValue: () => 0), + new Option("--overwrite", description: "Overwrite existing files", getDefaultValue: () => false), + } + .WithHandler(CommandHandler.Create(async (url, destination, maxdepth, maxsize, overwrite) => + { + var token = new CancellationTokenSource().Token; + + using var source = await Common.GetProvider(url); + + string localPath(string path) + { + var relpath = path.Substring(source.MountedPath.Length).TrimStart(Path.DirectorySeparatorChar); + return Path.Combine(destination.FullName, relpath); + } + + var started = DateTime.UtcNow; + var fileCount = 0L; + var folderCount = 0L; + var totalSize = 0L; + var downloadCount = 0L; + var downloadSize = 0L; + + await Common.Visit(source, maxdepth, async (entry, level) => + { + if (entry.IsMetaEntry) + return true; + + var path = localPath(entry.Path); + if (entry.IsFolder) + { + folderCount++; + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + } + else + { + fileCount++; + totalSize += entry.Size; + + if (entry.Size <= maxsize || maxsize <= 0) + { + var folder = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(folder) && !Directory.Exists(folder)) + Directory.CreateDirectory(folder); + + if (!overwrite && File.Exists(path)) + { + Console.WriteLine($"Skipping {entry.Path} as it already exists"); + } + else + { + downloadCount++; + downloadSize += entry.Size; + Console.WriteLine($"Downloading {entry.Path} to {path} ({Library.Utility.Utility.FormatSizeString(entry.Size)})"); + await using var stream = await entry.OpenRead(token); + await using var dest = File.OpenWrite(path); + await stream.CopyToAsync(dest, token); + } + } + } + + return true; + }, token); + + Console.WriteLine($"Found {fileCount} files and {folderCount} folders with a total size of {Library.Utility.Utility.FormatSizeString(totalSize)}"); + Console.WriteLine($"Downloaded {downloadCount} files with a total size of {Library.Utility.Utility.FormatSizeString(downloadSize)} in {DateTime.UtcNow - started}"); + })); +} diff --git a/Duplicati/CommandLine/SourceTool/Commands/List.cs b/Duplicati/CommandLine/SourceTool/Commands/List.cs new file mode 100644 index 000000000..f5ceca3fb --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/Commands/List.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; + +namespace Duplicati.CommandLine.SourceTool.Commands; + +/// +/// The list command +/// +public static class List +{ + /// + /// Creates the list command + /// + /// The command + public static Command Create() => + new Command("list", "Lists all paths on the remote") + { + new Argument("url", "The source URL") { + Arity = ArgumentArity.ExactlyOne + }, + new Option("--max-depth", description: "The maximum depth to list", getDefaultValue: () => 0), + } + .WithHandler(CommandHandler.Create(async (url, maxdepth) => + { + var token = new CancellationTokenSource().Token; + var folders = 0L; + var files = 0L; + + using var source = await Common.GetProvider(url); + await Common.Visit(source, maxdepth, (entry, level) => + { + if (entry.IsFolder) + folders++; + else + files++; + if (entry.IsFolder && level > 0) + level--; + if (!entry.IsRootEntry) + Console.WriteLine($"{new string(' ', (level + 1) * 2)}{entry.Path}"); + return Task.FromResult(true); + }, token); + + Console.WriteLine($"Found {folders} folders and {files} files"); + }) + ); + +} diff --git a/Duplicati/CommandLine/SourceTool/Duplicati.CommandLine.SourceTool.csproj b/Duplicati/CommandLine/SourceTool/Duplicati.CommandLine.SourceTool.csproj new file mode 100644 index 000000000..99c103755 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/Duplicati.CommandLine.SourceTool.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + Duplicati.CommandLine.SourceTool.Implementation + Copyright © 2025 Team Duplicati, MIT license + Duplicati.CommandLine.Sourcetool + + + + + + + + + + + + + + + + diff --git a/Duplicati/CommandLine/SourceTool/GetProvider.cs b/Duplicati/CommandLine/SourceTool/GetProvider.cs new file mode 100644 index 000000000..f4637e398 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/GetProvider.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.DynamicLoader; +using Duplicati.Library.Interface; + +namespace Duplicati.CommandLine.SourceTool; + +/// +/// Common methods for the source tool +/// +public static partial class Common +{ + /// + /// Gets the source provider for the given URL + /// + /// The URL to get the provider for + /// The source provider + public static async Task GetProvider(string url) => + (await SourceProviderLoader.GetSourceProvider(url, "", [], CancellationToken.None)) + ?? throw new UserInformationException(string.Format("The source \"{0}\" is not supported", url), "SourceNotSupported"); +} \ No newline at end of file diff --git a/Duplicati/CommandLine/SourceTool/ListEnumerator.cs b/Duplicati/CommandLine/SourceTool/ListEnumerator.cs new file mode 100644 index 000000000..5c6ca8845 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/ListEnumerator.cs @@ -0,0 +1,60 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Interface; + +namespace Duplicati.CommandLine.SourceTool; + +/// +/// Common methods for the source tool +/// +public static partial class Common +{ + /// + /// Visits all entries in the source provider + /// + /// The source provider to visit + /// The maximum depth to visit + /// The visitor function + /// The cancellation token + /// An awaitable task + public static async Task Visit(ISourceProvider source, int maxdepth, Func> visitor, CancellationToken token) + { + var visit = new Stack<(ISourceProviderEntry Entry, int Level)>(); + await foreach (var item in source.Enumerate(token)) + visit.Push((item, 0)); + + while (visit.Count() != 0) + { + var item = visit.Pop(); + var process = await visitor(item.Entry, item.Level); + if (item.Entry.IsFolder && process && (item.Level < maxdepth || maxdepth <= 0)) + { + await foreach (var subitem in item.Entry.Enumerate(token)) + { + if (subitem.IsFolder) + visit.Push((subitem, item.Level + 1)); + else + await visitor(subitem, item.Level); + } + } + } + } +} diff --git a/Duplicati/CommandLine/SourceTool/Program.cs b/Duplicati/CommandLine/SourceTool/Program.cs new file mode 100644 index 000000000..c8aea4225 --- /dev/null +++ b/Duplicati/CommandLine/SourceTool/Program.cs @@ -0,0 +1,68 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.CommandLine; +using System.CommandLine.Builder; +using System.CommandLine.Parsing; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.CommandLine.SourceTool; + +/// +/// The entry point of the application +/// +public static class Program +{ + /// + /// Executes the program + /// + /// The arguments + /// The return code + public static Task Main(string[] args) + { + Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.ServerUtil); + + var rootCmd = new RootCommand("Source CLI tool for Duplicati") + { + Commands.List.Create(), + Commands.Download.Create(), + }; + + return new CommandLineBuilder(rootCmd) + .UseDefaults() + .UseExceptionHandler((ex, context) => + { + if (ex is UserInformationException uie) + { + Console.WriteLine(uie.Message); + context.ExitCode = 2; + } + else + { + Console.WriteLine(ex.ToString()); + context.ExitCode = 1; + } + }) + .UseAdditionalHelpAliases() + .Build() + .InvokeAsync(args); + } +} diff --git a/Duplicati/Duplicati.Browser.Test/Drivers/BrowserDriver.cs b/Duplicati/Duplicati.Browser.Test/Drivers/BrowserDriver.cs index d1c6697d6..f961dd834 100644 --- a/Duplicati/Duplicati.Browser.Test/Drivers/BrowserDriver.cs +++ b/Duplicati/Duplicati.Browser.Test/Drivers/BrowserDriver.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Threading; using OpenQA.Selenium; using OpenQA.Selenium.Edge; diff --git a/Duplicati/Duplicati.Browser.Test/Duplicati.Browser.Test.csproj b/Duplicati/Duplicati.Browser.Test/Duplicati.Browser.Test.csproj index fe5a95e0a..c1bb330d4 100644 --- a/Duplicati/Duplicati.Browser.Test/Duplicati.Browser.Test.csproj +++ b/Duplicati/Duplicati.Browser.Test/Duplicati.Browser.Test.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/Duplicati/Duplicati.Browser.Test/Features/LocalBackup.feature.cs b/Duplicati/Duplicati.Browser.Test/Features/LocalBackup.feature.cs index 141b4f297..69f97a779 100644 --- a/Duplicati/Duplicati.Browser.Test/Features/LocalBackup.feature.cs +++ b/Duplicati/Duplicati.Browser.Test/Features/LocalBackup.feature.cs @@ -1,4 +1,4 @@ -// ------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------ // // This code was generated by SpecFlow (https://www.specflow.org/). // SpecFlow Version:3.7.0.0 @@ -15,8 +15,8 @@ namespace Duplicati.Browser.Test.Features using TechTalk.SpecFlow; using System; using System.Linq; - - + + [System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "3.7.0.0")] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [NUnit.Framework.TestFixtureAttribute()] @@ -24,15 +24,15 @@ namespace Duplicati.Browser.Test.Features [NUnit.Framework.CategoryAttribute("Duplicati")] public partial class DuplicatiLocalBackupFeature { - + private TechTalk.SpecFlow.ITestRunner testRunner; - + private string[] _featureTags = new string[] { "Duplicati"}; - + #line 1 "LocalBackup.feature" #line hidden - + [NUnit.Framework.OneTimeSetUpAttribute()] public virtual void FeatureSetup() { @@ -42,41 +42,41 @@ namespace Duplicati.Browser.Test.Features "Duplicati"}); testRunner.OnFeatureStart(featureInfo); } - + [NUnit.Framework.OneTimeTearDownAttribute()] public virtual void FeatureTearDown() { testRunner.OnFeatureEnd(); testRunner = null; } - + [NUnit.Framework.SetUpAttribute()] public virtual void TestInitialize() { } - + [NUnit.Framework.TearDownAttribute()] public virtual void TestTearDown() { testRunner.OnScenarioEnd(); } - + public virtual void ScenarioInitialize(TechTalk.SpecFlow.ScenarioInfo scenarioInfo) { testRunner.OnScenarioInitialize(scenarioInfo); testRunner.ScenarioContext.ScenarioContainer.RegisterInstanceAs(NUnit.Framework.TestContext.CurrentContext); } - + public virtual void ScenarioStart() { testRunner.OnScenarioStart(); } - + public virtual void ScenarioCleanup() { testRunner.CollectScenarioErrors(); } - + [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Configure Backup")] public virtual void ConfigureBackup() @@ -85,7 +85,7 @@ namespace Duplicati.Browser.Test.Features System.Collections.Specialized.OrderedDictionary argumentsOfScenario = new System.Collections.Specialized.OrderedDictionary(); TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Configure Backup", null, tagsOfScenario, argumentsOfScenario, this._featureTags); #line 6 -this.ScenarioInitialize(scenarioInfo); + this.ScenarioInitialize(scenarioInfo); #line hidden bool isScenarioIgnored = default(bool); bool isFeatureIgnored = default(bool); @@ -105,18 +105,18 @@ this.ScenarioInitialize(scenarioInfo); { this.ScenarioStart(); #line 7 - testRunner.Given("there is local backup defined", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); + testRunner.Given("there is local backup defined", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line hidden #line 8 - testRunner.When("you run the backup", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.When("you run the backup", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden #line 9 - testRunner.Then("the result should be 120", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.Then("the result should be 120", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden } this.ScenarioCleanup(); } - + [NUnit.Framework.TestAttribute()] [NUnit.Framework.DescriptionAttribute("Add two numbers permutations")] [NUnit.Framework.TestCaseAttribute("0", "0", "0", null)] @@ -131,7 +131,7 @@ this.ScenarioInitialize(scenarioInfo); argumentsOfScenario.Add("Expected result", expectedResult); TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Add two numbers permutations", null, tagsOfScenario, argumentsOfScenario, this._featureTags); #line 12 -this.ScenarioInitialize(scenarioInfo); + this.ScenarioInitialize(scenarioInfo); #line hidden bool isScenarioIgnored = default(bool); bool isFeatureIgnored = default(bool); @@ -151,16 +151,16 @@ this.ScenarioInitialize(scenarioInfo); { this.ScenarioStart(); #line 13 - testRunner.Given(string.Format("the first number is {0}", firstNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); + testRunner.Given(string.Format("the first number is {0}", firstNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given "); #line hidden #line 14 - testRunner.And(string.Format("the second number is {0}", secondNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); + testRunner.And(string.Format("the second number is {0}", secondNumber), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And "); #line hidden #line 15 - testRunner.When("the two numbers are added", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); + testRunner.When("the two numbers are added", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When "); #line hidden #line 16 - testRunner.Then(string.Format("the result should be {0}", expectedResult), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); + testRunner.Then(string.Format("the result should be {0}", expectedResult), ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then "); #line hidden } this.ScenarioCleanup(); diff --git a/Duplicati/Duplicati.Browser.Test/Hooks/DuplicatiHooks.cs b/Duplicati/Duplicati.Browser.Test/Hooks/DuplicatiHooks.cs index 7eeb2aaed..f0933d4e9 100644 --- a/Duplicati/Duplicati.Browser.Test/Hooks/DuplicatiHooks.cs +++ b/Duplicati/Duplicati.Browser.Test/Hooks/DuplicatiHooks.cs @@ -1,4 +1,24 @@ -using Duplicati.Browser.Test.Drivers; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Browser.Test.Drivers; using Duplicati.Browser.Test.PageObjects; using TechTalk.SpecFlow; diff --git a/Duplicati/Duplicati.Browser.Test/Hooks/SharedBrowserHooks.cs b/Duplicati/Duplicati.Browser.Test/Hooks/SharedBrowserHooks.cs index c9f975509..ccede8039 100644 --- a/Duplicati/Duplicati.Browser.Test/Hooks/SharedBrowserHooks.cs +++ b/Duplicati/Duplicati.Browser.Test/Hooks/SharedBrowserHooks.cs @@ -1,4 +1,24 @@ -using BoDi; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using BoDi; using Duplicati.Browser.Test.Drivers; using TechTalk.SpecFlow; diff --git a/Duplicati/Duplicati.Browser.Test/PageObjects/AddBackupPage.cs b/Duplicati/Duplicati.Browser.Test/PageObjects/AddBackupPage.cs index 882aede25..56f6f1334 100644 --- a/Duplicati/Duplicati.Browser.Test/PageObjects/AddBackupPage.cs +++ b/Duplicati/Duplicati.Browser.Test/PageObjects/AddBackupPage.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Threading; using Duplicati.Browser.Test.Drivers; using OpenQA.Selenium; diff --git a/Duplicati/Duplicati.Browser.Test/PageObjects/DuplicatiPageObject.cs b/Duplicati/Duplicati.Browser.Test/PageObjects/DuplicatiPageObject.cs index 3526929c4..9a69bf95b 100644 --- a/Duplicati/Duplicati.Browser.Test/PageObjects/DuplicatiPageObject.cs +++ b/Duplicati/Duplicati.Browser.Test/PageObjects/DuplicatiPageObject.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using Duplicati.Browser.Test.Drivers; using OpenQA.Selenium; using OpenQA.Selenium.Support.UI; diff --git a/Duplicati/Duplicati.Browser.Test/Steps/DuplicatiStepDefinitions.cs b/Duplicati/Duplicati.Browser.Test/Steps/DuplicatiStepDefinitions.cs index 3f540cb4e..5bb5032cf 100644 --- a/Duplicati/Duplicati.Browser.Test/Steps/DuplicatiStepDefinitions.cs +++ b/Duplicati/Duplicati.Browser.Test/Steps/DuplicatiStepDefinitions.cs @@ -1,4 +1,24 @@ -using System.IO; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.IO; using Duplicati.Browser.Test.Drivers; using Duplicati.Browser.Test.PageObjects; using FluentAssertions; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-open.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-open.png index 9064757cc..ec32368d2 100644 Binary files a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-open.png and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-open.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-pause.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-pause.png index d1f5f8f2e..914a798ba 100644 Binary files a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-pause.png and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-pause.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-quit.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-quit.png index 6f62bea26..3251cf159 100644 Binary files a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-quit.png and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-quit.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-resume.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-resume.png index 9a9173f1b..a727e2342 100644 Binary files a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-resume.png and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/context-menu-resume.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/normal-disconnected.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/normal-disconnected.png new file mode 100644 index 000000000..1c9813b2d Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/linux/normal-disconnected.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-open.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-open.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-open.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-open.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-pause.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-pause.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-pause.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-pause.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-quit.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-quit.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-quit.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-quit.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-resume.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-resume.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/context-menu-resume.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/context-menu-resume.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-disconnected.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-disconnected.png new file mode 100644 index 000000000..1f29949ed Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-disconnected.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-error.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-error.png new file mode 100644 index 000000000..b99f7749c Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-error.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-pause.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-pause.png new file mode 100644 index 000000000..54ab9241c Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-pause.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-running.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-running.png new file mode 100644 index 000000000..42dc139df Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-running.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-warning.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-warning.png new file mode 100644 index 000000000..ca0e6071b Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal-warning.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal.png new file mode 100644 index 000000000..41b40e3ff Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/dark/normal.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-open.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-open.png new file mode 100644 index 000000000..ec32368d2 Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-open.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-pause.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-pause.png new file mode 100644 index 000000000..914a798ba Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-pause.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-quit.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-quit.png new file mode 100644 index 000000000..3251cf159 Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-quit.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-resume.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-resume.png new file mode 100644 index 000000000..a727e2342 Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/context-menu-resume.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-disconnected.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-disconnected.png new file mode 100644 index 000000000..f74fbf15e Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-disconnected.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-error.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-error.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-error.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-error.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-pause.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-pause.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-pause.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-pause.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-running.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-running.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-running.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-running.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-warning.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-warning.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal-warning.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal-warning.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal.png similarity index 100% rename from Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/normal.png rename to Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/macos/light/normal.png diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/windows/normal-disconnected.png b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/windows/normal-disconnected.png new file mode 100644 index 000000000..d2260393b Binary files /dev/null and b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Assets/icons/windows/normal-disconnected.png differ diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/AvaloniaRunner.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/AvaloniaRunner.cs index 980b8c554..c93be6788 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/AvaloniaRunner.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/AvaloniaRunner.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,6 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -40,7 +42,8 @@ namespace Duplicati.GUI.TrayIcon public class AvaloniaRunner : TrayIconBase { private static readonly string LOGTAG = Log.LogTagFromType(); - private AvaloniaApp application; + private AvaloniaApp? application; + private bool closed = false; private ProcessBasedActionDelay actionDelayer = new ProcessBasedActionDelay(); private IEnumerable menuItems = Enumerable.Empty(); @@ -49,10 +52,10 @@ namespace Duplicati.GUI.TrayIcon base.Init(args); } - protected override void UpdateUIState(Action action) + protected override Task UpdateUIState(Action action) => RunOnUIThread(action); - internal void RunOnUIThread(Action action) + internal Task RunOnUIThread(Action action) => actionDelayer.ExecuteAction(() => { try @@ -68,6 +71,9 @@ namespace Duplicati.GUI.TrayIcon private void RunOnUIThreadInternal(Action action) { + if (closed) + return; + if (Dispatcher.UIThread.CheckAccess()) action(); else @@ -106,6 +112,8 @@ namespace Duplicati.GUI.TrayIcon #endif application = builder.Instance as AvaloniaApp; + if (application == null) + throw new InvalidOperationException("Failed to create Avalonia app"); application.SetMenu(menuItems); application.Configure(); @@ -114,7 +122,7 @@ namespace Duplicati.GUI.TrayIcon private async void CheckForAppInitialized(IClassicDesktopStyleApplicationLifetime lifetime) { - Exception lastEx = null; + Exception? lastEx = null; // Jump out of the UI thread, ot ensure that the check is done with posting to the UI thread, // as opposed to just calling the method directly @@ -164,7 +172,7 @@ namespace Duplicati.GUI.TrayIcon return new AvaloniaMenuItem(this, text, icon, callback, subitems); } - protected override void NotifyUser(string title, string message, NotificationType type) + public override void NotifyUser(string title, string message, NotificationType type) { //var icon = Win32NativeNotifyIcon.InfoFlags.NIIF_INFO; @@ -180,13 +188,16 @@ namespace Duplicati.GUI.TrayIcon //icon = Win32NativeNotifyIcon.InfoFlags.NIIF_ERROR; break; } - - //m_ntfIcon.ShowBalloonTip(title, message, icon); } protected override void Exit() { - UpdateUIState(() => this.application?.Shutdown()); + UpdateUIState(() => + { + this.closed = true; + this.application?.Shutdown(); + + }); } protected override void SetIcon(TrayIcons icon) @@ -209,10 +220,10 @@ namespace Duplicati.GUI.TrayIcon internal static string GetThemePath() { - // var isDark = string.Equals(Application.Current.ActualThemeVariant.ToString(), "Dark", StringComparison.OrdinalIgnoreCase); + var isDark = string.Equals(Application.Current?.ActualThemeVariant.ToString(), "Dark", StringComparison.OrdinalIgnoreCase); if (OperatingSystem.IsMacOS()) - return "macos"; + return isDark ? "macos/dark" : "macos/light"; if (OperatingSystem.IsWindows()) return "windows"; @@ -220,15 +231,22 @@ namespace Duplicati.GUI.TrayIcon return "linux"; } - + private static Dictionary BitmapCache = new Dictionary(); internal static Bitmap LoadBitmap(string iconName) { - return new Bitmap(AssetLoader.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().FullName}/Assets/icons/{GetThemePath()}/" + iconName))); + if (BitmapCache.TryGetValue(iconName, out var bitmap)) + return bitmap; + + return BitmapCache[iconName] = new Bitmap(AssetLoader.Open(new Uri($"avares://{Assembly.GetExecutingAssembly().FullName}/Assets/icons/{GetThemePath()}/" + iconName))); } + private static Dictionary IconCache = new Dictionary(); internal static WindowIcon LoadIcon(string iconName) { - return new WindowIcon(LoadBitmap(iconName)); + if (IconCache.TryGetValue(iconName, out var icon)) + return icon; + + return IconCache[iconName] = new WindowIcon(LoadBitmap(iconName)); } } @@ -237,11 +255,12 @@ namespace Duplicati.GUI.TrayIcon public string Text { get; private set; } public Action Callback { get; private set; } - public IList SubItems { get; private set; } + public IList? SubItems { get; private set; } public bool Enabled { get; private set; } public MenuIcons Icon { get; private set; } public bool IsDefault { get; private set; } - private NativeMenuItem nativeMenuItem; + public bool Hidden { get; private set; } + private NativeMenuItem? nativeMenuItem; private readonly AvaloniaRunner parent; public AvaloniaMenuItem(AvaloniaRunner parent, string text, MenuIcons icon, Action callback, IList subitems) @@ -278,6 +297,9 @@ namespace Duplicati.GUI.TrayIcon /// private void UpdateIcon() { + if (nativeMenuItem == null) + return; + nativeMenuItem.Icon = this.Icon switch { MenuIcons.Status => AvaloniaRunner.LoadBitmap("context-menu-open.png"), @@ -301,6 +323,14 @@ namespace Duplicati.GUI.TrayIcon // Not currently supported by Avalonia, // used to set the menu bold on Windows to indicate the default entry } + + public void SetHidden(bool hidden) + { + this.Hidden = hidden; + if (nativeMenuItem != null) + parent.RunOnUIThread(() => nativeMenuItem.IsVisible = !this.Hidden); + + } #endregion public NativeMenuItem GetNativeItem() @@ -324,9 +354,9 @@ namespace Duplicati.GUI.TrayIcon public class AvaloniaApp : Application { - private Avalonia.Controls.TrayIcon trayIcon; + private Avalonia.Controls.TrayIcon? trayIcon; - private List menuItems; + private List? menuItems; public override void Initialize() { @@ -343,6 +373,9 @@ namespace Duplicati.GUI.TrayIcon case TrayIcons.IdleError: this.trayIcon.Icon = AvaloniaRunner.LoadIcon("normal-error.png"); break; + case TrayIcons.IdleWarning: + this.trayIcon.Icon = AvaloniaRunner.LoadIcon("normal-warning.png"); + break; case TrayIcons.Paused: case TrayIcons.PausedError: this.trayIcon.Icon = AvaloniaRunner.LoadIcon("normal-pause.png"); @@ -355,13 +388,17 @@ namespace Duplicati.GUI.TrayIcon default: this.trayIcon.Icon = AvaloniaRunner.LoadIcon("normal.png"); break; + case TrayIcons.Disconnected: + this.trayIcon.Icon = AvaloniaRunner.LoadIcon("normal-disconnected.png"); + break; } } public void Configure() { this.Name = Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName; - this.trayIcon.ToolTipText = this.Name; + if (this.trayIcon != null) + this.trayIcon.ToolTipText = this.Name; } public void SetMenu(IEnumerable menuItems) @@ -377,6 +414,18 @@ namespace Duplicati.GUI.TrayIcon menu.Add(item.GetNativeItem()); } trayIcon.Menu = menu; + trayIcon.Clicked -= HandleTrayIconClick; + trayIcon.Clicked += HandleTrayIconClick; + } + } + + private readonly ClickDebouncer _clickDebouncer = new ClickDebouncer(); + + private void HandleTrayIconClick(object? sender, EventArgs e) + { + if (_clickDebouncer.ShouldProcessClick()) + { + this.menuItems?.FirstOrDefault(x => x.IsDefault)?.Callback(); } } @@ -412,10 +461,10 @@ namespace Duplicati.GUI.TrayIcon public bool IsEnabled(LogEventLevel level, string area) => level >= _minLevel; - public void Log(LogEventLevel level, string area, object source, string messageTemplate) - => Log(level, area, source, messageTemplate, null); + public void Log(LogEventLevel level, string area, object? source, string messageTemplate) + => Log(level, area, source, messageTemplate, []); - public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + public void Log(LogEventLevel level, string area, object? source, string messageTemplate, params object?[] propertyValues) => Console.WriteLine($"Avalonia [{level}]: {source} {messageTemplate} {string.Join(" ", propertyValues)}"); } diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/ClickDebouncer.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ClickDebouncer.cs new file mode 100644 index 000000000..e3f012c86 --- /dev/null +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ClickDebouncer.cs @@ -0,0 +1,27 @@ +using System; + +namespace Duplicati.GUI.TrayIcon +{ + internal class ClickDebouncer + { + private DateTime _lastClickTime = DateTime.MinValue; + private readonly TimeSpan _debounceInterval; + + public ClickDebouncer(TimeSpan? debounceInterval = null) + { + _debounceInterval = debounceInterval ?? TimeSpan.FromSeconds(1); + } + + public bool ShouldProcessClick() + { + var now = DateTime.UtcNow; + if (now - _lastClickTime < _debounceInterval) + { + return false; + } + + _lastClickTime = now; + return true; + } + } +} \ No newline at end of file diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj index 53c6b9bdb..2e38301db 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj @@ -10,7 +10,7 @@ copyused true app.manifest - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -29,11 +29,11 @@ - - - + + + - + diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs index b28d76eee..2eea00385 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HostedInstanceKeeper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,9 +20,7 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; using System.Linq; -using System.Text; namespace Duplicati.GUI.TrayIcon { @@ -32,27 +30,27 @@ namespace Duplicati.GUI.TrayIcon internal class HostedInstanceKeeper : IDisposable { private readonly System.Threading.Thread m_runner; - private System.Exception m_runnerException = null; - public event Action InstanceShutdown; + private Exception m_runnerException = null; + public Action InstanceShutdown; public HostedInstanceKeeper(string[] args) { - m_runner = new System.Threading.Thread((dummy_arg) => + m_runner = new System.Threading.Thread(_ => { try { //When running the hosted instance we do not really care what port we are using, // so we just throw a few out there and try them - if (args == null || !args.Any(x => x.Trim().StartsWith("--" + Duplicati.Server.WebServerLoader.OPTION_PORT + "=", StringComparison.OrdinalIgnoreCase))) - args = (args ?? new string[0]).Union(new string[] { "--" + Duplicati.Server.WebServerLoader.OPTION_PORT + "=8200,8300,8400,8500,8600,8700,8800,8900,8989" }).ToArray(); + if (args == null || !args.Any(x => x.Trim().StartsWith("--" + Server.WebServerLoader.OPTION_PORT + "=", StringComparison.OrdinalIgnoreCase))) + args = (args ?? new string[0]).Union(new string[] { "--" + Server.WebServerLoader.OPTION_PORT + "=8200,8300,8400,8500,8600,8700,8800,8900,8989" }).ToArray(); - Duplicati.Server.Program.Main(args); + Server.Program.Main(args); } catch (Exception ex) { m_runnerException = ex; - Duplicati.Server.Program.ServerStartedEvent?.Set(); - Duplicati.Server.Program.ApplicationExitEvent?.Set(); + Server.Program.ServerStartedEvent?.Set(); + Server.Program.ApplicationExitEvent?.Set(); } finally { @@ -71,23 +69,23 @@ namespace Duplicati.GUI.TrayIcon m_runner.Start(); - if (!Duplicati.Server.Program.ServerStartedEvent.WaitOne(TimeSpan.FromSeconds(100), true)) + if (!Server.Program.ServerStartedEvent.WaitOne(TimeSpan.FromSeconds(100), true)) { if (m_runnerException != null) - throw new Duplicati.Library.Interface.UserInformationException("Server crashed on startup", "HostedStartupErrorCrash", m_runnerException); + throw new Library.Interface.UserInformationException("Server crashed on startup", "HostedStartupErrorCrash", m_runnerException); else - throw new Duplicati.Library.Interface.UserInformationException("Hosted server startup timed out", "HostedStartupError"); + throw new Library.Interface.UserInformationException("Hosted server startup timed out", "HostedStartupError"); } if (m_runnerException != null) - throw new Duplicati.Library.Interface.UserInformationException("Server crashed on startup", "HostedStartupErrorCrash", m_runnerException); + throw new Library.Interface.UserInformationException("Server crashed on startup", "HostedStartupErrorCrash", m_runnerException); } public void Dispose() { try { - Duplicati.Server.Program.ApplicationExitEvent.Set(); + Server.Program.ApplicationExitEvent.Set(); if (!m_runner.Join(TimeSpan.FromSeconds(10))) { m_runner.Interrupt(); diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs index 005fba921..0f3ada47a 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/HttpServerConnection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -31,6 +31,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using CoCoL; using Duplicati.Library.Common.IO; using Duplicati.Library.RestAPI; using Duplicati.Server.Serialization; @@ -87,9 +88,10 @@ namespace Duplicati.GUI.TrayIcon private readonly string m_baseUri; private string m_password; private string m_accesstoken; + private bool m_isTryingWithPassword; - public delegate void StatusUpdateDelegate(IServerStatus status); - public event StatusUpdateDelegate OnStatusUpdated; + public Func OnStatusUpdated; + public Action ConnectionClosed; public long m_lastNotificationId = -1; public DateTime m_firstNotificationTime; @@ -100,22 +102,20 @@ namespace Duplicati.GUI.TrayIcon private long m_lastDataUpdateId = -1; private bool m_disableTrayIconLogin; - private volatile IServerStatus m_status; + private volatile IServerStatus m_status = new ServerStatusImpl(LiveControlState.Paused, SuggestedStatusIcon.Disconnected, 0, 0, 0); - private volatile bool m_shutdown = false; - private volatile Thread m_requestThread; - private volatile Thread m_pollThread; - private readonly AutoResetEvent m_waitLock; + private Task m_requestHandlerTask; + private Task m_pollThread; + private readonly CancellationTokenSource m_stopToken = new CancellationTokenSource(); private readonly Dictionary m_options; private readonly Program.PasswordSource m_passwordSource; public IServerStatus Status { get { return m_status; } } - private readonly object m_lock = new object(); - private readonly Queue m_workQueue = new Queue(); + private readonly IChannel m_workQueue = Channel.Create(name: "TrayIconRequestQueue"); - public HttpServerConnection(Uri server, string password, Program.PasswordSource passwordSource, bool disableTrayIconLogin, string acceptedHostCertificate, Dictionary options) + public HttpServerConnection(System.Uri server, string password, Program.PasswordSource passwordSource, bool disableTrayIconLogin, string acceptedHostCertificate, Dictionary options) { m_baseUri = Util.AppendDirSeparator(server.ToString(), "/"); @@ -131,7 +131,7 @@ namespace Duplicati.GUI.TrayIcon var acceptedCertificates = new HashSet(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrWhiteSpace(acceptedHostCertificate)) - acceptedCertificates.UnionWith(acceptedHostCertificate.Split(new char[] {',', ';'}, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); + acceptedCertificates.UnionWith(acceptedHostCertificate.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)); HTTPCLIENT = new(new HttpClientHandler { @@ -160,47 +160,53 @@ namespace Duplicati.GUI.TrayIcon } }; - // TODO: Not nice to do in constructor - // Get a connection - UpdateStatus(false); + m_requestHandlerTask = ThreadRunner(); + m_pollThread = LongPollRunner(); - m_waitLock = new AutoResetEvent(false); - m_requestThread = new Thread(ThreadRunner); - m_pollThread = new Thread(LongPollRunner); + m_requestHandlerTask.ContinueWith(t => + { + if (t.IsFaulted) + Library.Logging.Log.WriteWarningMessage(LOGTAG, "TrayIconRequestError", t.Exception, "Crashed request handler"); + ConnectionClosed?.Invoke(); + }); - m_requestThread.Name = "TrayIcon Request Thread"; - m_pollThread.Name = "TrayIcon Longpoll Thread"; - - m_requestThread.Start(); - m_pollThread.Start(); + m_pollThread.ContinueWith(t => + { + if (t.IsFaulted) + Library.Logging.Log.WriteWarningMessage(LOGTAG, "TrayIconRequestError", t.Exception, "Crashed poll thread"); + ConnectionClosed?.Invoke(); + }); } - private void UpdateStatus(bool longpoll) + public Task UpdateStatus() + => UpdateStatusAsync(false); + + private async Task UpdateStatusAsync(bool longpoll) { var query = longpoll ? $"?longpoll=true&lastEventId={m_lastEventId}&duration={LONGPOLL_TIMEOUT}" : ""; - m_status = PerformRequest("GET", $"/serverstate{query}", null, longpoll ? Library.Utility.Timeparser.ParseTimeSpan(LONGPOLL_TIMEOUT) : null); + m_status = await PerformRequestAsync("GET", $"/serverstate{query}", null, longpoll ? Library.Utility.Timeparser.ParseTimeSpan(LONGPOLL_TIMEOUT) : null); m_lastEventId = m_status.LastEventID; if (OnStatusUpdated != null) - OnStatusUpdated(m_status); + await OnStatusUpdated(m_status).ConfigureAwait(false); if (m_lastNotificationId != m_status.LastNotificationUpdateID) { m_lastNotificationId = m_status.LastNotificationUpdateID; - UpdateNotifications(); + await UpdateNotificationsAsync().ConfigureAwait(false); } if (m_lastDataUpdateId != m_status.LastDataUpdateID) { m_lastDataUpdateId = m_status.LastDataUpdateID; - UpdateApplicationSettings(); + await UpdateApplicationSettingsAsync().ConfigureAwait(false); } } - private void UpdateNotifications() + private async Task UpdateNotificationsAsync() { - var notifications = PerformRequest("GET", "/notifications", null, null); + var notifications = await PerformRequestAsync("GET", "/notifications", null, null).ConfigureAwait(false); if (notifications != null) { foreach (var n in notifications.Where(x => x.Timestamp > m_firstNotificationTime)) @@ -212,68 +218,61 @@ namespace Duplicati.GUI.TrayIcon } } - private void UpdateApplicationSettings() + private async Task UpdateApplicationSettingsAsync() { - var settings = PerformRequest>("GET", "/serversettings", null, null); + var settings = await PerformRequestAsync>("GET", "/serversettings", null, null).ConfigureAwait(false); if (settings != null && settings.TryGetValue("disable-tray-icon-login", out var str)) m_disableTrayIconLogin = Library.Utility.Utility.ParseBool(str, false); } - private void LongPollRunner() + private async Task LongPollRunner() { var started = DateTime.Now; var errorCount = 0; + var hasConnected = false; - while (!m_shutdown) + while (!m_stopToken.IsCancellationRequested) { - var waitTime = TimeSpan.FromSeconds(Math.Min(10, errorCount * 2)) - (DateTime.Now - started); - if (waitTime.TotalSeconds > 0) - Thread.Sleep(waitTime); - try { + var waitTime = TimeSpan.FromSeconds(Math.Min(10, errorCount * 2)) - (DateTime.Now - started); + if (waitTime.TotalSeconds > 0) + await Task.Delay(waitTime, m_stopToken.Token).ConfigureAwait(false); started = DateTime.Now; - UpdateStatus(true); + await UpdateStatusAsync(true); errorCount = 0; + hasConnected = true; + } + catch (Exception ex) when (ex.IsRetiredException() || m_stopToken.IsCancellationRequested) + { + Library.Logging.Log.WriteVerboseMessage(LOGTAG, "TrayIconPollRequestError", ex, "Failed to get response"); + return; } catch (Exception ex) { errorCount++; + // More than 1 error, and we are disconnected + if (errorCount >= 2 && OnStatusUpdated != null || !hasConnected) + await OnStatusUpdated.Invoke(new ServerStatusImpl(LiveControlState.Paused, SuggestedStatusIcon.Disconnected, m_lastEventId, m_lastDataUpdateId, m_lastNotificationId)); System.Diagnostics.Trace.WriteLine("Request error: " + ex.Message); - Library.Logging.Log.WriteWarningMessage(LOGTAG, "TrayIconRequestError", ex, "Failed to get response"); + Library.Logging.Log.WriteWarningMessage(LOGTAG, "TrayIconPollRequestError", ex, "Failed to get response"); } } } - private void ThreadRunner() + private async Task ThreadRunner() { - while (!m_shutdown) + while (true) { try { - BackgroundRequest req; - bool any = false; - do - { - req = null; - - lock (m_lock) - if (m_workQueue.Count > 0) - req = m_workQueue.Dequeue(); - - if (m_shutdown) - break; - - if (req != null) - { - any = true; - PerformRequest(req.Method, req.Endpoint, req.Body, req.Timeout); - } - - } while (req != null); - - if (!(any || m_shutdown)) - m_waitLock.WaitOne(TimeSpan.FromMinutes(1), true); + var req = await m_workQueue.ReadAsync().ConfigureAwait(false); + await PerformRequestAsync(req.Method, req.Endpoint, req.Body, req.Timeout).ConfigureAwait(false); + } + catch (Exception ex) when (ex.IsRetiredException() || m_stopToken.IsCancellationRequested) + { + Library.Logging.Log.WriteVerboseMessage(LOGTAG, "TrayIconRequestError", ex, "Failed to get response"); + return; } catch (Exception ex) { @@ -285,56 +284,82 @@ namespace Duplicati.GUI.TrayIcon public void Close() { - m_shutdown = true; - m_waitLock.Set(); - m_pollThread.Interrupt(); - m_pollThread.Join(TimeSpan.FromSeconds(10)); - if (!m_requestThread.Join(TimeSpan.FromSeconds(10))) - { - m_requestThread.Interrupt(); - m_requestThread.Join(TimeSpan.FromSeconds(10)); - } + m_stopToken.Cancel(); + m_workQueue.Retire(); + ConnectionClosed?.Invoke(); } private sealed record SigninResponse(string AccessToken); - private T PerformRequest(string method, string urlfragment, string body, TimeSpan? timeout) + private async Task PerformRequestAsync(string method, string urlfragment, string body, TimeSpan? timeout) { if (string.IsNullOrWhiteSpace(m_accesstoken) && !urlfragment.StartsWith("/auth/")) - ObtainAccessToken(); + await ObtainAccessTokenAsync().ConfigureAwait(false); - var hasTriedPassword = false; + var hasTriedPassword = m_isTryingWithPassword; while (true) { try { - return PerformRequestInternal(method, urlfragment, body, timeout); + return await PerformRequestInternalAsync(method, urlfragment, body, timeout).ConfigureAwait(false); } - catch (AggregateException aex) + catch (Exception ex) + when (ex is HttpRequestException hex && (hex.StatusCode == HttpStatusCode.Unauthorized || hex.StatusCode == HttpStatusCode.Forbidden) + || ex is AggregateException aex && aex.InnerExceptions.Any(x => x is HttpRequestException hex && (hex.StatusCode == HttpStatusCode.Unauthorized || hex.StatusCode == HttpStatusCode.Forbidden)) + ) { - if (hasTriedPassword || !aex.InnerExceptions.Any(x => x is HttpRequestException hex && hex.StatusCode == HttpStatusCode.Unauthorized)) + if (hasTriedPassword) throw; + // TODO: This error handling is error prone and can end up in infinite recursion + // Should rewrite the entire class and use websockets instead + // Only try once, and clear the token for the next try hasTriedPassword = true; m_accesstoken = null; - ObtainAccessToken(); + try + { + m_isTryingWithPassword = true; + await ObtainAccessTokenAsync().ConfigureAwait(false); + } + finally + { + m_isTryingWithPassword = false; + } } } } - private void ObtainAccessToken() + private async Task ObtainAccessTokenAsync() { - var token = ObtainSignInToken(); + // If we host the server, issue the access token from the service + if (FIXMEGlobal.IsServerStarted && m_passwordSource == Program.PasswordSource.HostedServer) + { + var provider = FIXMEGlobal.Provider.GetRequiredService(); + m_accesstoken = provider.CreateAccessToken("trayicon", provider.TemporaryFamilyId); + return; + } + + // If we know the password, issue a token from the API + if (!string.IsNullOrWhiteSpace(m_password)) + { + m_accesstoken = (await PerformRequestInternalAsync("POST", "/auth/login", JsonSerializer.Serialize(new { Password = m_password }), null).ConfigureAwait(false)).AccessToken; + return; + } + + // Otherwise, try to get a signin token + var token = await ObtainSignInTokenAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(token)) return; - m_accesstoken = PerformRequestInternal("POST", "/auth/signin", JsonSerializer.Serialize(new { SigninToken = token }), null).AccessToken; + + // Use the signin token to get an access token + m_accesstoken = (await PerformRequestInternalAsync("POST", "/auth/signin", JsonSerializer.Serialize(new { SigninToken = token }), null).ConfigureAwait(false)).AccessToken; } private async Task PerformRequestInternalAsync(string method, string endpoint, string body, TimeSpan? timeout) { - var request = new HttpRequestMessage(new HttpMethod(method), new Uri(m_apiUri + endpoint)); + var request = new HttpRequestMessage(new HttpMethod(method), new System.Uri(m_apiUri + endpoint)); if (!string.IsNullOrWhiteSpace(m_accesstoken)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_accesstoken); @@ -347,26 +372,19 @@ namespace Duplicati.GUI.TrayIcon ? timeout.Value + TimeSpan.FromSeconds(5) : TimeSpan.FromSeconds(100)); - var response = await HTTPCLIENT.SendAsync(request, cts.Token); + var response = await HTTPCLIENT.SendAsync(request, cts.Token).ConfigureAwait(false); response.EnsureSuccessStatusCode(); if (typeof(T) == typeof(string)) - return (T)(object)await response.Content.ReadAsStringAsync(); + return (T)(object)await response.Content.ReadAsStringAsync().ConfigureAwait(false); - using (var stream = await response.Content.ReadAsStreamAsync()) - return await JsonSerializer.DeserializeAsync(stream, serializerOptions); + using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + return await JsonSerializer.DeserializeAsync(stream, serializerOptions).ConfigureAwait(false); } - private T PerformRequestInternal(string method, string endpoint, string body, TimeSpan? timeout) - => PerformRequestInternalAsync(method, endpoint, body, timeout).Result; - private void ExecuteAndNotify(string method, string urifragment, string body) { - lock (m_lock) - { - m_workQueue.Enqueue(new BackgroundRequest(method, urifragment, body, null)); - m_waitLock.Set(); - } + m_workQueue.WriteNoWait(new BackgroundRequest(method, urifragment, body, null)); } public void Pause(string duration = null) @@ -386,21 +404,29 @@ namespace Duplicati.GUI.TrayIcon } private sealed record SigninTokenResponse(string Token); - private string IssueSigninToken(string password) - => PerformRequest("POST", "/auth/issuesignintoken", JsonSerializer.Serialize(new { Password = password }), null).Token; + private async Task IssueSigninTokenAsync(string password) + => (await PerformRequestInternalAsync("POST", "/auth/issuesignintoken", JsonSerializer.Serialize(new { Password = password }), null).ConfigureAwait(false)).Token; - private string ObtainSignInToken() + private async Task ObtainSignInTokenAsync() { string signinjwt = null; // If we host the server, issue the token from the service if (FIXMEGlobal.IsServerStarted && m_passwordSource == Program.PasswordSource.HostedServer) + { + if (FIXMEGlobal.DataConnection.ApplicationSettings.DisableSigninTokens) + return null; + signinjwt = FIXMEGlobal.Provider.GetRequiredService().CreateSigninToken("trayicon"); + } // If we have database access, grab the issuer key from the db and issue a token if (string.IsNullOrWhiteSpace(signinjwt) && m_passwordSource == Program.PasswordSource.Database) { Program.databaseConnection.ApplicationSettings.ReloadSettings(); + if (Program.databaseConnection.ApplicationSettings.DisableSigninTokens) + return null; + var cfg = Program.databaseConnection.ApplicationSettings.JWTConfig; if (!string.IsNullOrWhiteSpace(cfg)) signinjwt = new JWTTokenProvider(JsonSerializer.Deserialize(cfg)).CreateSigninToken("trayicon"); @@ -408,20 +434,28 @@ namespace Duplicati.GUI.TrayIcon // If we know the password, issue a token from the API if (string.IsNullOrWhiteSpace(signinjwt) && !string.IsNullOrWhiteSpace(m_password)) - signinjwt = IssueSigninToken(m_password); + signinjwt = await IssueSigninTokenAsync(m_password); return signinjwt; } - public string StatusWindowURL + public async Task GetStatusWindowURLAsync() { - get + string signinjwt = null; + if (!m_disableTrayIconLogin) { - var signinjwt = m_disableTrayIconLogin ? null : ObtainSignInToken(); - return string.IsNullOrWhiteSpace(signinjwt) - ? m_baseUri + STATUS_WINDOW - : m_baseUri + SIGNIN_WINDOW + $"?token={signinjwt}"; + try + { + signinjwt = await ObtainSignInTokenAsync().ConfigureAwait(false); + } + catch + { + } } + + return string.IsNullOrWhiteSpace(signinjwt) + ? m_baseUri + STATUS_WINDOW + : m_baseUri + SIGNIN_WINDOW + $"?token={signinjwt}"; } } } diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/IBrowserWindow.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/IBrowserWindow.cs index 858ae2f85..4a999072b 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/IBrowserWindow.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/IBrowserWindow.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/IServerStatus.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/IServerStatus.cs index 84b82b62f..47650c69b 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/IServerStatus.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/IServerStatus.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Server.Serialization; diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/ProcessBasedActionDelay.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ProcessBasedActionDelay.cs index b39d0ad2c..50e826ec4 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/ProcessBasedActionDelay.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ProcessBasedActionDelay.cs @@ -1,6 +1,27 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Threading.Tasks; using CoCoL; +using Duplicati.Library.Utility; namespace Duplicati.GUI.TrayIcon; @@ -60,11 +81,18 @@ public class ProcessBasedActionDelay : IDisposable /// Adds a new task to the processor /// /// The action to execute - public void ExecuteAction(Action action) + public async Task ExecuteAction(Action action) + { // Note: WriteNoWait() is used to avoid waiting for the action to be read, // as this would cause deadlocks if called from within the processor. // The buffer size should be sufficient to allow for a reasonable number of actions to be queued. - => m_inboundActionChannel.WriteNoWait(action); + var task = m_inboundActionChannel.WriteAsync(action); + + // Observe if the channel is full or retired + await Task.WhenAny(task, Task.Delay(500)); + if (task.IsCompleted) + await task; + } /// /// Signals the start of the processor @@ -77,7 +105,7 @@ public class ProcessBasedActionDelay : IDisposable /// public void Dispose() { - m_inboundActionChannel.Retire(); - m_initializedChannel.Retire(); + m_inboundActionChannel.RetireAsync(true).Await(); + m_initializedChannel.RetireAsync(true).Await(); } } diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs index a35712dda..a0d75182a 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,18 +22,40 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; +using System.Threading; +using CoCoL; +using Duplicati.CommandLine; +using Duplicati.Library.AutoUpdater; using Duplicati.Library.Interface; +using Duplicati.Library.RestAPI; +using Duplicati.Library.Utility; using Duplicati.Server; +using Uri = System.Uri; namespace Duplicati.GUI.TrayIcon { public static class Program { + /// + /// The source of the password + /// public enum PasswordSource { + /// + /// No password, using token information from the database + /// Database, - HostedServer + /// + /// No password supplied, using the hosted server + /// + HostedServer, + /// + /// The password was supplied on the commandline + /// + SuppliedPassword } public static HttpServerConnection Connection; @@ -45,15 +67,21 @@ namespace Duplicati.GUI.TrayIcon private const string DETACHED_PROCESS = "detached-process"; private const string BROWSER_COMMAND_OPTION = "browser-command"; + private static readonly IReadOnlySet DETATCHED_WEBERVER_OPTIONS = new HashSet(StringComparer.OrdinalIgnoreCase) + { + WebServerLoader.OPTION_WEBSERVICE_PASSWORD, + WebServerLoader.OPTION_PORT, + DataFolderManager.SERVER_DATAFOLDER_OPTION, + DataFolderManager.PORTABLE_MODE_OPTION + }; - private const string DEFAULT_HOSTURL = "http://localhost:8200"; + + private static string DEFAULT_HOSTURL => $"http://{Utility.IpVersionCompatibleLoopback}:8200"; private static string _browser_command = null; private static bool disableTrayIconLogin = false; private static bool openui = false; - private static Uri serverURL = new Uri(DEFAULT_HOSTURL); - - + private static Uri serverURL = new(DEFAULT_HOSTURL); public static string BrowserCommand { get { return _browser_command; } } public static Server.Database.Connection databaseConnection = null; @@ -63,33 +91,25 @@ namespace Duplicati.GUI.TrayIcon [STAThread] public static int Main(string[] _args) { - Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, Library.AutoUpdater.PackageHelper.NamedExecutable.TrayIcon); - List args = new List(_args); - Dictionary options = Library.Utility.CommandLineParser.ExtractOptions(args); + PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.TrayIcon); + var args = new List(_args); + var options = CommandLineParser.ExtractOptions(args); - if (OperatingSystem.IsWindows() && !Library.Utility.Utility.ParseBoolOption(options, DETACHED_PROCESS)) - Library.Utility.Win32.AttachConsole(Library.Utility.Win32.ATTACH_PARENT_PROCESS); + if (OperatingSystem.IsWindows() && !Utility.ParseBoolOption(options, DETACHED_PROCESS)) + Win32.AttachConsole(Win32.ATTACH_PARENT_PROCESS); - foreach (string s in args) - if ( - s.Equals("help", StringComparison.OrdinalIgnoreCase) || - s.Equals("/help", StringComparison.OrdinalIgnoreCase) || - s.Equals("usage", StringComparison.OrdinalIgnoreCase) || - s.Equals("/usage", StringComparison.OrdinalIgnoreCase)) - options["help"] = ""; - - if (options.ContainsKey("help")) + if (HelpOptionExtensions.IsArgumentAnyHelpString(args)) { Console.WriteLine("Supported commandline arguments:"); Console.WriteLine(); - foreach (ICommandLineArgument arg in SupportedCommands) + foreach (ICommandLineArgument arg in AllSupportedCommands) Console.WriteLine("--{0}: {1}", arg.Name, arg.LongDescription); Console.WriteLine("Additionally, these server options are also supported:"); Console.WriteLine(); - foreach (ICommandLineArgument arg in Server.Program.SupportedCommands) + foreach (ICommandLineArgument arg in WebserverSupportedCommands) Console.WriteLine("--{0}: {1}", arg.Name, arg.LongDescription); return 0; @@ -100,13 +120,35 @@ namespace Duplicati.GUI.TrayIcon HostedInstanceKeeper hosted = null; string password = null; + var passwordSource = PasswordSource.SuppliedPassword; var acceptedHostCertificate = options.GetValueOrDefault(ACCEPTED_SSL_CERTIFICATE, null); - if (!Library.Utility.Utility.ParseBoolOption(options, NOHOSTEDSERVER_OPTION)) + var detached = Utility.ParseBoolOption(options, NOHOSTEDSERVER_OPTION); + + var supportedCommands = BasicSupportedCommands.AsEnumerable(); + if (detached) + { + supportedCommands = DetachSupportedCommands + .Concat(supportedCommands); + } + else + { + supportedCommands = supportedCommands.Concat(WebserverSupportedCommands); + } + + // Validate options, and log to console + using (var logger = new ConsoleOutput(Console.Out, options)) + CommandLineArgumentValidator.ValidateArguments(supportedCommands, options, Server.Program.KnownDuplicateOptions, new HashSet()); + + if (!detached) { try { // Tell the hosted server it was started by the TrayIcon - Server.Program.Origin = "Tray icon"; + FIXMEGlobal.Origin = "Tray icon"; + passwordSource = PasswordSource.HostedServer; + // Ignore TrayIcon specific settings + foreach (var c in BasicSupportedCommands.Select(x => x.Name)) + Server.Program.ValidationIgnoredOptions.Add(c); hosted = new HostedInstanceKeeper(_args); } catch (Exception ex) @@ -132,20 +174,21 @@ namespace Duplicati.GUI.TrayIcon }.Uri; if (Server.Program.DataConnection.ApplicationSettings.UseHTTPS && string.IsNullOrWhiteSpace(acceptedHostCertificate)) - acceptedHostCertificate = Server.Program.DataConnection.ApplicationSettings.ServerSSLCertificate.GetCertHashString(); + acceptedHostCertificate = Server.Program.DataConnection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString(); } - else if (Library.Utility.Utility.ParseBoolOption(options, READCONFIGFROMDB_OPTION)) + else if (Utility.ParseBoolOption(options, READCONFIGFROMDB_OPTION)) { - if (File.Exists(Path.Combine(Server.Program.GetDataFolderPath(options), Server.Program.SERVER_DATABASE_FILENAME))) + if (File.Exists(Path.Combine(DataFolderManager.DATAFOLDER, DataFolderManager.SERVER_DATABASE_FILENAME))) { + passwordSource = PasswordSource.Database; databaseConnection = Server.Program.GetDatabaseConnection(options, true); if (databaseConnection != null) { disableTrayIconLogin = databaseConnection.ApplicationSettings.DisableTrayIconLogin; if (databaseConnection.ApplicationSettings.UseHTTPS && string.IsNullOrWhiteSpace(acceptedHostCertificate)) - acceptedHostCertificate = databaseConnection.ApplicationSettings.ServerSSLCertificate.GetCertHashString(); + acceptedHostCertificate = databaseConnection.ApplicationSettings.ServerSSLCertificate?.FirstOrDefault(x => x.HasPrivateKey)?.GetCertHashString(); var scheme = databaseConnection.ApplicationSettings.UseHTTPS ? "https" : "http"; serverURL = new UriBuilder(serverURL) @@ -170,7 +213,7 @@ namespace Duplicati.GUI.TrayIcon if (options.TryGetValue(HOSTURL_OPTION, out var url)) serverURL = new Uri(url); - if (string.IsNullOrWhiteSpace(password) && databaseConnection == null && hosted == null) + if (string.IsNullOrWhiteSpace(password) && passwordSource == PasswordSource.SuppliedPassword) { Console.WriteLine($@" When running the TrayIcon without a hosted server, you must provide the server password via the option --{WebServerLoader.OPTION_WEBSERVICE_PASSWORD}=. @@ -180,12 +223,12 @@ No password provided, unable to connect to server, exiting"); return 1; } - StartTray(_args, options, hosted, password, acceptedHostCertificate); + StartTray(_args, options, hosted, passwordSource, password, acceptedHostCertificate); return 0; } - private static void StartTray(string[] _args, Dictionary options, HostedInstanceKeeper hosted, string password, string acceptedHostCertificate) + private static void StartTray(string[] _args, Dictionary options, HostedInstanceKeeper hosted, PasswordSource passwordSource, string password, string acceptedHostCertificate) { using (hosted) { @@ -193,31 +236,41 @@ No password provided, unable to connect to server, exiting"); do { + if (reSpawn > 0) + Thread.Sleep(1000); + try { ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault; - using (Connection = new HttpServerConnection(serverURL, password, databaseConnection != null ? PasswordSource.Database : PasswordSource.HostedServer, disableTrayIconLogin, acceptedHostCertificate, options)) + using (Connection = new HttpServerConnection(serverURL, password, passwordSource, disableTrayIconLogin, acceptedHostCertificate, options)) { + // Make sure we have the latest status, but don't care if it fails + Connection.UpdateStatus().FireAndForget(); + using (var tk = RunTrayIcon()) { if (hosted != null && Server.Program.ApplicationInstance != null) Server.Program.ApplicationInstance.SecondInstanceDetected += - new Server.SingleInstance.SecondInstanceDelegate( - x => { tk.ShowUrlInWindow(serverURL.ToString()); }); + new SingleInstance.SecondInstanceDelegate( + x => tk.ShowStatusWindow()); // TODO: If we change to hosted browser this should be a callback if (openui) { - try + Connection.GetStatusWindowURLAsync().ContinueWith(t => { - tk.ShowUrlInWindow(Connection.StatusWindowURL); + if (t.IsFaulted) + { + Console.WriteLine("Failed to get status window URL: " + t.Exception.Message); + tk.NotifyUser("Failed to get status window URL", t.Exception.Message, NotificationType.Error); + return; + } + tk.ShowUrlInWindow(t.Result); Server.Program.IsFirstRun = false; Server.Program.ServerPortChanged = false; - } - catch - { - } + + }); } // If the server shuts down, shut down the tray-icon as well @@ -229,8 +282,9 @@ No password provided, unable to connect to server, exiting"); tk.InvokeExit(); }; + Connection.ConnectionClosed = shutdownEvent; if (hosted != null) - hosted.InstanceShutdown += shutdownEvent; + hosted.InstanceShutdown = shutdownEvent; tk.Init(_args); @@ -240,11 +294,13 @@ No password provided, unable to connect to server, exiting"); // Make sure that the server shutdown does not access the tray-icon, // as it would be disposed by now if (hosted != null) - hosted.InstanceShutdown -= shutdownEvent; + hosted.InstanceShutdown = null; + Connection.ConnectionClosed = null; + } } } - catch (WebException ex) + catch (HttpRequestException ex) { System.Diagnostics.Trace.WriteLine("Request error: " + ex); Console.WriteLine("Request error: " + ex); @@ -264,24 +320,33 @@ No password provided, unable to connect to server, exiting"); private static TrayIconBase RunTrayIcon() => new AvaloniaRunner(); - public static ICommandLineArgument[] SupportedCommands - { - get - { - var args = new List() - { - new CommandLineArgument(HOSTURL_OPTION, CommandLineArgument.ArgumentType.String, "Selects the url to connect to", "Supply the url that the TrayIcon will connect to and show status for", DEFAULT_HOSTURL), - new CommandLineArgument(NOHOSTEDSERVER_OPTION, CommandLineArgument.ArgumentType.String, "Disables local server", "Set this option to not spawn a local service, use if the TrayIcon should connect to a running service"), - new CommandLineArgument(READCONFIGFROMDB_OPTION, CommandLineArgument.ArgumentType.String, "Read server connection info from DB", $"Set this option to read server connection info for running service from its database (only together with {NOHOSTEDSERVER_OPTION})"), - new CommandLineArgument(BROWSER_COMMAND_OPTION, CommandLineArgument.ArgumentType.String, "Sets the browser command", "Set this option to override the default browser detection"), - new CommandLineArgument(ACCEPTED_SSL_CERTIFICATE, CommandLineArgument.ArgumentType.String, "Accepts a specific SSL certificate", "Set this option to accept a specific SSL certificate, the value should be the hash of the certificate in hexadecimal format. Use * to accept any certificate (dangerous)"), - }; + public static ICommandLineArgument[] DetachSupportedCommands => + [ + new CommandLineArgument(NOHOSTEDSERVER_OPTION, CommandLineArgument.ArgumentType.String, "Disables local server", "Set this option to not spawn a local service, use if the TrayIcon should connect to a running service"), + new CommandLineArgument(READCONFIGFROMDB_OPTION, CommandLineArgument.ArgumentType.String, "Read server connection info from DB", $"Set this option to read server connection info for running service from its database (only together with {NOHOSTEDSERVER_OPTION})"), + .. WebserverSupportedCommands.Where(x => DETATCHED_WEBERVER_OPTIONS.Contains(x.Name)) + ]; - if (OperatingSystem.IsWindows()) - args.Add(new CommandLineArgument(DETACHED_PROCESS, CommandLineArgument.ArgumentType.String, "Runs the tray-icon detached", "This option runs the tray-icon in detached mode, meaning that the process will exit immediately and not send output to the console of the caller")); + public static ICommandLineArgument[] BasicSupportedCommands => + [ + new CommandLineArgument(HOSTURL_OPTION, CommandLineArgument.ArgumentType.String, "Selects the url to connect to", "Supply the url that the TrayIcon will connect to and show status for", DEFAULT_HOSTURL), + new CommandLineArgument(BROWSER_COMMAND_OPTION, CommandLineArgument.ArgumentType.String, "Sets the browser command", "Set this option to override the default browser detection"), + new CommandLineArgument(ACCEPTED_SSL_CERTIFICATE, CommandLineArgument.ArgumentType.String, "Accepts a specific SSL certificate", "Set this option to accept a specific SSL certificate, the value should be the hash of the certificate in hexadecimal format. Use * to accept any certificate (dangerous)"), + .. WindowsSupportedCommands + ]; - return args.ToArray(); - } - } + private static ICommandLineArgument[] WindowsSupportedCommands => + OperatingSystem.IsWindows() + ? [new CommandLineArgument(DETACHED_PROCESS, CommandLineArgument.ArgumentType.String, "Runs the tray-icon detached", "This option runs the tray-icon in detached mode, meaning that the process will exit immediately and not send output to the console of the caller")] + : []; + + public static ICommandLineArgument[] WebserverSupportedCommands + => Server.Program.SupportedCommands; + + public static ICommandLineArgument[] AllSupportedCommands => + [ + .. DetachSupportedCommands, + .. BasicSupportedCommands + ]; } } diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs index 8973681eb..206b1a3fe 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/TrayIconBase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,6 +23,7 @@ using System; using Duplicati.Server.Serialization; using Duplicati.Server.Serialization.Interface; using System.Collections.Generic; +using System.Threading.Tasks; namespace Duplicati.GUI.TrayIcon { @@ -43,7 +44,8 @@ namespace Duplicati.GUI.TrayIcon IdleWarning, IdleError, PausedError, - RunningError + RunningError, + Disconnected } public enum WindowIcons @@ -64,11 +66,16 @@ namespace Duplicati.GUI.TrayIcon void SetDefault(bool isDefault); void SetIcon(MenuIcons icon); void SetText(string text); + void SetEnabled(bool enabled); + void SetHidden(bool hidden); } public abstract class TrayIconBase : IDisposable { + protected IMenuItem m_reconnectMenu; + protected IMenuItem m_openMenu; protected IMenuItem m_pauseMenu; + protected IMenuItem m_quitMenu; protected bool m_stateIsPaused; protected Action m_onSingleClick; protected Action m_onDoubleClick; @@ -89,12 +96,17 @@ namespace Duplicati.GUI.TrayIcon public void InvokeExit() { - UpdateUIState(() => { this.Exit(); }); + UpdateUIState(() => + { + Program.Connection?.Close(); + this.Exit(); + }); } - protected virtual void UpdateUIState(Action action) + protected virtual Task UpdateUIState(Action action) { action(); + return Task.CompletedTask; } protected abstract IMenuItem CreateMenuItem(string text, MenuIcons icon, Action callback, IList subitems); @@ -105,11 +117,11 @@ namespace Duplicati.GUI.TrayIcon protected abstract void SetMenu(IEnumerable items); - protected abstract void NotifyUser(string title, string message, NotificationType type); + public abstract void NotifyUser(string title, string message, NotificationType type); protected virtual void RegisterStatusUpdateCallback() { - Program.Connection.OnStatusUpdated += OnStatusUpdated; + Program.Connection.OnStatusUpdated = OnStatusUpdated; } protected virtual void RegisterNotificationCallback() @@ -149,23 +161,49 @@ namespace Duplicati.GUI.TrayIcon protected IEnumerable BuildMenu() { - var tmp = CreateMenuItem("Open", MenuIcons.Status, OnStatusClicked, null); - tmp.SetDefault(true); - return new IMenuItem[] { - tmp, + m_reconnectMenu = CreateMenuItem("Reconnect", MenuIcons.Resume, Reconnect, null); + m_reconnectMenu.SetHidden(true); + m_openMenu = CreateMenuItem("Open", MenuIcons.Status, OnStatusClicked, null); + m_openMenu.SetDefault(true); + return [ + m_reconnectMenu, + m_openMenu, m_pauseMenu = CreateMenuItem("Pause", MenuIcons.Pause, OnPauseClicked, null ), - CreateMenuItem("Quit", MenuIcons.Quit, OnQuitClicked, null), - }; + m_quitMenu = CreateMenuItem("Quit", MenuIcons.Quit, OnQuitClicked, null), + ]; } - protected void ShowStatusWindow() + private void Reconnect() { - var window = ShowUrlInWindow(Program.Connection.StatusWindowURL); - if (window != null) + Program.Connection.UpdateStatus().ContinueWith(t => { - window.SetIcon(WindowIcons.Regular); - window.SetTitle("Duplicati status"); - } + if (t.IsFaulted) + { + Console.WriteLine("Failed to reconnect: " + t.Exception.Message); + NotifyUser("Failed to reconnect", t.Exception.Message, NotificationType.Error); + } + + }); + } + + public void ShowStatusWindow() + { + Program.Connection.GetStatusWindowURLAsync().ContinueWith(t => + { + if (t.IsFaulted) + { + Console.WriteLine("Failed to get status window URL: " + t.Exception.Message); + NotifyUser("Failed to get status window URL", t.Exception.Message, NotificationType.Error); + return; + } + + var window = ShowUrlInWindow(t.Result); + if (window != null) + { + window.SetIcon(WindowIcons.Regular); + window.SetTitle("Duplicati status"); + } + }); } protected void OnStatusClicked() @@ -186,9 +224,8 @@ namespace Duplicati.GUI.TrayIcon Program.Connection.Pause(); } - protected void OnStatusUpdated(IServerStatus status) - { - this.UpdateUIState(() => + protected Task OnStatusUpdated(IServerStatus status) + => this.UpdateUIState(() => { switch (status.SuggestedStatusIcon) { @@ -207,6 +244,9 @@ namespace Duplicati.GUI.TrayIcon case SuggestedStatusIcon.Paused: this.SetIcon(TrayIcons.Paused); break; + case SuggestedStatusIcon.Disconnected: + this.SetIcon(TrayIcons.Disconnected); + break; case SuggestedStatusIcon.Ready: default: this.SetIcon(TrayIcons.Idle); @@ -226,8 +266,11 @@ namespace Duplicati.GUI.TrayIcon m_pauseMenu.SetText("Resume"); m_stateIsPaused = true; } + + m_openMenu.SetHidden(status.SuggestedStatusIcon == SuggestedStatusIcon.Disconnected); + m_pauseMenu.SetHidden(status.SuggestedStatusIcon == SuggestedStatusIcon.Disconnected); + m_reconnectMenu.SetHidden(status.SuggestedStatusIcon != SuggestedStatusIcon.Disconnected); }); - } #region IDisposable implementation public abstract void Dispose(); diff --git a/Duplicati/Library/AutoUpdater/AutoUpdateSettings.cs b/Duplicati/Library/AutoUpdater/AutoUpdateSettings.cs index 115152504..b6b66e191 100644 --- a/Duplicati/Library/AutoUpdater/AutoUpdateSettings.cs +++ b/Duplicati/Library/AutoUpdater/AutoUpdateSettings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,6 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -27,28 +29,78 @@ using System.Text.RegularExpressions; namespace Duplicati.Library.AutoUpdater { + /// + /// Contains settings for the auto-updater + /// public static class AutoUpdateSettings { - private static readonly Dictionary _cache = new Dictionary(); + /// + /// The appname file resource name + /// private const string APP_NAME = "AutoUpdateAppName.txt"; + /// + /// The update URL file resource name + /// private const string UPDATE_URL = "AutoUpdateURL.txt"; + /// + /// The update key file resource name + /// private const string UPDATE_KEY = "AutoUpdateSignKeys.txt"; + /// + /// The update channel file resource name + /// private const string UPDATE_CHANNEL = "AutoUpdateBuildChannel.txt"; + /// + /// The update install file template resource name + /// private const string UPDATE_INSTALL_FILE = "AutoUpdateInstallIDTemplate.txt"; + /// + /// The update machine file template resource name + /// private const string UPDATE_MACHINE_FILE = "AutoUpdateMachineIDTemplate.txt"; + /// + /// The OEM file name + /// private const string OEM_APP_NAME = "oem-app-name.txt"; + /// + /// The OEM update URL file name + /// private const string OEM_UPDATE_URL = "oem-update-url.txt"; + /// + /// The OEM update key file name + /// private const string OEM_UPDATE_KEY = "oem-update-key.txt"; + /// + /// The OEM update install template file name + /// private const string OEM_UPDATE_INSTALL_FILE = "oem-update-installid.txt"; + /// + /// The update URL environment variable name template + /// public const string UPDATEURL_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_URLS"; + /// + /// The update channel environment variable name template + /// public const string UPDATECHANNEL_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_CHANNEL"; + /// + /// The package prefix group name in the regex + /// internal const string MATCH_UPDATE_URL_PREFIX_GROUP = "prefix"; + /// + /// The channel group name in the regex + /// internal const string MATCH_UPDATE_URL_CHANNEL_GROUP = "channel"; + /// + /// The filename group name in the regex + /// internal const string MATCH_UPDATE_URL_FILENAME_GROUP = "filename"; + /// + /// The regex to match the auto-update URL and replace the channel + /// internal static readonly Regex MATCH_AUTOUPDATE_URL = new Regex(string.Format( "(?<{0}>.+)(?<{1}>{3})(?<{2}>/([^/]+).manifest)", @@ -57,27 +109,21 @@ namespace Duplicati.Library.AutoUpdater MATCH_UPDATE_URL_FILENAME_GROUP, string.Join("|", Enum.GetNames(typeof(ReleaseType)).Union(new[] { "preview" }))), RegexOptions.Compiled | RegexOptions.IgnoreCase); - - static AutoUpdateSettings() + /// + /// Reads a resource text from the assembly, with an optional OEM filename override + /// + /// The embedded resource name + /// The OEM filename override + /// The text of the resource + private static string ReadResourceText(string name, string? oemname) { - ReadResourceText(APP_NAME, OEM_APP_NAME); - ReadResourceText(UPDATE_URL, OEM_UPDATE_URL); - ReadResourceText(UPDATE_KEY, OEM_UPDATE_KEY); - ReadResourceText(UPDATE_INSTALL_FILE, OEM_UPDATE_INSTALL_FILE); - ReadResourceText(UPDATE_MACHINE_FILE, null); - ReadResourceText(UPDATE_CHANNEL, null); - } - - private static string ReadResourceText(string name, string oemname) - { - // First try to read from _cache - if (_cache.TryGetValue(name, out string result)) - return result; - + string? result = null; try { - using (var rd = new System.IO.StreamReader(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(AutoUpdateSettings), name))) - result = rd.ReadToEnd(); + using (var rs = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(AutoUpdateSettings), name)) + if (rs != null) + using (var rd = new System.IO.StreamReader(rs)) + result = rd.ReadToEnd(); } catch (Exception ex) { @@ -99,121 +145,154 @@ namespace Duplicati.Library.AutoUpdater else result = result.Trim(); - _cache[name] = result; return result; } - public static string[] URLs - { - get - { - if (UsesAlternateURLs) - return Environment.GetEnvironmentVariable(string.Format(UPDATEURL_ENVNAME_TEMPLATE, AppName)).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - else - return ReadResourceText(UPDATE_URL, OEM_UPDATE_URL).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - } - } + /// + /// The URLs to check for updates + /// + public static string[] URLs => _URLs.Value; - public static bool UsesAlternateURLs + /// + /// The alternate URLs to check for updates, lazy evaluated + /// + private static readonly Lazy _alternateURLs = new Lazy(() => { - get - { - return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(string.Format(UPDATEURL_ENVNAME_TEMPLATE, AppName))); - } - } + return Environment.GetEnvironmentVariable(string.Format(UPDATEURL_ENVNAME_TEMPLATE, AppName)); + }); - public static ReleaseType DefaultUpdateChannel + /// + /// The URLs to check for updates, lazy evaluated + /// + private static readonly Lazy _URLs = new Lazy(() => { - get - { - var channelstring = Environment.GetEnvironmentVariable(string.Format(UPDATECHANNEL_ENVNAME_TEMPLATE, AppName)); + if (!string.IsNullOrWhiteSpace(_alternateURLs.Value)) + return _alternateURLs.Value.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + else + return ReadResourceText(UPDATE_URL, OEM_UPDATE_URL).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + }); - if (UsesAlternateURLs && string.IsNullOrWhiteSpace(channelstring)) + /// + /// Indicates if alternate URLs are in use + /// + public static bool UsesAlternateURLs => !string.IsNullOrWhiteSpace(_alternateURLs.Value); + + /// + /// The default update channel + /// + public static ReleaseType DefaultUpdateChannel => _defaultUpdateChannel.Value; + + /// + /// The default update channel, lazy evaluated + /// + private static readonly Lazy _defaultUpdateChannel = new Lazy(() => + { + var channelstring = Environment.GetEnvironmentVariable(string.Format(UPDATECHANNEL_ENVNAME_TEMPLATE, AppName)); + + if (UsesAlternateURLs && string.IsNullOrWhiteSpace(channelstring)) + { + foreach (var url in URLs) { - foreach (var url in URLs) + var match = AutoUpdateSettings.MATCH_AUTOUPDATE_URL.Match(url); + if (match.Success) { - var match = AutoUpdateSettings.MATCH_AUTOUPDATE_URL.Match(url); - if (match.Success) - { - channelstring = match.Groups[AutoUpdateSettings.MATCH_UPDATE_URL_CHANNEL_GROUP].Value; - break; - } + channelstring = match.Groups[AutoUpdateSettings.MATCH_UPDATE_URL_CHANNEL_GROUP].Value; + break; } } - - if (string.IsNullOrWhiteSpace(channelstring)) - channelstring = BuildUpdateChannel; - - // Update from older builds - if (string.Equals(channelstring, "preview", StringComparison.OrdinalIgnoreCase)) - channelstring = ReleaseType.Experimental.ToString(); - if (string.Equals(channelstring, "rene", StringComparison.OrdinalIgnoreCase)) - channelstring = ReleaseType.Canary.ToString(); - - ReleaseType rt; - if (!Enum.TryParse(channelstring, true, out rt)) - rt = ReleaseType.Stable; - - return rt; } - } - public static string AppName - { - get { return ReadResourceText(APP_NAME, OEM_APP_NAME); } - } + if (string.IsNullOrWhiteSpace(channelstring)) + channelstring = BuildUpdateChannel; - public static string BuildUpdateChannel - { - get { return ReadResourceText(UPDATE_CHANNEL, null); } - } + // Update from older builds + if (string.Equals(channelstring, "preview", StringComparison.OrdinalIgnoreCase)) + channelstring = ReleaseType.Experimental.ToString(); + if (string.Equals(channelstring, "rene", StringComparison.OrdinalIgnoreCase)) + channelstring = ReleaseType.Canary.ToString(); - public static string UpdateInstallFileText - { - get { return string.Format(ReadResourceText(UPDATE_INSTALL_FILE, OEM_UPDATE_INSTALL_FILE), Guid.NewGuid().ToString("N")); } - } + ReleaseType rt; + if (!Enum.TryParse(channelstring, true, out rt)) + rt = ReleaseType.Stable; + return rt; + }); + + /// + /// The application name + /// + public static string AppName => _appName.Value; + + /// + /// The application name, lazy evaluated + /// + private static readonly Lazy _appName = new Lazy(() => ReadResourceText(APP_NAME, OEM_APP_NAME)); + + /// + /// The build update channel + /// + public static string BuildUpdateChannel => _buildUpdateChannel.Value; + + /// + /// The build update channel, lazy evaluated + /// + private static readonly Lazy _buildUpdateChannel = new Lazy(() => ReadResourceText(UPDATE_CHANNEL, null)); + + /// + /// The text for the update install file + /// + public static string UpdateInstallFileText => string.Format(ReadResourceText(UPDATE_INSTALL_FILE, OEM_UPDATE_INSTALL_FILE), Guid.NewGuid().ToString("N")); + + /// + /// Updates the machine file text with the machine ID + /// + /// The machine ID to use + /// The updated text public static string UpdateMachineFileText(string machineid) => string.Format(ReadResourceText(UPDATE_MACHINE_FILE, "{0}"), string.IsNullOrWhiteSpace(machineid) ? Guid.NewGuid().ToString("N") : machineid); - public static RSACryptoServiceProvider[] SignKeys + /// + /// The keys to use for signing + /// + public static RSACryptoServiceProvider[] SignKeys => _signKeys.Value; + + /// + /// The keys to use for signing, lazy evaluated + /// + private static readonly Lazy _signKeys = new Lazy(() => { - get + var keys = new List(); + + try { - var keys = new List(); + var src = ReadResourceText(UPDATE_KEY, OEM_UPDATE_KEY); - try + // Allow multiple keys, one per line + // For fallback, read the whole string as a key, in case there are old ones with line breaks + var keystrings = src.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .Prepend(src.Trim()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(); + + foreach (var str in keystrings) { - var src = ReadResourceText(UPDATE_KEY, OEM_UPDATE_KEY); - - // Allow multiple keys, one per line - // For fallback, read the whole string as a key, in case there are old ones with line breaks - - var keystrings = src.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .Prepend(src.Trim()) - .Where(x => !string.IsNullOrWhiteSpace(x)) - .Distinct(); - - foreach (var str in keystrings) + try { - try - { - var key = new RSACryptoServiceProvider(); - key.FromXmlString(str); - keys.Add(key); - } - catch - { } + var key = new RSACryptoServiceProvider(); + key.FromXmlString(str); + keys.Add(key); } + catch + { } } - catch - { - } - - return keys.ToArray(); } - } + catch + { + } + + return keys.ToArray(); + }); } } diff --git a/Duplicati/Library/AutoUpdater/DataFolderLocator.cs b/Duplicati/Library/AutoUpdater/DataFolderLocator.cs new file mode 100644 index 000000000..b4b98b315 --- /dev/null +++ b/Duplicati/Library/AutoUpdater/DataFolderLocator.cs @@ -0,0 +1,164 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; +using Duplicati.Library.Common.IO; + +#nullable enable + +namespace Duplicati.Library.AutoUpdater; + +/// +/// Helper class to locate the default storage folder for the application +/// +public static class DataFolderLocator +{ + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(DataFolderLocator)); + + /// + /// Finds a default storage folder, using the operating system specific locations. + /// The targetfilename is used to detect locations that are used in previous versions. + /// If the targetfilename is found in an old location, but not the current, the old location is used. + /// Note that the folder is not created, only the path is returned. + /// If the data folder is overriden, the overriden folder is used, and no search is performed. + /// + /// The filename to look for + /// Whether to create the folder if it does not exist + /// The default storage folder + public static string GetDefaultStorageFolder(string targetfilename, bool autoCreate) + { + var folder = DataFolderManager.OVERRIDEN_DATAFOLDER + ? DataFolderManager.DATAFOLDER + : GetDefaultStorageFolderInternal(targetfilename, AutoUpdateSettings.AppName); + + if (SystemIO.IO_OS.DirectoryExists(folder)) + { + if (!SystemIO.IO_OS.FileExists(System.IO.Path.Combine(folder, Util.InsecurePermissionsMarkerFile))) + try { SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(folder); } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", folder, ex.Message); + } + } + else if (autoCreate) + { + // Create the folder + SystemIO.IO_OS.DirectoryCreate(folder); + + try + { + // Make sure the folder is only accessible by the current user + SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(folder); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", folder, ex.Message); + } + } + + return folder; + } + + /// + /// Finds a default storage folder, using the operating system specific locations. + /// The targetfilename is used to detect locations that are used in previous versions. + /// If the targetfilename is found in an old location, but not the current, the old location is used. + /// Note that the folder is not created, only the path is returned. + /// + /// The filename to look for + /// The name of the application + /// The default storage folder + internal static string GetDefaultStorageFolderInternal(string targetfilename, string appName) + { + //Normal mode uses the systems "(Local) Application Data" folder + // %LOCALAPPDATA% on Windows, ~/.config on Linux + var folder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName); + + if (OperatingSystem.IsWindows()) + { + // Special handling for Windows: + // - Older versions use %APPDATA% + // - New versions use %LOCALAPPDATA% + // - And prevent using C:\Windows\ + var newlocation = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName); + + var folderOrder = new List() { + folder, + newlocation + }; + + // If %LOCALAPPDATA% is inside the Windows folder, prefer a LocalService folder instead + if (Common.IO.Util.IsPathUnderWindowsFolder(newlocation)) + folderOrder.Add(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), appName)); + + // Prefer the most recent location + var matches = folderOrder.AsEnumerable() + .Reverse() + .Where(x => System.IO.File.Exists(System.IO.Path.Combine(x, targetfilename))) + .ToList(); + + // Use the most recent location found with content + // If none are found, use the most recent location + folder = matches.FirstOrDefault() ?? folderOrder.Last(); + } + + if (OperatingSystem.IsMacOS()) + { + // Special handling for MacOS: + // - Older versions use ~/.config/ + // - but new versions use ~/Library/Application\ Support/ + var configfolder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", appName); + + var prevfile = System.IO.Path.Combine(configfolder, targetfilename); + var curfile = System.IO.Path.Combine(folder, targetfilename); + + // If the old file exists, and not the new file, we use the old + // Otherwise we use the new location + if (!System.IO.File.Exists(curfile) && System.IO.File.Exists(prevfile)) + folder = configfolder; + } + + if (OperatingSystem.IsLinux() && (folder == $"/{appName}" || folder == appName)) + { + // Special handling for Linux with no home folder: + // - Older versions use / + // - but new versions use /var/lib/ + var libfolder = System.IO.Path.Combine("var", "lib", appName); + + var curfile = System.IO.Path.Combine(libfolder, targetfilename); + var prevfile = System.IO.Path.Combine(folder, targetfilename); + + // If the old file exists, and not the new file, we use the old + // Otherwise we use the new location + if (System.IO.File.Exists(curfile) || !System.IO.File.Exists(prevfile)) + folder = libfolder; + } + + return folder; + } +} + + diff --git a/Duplicati/Library/AutoUpdater/DataFolderManager.cs b/Duplicati/Library/AutoUpdater/DataFolderManager.cs new file mode 100644 index 000000000..79d73761c --- /dev/null +++ b/Duplicati/Library/AutoUpdater/DataFolderManager.cs @@ -0,0 +1,266 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.IO; +using System.Linq; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.AutoUpdater; + +/// +/// Manages the data folder for the application +/// +public static class DataFolderManager +{ + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(DataFolderManager)); + + /// + /// The folder where the machine id is placed + /// + public static readonly string DATAFOLDER; + + /// + /// The installation ID filename stored in + /// + private const string INSTALL_FILE = "installation.txt"; + + /// + /// The machine ID filename stored in + /// + private const string MACHINE_FILE = "machineid.txt"; + + /// + /// The option name for portable mode + /// + public const string PORTABLE_MODE_OPTION = "portable-mode"; + /// + /// The option anme for the server data folder + /// + public const string SERVER_DATAFOLDER_OPTION = "server-datafolder"; + + /// + /// The app name to use for variables + /// + private static readonly string APPNAME = AutoUpdateSettings.AppName; + + /// + /// The name of the environment variable that allows overriding the path to the data folder used by Duplicati + /// + public static readonly string DATAFOLDER_ENV_NAME = $"{APPNAME}_HOME".ToUpperInvariant(); + + /// + /// Name of the database file + /// + public static readonly string SERVER_DATABASE_FILENAME = $"{APPNAME}-server.sqlite"; + + /// + /// Flag to indicate if the application is running in portable mode + /// + public static readonly bool PORTABLE_MODE; + + /// + /// Flag to indicate if the data folder was overriden + /// + public static readonly bool OVERRIDEN_DATAFOLDER; + + /// + /// Replication of the argument parsing from the main Duplicati codebase + /// + /// The option to extract + /// null if the option is not found, otherwise the value of the option + private static string? ExtractOptionSlim(string option) + { + var opt = $"--{option}"; + var args = Environment.GetCommandLineArgs().Skip(1).ToArray(); + var match = args.Select((token, index) => new { token, index }) + .LastOrDefault(x => + string.Equals(x.token, opt, StringComparison.OrdinalIgnoreCase) + || + x.token.StartsWith(opt + "=", StringComparison.OrdinalIgnoreCase) + ); + + // Not found, try the environment variable + if (string.IsNullOrWhiteSpace(match?.token)) + return Environment.GetEnvironmentVariable($"{AutoUpdateSettings.AppName}__{option.Replace('-', '_')}".ToUpperInvariant()); + + // Found in the form --option=value + if (match.token.StartsWith(opt + "=", StringComparison.OrdinalIgnoreCase)) + return match.token.Substring(opt.Length + 1).Trim('"'); + + // Found in the form --option value + if (match.index + 1 < args.Length) + { + var value = args[match.index + 1]; + if (!value.StartsWith("--")) + return value; + } + + // Found, but no value, just the option + return ""; + } + + /// + /// Replication of the boolean parsing from the main Duplicati codebase + /// + /// The value to parse + /// true if the value is a truthy value, otherwise false + private static bool ParseBoolSlim(string? value) + { + // In debug builds, we default to portable mode + if (value == null) +#if DEBUG + return true; +#else + return false; +#endif + + if ( + value.Equals("false", StringComparison.OrdinalIgnoreCase) + || value.Equals("0", StringComparison.OrdinalIgnoreCase) + || value.Equals("no", StringComparison.OrdinalIgnoreCase) + || value.Equals("off", StringComparison.OrdinalIgnoreCase) + ) + return false; + + return true; + } + + static DataFolderManager() + { + // Trigger portable mode, if the flag is set + PORTABLE_MODE = ParseBoolSlim(ExtractOptionSlim(PORTABLE_MODE_OPTION)); + + // The environment variable is a legacy setting + var envOverride = Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME); + + // These are mainly supported by the Server + var datafolderArg = ExtractOptionSlim(SERVER_DATAFOLDER_OPTION); + + // Prefer the command line argument over the environment variable + if (!string.IsNullOrWhiteSpace(datafolderArg)) + { + OVERRIDEN_DATAFOLDER = true; + DATAFOLDER = Util.AppendDirSeparator(Environment.ExpandEnvironmentVariables(datafolderArg).Trim('"')); + } + // Portable mode is prefered over the environment variable + else if (PORTABLE_MODE) + { + OVERRIDEN_DATAFOLDER = true; + DATAFOLDER = Util.AppendDirSeparator(Path.Combine(UpdaterManager.INSTALLATIONDIR, "data")); + } + // Use the legacy environment variable, if set + else if (!string.IsNullOrWhiteSpace(envOverride)) + { + OVERRIDEN_DATAFOLDER = true; + DATAFOLDER = Util.AppendDirSeparator(Environment.ExpandEnvironmentVariables(envOverride).Trim('"')); + } + // Use the default location + else + { + OVERRIDEN_DATAFOLDER = false; + DATAFOLDER = Util.AppendDirSeparator(DataFolderLocator.GetDefaultStorageFolderInternal(SERVER_DATABASE_FILENAME, APPNAME)); + } + + if (Directory.Exists(DATAFOLDER)) + { + if (!File.Exists(Path.Combine(DATAFOLDER, Util.InsecurePermissionsMarkerFile))) + try { SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(DATAFOLDER); } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", DATAFOLDER, ex.Message); + } + } + else + { + Directory.CreateDirectory(DATAFOLDER); + try { SystemIO.IO_OS.DirectorySetPermissionUserRWOnly(DATAFOLDER); } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToSetPermissions", ex, "Failed to set permissions for {0}: {1}", DATAFOLDER, ex.Message); + } + } + + if (!File.Exists(Path.Combine(DATAFOLDER, INSTALL_FILE))) + { + // In case there was already a machine id file from 2.0.8.1 or older, copy it to the new location + if (File.Exists(Path.Combine(DATAFOLDER, "updates", INSTALL_FILE))) + File.Copy(Path.Combine(DATAFOLDER, "updates", INSTALL_FILE), Path.Combine(DATAFOLDER, INSTALL_FILE), true); + else + File.WriteAllText(Path.Combine(DATAFOLDER, INSTALL_FILE), AutoUpdateSettings.UpdateInstallFileText); + } + + if (!File.Exists(Path.Combine(DATAFOLDER, MACHINE_FILE))) + File.WriteAllText(Path.Combine(DATAFOLDER, MACHINE_FILE), AutoUpdateSettings.UpdateMachineFileText(InstallID)); + } + + /// + /// The unique machine installation ID + /// + public static string InstallID => _installID.Value; + + /// + /// The unique machine ID, lazy evaluated + /// + private static readonly Lazy _installID = new(() => + { + try { return File.ReadAllLines(Path.Combine(DATAFOLDER!, INSTALL_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } + catch { } + + return ""; + }); + + /// + /// The unique machine ID + /// + public static string MachineID => _machineID.Value; + + /// + /// The unique machine ID, lazy evaluated + /// + private static readonly Lazy _machineID = new(() => + { + string? machinedId = null; + try { machinedId = File.ReadAllLines(Path.Combine(DATAFOLDER!, MACHINE_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } + catch { } + + return string.IsNullOrWhiteSpace(machinedId) + ? InstallID + : machinedId; + }); + + /// + /// The machine name, lazy evaluated + /// + private static readonly Lazy _machineName = new(MachineNameReader.GetMachineName); + + /// + /// The machine name + /// + public static readonly string MachineName = _machineName.Value; + +} \ No newline at end of file diff --git a/Duplicati/Library/AutoUpdater/Duplicati.Library.AutoUpdater.csproj b/Duplicati/Library/AutoUpdater/Duplicati.Library.AutoUpdater.csproj index 47afc9ba0..368c8c9b7 100644 --- a/Duplicati/Library/AutoUpdater/Duplicati.Library.AutoUpdater.csproj +++ b/Duplicati/Library/AutoUpdater/Duplicati.Library.AutoUpdater.csproj @@ -2,7 +2,7 @@ net8.0 - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/AutoUpdater/PackageEntry.cs b/Duplicati/Library/AutoUpdater/PackageEntry.cs index 84b3e00fe..2ca183a57 100644 --- a/Duplicati/Library/AutoUpdater/PackageEntry.cs +++ b/Duplicati/Library/AutoUpdater/PackageEntry.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/AutoUpdater/PackageHelper.cs b/Duplicati/Library/AutoUpdater/PackageHelper.cs index 67b9b74ce..4ceefc41e 100644 --- a/Duplicati/Library/AutoUpdater/PackageHelper.cs +++ b/Duplicati/Library/AutoUpdater/PackageHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -85,6 +85,18 @@ namespace Duplicati.Library.AutoUpdater /// The remote managed agent /// Agent, + /// + /// The secret tool + /// + SecretTool, + /// + /// The sync tool + /// + SyncTool, + /// + /// The source tool + /// + SourceTool } @@ -110,6 +122,9 @@ namespace Duplicati.Library.AutoUpdater NamedExecutable.ServerUtil => OperatingSystem.IsWindows() ? "Duplicati.CommandLine.ServerUtil.exe" : "duplicati-server-util", NamedExecutable.Service => OperatingSystem.IsWindows() ? "Duplicati.Service.exe" : "duplicati-service", NamedExecutable.Agent => OperatingSystem.IsWindows() ? "Duplicati.Agent.exe" : "duplicati-agent", + NamedExecutable.SecretTool => OperatingSystem.IsWindows() ? "Duplicati.CommandLine.SecretTool.exe" : "duplicati-secret-tool", + NamedExecutable.SyncTool => OperatingSystem.IsWindows() ? "Duplicati.CommandLine.SyncTool.exe" : "duplicati-sync-tool", + NamedExecutable.SourceTool => OperatingSystem.IsWindows() ? "Duplicati.CommandLine.Sourcetool.exe" : "duplicati-source-tool", _ => throw new ArgumentException($"Named executable not known: {exe}", nameof(exe)) }; diff --git a/Duplicati/Library/AutoUpdater/PreloadSettingsLoader.cs b/Duplicati/Library/AutoUpdater/PreloadSettingsLoader.cs index 474bd8daa..f1e34cbfc 100644 --- a/Duplicati/Library/AutoUpdater/PreloadSettingsLoader.cs +++ b/Duplicati/Library/AutoUpdater/PreloadSettingsLoader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -52,6 +52,38 @@ public static class PreloadSettingsLoader /// private static readonly bool PreloadDebug = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(PreloadSettingsDebugEnvVar)); + /// + /// The folder name for the preload settings + /// + private const string FOLDER_NAME = "Duplicati"; + /// + /// The file name for the preload settings + /// + private const string FILE_NAME = "preload.json"; + + /// + /// The path for preload settings specified with an environment variable + /// + private static readonly string PreloadSettingsFile = Environment.GetEnvironmentVariable(PreloadSettingsEnvVar) ?? ""; + + /// + /// Paths that are suited for portable installations + /// + private static readonly string[] PortablePreloadPaths = new string[] { + + // The path for preload settings with the install directory + Path.Combine( + Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? "", + FILE_NAME + ), + + // The path for preload settings specified with an environment variable + PreloadSettingsFile, + } + .Where(x => !string.IsNullOrEmpty(x)) + .Distinct() + .ToArray(); + /// /// The preload paths to search for settings in. /// Each path is checked and applied to obtain the final settings. @@ -61,22 +93,27 @@ public static class PreloadSettingsLoader private static readonly string[] PreloadPaths = new string[] { // The default path for preload settings - Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), - "Duplicati", - "preload.json" + OperatingSystem.IsMacOS() + ? "" + : Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), + FOLDER_NAME, + FILE_NAME ), - // The path for preload settings with the install directory - Path.Combine( - Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? "", - "preload.json" - ), + // MacOS reports CommonApplicationData as /usr/share, which is not writable + // so we use /usr/local/share instead + OperatingSystem.IsMacOS() || OperatingSystem.IsLinux() + ? Path.Combine("/usr/local/share", FOLDER_NAME, FILE_NAME) + : "", - // The path for preload settings specified with an environment variable - Environment.GetEnvironmentVariable(PreloadSettingsEnvVar) ?? "", + // User-context path for preload settings, + // the same default path as where other data is stored + Path.Combine(DataFolderManager.DATAFOLDER, FILE_NAME), } + .Concat(PortablePreloadPaths) .Where(x => !string.IsNullOrEmpty(x)) + .Distinct() .ToArray(); /// @@ -95,7 +132,7 @@ public static class PreloadSettingsLoader /// The database settings public static void ConfigurePreloadSettings(ref string[] arguments, PackageHelper.NamedExecutable executable, out Dictionary dbsettings) { - var (env, args, db) = GetExecutableMergedSettings(executable); + var (env, args, db) = GetExecutableMergedSettings(executable, DataFolderManager.PORTABLE_MODE); dbsettings = db; ApplyEnvironmentVariables(env); @@ -110,12 +147,15 @@ public static class PreloadSettingsLoader private static string GetArgumentName(string arg) => arg.Split('=', 2)[0]; + /// /// Gets the merged settings for the given executable /// /// The executable to get settings for + /// The source commandline arguments + /// The portable mode flag /// The merged settings - private static (Dictionary env, List args, Dictionary db) GetExecutableMergedSettings(PackageHelper.NamedExecutable executable) + private static (Dictionary env, List args, Dictionary db) GetExecutableMergedSettings(PackageHelper.NamedExecutable executable, bool portableMode) { // Collect settings in generic and specific dictionaries // The executable-specific settings take precedence over the generic ones, @@ -138,7 +178,22 @@ public static class PreloadSettingsLoader target[kvp.Key] = kvp.Value; } - foreach (var path in PreloadPaths) + var paths = PreloadPaths; + if (portableMode) + { + if (PreloadDebug) + Console.WriteLine("Portable mode detected, using portable preload paths only"); + paths = PortablePreloadPaths; + } + + if (!string.IsNullOrWhiteSpace(PreloadSettingsFile) && PreloadSettingsFile.StartsWith("!")) + { + if (PreloadDebug) + Console.WriteLine($"Preload settings file specified with environment variable and exclamation mark, ignoring others"); + paths = new[] { PreloadSettingsFile.Substring(1) }; + } + + foreach (var path in paths) { if (!Path.IsPathRooted(path)) { @@ -257,16 +312,20 @@ public static class PreloadSettingsLoader { if (result == null) { - Console.WriteLine($"Loaded empty preload settings from {path}"); + if (PreloadDebug) + Console.WriteLine($"Loaded empty preload settings from {path}"); return null; } var jsData = JsonSerializer.Deserialize(File.ReadAllText(path)); var unmatched_keys = jsData.EnumerateObject().Select(x => x.Name).Except(["db", "env", "args"]); - if (unmatched_keys.Any()) - Console.WriteLine($"Unexpected key(s) in preload settings: {string.Join(", ", unmatched_keys)}"); + if (PreloadDebug) + { + if (unmatched_keys.Any()) + Console.WriteLine($"Unexpected key(s) in preload settings: {string.Join(", ", unmatched_keys)}"); - Console.WriteLine($"Loaded preload settings from {path}"); + Console.WriteLine($"Loaded preload settings from {path}"); + } var allowedSources = Enum.GetValues() .Select(MapExecutableName) @@ -277,12 +336,15 @@ public static class PreloadSettingsLoader var unmatched_env = result.env?.Keys.Where(x => !allowedSources.Contains(x)) ?? []; var unmatched_args = result.args?.Keys.Where(x => !allowedSources.Contains(x)) ?? []; - if (unmatched_db.Any()) - Console.WriteLine($"Found unknown executable name(s) in db preload settings: {string.Join(", ", unmatched_db)}"); - if (unmatched_env.Any()) - Console.WriteLine($"Found unknown executable name(s) in env preload settings: {string.Join(", ", unmatched_env)}"); - if (unmatched_args.Any()) - Console.WriteLine($"Found unknown executable name(s) in args preload settings: {string.Join(", ", unmatched_args)}"); + if (PreloadDebug) + { + if (unmatched_db.Any()) + Console.WriteLine($"Found unknown executable name(s) in db preload settings: {string.Join(", ", unmatched_db)}"); + if (unmatched_env.Any()) + Console.WriteLine($"Found unknown executable name(s) in env preload settings: {string.Join(", ", unmatched_env)}"); + if (unmatched_args.Any()) + Console.WriteLine($"Found unknown executable name(s) in args preload settings: {string.Join(", ", unmatched_args)}"); + } } return result; diff --git a/Duplicati/Library/AutoUpdater/Program.cs b/Duplicati/Library/AutoUpdater/Program.cs index 8610ba18d..ebfb6d949 100644 --- a/Duplicati/Library/AutoUpdater/Program.cs +++ b/Duplicati/Library/AutoUpdater/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -53,6 +53,7 @@ namespace Duplicati.Library.AutoUpdater return 0; case "check": { + Console.WriteLine($"Checking for updates on channel {AutoUpdateSettings.DefaultUpdateChannel} ..."); var update = UpdaterManager.CheckForUpdate(); if (update == null) Console.WriteLine("No updates found"); @@ -68,7 +69,7 @@ namespace Duplicati.Library.AutoUpdater var update = UpdaterManager.CheckForUpdate(); if (update == null || update.Version == UpdaterManager.SelfVersion.Version) { - Console.WriteLine("You are running the latest version: {0} ({1})", UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); + Console.WriteLine("You are running the latest version: {0} ({1})", UpdaterManager.SelfVersion.Displayname, UpdaterManager.SelfVersion.Version); return 0; } @@ -116,8 +117,8 @@ namespace Duplicati.Library.AutoUpdater Console.WriteLine("{0} - Choose different channel than the default {1}, valid settings: {2}", string.Format(AutoUpdateSettings.UPDATECHANNEL_ENVNAME_TEMPLATE, AutoUpdateSettings.AppName), AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel, string.Join(",", Enum.GetNames(typeof(ReleaseType)).Where(x => x != ReleaseType.Unknown.ToString()))); Console.WriteLine(); Console.WriteLine("Updates are downloaded from: {0}", string.Join(";", AutoUpdateSettings.URLs)); - Console.WriteLine("Machine settings are installed in: {0}", UpdaterManager.UPDATEDIR); - Console.WriteLine("This version is \"{0}\" ({1}) and is installed in: {2}", UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version, UpdaterManager.INSTALLATIONDIR); + Console.WriteLine("Settings and configuration files are placed in: {0}", DataFolderManager.DATAFOLDER); + Console.WriteLine("This version is \"{0}\" ({1}) and is installed in: {2}", UpdaterManager.SelfVersion.Displayname, UpdaterManager.SelfVersion.Version, UpdaterManager.INSTALLATIONDIR); Console.WriteLine(); } } diff --git a/Duplicati/Library/AutoUpdater/UpdateInfo.cs b/Duplicati/Library/AutoUpdater/UpdateInfo.cs index af1604eb8..0c2b854d1 100644 --- a/Duplicati/Library/AutoUpdater/UpdateInfo.cs +++ b/Duplicati/Library/AutoUpdater/UpdateInfo.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable diff --git a/Duplicati/Library/AutoUpdater/UpdaterManager.cs b/Duplicati/Library/AutoUpdater/UpdaterManager.cs index e626fd984..68fb4374b 100644 --- a/Duplicati/Library/AutoUpdater/UpdaterManager.cs +++ b/Duplicati/Library/AutoUpdater/UpdaterManager.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,42 +19,43 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Linq; using System.Collections.Generic; using System.IO; using Duplicati.Library.Utility; -using Duplicati.Library.Common; using System.Diagnostics; using System.Text.Json; using System.Net.Http; using System.Threading; +using Duplicati.Library.Common.IO; namespace Duplicati.Library.AutoUpdater { + /// + /// Handles operations related to updating the application + /// public static class UpdaterManager { /// /// The RSA key used to sign the manifest /// - private static readonly System.Security.Cryptography.RSA[] SIGN_KEYS = AutoUpdateSettings.SignKeys; + private static System.Security.Cryptography.RSA[] SIGN_KEYS => AutoUpdateSettings.SignKeys; /// /// Urls to check for updated packages /// - private static readonly string[] MANIFEST_URLS = AutoUpdateSettings.URLs; + private static string[] MANIFEST_URLS => AutoUpdateSettings.URLs; /// /// The app name to show /// - private static readonly string APPNAME = AutoUpdateSettings.AppName; + private static string APPNAME => AutoUpdateSettings.AppName; /// /// The version that the updater supports /// public const int SUPPORTED_PACKAGE_UPDATER_VERSION = 2; /// - /// The folder where the machine id is placed - /// - public static readonly string UPDATEDIR; - /// /// The directory where the program is running from /// public static readonly string INSTALLATIONDIR; @@ -63,6 +64,10 @@ namespace Duplicati.Library.AutoUpdater /// public static readonly bool DISABLE_UPDATE_CHECK = Debugger.IsAttached || Utility.Utility.ParseBool(Environment.GetEnvironmentVariable(string.Format(SKIPUPDATE_ENVNAME_TEMPLATE, APPNAME)), false); /// + /// The operating system display name + /// + public static readonly string OperatingSystemName = OperatingSystem.IsWindows() ? "Windows" : OperatingSystem.IsLinux() ? "Linux" : OperatingSystem.IsMacOS() ? "MacOS" : "Unknown"; + /// /// The update information for the running version /// public static readonly UpdateInfo SelfVersion; @@ -70,17 +75,13 @@ namespace Duplicati.Library.AutoUpdater /// /// Event trigger for errors on update /// - public static event Action OnError; + public static event Action? OnError; /// /// Common formatting string for date-time values /// private const string DATETIME_FORMAT = "yyyymmddhhMMss"; /// - /// The template for the environment variable name that allows an overriden root folder - /// - private const string UPDATEINSTALLDIR_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_UPDATE_ROOT"; - /// /// The template for the environment variable that toggles disabling updates /// public const string SKIPUPDATE_ENVNAME_TEMPLATE = "AUTOUPDATER_{0}_SKIP_UPDATE"; @@ -92,20 +93,11 @@ namespace Duplicati.Library.AutoUpdater /// The name of the file that contains the package type id, located in the folder /// private const string PACKAGE_TYPE_FILE = "package_type_id.txt"; - /// - /// The installation ID filename stored in - /// - private const string INSTALL_FILE = "installation.txt"; - - /// - /// The machine ID filename stored in - /// - private const string MACHINE_FILE = "machineid.txt"; /// /// Gets the last version found from an update /// - public static UpdateInfo LastUpdateCheckVersion { get; private set; } + public static UpdateInfo? LastUpdateCheckVersion { get; private set; } /// /// The default timeout in seconds for download operations @@ -123,65 +115,10 @@ namespace Duplicati.Library.AutoUpdater static UpdaterManager() { // Set the installation path - INSTALLATIONDIR = Path.GetDirectoryName(Duplicati.Library.Utility.Utility.getEntryAssembly().Location); - - // Check for override - if (string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable(string.Format(UPDATEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME)))) - { - // OS specific folders for probing - var candidates = new List(); - if (OperatingSystem.IsWindows()) - { - candidates.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), APPNAME)); - candidates.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME)); - } - else - { - candidates.Add(System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), APPNAME)); - } - - if (OperatingSystem.IsMacOS()) - candidates.Add(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support", APPNAME)); - - // Find the first writeable directory in the list - UPDATEDIR = candidates.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p) && System.IO.Directory.Exists(p) && TestDirectoryIsWriteable(p)); - - // Try to create a writeable folder, if none is found - if (string.IsNullOrWhiteSpace(UPDATEDIR)) - UPDATEDIR = candidates.Where(p => !string.IsNullOrWhiteSpace(p) && !System.IO.Directory.Exists(p)) - .Select(p => - { - try { System.IO.Directory.CreateDirectory(p); } - catch { } - return p; - }) - .Where(p => System.IO.Directory.Exists(p) && TestDirectoryIsWriteable(p)) - .FirstOrDefault(); - } - else - { - // Use override, no checks - UPDATEDIR = Environment.ExpandEnvironmentVariables(System.Environment.GetEnvironmentVariable(string.Format(UPDATEINSTALLDIR_ENVNAME_TEMPLATE, APPNAME))); - } - - if (!string.IsNullOrWhiteSpace(UPDATEDIR)) - { - if (!System.IO.File.Exists(System.IO.Path.Combine(UPDATEDIR, INSTALL_FILE))) - { - // In case there was already a machine id file, copy it to the new location - if (System.IO.File.Exists(System.IO.Path.Combine(UPDATEDIR, "updates", INSTALL_FILE))) - System.IO.File.Copy(System.IO.Path.Combine(UPDATEDIR, "updates", INSTALL_FILE), System.IO.Path.Combine(UPDATEDIR, INSTALL_FILE), true); - else - System.IO.File.WriteAllText(System.IO.Path.Combine(UPDATEDIR, INSTALL_FILE), AutoUpdateSettings.UpdateInstallFileText); - } - - if (!System.IO.File.Exists(System.IO.Path.Combine(UPDATEDIR, MACHINE_FILE))) - System.IO.File.WriteAllText(System.IO.Path.Combine(UPDATEDIR, MACHINE_FILE), AutoUpdateSettings.UpdateMachineFileText(InstallID)); - - } + INSTALLATIONDIR = Util.AppendDirSeparator(Path.GetDirectoryName(Utility.Utility.getEntryAssembly().Location)); // Attempt to read the installed manifest file - UpdateInfo selfVersion = null; + UpdateInfo? selfVersion = null; try { selfVersion = ReadInstalledManifest(INSTALLATIONDIR); @@ -191,116 +128,67 @@ namespace Duplicati.Library.AutoUpdater } // In case the installed manifest is broken, try to set some sane values - if (selfVersion == null) - { - SelfVersion = new UpdateInfo( - MinimumCompatibleVersion: 1, - PackageUpdaterVersion: 1, - IncompatibleUpdateUrl: string.Empty, - GenericUpdatePageUrl: "https://duplicati.com/download", - UpdateSeverity: null, - ChangeInfo: null, - Packages: null, - Displayname: string.IsNullOrWhiteSpace(Duplicati.License.VersionNumbers.TAG) ? "Current" : Duplicati.License.VersionNumbers.TAG, - Version: System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), - ReleaseTime: new DateTime(0), - ReleaseType: + SelfVersion = selfVersion ?? new UpdateInfo( + MinimumCompatibleVersion: 1, + PackageUpdaterVersion: 1, + IncompatibleUpdateUrl: string.Empty, + GenericUpdatePageUrl: "https://duplicati.com/download", + UpdateSeverity: null, + ChangeInfo: null, + Packages: null, + Displayname: string.IsNullOrWhiteSpace(License.VersionNumbers.TAG) ? "Current" : License.VersionNumbers.TAG, + Version: System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(), + ReleaseTime: new DateTime(0), + ReleaseType: #if DEBUG - "Debug" + "Debug" #else string.IsNullOrWhiteSpace(AutoUpdateSettings.BuildUpdateChannel) ? "Nightly" : AutoUpdateSettings.BuildUpdateChannel #endif - ); - } + ); } - public static Version TryParseVersion(string str) + public static Version TryParseVersion(string? str) { - Version v; - if (Version.TryParse(str, out v)) + if (Version.TryParse(str, out var v)) return v; else return new Version(0, 0); } - private static bool TestDirectoryIsWriteable(string path) - { - var p2 = System.IO.Path.Combine(path, "test-" + DateTime.UtcNow.ToString(DATETIME_FORMAT, System.Globalization.CultureInfo.InvariantCulture)); - var probe = System.IO.Directory.Exists(path) ? p2 : path; - - if (!System.IO.Directory.Exists(probe)) - { - try - { - System.IO.Directory.CreateDirectory(probe); - if (probe != path) - System.IO.Directory.Delete(probe); - return true; - } - catch - { - } - } - - return false; - } - - /// - /// The unique machine installation ID - /// - public static string InstallID - { - get - { - try { return System.IO.File.ReadAllLines(System.IO.Path.Combine(UPDATEDIR, INSTALL_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } - catch { } - - return ""; - } - } - - /// - /// The unique machine ID - /// - public static string MachineID - { - get - { - string machinedId = null; - try { machinedId = System.IO.File.ReadAllLines(System.IO.Path.Combine(UPDATEDIR, MACHINE_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } - catch { } - - return string.IsNullOrWhiteSpace(machinedId) - ? InstallID - : machinedId; - } - } - - /// - /// The machine name - /// - public static string MachineName => System.Environment.MachineName; /// /// The package type ID /// - public static string PackageTypeId + public static string PackageTypeId => _packageTypeId.Value; + + /// + /// The release channel to use for updates + /// + public static ReleaseType CurrentChannel { get; set; } = AutoUpdateSettings.DefaultUpdateChannel; + + /// + /// The package type ID, lazy evaluated + /// + private static readonly Lazy _packageTypeId = new(() => { - get - { - try { return System.IO.File.ReadAllLines(System.IO.Path.Combine(INSTALLATIONDIR, PACKAGE_TYPE_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } - catch { } + try { return File.ReadAllLines(Path.Combine(INSTALLATIONDIR!, PACKAGE_TYPE_FILE)).FirstOrDefault(x => !string.IsNullOrWhiteSpace(x))?.Trim() ?? ""; } + catch { } #if DEBUG - return "debug"; + return "debug"; #else - return ""; + return ""; #endif - } - } + }); - public static UpdateInfo CheckForUpdate(ReleaseType channel = ReleaseType.Unknown) + /// + /// Checks for updates and returns the update information if available + /// + /// The release channel to check for updates + /// The update information if available, or null if no updates are available + public static UpdateInfo? CheckForUpdate(ReleaseType channel = ReleaseType.Unknown) { if (channel == ReleaseType.Unknown) channel = AutoUpdateSettings.DefaultUpdateChannel; @@ -331,19 +219,21 @@ namespace Duplicati.Library.AutoUpdater if (SIGN_KEYS.Length == 0) throw new Exception("No signing keys are available, cannot check update"); - using (var tmpfile = new Library.Utility.TempFile()) + using (var tmpfile = new TempFile()) { using var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add(System.Net.HttpRequestHeader.UserAgent.ToString(), string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(InstallID) ? "" : " -" + InstallID)); - request.Headers.Add("X-Install-ID", InstallID); + request.Headers.Add(System.Net.HttpRequestHeader.UserAgent.ToString(), string.Format("{0} v{1}{2}", APPNAME, SelfVersion.Version, string.IsNullOrWhiteSpace(DataFolderManager.InstallID) ? "" : " -" + DataFolderManager.InstallID)); + request.Headers.Add("X-Install-ID", DataFolderManager.InstallID); + request.Headers.Add("X-Package-Type-ID", PackageTypeId); using var timeoutToken = new CancellationTokenSource(); timeoutToken.CancelAfter(TimeSpan.FromSeconds(SHORT_OPERATION_TIMEOUT_SECONDS)); - HttpClientHelper.DefaultClient.DownloadFile(request, tmpfile, null, timeoutToken.Token).ConfigureAwait(false).GetAwaiter().GetResult(); + using (var client = HttpClientHelper.CreateClient()) + client.DownloadFile(request, tmpfile, null, timeoutToken.Token).Await(); - using (var fs = System.IO.File.OpenRead(tmpfile)) + using (var fs = File.OpenRead(tmpfile)) { var verifyOps = SIGN_KEYS.Select(k => new JSONSignature.VerifyOperation( Algorithm: JSONSignature.RSA_SHA256, @@ -354,6 +244,8 @@ namespace Duplicati.Library.AutoUpdater throw new Exception("No valid signature found in manifest file"); var update = JsonSerializer.Deserialize(fs); + if (update == null) + return null; if (TryParseVersion(update.Version) <= TryParseVersion(SelfVersion.Version)) return null; @@ -394,10 +286,15 @@ namespace Duplicati.Library.AutoUpdater return null; } - private static UpdateInfo ReadInstalledManifest(string folder) + /// + /// Reads the installed manifest file + /// + /// The folder to read the manifest from + /// The manifest if found, or null if not found + private static UpdateInfo? ReadInstalledManifest(string folder) { - var manifest = System.IO.Path.Combine(folder, UPDATE_MANIFEST_FILENAME); - if (System.IO.File.Exists(manifest)) + var manifest = Path.Combine(folder, UPDATE_MANIFEST_FILENAME); + if (File.Exists(manifest)) { try { @@ -406,7 +303,7 @@ namespace Duplicati.Library.AutoUpdater PublicKey: k.ToXmlString(false) )); - using (var fs = System.IO.File.OpenRead(manifest)) + using (var fs = File.OpenRead(manifest)) { if (!JSONSignature.VerifyAtLeastOne(fs, verifyOps)) throw new Exception("Installed manifest signature is invalid"); @@ -424,11 +321,16 @@ namespace Duplicati.Library.AutoUpdater return null; } - public static bool DownloadUpdate(UpdateInfo version, PackageEntry package, string targetPath, Action progress = null) + /// + /// Downloads the update package + /// + /// The version to download + /// The package to download + /// The path to save the downloaded package to + /// The progress callback + /// True if the download was successful, otherwise false + public static bool DownloadUpdate(UpdateInfo version, PackageEntry package, string targetPath, Action? progress = null) { - if (UPDATEDIR == null) - return false; - var updates = package.RemoteUrls.ToList(); // If alternate update URLs are specified, @@ -455,21 +357,22 @@ namespace Duplicati.Library.AutoUpdater { try { - using (var tempfile = System.IO.File.Open(tempfilename, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) + using (var tempfile = File.Open(tempfilename, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) { - Action cb = null; + Action? cb = null; if (progress != null) cb = (s) => { progress(Math.Min(1.0, Math.Max(0.0, (double)s / package.Length))); }; using var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add(System.Net.HttpRequestHeader.UserAgent.ToString(), string.Format("{0} v{1}", APPNAME, SelfVersion.Version)); - request.Headers.Add("X-Install-ID", InstallID); + request.Headers.Add("X-Install-ID", DataFolderManager.InstallID); using var timeoutToken = new CancellationTokenSource(); timeoutToken.CancelAfter(TimeSpan.FromSeconds(DOWNLOAD_OPERATION_TIMEOUT_SECONDS)); - HttpClientHelper.DefaultClient.DownloadFile(request, tempfile, cb, timeoutToken.Token).ConfigureAwait(false).GetAwaiter().GetResult(); + using (var client = HttpClientHelper.CreateClient()) + client.DownloadFile(request, tempfile, cb, timeoutToken.Token).Await(); var sha256 = System.Security.Cryptography.SHA256.Create(); var md5 = System.Security.Cryptography.MD5.Create(); @@ -512,10 +415,11 @@ namespace Duplicati.Library.AutoUpdater /// The version of the manifest /// The URL to use for incompatible updates /// The URL to use for generic updates - public static void CreateSignedManifest(IEnumerable keys, string sourcedata, string outputfolder, string version = null, string incompatibleUpdateUrl = null, string genericUpdatePageUrl = null, string releaseType = null, IEnumerable packages = null) + public static void CreateSignedManifest(IEnumerable keys, string sourcedata, string outputfolder, string? version = null, string? incompatibleUpdateUrl = null, string? genericUpdatePageUrl = null, string? releaseType = null, IEnumerable? packages = null) { // Read the existing manifest - var remoteManifest = JsonSerializer.Deserialize(string.IsNullOrWhiteSpace(sourcedata) ? "{}" : sourcedata); + var remoteManifest = JsonSerializer.Deserialize(string.IsNullOrWhiteSpace(sourcedata) ? "{}" : sourcedata) + ?? throw new Exception("Failed to deserialize the manifest from source data"); if (remoteManifest.ReleaseTime.Ticks == 0) remoteManifest = remoteManifest with { ReleaseTime = DateTime.UtcNow }; diff --git a/Duplicati/Library/Backend/AliyunOSS/AliyunOSSBackend.cs b/Duplicati/Library/Backend/AliyunOSS/AliyunOSSBackend.cs index 3a661dd73..d77909b41 100644 --- a/Duplicati/Library/Backend/AliyunOSS/AliyunOSSBackend.cs +++ b/Duplicati/Library/Backend/AliyunOSS/AliyunOSSBackend.cs @@ -1,73 +1,75 @@ -using Aliyun.OSS; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Aliyun.OSS; using Aliyun.OSS.Common; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System; -using System.Collections.Generic; -using System.IO; +using Duplicati.Library.Utility.Options; using System.Net; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend.AliyunOSS { /// /// Aliyun Object Storage Service(OSS) is a massive, secure, low-cost, and highly reliable cloud storage service, offering up to 99.995% service availability.It offers a variety of storage types to choose from, comprehensively optimizing storage costs. - /// en: https://www.alibabacloud.com/zh/product/object-storage-service + /// en: https://www.alibabacloud.com/en/product/object-storage-service /// zh: https://www.aliyun.com/product/oss /// public class OSS : IBackend, IStreamingBackend, IRenameEnabledBackend { private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private const string OSS_REGION = "oss-region"; private const string OSS_BUCKET_NAME = "oss-bucket-name"; private const string OSS_ENDPOINT = "oss-endpoint"; private const string OSS_ACCESS_KEY_ID = "oss-access-key-id"; private const string OSS_ACCESS_KEY_SECRET = "oss-access-key-secret"; - private AliyunOSSOptions _ossOptions; + private readonly AliyunOSSOptions _ossOptions; + private readonly TimeoutOptionsHelper.Timeouts _timeouts; public OSS() - { } - - public OSS(string url, Dictionary options) { - _ossOptions = new AliyunOSSOptions(); + _ossOptions = null!; + _timeouts = null!; + } + + public OSS(string url, Dictionary options) + { + _timeouts = TimeoutOptionsHelper.Parse(options); var uri = new Utility.Uri(url?.Trim()); var prefix = uri.HostAndPath?.TrimPath(); - if (!string.IsNullOrEmpty(prefix)) - { - _ossOptions.Path = prefix; - } + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, OSS_ACCESS_KEY_ID, OSS_ACCESS_KEY_SECRET) + .RequireCredentials(); - if (options.ContainsKey(OSS_REGION)) + _ossOptions = new AliyunOSSOptions() { - _ossOptions.Region = options[OSS_REGION]; - } - - if (options.ContainsKey(OSS_ACCESS_KEY_ID)) - { - _ossOptions.AccessKeyId = options[OSS_ACCESS_KEY_ID]; - } - - if (options.ContainsKey(OSS_ACCESS_KEY_SECRET)) - { - _ossOptions.AccessKeySecret = options[OSS_ACCESS_KEY_SECRET]; - } - - if (options.ContainsKey(OSS_BUCKET_NAME)) - { - _ossOptions.BucketName = options[OSS_BUCKET_NAME]; - } - - if (options.ContainsKey(OSS_ENDPOINT)) - { - _ossOptions.Endpoint = options[OSS_ENDPOINT]; - } + BucketName = options.GetValueOrDefault(OSS_BUCKET_NAME), + Endpoint = options.GetValueOrDefault(OSS_ENDPOINT), + AccessKeyId = auth.Username!, + AccessKeySecret = auth.Password!, + Path = prefix ?? string.Empty + }; } private OssClient GetClient(bool isUseNewServiceClient = true) @@ -85,7 +87,8 @@ namespace Duplicati.Library.Backend.AliyunOSS }); } - public IEnumerable List() + + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { var bucketName = _ossOptions.BucketName; @@ -93,51 +96,48 @@ namespace Duplicati.Library.Backend.AliyunOSS var client = GetClient(false); - var list = new List(); - try + var nextMarker = string.Empty; + bool isTruncated; + do { - var nextMarker = string.Empty; - var isTruncated = false; - do + var listObjectsRequest = new ListObjectsRequest(bucketName) { - var listObjectsRequest = new ListObjectsRequest(bucketName) - { - MaxKeys = 1000, - Marker = nextMarker, - Prefix = prefix, - }; - var result = client.ListObjects(listObjectsRequest); + MaxKeys = 1000, + Marker = nextMarker, + Prefix = prefix, + }; + + ObjectListing result; + try + { + result = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, async ct => await Task.Run(() => client.ListObjects(listObjectsRequest), ct).ConfigureAwait(ConfigureAwaitOptions.ForceYielding)) + .ConfigureAwait(false); + if (result.HttpStatusCode != HttpStatusCode.OK) - { throw new Exception(result.HttpStatusCode.ToString()); - } - foreach (var summary in result.ObjectSummaries) - { - list.Add(summary); - } + } + catch (OssException ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "List", ex, "List object failed. {0}, {1}", ex.Message, ex.ErrorCode); + throw; + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "List", ex, "List object failed. {0}", ex.Message); + throw; + } - nextMarker = result.NextMarker; - isTruncated = result.IsTruncated; - } while (isTruncated); - } - catch (OssException ex) - { - Logging.Log.WriteErrorMessage(LOGTAG, "List", ex, "List object failed. {0}, {1}", ex.Message, ex.ErrorCode); - throw; - } - catch (Exception ex) - { - Logging.Log.WriteErrorMessage(LOGTAG, "List", ex, "List object failed. {0}", ex.Message); - throw; - } + foreach (var summary in result.ObjectSummaries) + { + var fileName = Path.GetFileName(summary.Key); + var time = summary.LastModified; // DateTimeOffset.Parse(summary.LastModified).ToLocalTime().DateTime; + yield return new FileEntry(fileName, summary.Size, time, time); + } - foreach (var item in list) - { - var fileName = Path.GetFileName(item.Key); - var time = item.LastModified; // DateTimeOffset.Parse(item.LastModified).ToLocalTime().DateTime; - yield return new FileEntry(fileName, item.Size, time, time); - } + nextMarker = result.NextMarker; + isTruncated = result.IsTruncated; + } while (isTruncated); } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) @@ -152,29 +152,27 @@ namespace Duplicati.Library.Backend.AliyunOSS await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { var client = GetClient(); - var bucketName = _ossOptions.BucketName; - var objectName = $"{_ossOptions.Path.TrimPath()}/{remotename}".TrimPath(); - var res = client.DeleteObject(bucketName, objectName); - if (res?.HttpStatusCode != HttpStatusCode.OK) + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => { - Logging.Log.WriteInformationMessage(LOGTAG, "Delete", "Delete object failed. it may have been deleted"); - } + var res = client.DeleteObject(bucketName, objectName); + if (res?.HttpStatusCode != HttpStatusCode.OK) + Logging.Log.WriteInformationMessage(LOGTAG, "Delete", "Delete object failed. it may have been deleted"); + return Task.CompletedTask; + }); } catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "Delete", ex, "Delete object failed. {0}", ex.Message); throw; } - - return Task.CompletedTask; } public Task TestAsync(CancellationToken cancelToken) @@ -200,8 +198,9 @@ namespace Duplicati.Library.Backend.AliyunOSS var client = GetClient(); try { + using var timeoutStream = stream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); var objectResult = await Task.Factory.FromAsync( - (cb, state) => client.BeginPutObject(bucketName, objectName, stream, cb, state), + (cb, state) => client.BeginPutObject(bucketName, objectName, timeoutStream, cb, state), client.EndPutObject, null).ConfigureAwait(false); @@ -218,23 +217,24 @@ namespace Duplicati.Library.Backend.AliyunOSS public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { var bucketName = _ossOptions.BucketName; - var objectName = $"{_ossOptions.Path.TrimPath()}/{remotename}".TrimPath(); - var client = GetClient(false); try { - var obj = await Task.Factory.FromAsync( - (cb, state) => client.BeginGetObject(bucketName, objectName, null, null), - client.EndGetObject, - null).ConfigureAwait(false); + var obj = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, async ct + => await Task.Factory.FromAsync( + (cb, state) => client.BeginGetObject(bucketName, objectName, null, null), + client.EndGetObject, + null).ConfigureAwait(false) + ).ConfigureAwait(false); if (obj.HttpStatusCode != HttpStatusCode.OK) throw new Exception("Get failed"); using (var requestStream = obj.Content) - await Library.Utility.Utility.CopyStreamAsync(requestStream, stream, cancelToken).ConfigureAwait(false); + using (var timeoutStream = requestStream.ObserveWriteTimeout(_timeouts.ReadWriteTimeout, false)) + await Utility.Utility.CopyStreamAsync(requestStream, timeoutStream, cancelToken).ConfigureAwait(false); } catch (Exception ex) { @@ -262,15 +262,15 @@ namespace Duplicati.Library.Backend.AliyunOSS // copy file var req = new CopyObjectRequest(sourceBucket, sourceObject, targetBucket, targetObject); - var res = await Task.Factory.FromAsync( - (cb, state) => client.BeginCopyObject(req, cb, state), - client.EndCopyResult, - null).ConfigureAwait(false); + var res = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, async ct + => await Task.Factory.FromAsync( + (cb, state) => client.BeginCopyObject(req, cb, state), + client.EndCopyResult, + null).ConfigureAwait(false) + ).ConfigureAwait(false); if (res?.HttpStatusCode != HttpStatusCode.OK) - { throw new Exception("file rename failed"); - } // del old file await DeleteAsync(oldname, cancelToken).ConfigureAwait(false); @@ -297,13 +297,13 @@ namespace Duplicati.Library.Backend.AliyunOSS { get { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OSS_REGION, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSRegionDescriptionShort, Strings.OSSBackend.OSSRegionDescriptionLong), - new CommandLineArgument(OSS_ACCESS_KEY_ID, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSAccessKeyIdDescriptionShort, Strings.OSSBackend.OSSAccessKeyIdDescriptionLong), - new CommandLineArgument(OSS_ACCESS_KEY_SECRET, CommandLineArgument.ArgumentType.Password, Strings.OSSBackend.OSSAccessKeySecretDescriptionShort, Strings.OSSBackend.OSSAccessKeySecretDescriptionLong), + return new List([ + new CommandLineArgument(OSS_ACCESS_KEY_ID, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSAccessKeyIdDescriptionShort, Strings.OSSBackend.OSSAccessKeyIdDescriptionLong, null, [AuthOptionsHelper.AuthUsernameOption]), + new CommandLineArgument(OSS_ACCESS_KEY_SECRET, CommandLineArgument.ArgumentType.Password, Strings.OSSBackend.OSSAccessKeySecretDescriptionShort, Strings.OSSBackend.OSSAccessKeySecretDescriptionLong, null, [AuthOptionsHelper.AuthPasswordOption]), new CommandLineArgument(OSS_BUCKET_NAME, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSBucketNameDescriptionShort, Strings.OSSBackend.OSSBucketNameDescriptionLong), - new CommandLineArgument(OSS_ENDPOINT, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSEndpointDescriptionShort, Strings.OSSBackend.OSSEndpointDescriptionLong) - }); + new CommandLineArgument(OSS_ENDPOINT, CommandLineArgument.ArgumentType.String, Strings.OSSBackend.OSSEndpointDescriptionShort, Strings.OSSBackend.OSSEndpointDescriptionLong), + .. TimeoutOptionsHelper.GetOptions() + ]); } } diff --git a/Duplicati/Library/Backend/AliyunOSS/AliyunOSSOptions.cs b/Duplicati/Library/Backend/AliyunOSS/AliyunOSSOptions.cs index 97dc0b352..97490a33c 100644 --- a/Duplicati/Library/Backend/AliyunOSS/AliyunOSSOptions.cs +++ b/Duplicati/Library/Backend/AliyunOSS/AliyunOSSOptions.cs @@ -1,4 +1,24 @@ -namespace Duplicati.Library.Backend.AliyunOSS +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.Library.Backend.AliyunOSS { /// /// Aliyun OSS Configuration @@ -11,25 +31,19 @@ /// A bucket is a container used for storing objects (Object), and all objects must belong to a bucket. /// 存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。 /// - public string BucketName { get; set; } - - /// - /// Region represents the physical location of the OSS data center. - /// 地域表示 OSS 的数据中心所在物理位置。 - /// - public string Region { get; set; } + public required string? BucketName { get; set; } /// /// AccessKeyId is used to identify the user. AccessKeySecret is the key used by the user to encrypt signature strings and by OSS to verify signature strings. The AccessKeySecret must be kept confidential. /// AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。 /// - public string AccessKeyId { get; set; } + public required string AccessKeyId { get; set; } /// /// AccessKeyId is used to identify the user. AccessKeySecret is the key used by the user to encrypt signature strings and by OSS to verify signature strings. The AccessKeySecret must be kept confidential. /// AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。 /// - public string AccessKeySecret { get; set; } + public required string AccessKeySecret { get; set; } /// /// Endpoint represents the access domain name for OSS external services. @@ -40,13 +54,13 @@ /// oss-cn-hongkong.aliyuncs.com /// oss-us-west-1.aliyuncs.com /// - public string Endpoint { get; set; } + public required string? Endpoint { get; set; } /// /// A path or subfolder in a bucket /// 桶中的路径或子文件夹 /// - public string Path { get; set; } + public required string Path { get; set; } } } diff --git a/Duplicati/Library/Backend/AliyunOSS/Duplicati.Library.Backend.AliyunOSS.csproj b/Duplicati/Library/Backend/AliyunOSS/Duplicati.Library.Backend.AliyunOSS.csproj index 3fcfc15b0..9296dfe7f 100644 --- a/Duplicati/Library/Backend/AliyunOSS/Duplicati.Library.Backend.AliyunOSS.csproj +++ b/Duplicati/Library/Backend/AliyunOSS/Duplicati.Library.Backend.AliyunOSS.csproj @@ -3,7 +3,10 @@ net8.0 Library false - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.AliyunOSS diff --git a/Duplicati/Library/Backend/AliyunOSS/Extensions.cs b/Duplicati/Library/Backend/AliyunOSS/Extensions.cs index d6bd38c9e..df36d9909 100644 --- a/Duplicati/Library/Backend/AliyunOSS/Extensions.cs +++ b/Duplicati/Library/Backend/AliyunOSS/Extensions.cs @@ -1,4 +1,24 @@ -namespace Duplicati.Library.Backend.AliyunOSS +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.Library.Backend.AliyunOSS { public static class Extensions { @@ -8,7 +28,8 @@ /// /// The path to trim. /// The trimmed path. - public static string TrimPath(this string path) + [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull("path")] + public static string? TrimPath(this string? path) { return path?.Trim().Trim('/').Trim('\\').Trim('/').Trim(); } diff --git a/Duplicati/Library/Backend/AliyunOSS/Strings.cs b/Duplicati/Library/Backend/AliyunOSS/Strings.cs index a94469c89..ff7ee4dd1 100644 --- a/Duplicati/Library/Backend/AliyunOSS/Strings.cs +++ b/Duplicati/Library/Backend/AliyunOSS/Strings.cs @@ -1,4 +1,24 @@ -using Duplicati.Library.Localization.Short; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { diff --git a/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs b/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs index 34adea33d..68810cfef 100644 --- a/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs +++ b/Duplicati/Library/Backend/AzureBlob/AzureBlobBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,11 +19,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using Azure; +using Azure.Storage.Blobs.Models; using Duplicati.Library.Interface; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using Duplicati.Library.Utility.Options; namespace Duplicati.Library.Backend.AzureBlob { @@ -37,173 +36,129 @@ namespace Duplicati.Library.Backend.AzureBlob // This constructor is needed by the BackendLoader. public AzureBlobBackend() { + _azureBlob = null!; } // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. - public AzureBlobBackend(string url, Dictionary options) + public AzureBlobBackend(string url, Dictionary options) { var uri = new Utility.Uri(url); uri.RequireHost(); - string storageAccountName = null; - string accessKey = null; - string sasToken = null; - string containerName = uri.Host.ToLowerInvariant(); + var containerName = uri.Host.ToLowerInvariant(); - if (options.ContainsKey("auth-username")) - storageAccountName = options["auth-username"]; - if (options.ContainsKey("auth-password")) - accessKey = options["auth-password"]; + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, "azure-account-name", "azure-access-key"); + var timeouts = TimeoutOptionsHelper.Parse(options); - if (options.ContainsKey("azure_account_name")) - storageAccountName = options["azure_account_name"]; - if (options.ContainsKey("azure-account-name")) - storageAccountName = options["azure-account-name"]; - - if (options.ContainsKey("azure_access_key")) - accessKey = options["azure_access_key"]; - if (options.ContainsKey("azure-access-key")) - accessKey = options["azure-access-key"]; - - if (options.ContainsKey("azure-access-sas-token")) - sasToken = options["azure-access-sas-token"]; - - if (!string.IsNullOrEmpty(uri.Username)) - storageAccountName = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - accessKey = uri.Password; - - if (string.IsNullOrWhiteSpace(storageAccountName)) - { + var sasToken = options.GetValueOrDefault("azure-access-sas-token"); + if (!auth.HasUsername) throw new UserInformationException(Strings.AzureBlobBackend.NoStorageAccountName, "AzureNoAccountName"); - } - if (string.IsNullOrWhiteSpace(accessKey) && string.IsNullOrWhiteSpace(sasToken)) - { + + if (!auth.HasPassword && string.IsNullOrWhiteSpace(sasToken)) throw new UserInformationException(Strings.AzureBlobBackend.NoAccessKeyOrSasToken, "AzureNoAccessKeyOrSasToken"); - } - - _azureBlob = new AzureBlobWrapper(storageAccountName, accessKey, sasToken, containerName); + _azureBlob = new AzureBlobWrapper(auth.Username!, auth.Password, sasToken, containerName, timeouts); } - public string DisplayName - { - get { return Strings.AzureBlobBackend.DisplayName; } - } + public string DisplayName => Strings.AzureBlobBackend.DisplayName; - public string ProtocolKey - { - get { return "azure"; } - } + public string ProtocolKey => "azure"; - public IEnumerable List() - { - return _azureBlob.ListContainerEntries(); - } + public IAsyncEnumerable ListAsync(CancellationToken cancelToken) + => _azureBlob.ListContainerEntriesAsync(cancelToken); public async Task PutAsync(string remotename, string localname, CancellationToken cancelToken) { - using (var fs = File.Open(localname, - FileMode.Open, FileAccess.Read, FileShare.Read)) - { - await PutAsync(remotename, fs, cancelToken); - } + await using var fs = File.Open(localname, + FileMode.Open, FileAccess.Read, FileShare.Read); + await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public async Task PutAsync(string remotename, Stream input, CancellationToken cancelToken) + public Task PutAsync(string remotename, Stream input, CancellationToken cancelToken) { - await _azureBlob.AddFileStream(remotename, input, cancelToken); + return WrapWithExceptionHandler(_azureBlob.AddFileStream(remotename, input, cancelToken)); } public async Task GetAsync(string remotename, string localname, CancellationToken cancellationToken) { - using (var fs = File.Open(localname, + await using var fs = File.Open(localname, FileMode.Create, FileAccess.Write, - FileShare.None)) - { - await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); - } + FileShare.None); + await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); } public Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken) { - return _azureBlob.GetFileStreamAsync(remotename, output, cancellationToken); + return WrapWithExceptionHandler(_azureBlob.GetFileStreamAsync(remotename, output, cancellationToken)); } public Task DeleteAsync(string remotename, CancellationToken cancellationToken) { - return _azureBlob.DeleteObjectAsync(remotename, cancellationToken); + return WrapWithExceptionHandler(_azureBlob.DeleteObjectAsync(remotename, cancellationToken)); } public IList SupportedCommands { get { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("azure_account_name", - CommandLineArgument.ArgumentType.String, - Strings.AzureBlobBackend.StorageAccountNameDescriptionShort, - Strings.AzureBlobBackend.StorageAccountNameDescriptionLong, - null, null, null, "This is deprecated. Use azure-account-name instead."), - new CommandLineArgument("azure_access_key", - CommandLineArgument.ArgumentType.Password, - Strings.AzureBlobBackend.AccessKeyDescriptionShort, - Strings.AzureBlobBackend.AccessKeyDescriptionLong, - null, null, null, "This is deprecated. Use azure-access-key instead."), - new CommandLineArgument("azure_blob_container_name", - CommandLineArgument.ArgumentType.String, - Strings.AzureBlobBackend.ContainerNameDescriptionShort, - Strings.AzureBlobBackend.ContainerNameDescriptionLong, - null, null, null, "This is deprecated. Use azure-blob-container-name instead."), + return [ new CommandLineArgument("azure-account-name", CommandLineArgument.ArgumentType.String, Strings.AzureBlobBackend.StorageAccountNameDescriptionShort, - Strings.AzureBlobBackend.StorageAccountNameDescriptionLong), + Strings.AzureBlobBackend.StorageAccountNameDescriptionLong, + null, + [AuthOptionsHelper.AuthUsernameOption]), new CommandLineArgument("azure-access-key", CommandLineArgument.ArgumentType.Password, Strings.AzureBlobBackend.AccessKeyDescriptionShort, - Strings.AzureBlobBackend.AccessKeyDescriptionLong), + Strings.AzureBlobBackend.AccessKeyDescriptionLong, + null, + [AuthOptionsHelper.AuthPasswordOption]), new CommandLineArgument("azure-access-sas-token", CommandLineArgument.ArgumentType.Password, Strings.AzureBlobBackend.SasTokenDescriptionShort, Strings.AzureBlobBackend.SasTokenDescriptionLong), - new CommandLineArgument("azure-blob-container-name", - CommandLineArgument.ArgumentType.String, - Strings.AzureBlobBackend.ContainerNameDescriptionShort, - Strings.AzureBlobBackend.ContainerNameDescriptionLong), - new CommandLineArgument("auth-password", - CommandLineArgument.ArgumentType.Password, - Strings.AzureBlobBackend.AuthPasswordDescriptionShort, - Strings.AzureBlobBackend.AuthPasswordDescriptionLong), - new CommandLineArgument("auth-username", - CommandLineArgument.ArgumentType.String, - Strings.AzureBlobBackend.AuthUsernameDescriptionShort, - Strings.AzureBlobBackend.AuthUsernameDescriptionLong) - }); - + .. TimeoutOptionsHelper.GetOptions() + ]; } } - public string Description - { - get - { - return Strings.AzureBlobBackend.Description_v2; - } - } + public string Description => Strings.AzureBlobBackend.DescriptionV2; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(_azureBlob.DnsNames); public Task TestAsync(CancellationToken cancellationToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancellationToken); public Task CreateFolderAsync(CancellationToken cancellationToken) { - return _azureBlob.AddContainerAsync(cancellationToken); + return WrapWithExceptionHandler(_azureBlob.AddContainerAsync(cancellationToken)); + } + + /// + /// Wraps the task with exception handling + /// + private async Task WrapWithExceptionHandler(Task task) + { + try + { + await task.ConfigureAwait(false); + } + catch (RequestFailedException e) + when (e.Status == 404 + || e.ErrorCode == BlobErrorCode.BlobNotFound + || e.ErrorCode == BlobErrorCode.ResourceNotFound) + { + throw new FileMissingException(e.Message, e); + } + catch (RequestFailedException e) + when (e.ErrorCode == BlobErrorCode.ContainerNotFound + || e.ErrorCode == BlobErrorCode.ContainerBeingDeleted + || e.ErrorCode == BlobErrorCode.ContainerDisabled) + { + throw new FolderMissingException(e.Message, e); + } } public void Dispose() diff --git a/Duplicati/Library/Backend/AzureBlob/AzureBlobWrapper.cs b/Duplicati/Library/Backend/AzureBlob/AzureBlobWrapper.cs index 1beb24c99..1ba6c34a8 100644 --- a/Duplicati/Library/Backend/AzureBlob/AzureBlobWrapper.cs +++ b/Duplicati/Library/Backend/AzureBlob/AzureBlobWrapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,16 +19,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Azure.Storage.Blobs; +using Azure.Storage.Blobs.Models; +using System.Runtime.CompilerServices; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; +using Uri = System.Uri; +using Duplicati.Library.Utility.Options; using Duplicati.Library.Utility; -using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Blob; namespace Duplicati.Library.Backend.AzureBlob { @@ -37,142 +35,144 @@ namespace Duplicati.Library.Backend.AzureBlob /// public class AzureBlobWrapper { - private readonly string _containerName; - private readonly CloudBlobContainer _container; - private readonly OperationContext _operationContext; - - // Note: May need metadata; need to test with Azure blobs - private const BlobListingDetails ListDetails = BlobListingDetails.None; + private readonly BlobContainerClient _container; + private readonly TimeoutOptionsHelper.Timeouts _timeouts; + /// + /// Gets an array of DNS names associated with the blob container. + /// + /// An array of DNS hostnames for the primary and secondary URIs of the container. public string[] DnsNames { get { var lst = new List(); - if (_container != null) - { - if (_container.Uri != null) - lst.Add(_container.Uri.Host); - - if (_container.StorageUri != null) - { - if (_container.StorageUri.PrimaryUri != null) - lst.Add(_container.StorageUri.PrimaryUri.Host); - if (_container.StorageUri.SecondaryUri != null) - lst.Add(_container.StorageUri.SecondaryUri.Host); - } - } - + if (_container != null && _container.Uri != null) lst.Add(_container.Uri.Host); return lst.ToArray(); } } - public AzureBlobWrapper(string accountName, string accessKey, string sasToken, string containerName) + /// + /// Initializes a new instance of the AzureBlobWrapper class. + /// + /// The Azure storage account name. + /// The access key for the storage account. + /// The Shared Access Signature (SAS) token for authentication. + /// The name of the blob container. + /// The timeout options. + public AzureBlobWrapper(string accountName, string? accessKey, string? sasToken, string containerName, TimeoutOptionsHelper.Timeouts timeouts) { - _operationContext = new() - { - CustomUserAgent = string.Format( - "APN/1.0 Duplicati/{0} AzureBlob/2.0 {1}", - System.Reflection.Assembly.GetExecutingAssembly().GetName().Version, - Microsoft.WindowsAzure.Storage.Shared.Protocol.Constants.HeaderConstants.UserAgent - ) - }; - - string connectionString; + BlobServiceClient blobServiceClient; if (sasToken != null) { - connectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};SharedAccessSignature={1}", - accountName, sasToken); + var sasUri = new Uri($"https://{accountName}.blob.core.windows.net/?{sasToken}"); + blobServiceClient = new BlobServiceClient(sasUri); } else { - connectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", - accountName, accessKey); + var connectionString = $"DefaultEndpointsProtocol=https;AccountName={accountName};AccountKey={accessKey};EndpointSuffix=core.windows.net"; + blobServiceClient = new BlobServiceClient(connectionString); } - var storageAccount = CloudStorageAccount.Parse(connectionString); - var blobClient = storageAccount.CreateCloudBlobClient(); - - _containerName = containerName; - _container = blobClient.GetContainerReference(containerName); + _container = blobServiceClient.GetBlobContainerClient(containerName); + _timeouts = timeouts; } + /// + /// Creates a new blob container asynchronously and sets its access permissions to private. + /// + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous operation. public async Task AddContainerAsync(CancellationToken cancellationToken) { - await _container.CreateAsync(default, default, _operationContext, cancellationToken).ConfigureAwait(false); - await _container.SetPermissionsAsync(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Off }, default, default, _operationContext, cancellationToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct => + // Even though PublicAccessType.None is by default, we set it explicitly to highlight it. + await _container.CreateAsync(PublicAccessType.None, cancellationToken: ct).ConfigureAwait(false) + ).ConfigureAwait(false); } - public virtual Task GetFileStreamAsync(string keyName, Stream target, CancellationToken cancellationToken) + /// + /// Downloads a blob to a stream asynchronously. + /// + /// The name of the blob to download. + /// The stream to download the blob to. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous download operation. + public async Task GetFileStreamAsync(string keyName, Stream target, CancellationToken cancellationToken) { - return _container.GetBlockBlobReference(keyName).DownloadToStreamAsync(target, default, default, _operationContext, cancellationToken); + var blobClient = _container.GetBlobClient(keyName); + using var timeoutStream = target.ObserveWriteTimeout(_timeouts.ReadWriteTimeout, false); + await blobClient.DownloadToAsync(target, cancellationToken).ConfigureAwait(false); } - public virtual Task AddFileStream(string keyName, Stream source, CancellationToken cancelToken) + /// + /// Uploads a stream to a blob asynchronously. + /// + /// The name to give the uploaded blob. + /// The stream containing the data to upload. + /// A token to monitor for cancellation requests. + /// A task that represents the asynchronous upload operation. + public async Task AddFileStream(string keyName, Stream source, CancellationToken cancelToken) { - return _container.GetBlockBlobReference(keyName).UploadFromStreamAsync(source, source.Length, default, default, _operationContext, cancelToken); + var blobClient = _container.GetBlobClient(keyName); + using var timeoutStream = source.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + await blobClient.UploadAsync(timeoutStream, true, cancelToken).ConfigureAwait(false); } - public Task DeleteObjectAsync(string keyName, CancellationToken cancelToken) + /// + /// Deletes a blob if it exists asynchronously. + /// + /// The name of the blob to delete. + /// A token to monitor for cancellation requests. + public async Task DeleteObjectAsync(string keyName, CancellationToken cancelToken) { - return _container.GetBlockBlobReference(keyName).DeleteIfExistsAsync(default, default, default, _operationContext, cancelToken); + var blobClient = _container.GetBlobClient(keyName); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, async ct => + await blobClient.DeleteIfExistsAsync(cancellationToken: ct).ConfigureAwait(false) + ).ConfigureAwait(false); } - private async Task> ListContainerEntriesAsync() + /// + /// List container files. + /// + /// A token to monitor for cancellation requests. + /// + /// Thrown when the container is not found + public virtual async IAsyncEnumerable ListContainerEntriesAsync([EnumeratorCancellation] CancellationToken cancelToken) { - var segment = await _container.ListBlobsSegmentedAsync(null, false, ListDetails, null, null, null, _operationContext); - var list = new List(); + await using var blobEnumerator = _container.GetBlobsAsync().GetAsyncEnumerator(cancelToken); - list.AddRange(segment.Results); - - while (segment.ContinuationToken != null) + while (true) { - segment = await _container.ListBlobsSegmentedAsync(null, false, ListDetails, null, segment.ContinuationToken, null, _operationContext); - list.AddRange(segment.Results); - } + bool hasNext; - return list; - } - - public virtual List ListContainerEntries() - { - var listBlobItems = ListContainerEntriesAsync().GetAwaiter().GetResult(); - try - { - return listBlobItems.Select(x => + try { - var absolutePath = x.StorageUri.PrimaryUri.AbsolutePath; - var containerSegment = string.Concat("/", _containerName, "/"); - var blobName = absolutePath.Substring(absolutePath.IndexOf( - containerSegment, System.StringComparison.Ordinal) + containerSegment.Length); - - try - { - if (x is CloudBlockBlob cb) - { - var lastModified = new System.DateTime(); - if (cb.Properties.LastModified != null) - lastModified = new System.DateTime(cb.Properties.LastModified.Value.Ticks, System.DateTimeKind.Utc); - return new FileEntry(Uri.UrlDecode(blobName.Replace("+", "%2B")), cb.Properties.Length, lastModified, lastModified); - } - } - catch - { - // If the metadata fails to parse, return the basic entry - } - - return new FileEntry(Uri.UrlDecode(blobName.Replace("+", "%2B"))); - }) - .Cast() - .ToList(); - } - catch (StorageException ex) - { - if (ex.RequestInformation.HttpStatusCode == 404) + hasNext = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, async ct + => await blobEnumerator.MoveNextAsync().ConfigureAwait(false) + ).ConfigureAwait(false); + } + catch (Azure.RequestFailedException ex) when (ex.Status == 404) { throw new FolderMissingException(ex); } - throw; + + if (!hasNext) break; + + cancelToken.ThrowIfCancellationRequested(); + + if (blobEnumerator.Current is { } blob) + { + var blobName = Uri.UnescapeDataString(blob.Name.Replace("+", "%2B")); + var lastModified = blob.Properties.LastModified?.UtcDateTime ?? DateTime.UtcNow; + + yield return new FileEntry( + blobName, + blob.Properties.ContentLength ?? 0, + lastModified, + lastModified + ); + } } } } diff --git a/Duplicati/Library/Backend/AzureBlob/Duplicati.Library.Backend.AzureBlob.csproj b/Duplicati/Library/Backend/AzureBlob/Duplicati.Library.Backend.AzureBlob.csproj index fc5228f80..44eb59ab0 100644 --- a/Duplicati/Library/Backend/AzureBlob/Duplicati.Library.Backend.AzureBlob.csproj +++ b/Duplicati/Library/Backend/AzureBlob/Duplicati.Library.Backend.AzureBlob.csproj @@ -3,19 +3,14 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.AzureBlob - - - - - - - - - - + + diff --git a/Duplicati/Library/Backend/AzureBlob/Strings.cs b/Duplicati/Library/Backend/AzureBlob/Strings.cs index e70a17c74..9c4be64c2 100644 --- a/Duplicati/Library/Backend/AzureBlob/Strings.cs +++ b/Duplicati/Library/Backend/AzureBlob/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,23 +20,21 @@ // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.AzureBlob.Strings { - internal static class AzureBlobBackend { - public static string Description_v2 { get { return LC.L(@"This backend can read and write data to Azure blob storage. Allowed format is ""azure://bucketname""."); } } - public static string DisplayName { get { return LC.L(@"Azure blob"); } } - public static string ContainerNameDescriptionLong { get { return LC.L(@"All files will be written to the container specified."); } } - public static string ContainerNameDescriptionShort { get { return LC.L(@"The name of the storage container"); } } - public static string NoStorageAccountName { get { return LC.L(@"No Azure storage account name given"); } } - public static string StorageAccountNameDescriptionLong { get { return LC.L(@"The Azure storage account name which can be obtained by clicking the ""Manage Access Keys"" button on the storage account dashboard."); } } - public static string StorageAccountNameDescriptionShort { get { return LC.L(@"The storage account name"); } } - public static string AccessKeyDescriptionLong { get { return LC.L(@"The Azure access key which can be obtained by clicking the ""Manage Access Keys"" button on the storage account dashboard."); } } - public static string AccessKeyDescriptionShort { get { return LC.L(@"The access key"); } } - public static string SasTokenDescriptionLong { get { return LC.L(@"The Azure shared access signature (SAS) token which can be obtained by selecting the ""Shared access signature"" blade on the storage account dashboard, or inside a container blade."); } } - public static string SasTokenDescriptionShort { get { return LC.L(@"The SAS token"); } } - public static string NoAccessKeyOrSasToken { get { return LC.L(@"No Azure access key or SAS token given"); } } - public static string AuthPasswordDescriptionLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string AuthPasswordDescriptionShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string AuthUsernameDescriptionLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string AuthUsernameDescriptionShort { get { return LC.L(@"Supply the username used to connect to the server"); } } - } -} + +namespace Duplicati.Library.Backend.AzureBlob.Strings; + +internal static class AzureBlobBackend +{ + public static string DescriptionV2 => LC.L(@"This backend can read and write data to Azure blob storage. Allowed format is ""azure://bucketname""."); + public static string DisplayName => LC.L(@"Azure blob"); + public static string ContainerNameDescriptionLong => LC.L(@"All files will be written to the container specified."); + public static string ContainerNameDescriptionShort => LC.L(@"The name of the storage container"); + public static string NoStorageAccountName => LC.L(@"No Azure storage account name given"); + public static string StorageAccountNameDescriptionLong => LC.L(@"The Azure storage account name which can be obtained by clicking the ""Manage Access Keys"" button on the storage account dashboard."); + public static string StorageAccountNameDescriptionShort => LC.L(@"The storage account name"); + public static string AccessKeyDescriptionLong => LC.L(@"The Azure access key which can be obtained by clicking the ""Manage Access Keys"" button on the storage account dashboard."); + public static string AccessKeyDescriptionShort => LC.L(@"The access key"); + public static string SasTokenDescriptionLong => LC.L(@"The Azure shared access signature (SAS) token which can be obtained by selecting the ""Shared access signature"" blade on the storage account dashboard, or inside a container blade."); + public static string SasTokenDescriptionShort => LC.L(@"The SAS token"); + public static string NoAccessKeyOrSasToken => LC.L(@"No Azure access key or SAS token given"); +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/B2.cs b/Duplicati/Library/Backend/Backblaze/B2.cs index cfaefb445..63c7a04c0 100644 --- a/Duplicati/Library/Backend/Backblaze/B2.cs +++ b/Duplicati/Library/Backend/Backblaze/B2.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,561 +22,654 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; +using System.Net.Http.Headers; +using System.Reflection; +using Duplicati.Library.Backend.Backblaze.Model; +using FileEntry = Duplicati.Library.Common.IO.FileEntry; +using System.Runtime.CompilerServices; +using Duplicati.Library.Utility.Options; -namespace Duplicati.Library.Backend.Backblaze +namespace Duplicati.Library.Backend.Backblaze; + +/// +/// Backblaze B2 Backend +/// +public class B2 : IStreamingBackend { - public class B2 : IBackend, IStreamingBackend + /// + /// The option key for specifying the Backblaze B2 account ID + /// + private const string B2_ID_OPTION = "b2-accountid"; + + /// + /// The option key for specifying the Backblaze B2 application key + /// + private const string B2_KEY_OPTION = "b2-applicationkey"; + + /// + /// The option key for specifying the number of files to retrieve per API request + /// + private const string B2_PAGESIZE_OPTION = "b2-page-size"; + + /// + /// The option key for specifying a custom download URL for the B2 service + /// + private const string B2_DOWNLOAD_URL_OPTION = "b2-download-url"; + + /// + /// The option key for specifying the bucket type when creating new buckets + /// + private const string B2_CREATE_BUCKET_TYPE_OPTION = "b2-create-bucket-type"; + + /// + /// The default bucket type for new buckets - set to private access + /// + private const string DEFAULT_BUCKET_TYPE = "allPrivate"; + + /// + /// The default number of files to retrieve per API request + /// + private const int DEFAULT_PAGE_SIZE = 500; + + /// + /// Recommended chunk size as per Backblaze B2 documentation (100MB) + /// + private const int B2_RECOMMENDED_CHUNK_SIZE = 100 * 1024 * 1024; + + /// + /// Default retry-after time in seconds for B2 API requests + /// + private const int B2_RETRY_AFTER_SECONDS = 5; + + /// + /// The name of the B2 bucket being accessed + /// + private readonly string _bucketName; + + /// + /// The path prefix for all operations within the bucket + /// + private readonly string _prefix; + + /// + /// URL-encoded version of the path prefix for API requests + /// + private readonly string _urlencodedPrefix; + + /// + /// The type of bucket (e.g., allPrivate, allPublic) being accessed or created + /// + private readonly string? _bucketType; + + /// + /// The number of files to retrieve per API request + /// + private readonly int _pageSize; + + /// + /// Custom download URL for the B2 service, if specified + /// + private readonly string? _downloadUrl; + + /// + /// Helper class for handling B2 authentication and API requests + /// + private readonly B2AuthHelper _b2AuthHelper; + + /// + /// The timeout options for the backend + /// + private readonly TimeoutOptionsHelper.Timeouts _timeouts; + + /// + /// Cached upload URL and authorization token for file uploads + /// + private UploadUrlResponse? _uploadUrl; + + /// + /// Cache of file listings, mapping filenames to their versions + /// + private Dictionary>? _filecache; + + /// + /// The current bucket entity containing bucket information and credentials + /// + private BucketEntity? _bucket; + + /// + /// A scoped instance of Http client to be used with the backend, this will use a + /// infinite timeout as a baseline, and requests will be controlled with timeout + /// cancellation tokens. It will be disposed when the backend is disposed + /// + private readonly HttpClient _httpClient; + + /// + /// Cache lock for BucketEntity + /// + private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1); + + /// + /// Empty constructor is required for the backend to be loaded by the backend factory + /// + public B2() { - private const string B2_ID_OPTION = "b2-accountid"; - private const string B2_KEY_OPTION = "b2-applicationkey"; - private const string B2_PAGESIZE_OPTION = "b2-page-size"; - private const string B2_DOWNLOAD_URL_OPTION = "b2-download-url"; + _bucketName = null!; + _prefix = null!; + _urlencodedPrefix = null!; + _b2AuthHelper = null!; + _timeouts = null!; + _httpClient = null!; + } - private const string B2_CREATE_BUCKET_TYPE_OPTION = "b2-create-bucket-type"; - private const string DEFAULT_BUCKET_TYPE = "allPrivate"; + /// + /// Actual constructor for the backend that accepts the url and options + /// + /// URL in Duplicati Uri format + /// options to be used in the backend + public B2(string url, Dictionary options) + { + var uri = new Utility.Uri(url); - private const int DEFAULT_PAGE_SIZE = 500; + _bucketName = uri.Host; + _prefix = Util.AppendDirSeparator("/" + uri.Path, "/"); - private readonly string m_bucketname; - private readonly string m_prefix; - private readonly string m_urlencodedprefix; - private readonly string m_bucketType; - private readonly int m_pagesize; - private readonly string m_downloadUrl; - private readonly B2AuthHelper m_helper; - private UploadUrlResponse m_uploadUrl; + // For B2 we do not use a leading slash + while (_prefix.StartsWith("/", StringComparison.Ordinal)) + _prefix = _prefix.Substring(1); - private Dictionary> m_filecache; + _urlencodedPrefix = string.Join("/", _prefix.Split(new[] { '/' }).Select(x => Utility.Uri.UrlPathEncode(x))); - private BucketEntity m_bucket; + _bucketType = DEFAULT_BUCKET_TYPE; + if (options.TryGetValue(B2_CREATE_BUCKET_TYPE_OPTION, out var option1)) + _bucketType = option1; - public B2() + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, B2_ID_OPTION, B2_KEY_OPTION); + + if (!auth.HasUsername) + throw new UserInformationException(Strings.B2.NoB2UserIDError, "B2MissingUserID"); + + if (!auth.HasPassword) + throw new UserInformationException(Strings.B2.NoB2KeyError, "B2MissingKey"); + + _pageSize = DEFAULT_PAGE_SIZE; + if (options.ContainsKey(B2_PAGESIZE_OPTION)) { + int.TryParse(options[B2_PAGESIZE_OPTION], out _pageSize); + + if (_pageSize <= 0) + throw new UserInformationException(Strings.B2.InvalidPageSizeError(B2_PAGESIZE_OPTION, options[B2_PAGESIZE_OPTION]), "B2InvalidPageSize"); } - public B2(string url, Dictionary options) + _downloadUrl = null; + if (options.TryGetValue(B2_DOWNLOAD_URL_OPTION, out var option)) _downloadUrl = option; + + _httpClient = HttpClientHelper.CreateClient(); + _httpClient.Timeout = Timeout.InfiniteTimeSpan; + + _timeouts = TimeoutOptionsHelper.Parse(options); + _b2AuthHelper = new B2AuthHelper(auth.Username!, auth.Password!, _httpClient, _timeouts); + + } + + /// + /// Retrieves the bucket list from the B2 service using the account ID + /// and searchs for a match for the configured bucket name. + /// + /// If not found, throws a FolderMissingException. + /// + /// Caches the result in the _mBucket field. + /// + /// CancellationToken that is combined with internal timeout token + /// + private async Task GetBucketAsync(CancellationToken cancellationToken) + { + if (_bucket != null) + return _bucket; + + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken).ConfigureAwait(false); + await _cacheLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + try { - var uri = new Utility.Uri(url); + var buckets = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct + => await _b2AuthHelper.PostAndGetJsonDataAsync( + $"{config.APIUrl}/b2api/v1/b2_list_buckets", + new ListBucketsRequest(accountId: config.AccountID), + ct).ConfigureAwait(false) + ).ConfigureAwait(false); - m_bucketname = uri.Host; - m_prefix = Util.AppendDirSeparator("/" + uri.Path, "/"); - - // For B2 we do not use a leading slash - while (m_prefix.StartsWith("/", StringComparison.Ordinal)) - m_prefix = m_prefix.Substring(1); - - m_urlencodedprefix = string.Join("/", m_prefix.Split(new[] { '/' }).Select(x => Library.Utility.Uri.UrlPathEncode(x))); - - m_bucketType = DEFAULT_BUCKET_TYPE; - if (options.ContainsKey(B2_CREATE_BUCKET_TYPE_OPTION)) - m_bucketType = options[B2_CREATE_BUCKET_TYPE_OPTION]; - - string accountId = null; - string accountKey = null; - - if (options.ContainsKey("auth-username")) - accountId = options["auth-username"]; - if (options.ContainsKey("auth-password")) - accountKey = options["auth-password"]; - - if (options.ContainsKey(B2_ID_OPTION)) - accountId = options[B2_ID_OPTION]; - if (options.ContainsKey(B2_KEY_OPTION)) - accountKey = options[B2_KEY_OPTION]; - if (!string.IsNullOrEmpty(uri.Username)) - accountId = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - accountKey = uri.Password; - - if (string.IsNullOrEmpty(accountId)) - throw new UserInformationException(Strings.B2.NoB2UserIDError, "B2MissingUserID"); - if (string.IsNullOrEmpty(accountKey)) - throw new UserInformationException(Strings.B2.NoB2KeyError, "B2MissingKey"); - - m_helper = new B2AuthHelper(accountId, accountKey); - - m_pagesize = DEFAULT_PAGE_SIZE; - if (options.ContainsKey(B2_PAGESIZE_OPTION)) - { - int.TryParse(options[B2_PAGESIZE_OPTION], out m_pagesize); - - if (m_pagesize <= 0) - throw new UserInformationException(Strings.B2.InvalidPageSizeError(B2_PAGESIZE_OPTION, options[B2_PAGESIZE_OPTION]), "B2InvalidPageSize"); - } - - m_downloadUrl = null; - if (options.ContainsKey(B2_DOWNLOAD_URL_OPTION)) - { - m_downloadUrl = options[B2_DOWNLOAD_URL_OPTION]; - } + return _bucket ??= buckets?.Buckets?.FirstOrDefault(x => + string.Equals(x.BucketName, _bucketName, StringComparison.OrdinalIgnoreCase)) + ?? throw new FolderMissingException(); } - - private BucketEntity Bucket + finally { - get - { - if (m_bucket == null) + _cacheLock.Release(); + } + } + + /// + /// Retrieves the upload URL and authorization token for the current bucket. + /// + /// CancellationToken that is combined with internal timeout token + private async Task GetUploadUrlDataAsync(CancellationToken cancellationToken) + { + if (_uploadUrl != null) + return _uploadUrl; + + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken).ConfigureAwait(false); + var bucket = await GetBucketAsync(cancellationToken).ConfigureAwait(false); + + _uploadUrl = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct + => await _b2AuthHelper.PostAndGetJsonDataAsync( + $"{config.APIUrl}/b2api/v1/b2_get_upload_url", + new UploadUrlRequest { - var buckets = m_helper.PostAndGetJSONData( - string.Format("{0}/b2api/v1/b2_list_buckets", m_helper.APIUrl), - new ListBucketsRequest() - { - AccountID = m_helper.AccountID - } - ); + BucketID = bucket.BucketID + ?? throw new Exception("BucketID is null") + }, + ct).ConfigureAwait(false) + ).ConfigureAwait(false); - if (buckets != null && buckets.Buckets != null) - m_bucket = buckets.Buckets.FirstOrDefault(x => string.Equals(x.BucketName, m_bucketname, StringComparison.OrdinalIgnoreCase)); + return _uploadUrl; + } - if (m_bucket == null) - throw new FolderMissingException(); - } + /// + /// Retrieves the fileID searching by filename. + /// + /// Filename + /// Cancellation Token + /// + /// Exception thrown when file is not found + private async Task GetFileId(string filename, CancellationToken cancelToken) + { + if (_filecache != null && _filecache.TryGetValue(filename, out var value)) + return value.OrderByDescending(x => x.UploadTimestamp).First().FileID + ?? throw new InvalidDataException("Missing file ID"); - return m_bucket; - } + await RebuildFileCache(cancelToken).ConfigureAwait(false); + + if (_filecache != null && _filecache.TryGetValue(filename, out var value1)) + return value1.OrderByDescending(x => x.UploadTimestamp).First().FileID + ?? throw new InvalidDataException("Missing file ID"); + + throw new FileMissingException(); + } + + /// + public IList SupportedCommands => + new List([ + new CommandLineArgument(B2_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2accountidDescriptionShort, Strings.B2.B2accountidDescriptionLong, null, [AuthOptionsHelper.AuthUsernameOption], null), + new CommandLineArgument(B2_KEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.B2.B2applicationkeyDescriptionShort, Strings.B2.B2applicationkeyDescriptionLong, null, [AuthOptionsHelper.AuthPasswordOption], null), + new CommandLineArgument(B2_CREATE_BUCKET_TYPE_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2createbuckettypeDescriptionShort, Strings.B2.B2createbuckettypeDescriptionLong, DEFAULT_BUCKET_TYPE), + new CommandLineArgument(B2_PAGESIZE_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.B2.B2pagesizeDescriptionShort, Strings.B2.B2pagesizeDescriptionLong, DEFAULT_PAGE_SIZE.ToString()), + new CommandLineArgument(B2_DOWNLOAD_URL_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2downloadurlDescriptionShort, Strings.B2.B2downloadurlDescriptionLong), + ..TimeoutOptionsHelper.GetOptions() + ]); + + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) + { + TempFile? tmp = null; + + var measure = stream; + while (measure is OverrideableStream os && + os.GetType().GetField("m_basestream", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(os) is Stream baseStream) + { + measure = baseStream; } - private UploadUrlResponse UploadUrlData + if (measure == null) + throw new Exception($"Unable to unwrap stream from: {stream.GetType()}"); + + string sha1; + if (measure.CanSeek) { - get + var p = measure.Position; + + // Compute the hash + using (var hashalg = HashFactory.CreateHasher("SHA1")) + sha1 = Utility.Utility.ByteArrayAsHexString(hashalg.ComputeHash(measure)); + + measure.Position = p; + } + else + { + // No seeking possible, use a temp file + tmp = new TempFile(); + await using (var sr = File.OpenWrite(tmp)) + using (var hasher = HashFactory.CreateHasher("SHA1")) + await using (var hc = new HashCalculatingStream(measure, hasher)) { - if (m_uploadUrl == null) - m_uploadUrl = m_helper.PostAndGetJSONData( - string.Format("{0}/b2api/v1/b2_get_upload_url", m_helper.APIUrl), - new UploadUrlRequest() { BucketID = Bucket.BucketID } - ); - - return m_uploadUrl; + await Utility.Utility.CopyStreamAsync(hc, sr, cancelToken).ConfigureAwait(false); + sha1 = hc.GetFinalHashString(); } + + stream = File.OpenRead(tmp); } - private string GetFileID(string filename) + if (_filecache == null) + await RebuildFileCache(cancelToken).ConfigureAwait(false); + + var uploadUrlData = await GetUploadUrlDataAsync(cancelToken).ConfigureAwait(false); + try { - if (m_filecache != null && m_filecache.ContainsKey(filename)) - return m_filecache[filename].OrderByDescending(x => x.UploadTimestamp).First().FileID; + using var request = new HttpRequestMessage(HttpMethod.Post, uploadUrlData.UploadUrl); + using var timeoutStream = stream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); - List(); - if (m_filecache.ContainsKey(filename)) - return m_filecache[filename].OrderByDescending(x => x.UploadTimestamp).First().FileID; + request.Headers.TryAddWithoutValidation("Authorization", uploadUrlData.AuthorizationToken); + request.Headers.Add("X-Bz-Content-Sha1", sha1); + request.Headers.Add("X-Bz-File-Name", _urlencodedPrefix + Utility.Uri.UrlPathEncode(remotename)); + request.Content = new StreamContent(timeoutStream, B2_RECOMMENDED_CHUNK_SIZE); - throw new FileMissingException(); - } + request.Content.Headers.Add("Content-Type", "application/octet-stream"); + request.Content.Headers.Add("Content-Length", timeoutStream.Length.ToString()); - private string DownloadUrl - { - get - { - if (string.IsNullOrEmpty(m_downloadUrl)) + var response = await _httpClient.UploadStream(request, cancelToken).ConfigureAwait(false); + var rdata = await response.Content.ReadAsStreamAsync(cancelToken).ConfigureAwait(false); + + UploadFileResponse fileinfo; + using (var tr = new StreamReader(rdata)) + await using (var jr = new Newtonsoft.Json.JsonTextReader(tr)) + fileinfo = new Newtonsoft.Json.JsonSerializer().Deserialize(jr) + ?? throw new Exception("Failed to parse response"); + + // Delete old versions + if (_filecache!.ContainsKey(remotename)) + await DeleteAsync(remotename, cancelToken).ConfigureAwait(false); + + _filecache[remotename] = + [ + new FileEntity { - return m_helper.DownloadUrl; + FileID = fileinfo.FileID, + FileName = fileinfo.FileName, + Action = "upload", + Size = fileinfo.ContentLength, + UploadTimestamp = (long)(DateTime.UtcNow - Utility.Utility.EPOCH).TotalMilliseconds } - else - { - return m_downloadUrl; - } - } + ]; } - - public IList SupportedCommands + catch (Exception ex) { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(B2_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2accountidDescriptionShort, Strings.B2.B2accountidDescriptionLong, null, new string[] {"auth-password"}, null), - new CommandLineArgument(B2_KEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.B2.B2applicationkeyDescriptionShort, Strings.B2.B2applicationkeyDescriptionLong, null, new string[] {"auth-username"}, null), - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.B2.AuthPasswordDescriptionShort, Strings.B2.AuthPasswordDescriptionLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.B2.AuthUsernameDescriptionShort, Strings.B2.AuthUsernameDescriptionLong), - new CommandLineArgument(B2_CREATE_BUCKET_TYPE_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2createbuckettypeDescriptionShort, Strings.B2.B2createbuckettypeDescriptionLong, DEFAULT_BUCKET_TYPE), - new CommandLineArgument(B2_PAGESIZE_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.B2.B2pagesizeDescriptionShort, Strings.B2.B2pagesizeDescriptionLong, DEFAULT_PAGE_SIZE.ToString()), - new CommandLineArgument(B2_DOWNLOAD_URL_OPTION, CommandLineArgument.ArgumentType.String, Strings.B2.B2downloadurlDescriptionShort, Strings.B2.B2downloadurlDescriptionLong), - }); + _filecache = null; - } + var code = (int)B2AuthHelper.GetExceptionStatusCode(ex); + if (code is >= 500 and <= 599) + _uploadUrl = null; + + throw; } - - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + finally { - TempFile tmp = null; + tmp?.Dispose(); + } + } - // A bit dirty, but we need the underlying stream to compute the hash without any interference - var measure = stream; - while (measure is OverrideableStream) - measure = typeof(OverrideableStream).GetField("m_basestream", System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(measure) as System.IO.Stream; + /// + /// Download files from remote + /// + /// Filename at remote location + /// Destination stream to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancellationToken) + { + if (_filecache == null || !_filecache.ContainsKey(remotename)) + await RebuildFileCache(cancellationToken).ConfigureAwait(false); - if (measure == null) - throw new Exception(string.Format("Unable to unwrap stream from: {0}", stream.GetType())); + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken).ConfigureAwait(false); - string sha1; - if (measure.CanSeek) - { - var p = measure.Position; + using var request = _filecache != null && _filecache.ContainsKey(remotename) + ? await _b2AuthHelper.CreateRequestAsync( + $"{config.DownloadUrl}/b2api/v1/b2_download_file_by_id?fileId={Utility.Uri.UrlEncode(await GetFileId(remotename, cancellationToken))}", HttpMethod.Get, cancellationToken).ConfigureAwait(false) + : await _b2AuthHelper.CreateRequestAsync( + $"{config.DownloadUrl}/{_urlencodedPrefix}{Utility.Uri.UrlPathEncode(remotename)}", HttpMethod.Get, cancellationToken).ConfigureAwait(false); - // Compute the hash - using (var hashalg = HashFactory.CreateHasher("SHA1")) - sha1 = Library.Utility.Utility.ByteArrayAsHexString(hashalg.ComputeHash(measure)); + HttpResponseMessage? response = null; + try + { + response = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, ct => _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct)) + .ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); + using var timeoutStream = responseStream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + await timeoutStream.CopyToAsync(stream, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + if (B2AuthHelper.GetExceptionStatusCode(ex) == HttpStatusCode.NotFound) + throw new FileMissingException(); - measure.Position = p; - } - else - { - // No seeking possible, use a temp file - tmp = new TempFile(); - using (var sr = System.IO.File.OpenWrite(tmp)) - using (var hasher = HashFactory.CreateHasher("SHA1")) - using (var hc = new HashCalculatingStream(measure, hasher)) - { - await Utility.Utility.CopyStreamAsync(hc, sr, cancelToken).ConfigureAwait(false); - sha1 = hc.GetFinalHashString(); - } + await _b2AuthHelper.AttemptParseAndThrowExceptionAsync(ex, response, cancellationToken).ConfigureAwait(false); + throw; + } + finally + { + response?.Dispose(); + } + } - stream = System.IO.File.OpenRead(tmp); - } + /// + /// Implementation of interface method for listing remote folder contents + /// + /// List of IFileEntry with directory listing result + private async Task>> RebuildFileCache(CancellationToken cancellationToken) + { + _filecache = null; + var cache = new Dictionary>(); + string? nextFileId = null; + string? nextFileName = null; + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken).ConfigureAwait(false); + var listVersionUrl = $"{config.APIUrl}/b2api/v1/b2_list_file_versions"; - if (m_filecache == null) - List(); + var listRetryHelper = RetryAfterHelper.CreateOrGetRetryAfterHelper(listVersionUrl); + do + { try { - var fileinfo = await m_helper.GetJSONDataAsync( - UploadUrlData.UploadUrl, - cancelToken, - req => - { - req.Method = "POST"; - req.Headers["Authorization"] = UploadUrlData.AuthorizationToken; - req.Headers["X-Bz-Content-Sha1"] = sha1; - req.Headers["X-Bz-File-Name"] = m_urlencodedprefix + Utility.Uri.UrlPathEncode(remotename); - req.ContentType = "application/octet-stream"; - req.ContentLength = stream.Length; - }, - - async (req, reqCancelToken) => - { - using (var rs = req.GetRequestStream()) - await Utility.Utility.CopyStreamAsync(stream, rs, reqCancelToken); - } + var resp = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct + => await _b2AuthHelper.PostAndGetJsonDataAsync( + $"{config.APIUrl}/b2api/v1/b2_list_file_versions", + new ListFilesRequest + { + BucketID = (await GetBucketAsync(cancellationToken)).BucketID + ?? throw new Exception("BucketID is null"), + MaxFileCount = _pageSize, + Prefix = _prefix, + StartFileID = nextFileId, + StartFileName = nextFileName + }, + ct + ).ConfigureAwait(false) ).ConfigureAwait(false); - // Delete old versions - if (m_filecache.ContainsKey(remotename)) - await DeleteAsync(remotename, cancelToken).ConfigureAwait(false); - - m_filecache[remotename] = - [ - new FileEntity() - { - FileID = fileinfo.FileID, - FileName = fileinfo.FileName, - Action = "upload", - Size = fileinfo.ContentLength, - UploadTimestamp = (long)(DateTime.UtcNow - Utility.Utility.EPOCH).TotalMilliseconds - } - ]; - } - catch (Exception ex) - { - m_filecache = null; - - var code = (int)B2AuthHelper.GetExceptionStatusCode(ex); - if (code >= 500 && code <= 599) - m_uploadUrl = null; - - throw; - } - finally - { - tmp?.Dispose(); - } - } - - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) - { - AsyncHttpRequest req; - if (m_filecache == null || !m_filecache.ContainsKey(remotename)) - List(); - - if (m_filecache != null && m_filecache.ContainsKey(remotename)) - req = new AsyncHttpRequest(m_helper.CreateRequest(string.Format("{0}/b2api/v1/b2_download_file_by_id?fileId={1}", DownloadUrl, Library.Utility.Uri.UrlEncode(GetFileID(remotename))))); - else - req = new AsyncHttpRequest(m_helper.CreateRequest(string.Format("{0}/{1}{2}", DownloadUrl, m_urlencodedprefix, Library.Utility.Uri.UrlPathEncode(remotename)))); - - try - { - using (var resp = req.GetResponse()) - using (var rs = req.GetResponseStream()) - await Library.Utility.Utility.CopyStreamAsync(rs, stream, cancelToken).ConfigureAwait(false); - } - catch (Exception ex) - { - if (B2AuthHelper.GetExceptionStatusCode(ex) == HttpStatusCode.NotFound) - throw new FileMissingException(); - - B2AuthHelper.AttemptParseAndThrowException(ex); - - throw; - } - } - - public IEnumerable List() - { - m_filecache = null; - var cache = new Dictionary>(); - string nextFileID = null; - string nextFileName = null; - do - { - var resp = m_helper.PostAndGetJSONData( - string.Format("{0}/b2api/v1/b2_list_file_versions", m_helper.APIUrl), - new ListFilesRequest() - { - BucketID = Bucket.BucketID, - MaxFileCount = m_pagesize, - Prefix = m_prefix, - StartFileID = nextFileID, - StartFileName = nextFileName - } - ); - - nextFileID = resp.NextFileID; + nextFileId = resp.NextFileID; nextFileName = resp.NextFileName; if (resp.Files == null || resp.Files.Length == 0) break; - foreach (var f in resp.Files) + foreach (var file in resp.Files) { - if (!f.FileName.StartsWith(m_prefix, StringComparison.Ordinal)) + if (string.IsNullOrWhiteSpace(file.FileName)) continue; - var name = f.FileName.Substring(m_prefix.Length); - if (name.Contains("/")) + if (!file.FileName.StartsWith(_prefix, StringComparison.Ordinal)) continue; + var name = file.FileName[_prefix.Length..]; + if (name.Contains('/')) + continue; - List lst; - cache.TryGetValue(name, out lst); + cache.TryGetValue(name, out var lst); if (lst == null) - cache[name] = lst = new List(1); - lst.Add(f); + cache[name] = lst = new System.Collections.Generic.List(1); + lst.Add(file); } - - } while (nextFileID != null); - - m_filecache = cache; - - return - (from x in m_filecache - let newest = x.Value.OrderByDescending(y => y.UploadTimestamp).First() - let ts = Utility.Utility.EPOCH.AddMilliseconds(newest.UploadTimestamp) - select (IFileEntry)new FileEntry(x.Key, newest.Size, ts, ts) - ).ToList(); - } - - public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) - { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) - await PutAsync(remotename, fs, cancelToken); - } - - public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) - { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) - await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); - } - - public async Task DeleteAsync(string remotename, CancellationToken cancelToken) - { - try - { - if (m_filecache == null || !m_filecache.ContainsKey(remotename)) - List(); - - if (!m_filecache.ContainsKey(remotename)) - throw new FileMissingException(); - - foreach (var n in m_filecache[remotename].OrderBy(x => x.UploadTimestamp)) - await m_helper.PostAndGetJSONDataAsync( - string.Format("{0}/b2api/v1/b2_delete_file_version", m_helper.APIUrl), - cancelToken, - new DeleteRequest() - { - FileName = m_prefix + remotename, - FileID = n.FileID - } - ); - - m_filecache[remotename].Clear(); } - catch + catch (TooManyRequestException tex) { - m_filecache = null; - throw; + // Backblaze's B2 API rate limit reached. Presently they don't add Retry-After headers, we will default to a delay, but respect it if present. + if (tex.RetryAfter == null || (tex.RetryAfter.Date == null && tex.RetryAfter.Delta == null)) + listRetryHelper.SetRetryAfter( + new RetryConditionHeaderValue(TimeSpan.FromSeconds(B2_RETRY_AFTER_SECONDS))); + else + listRetryHelper.SetRetryAfter(tex.RetryAfter); + + await listRetryHelper.WaitForRetryAfterAsync(cancellationToken); } - } - - public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } - - public async Task CreateFolderAsync(CancellationToken cancelToken) - { - m_bucket = await m_helper.PostAndGetJSONDataAsync( - string.Format("{0}/b2api/v1/b2_create_bucket", m_helper.APIUrl), - cancelToken, - new BucketEntity() - { - AccountID = m_helper.AccountID, - BucketName = m_bucketname, - BucketType = m_bucketType - } - ).ConfigureAwait(false); - } - - public string DisplayName - { - get { return Strings.B2.DisplayName; } - } - - public string ProtocolKey - { - get { return "b2"; } - } - - public string Description - { - get { return Strings.B2.Description; } - } - - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new string[] { - new System.Uri(B2AuthHelper.AUTH_URL).Host, - m_helper?.APIDnsName, - m_helper?.DownloadDnsName - }.Where(x => !string.IsNullOrEmpty(x)) - .ToArray()); - - public void Dispose() - { - } - - private class DeleteRequest - { - [JsonProperty("fileName")] - public string FileName { get; set; } - [JsonProperty("fileId")] - public string FileID { get; set; } - } - - private class DeleteResponse : DeleteRequest - { - } - - private class UploadUrlRequest : BucketIDEntity - { - } - - private class UploadUrlResponse : BucketIDEntity - { - [JsonProperty("uploadUrl")] - public string UploadUrl { get; set; } - [JsonProperty("authorizationToken")] - public string AuthorizationToken { get; set; } - } - - private class AccountIDEntity - { - [JsonProperty("accountId")] - public string AccountID { get; set; } - } - - private class BucketIDEntity - { - [JsonProperty("bucketId")] - public string BucketID { get; set; } - } - - private class BucketEntity : AccountIDEntity - { - [JsonProperty("bucketId", NullValueHandling = NullValueHandling.Ignore)] - public string BucketID { get; set; } - [JsonProperty("bucketName")] - public string BucketName { get; set; } - [JsonProperty("bucketType")] - public string BucketType { get; set; } - } - - private class ListBucketsRequest : AccountIDEntity - { - } - - private class ListBucketsResponse - { - [JsonProperty("buckets")] - public BucketEntity[] Buckets { get; set; } - } - - private class ListFilesRequest : BucketIDEntity - { - [JsonProperty("startFileName", NullValueHandling = NullValueHandling.Ignore)] - public string StartFileName { get; set; } - [JsonProperty("startFileId", NullValueHandling = NullValueHandling.Ignore)] - public string StartFileID { get; set; } - [JsonProperty("maxFileCount")] - public long MaxFileCount { get; set; } - [JsonProperty("prefix")] - public string Prefix { get; set; } - } - - private class ListFilesResponse - { - [JsonProperty("nextFileName")] - public string NextFileName { get; set; } - [JsonProperty("nextFileId")] - public string NextFileID { get; set; } - [JsonProperty("files")] - public FileEntity[] Files { get; set; } - } - - private class FileEntity - { - [JsonProperty("fileId")] - public string FileID { get; set; } - [JsonProperty("fileName")] - public string FileName { get; set; } - [JsonProperty("action")] - public string Action { get; set; } - [JsonProperty("size")] - public long Size { get; set; } - [JsonProperty("uploadTimestamp")] - public long UploadTimestamp { get; set; } - - } - - private class UploadFileResponse : AccountIDEntity - { - [JsonProperty("bucketId")] - public string BucketID { get; set; } - [JsonProperty("fileId")] - public string FileID { get; set; } - [JsonProperty("fileName")] - public string FileName { get; set; } - [JsonProperty("contentLength")] - public long ContentLength { get; set; } - [JsonProperty("contentSha1")] - public string ContentSha1 { get; set; } - [JsonProperty("contentType")] - public string ContentType { get; set; } - } + } while (nextFileId != null); + return _filecache = cache; } -} + /// + /// Implementation of interface method for listing remote folder contents + /// + /// List of IFileEntry with directory listing result + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var filecache = await RebuildFileCache(cancellationToken).ConfigureAwait(false); + foreach (var x in filecache) + { + var newest = x.Value.OrderByDescending(y => y.UploadTimestamp).First(); + var ts = Utility.Utility.EPOCH.AddMilliseconds(newest.UploadTimestamp); + yield return new FileEntry(x.Key, newest.Size, ts, ts); + } + } + + // + public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) + { + await using FileStream fs = File.OpenRead(filename); + await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); + } + + /// + /// Download files from remote + /// + /// Filename at remote location + /// Destination file to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, string filename, CancellationToken cancellationToken) + { + await using var fs = File.Create(filename); + await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Delete remote file if it exists, if now, throws FileMissingException + /// + /// filename to be deleted on the remote + /// CancellationToken that is combined with internal timeout token + /// + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) + { + try + { + var filecache = _filecache; + if (filecache == null || !filecache.ContainsKey(remotename)) + filecache = await RebuildFileCache(cancellationToken).ConfigureAwait(false); + + if (!filecache.TryGetValue(remotename, out var value)) + throw new FileMissingException(); + + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken).ConfigureAwait(false); + + foreach (var n in value.OrderBy(x => x.UploadTimestamp)) + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct + => await _b2AuthHelper.PostAndGetJsonDataAsync( + $"{config.APIUrl}/b2api/v1/b2_delete_file_version", + new DeleteRequest + { + FileName = _prefix + remotename, + FileId = n.FileID + }, + ct + ).ConfigureAwait(false) + ).ConfigureAwait(false); + + filecache[remotename].Clear(); + } + catch + { + _filecache = null; + throw; + } + } + + /// + /// Performs test with backend (internally it uses the List() command, which by definition means the backend is working) + /// + /// Cancellation Token + public Task TestAsync(CancellationToken cancelToken) + => this.TestListAsync(cancelToken); + + /// + /// Create remote folder + /// + /// CancellationToken that will be combined with internal timeout token + /// + /// + public async Task CreateFolderAsync(CancellationToken cancellationToken) + { + var config = await _b2AuthHelper.GetConfigAsync(cancellationToken); + _bucket = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct + => await _b2AuthHelper.PostAndGetJsonDataAsync( + $"{config.APIUrl}/b2api/v1/b2_create_bucket", + new BucketEntity + { + AccountID = config.AccountID, + BucketName = _bucketName, + BucketType = _bucketType + }, + ct + ).ConfigureAwait(false) + ).ConfigureAwait(false); + } + + /// + public string DisplayName => Strings.B2.DisplayName; + + /// + public string ProtocolKey => "b2"; + + /// + public string Description => Strings.B2.Description; + + /// + public async Task GetDNSNamesAsync(CancellationToken cancelToken) + { + var config = await _b2AuthHelper.GetConfigAsync(cancelToken); + return new string?[] { + B2AuthHelper.AUTH_URL, + config.APIUrl, + config.DownloadUrl + }.WhereNotNullOrWhiteSpace() + .Select(x => new System.Uri(x).Host) + .Distinct() + .ToArray(); + } + + /// + /// Handles disposal of the backend's resources. + /// + public void Dispose() + { + try + { + _httpClient?.Dispose(); + } + catch + { + // ignored + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs b/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs index 2ec300a44..0b569ede8 100644 --- a/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs +++ b/Duplicati/Library/Backend/Backblaze/B2AuthHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,188 +19,198 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Net; using System.Text; +using Duplicati.Library.Backend.Backblaze.Model; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using Duplicati.Library.Utility; +using Exception = System.Exception; -namespace Duplicati.Library.Backend.Backblaze +namespace Duplicati.Library.Backend.Backblaze; + +/// +/// Helper class for handling Backblaze B2 authorization +/// +/// Username +/// Password +/// HttpClient instance to use and pass along to JsonWebHelperHttpClient +public class B2AuthHelper(string userid, string password, HttpClient httpClient, TimeoutOptionsHelper.Timeouts timeouts) : JsonWebHelperHttpClient(httpClient) { - public class B2AuthHelper : JSONWebHelper + /// + /// The configuration details after authorization + /// + /// The account ID + /// The API URL + /// The authorization token + /// The download URL + public record AuthResponse(string AccountID, string APIUrl, string AuthorizationToken, string DownloadUrl); + + /// + /// Cached authorization response + /// + private AuthResponse? _CachedAuthResponse; + + /// + /// Cached authorization response expiration time + /// + private DateTime _configExpires; + + /// + /// Defines lifetime of cached authorization response + /// + private const int CACHE_EXPIRATION_MINUTES = 60; + + /// + /// Authorization URL + /// + internal const string AUTH_URL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_account"; + + /// + /// Maximum number of retries for authorization + /// + private const int MAX_AUTHORIZATION_RETRIES = 5; + + /// + /// Creates an HTTP request message with authorization header + /// + /// The URL for the request + /// HTTP method (defaults to GET if null) + /// Configured HttpRequestMessage + public override async Task CreateRequestAsync(string url, HttpMethod method, CancellationToken cancellationToken) { - private readonly string m_credentials; - private AuthResponse m_config; - private DateTime m_configExpires; - internal const string AUTH_URL = "https://api.backblazeb2.com/b2api/v1/b2_authorize_account"; - - public B2AuthHelper(string userid, string password) - : base() - { - m_credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes(userid + ":" + password)); - } - - public override HttpWebRequest CreateRequest(string url, string method = null) - { - var r = base.CreateRequest(url, method); - r.Headers["Authorization"] = AuthorizationToken; - return r; - } - - public string AuthorizationToken { get { return Config.AuthorizationToken; } } - public string APIUrl { get { return Config.APIUrl; } } - public string DownloadUrl { get { return Config.DownloadUrl; } } - public string AccountID { get { return Config.AccountID; } } - - private string DropTrailingSlashes(string url) - { - while(url.EndsWith("/", StringComparison.Ordinal)) - url = url.Substring(0, url.Length - 1); - return url; - } - - public string APIDnsName - { - get - { - if (m_config == null || string.IsNullOrWhiteSpace(m_config.APIUrl)) - return null; - return new System.Uri(m_config.APIUrl).Host; - } - } - - public string DownloadDnsName - { - get - { - if (m_config == null || string.IsNullOrWhiteSpace(m_config.DownloadUrl)) - return null; - return new System.Uri(m_config.DownloadUrl).Host; - } - } - - private AuthResponse Config - { - get - { - if (m_config == null || m_configExpires < DateTime.UtcNow) - { - var retries = 0; - - while(true) - { - try - { - var req = base.CreateRequest(AUTH_URL); - req.Headers.Add("Authorization", string.Format("Basic {0}", m_credentials)); - req.ContentType = "application/json; charset=utf-8"; - - using(var resp = (HttpWebResponse)new AsyncHttpRequest(req).GetResponse()) - m_config = ReadJSONResponse(resp); - - m_config.APIUrl = DropTrailingSlashes(m_config.APIUrl); - m_config.DownloadUrl = DropTrailingSlashes(m_config.DownloadUrl); - - m_configExpires = DateTime.UtcNow + TimeSpan.FromHours(1); - return m_config; - } - catch (Exception ex) - { - var clienterror = false; - - try - { - // Only retry once on client errors - if (ex is WebException exception && exception.Response is HttpWebResponse response) - { - var sc = (int)response.StatusCode; - clienterror = (sc >= 400 && sc <= 499); - } - } - catch - { - } - - if (retries >= (clienterror ? 1 : 5)) - { - AttemptParseAndThrowException(ex); - throw; - } - - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); - retries++; - } - } - } - - return m_config; - } - } - - public static void AttemptParseAndThrowException(Exception ex) - { - Exception newex = null; - try - { - if (ex is WebException exception && exception.Response is HttpWebResponse hs) - { - string rawdata = null; - using(var rs = Library.Utility.AsyncHttpRequest.TrySetTimeout(hs.GetResponseStream())) - using(var sr = new System.IO.StreamReader(rs)) - rawdata = sr.ReadToEnd(); - - newex = new Exception("Raw message: " + rawdata); - - var msg = JsonConvert.DeserializeObject(rawdata); - newex = new Exception(string.Format("{0} - {1}: {2}", msg.Status, msg.Code, msg.Message)); - } - } - catch - { - } - - if (newex != null) - throw newex; - } - - protected override void ParseException(Exception ex) - { - AttemptParseAndThrowException(ex); - } - - public static HttpStatusCode GetExceptionStatusCode(Exception ex) - { - if (ex is WebException exception && exception.Response is HttpWebResponse response) - return response.StatusCode; - else - return default(HttpStatusCode); - } - - - private class ErrorResponse - { - [JsonProperty("code")] - public string Code { get; set; } - [JsonProperty("message")] - public string Message { get; set; } - [JsonProperty("status")] - public long Status { get; set; } - - } - - private class AuthResponse - { - [JsonProperty("accountId")] - public string AccountID { get; set; } - [JsonProperty("apiUrl")] - public string APIUrl { get; set; } - [JsonProperty("authorizationToken")] - public string AuthorizationToken { get; set; } - [JsonProperty("downloadUrl")] - public string DownloadUrl { get; set; } - } - + var request = await base.CreateRequestAsync(url, method, cancellationToken).ConfigureAwait(false); + var config = await GetConfigAsync(cancellationToken).ConfigureAwait(false); + request.Headers.TryAddWithoutValidation("Authorization", config.AuthorizationToken); + request.Headers.Add("User-Agent", UserAgent); + return request; } -} + /// + /// Cleans the url to remove trailing slashes + /// + /// URL + private static string DropTrailingSlashes(string url) + { + if (string.IsNullOrWhiteSpace(url)) + return url; + while (url.EndsWith("/", StringComparison.Ordinal)) + url = url.Substring(0, url.Length - 1); + return url; + } + + public async Task GetConfigAsync(CancellationToken cancelToken) + { + if (_CachedAuthResponse != null && _configExpires >= DateTime.UtcNow) + return _CachedAuthResponse; + + var retries = 0; + + while (true) + { + HttpResponseMessage? response = null; + try + { + using var request = await base.CreateRequestAsync(AUTH_URL, HttpMethod.Get, cancelToken).ConfigureAwait(false); + + request.Headers.Add("Authorization", $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(userid + ":" + password))}"); + request.Headers.Add("ContentType", "application/json; charset=utf-8"); + + + var authResponse = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancelToken, async ct => + { + response = await _httpClient.SendAsync(request, ct).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + return await ReadJsonResponseAsync(response, ct).ConfigureAwait(false) + ?? throw new Exception("Failed to parse authorization response"); + }).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(authResponse.AccountID)) + throw new Exception("Account ID is missing from authorization response"); + + if (string.IsNullOrWhiteSpace(authResponse.APIUrl)) + throw new Exception("API URL is missing from authorization response"); + + if (string.IsNullOrWhiteSpace(authResponse.AuthorizationToken)) + throw new Exception("Authorization token is missing from authorization response"); + + if (string.IsNullOrWhiteSpace(authResponse.DownloadUrl)) + throw new Exception("Download URL is missing from authorization response"); + + authResponse.APIUrl = DropTrailingSlashes(authResponse.APIUrl); + authResponse.DownloadUrl = DropTrailingSlashes(authResponse.DownloadUrl); + _configExpires = DateTime.UtcNow + TimeSpan.FromMinutes(CACHE_EXPIRATION_MINUTES); + + return _CachedAuthResponse = new AuthResponse(authResponse.AccountID, authResponse.APIUrl, authResponse.AuthorizationToken, authResponse.DownloadUrl); + } + catch (Exception ex) + { + var clientError = false; + + try + { + // Only retry once on client errors + if (ex is HttpRequestException { StatusCode: not null } exception) + { + var sc = (int)exception.StatusCode; + clientError = sc is >= 400 and <= 499; + } + } + catch + { + // ignored + } + + if (retries >= (clientError ? 1 : MAX_AUTHORIZATION_RETRIES)) + { + await AttemptParseAndThrowExceptionAsync(ex, response, cancelToken); + throw; + } + + Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); + retries++; + } + finally + { + response?.Dispose(); + } + } + } + + /// + /// Parses the exception and throws a new exception with the error message + /// + /// Exception to be parsed + /// Response context + /// New detailed exception + public override async Task AttemptParseAndThrowExceptionAsync(Exception ex, HttpResponseMessage? responseContext, CancellationToken cancelToken) + { + if (ex is not HttpRequestException || responseContext == null) + return; + + if (ex is HttpRequestException && responseContext.StatusCode == HttpStatusCode.TooManyRequests) + throw new TooManyRequestException(responseContext.Headers.RetryAfter); + + var rawData = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancelToken, ct => + { + using var stream = responseContext.Content.ReadAsStream(); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + }).ConfigureAwait(false); + + var errorResponse = JsonConvert.DeserializeObject(rawData) + ?? new ErrorResponse { Status = (int)responseContext.StatusCode, Message = responseContext.ReasonPhrase }; + throw new UserInformationException($"Backblaze ErrorResponse: {errorResponse.Status} - {errorResponse.Code}: {errorResponse.Message}", "BackblazeErrorResponse"); + } + + /// + /// Extract Http status code from exception + /// + /// Exception to be parsed + public static HttpStatusCode GetExceptionStatusCode(Exception ex) + { + return ex is HttpRequestException { StatusCode: not null } httpEx ? httpEx.StatusCode.Value : default; + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Duplicati.Library.Backend.Backblaze.csproj b/Duplicati/Library/Backend/Backblaze/Duplicati.Library.Backend.Backblaze.csproj index 8b90ed589..1e4aef315 100644 --- a/Duplicati/Library/Backend/Backblaze/Duplicati.Library.Backend.Backblaze.csproj +++ b/Duplicati/Library/Backend/Backblaze/Duplicati.Library.Backend.Backblaze.csproj @@ -3,10 +3,14 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.Box + diff --git a/Duplicati/Library/Backend/Backblaze/Model/AccountIDEntity.cs b/Duplicati/Library/Backend/Backblaze/Model/AccountIDEntity.cs new file mode 100644 index 000000000..822cbd1a0 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/AccountIDEntity.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Base class with AccountID property +/// +internal abstract class AccountIDEntity +{ + [JsonProperty("accountId")] + public string? AccountID { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ApiAuthResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/ApiAuthResponse.cs new file mode 100644 index 000000000..50582ef6a --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ApiAuthResponse.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Model for JSON parsing of AuthResponse +/// +internal class ApiAuthResponse +{ + [JsonProperty("accountId")] + public string? AccountID { get; set; } + [JsonProperty("apiUrl")] + public string? APIUrl { get; set; } + [JsonProperty("authorizationToken")] + public string? AuthorizationToken { get; set; } + [JsonProperty("downloadUrl")] + public string? DownloadUrl { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/BucketEntity.cs b/Duplicati/Library/Backend/Backblaze/Model/BucketEntity.cs new file mode 100644 index 000000000..02b70ec8e --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/BucketEntity.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a bucket entity in the Backblaze B2 storage system. +/// Contains information about a storage bucket including its identifier and type. +/// +internal class BucketEntity : AccountIDEntity +{ + /// + /// Gets or sets the unique identifier of the bucket. + /// This property can be null as specified by the NullValueHandling attribute. + /// + [JsonProperty("bucketId", NullValueHandling = NullValueHandling.Ignore)] + public string? BucketID { get; set; } + + /// + /// Gets or sets the name of the bucket. + /// + [JsonProperty("bucketName")] + public required string BucketName { get; set; } + + /// + /// Gets or sets the type of the bucket (e.g., "allPrivate", "allPublic"). + /// + [JsonProperty("bucketType")] + public string? BucketType { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/BucketIDEntity.cs b/Duplicati/Library/Backend/Backblaze/Model/BucketIDEntity.cs new file mode 100644 index 000000000..772b3be1c --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/BucketIDEntity.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Base class for BuckedID property +/// +internal abstract class BucketIDEntity +{ + /// + /// Gets or sets the unique identifier of the bucket. + /// + [JsonProperty("bucketId")] + public string? BucketID { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/DeleteRequest.cs b/Duplicati/Library/Backend/Backblaze/Model/DeleteRequest.cs new file mode 100644 index 000000000..02255bbb2 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/DeleteRequest.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a request to delete a file from Backblaze B2. +/// Contains the necessary information to identify the file to be deleted. +/// +internal class DeleteRequest +{ + /// + /// Gets or sets the name of the file to be deleted. + /// This is the full path of the file in the bucket. + /// + [JsonProperty("fileName")] + public string? FileName { get; set; } + + /// + /// Gets or sets the unique identifier of the file to be deleted. + /// This ID is assigned by Backblaze B2 when the file is uploaded. + /// + [JsonProperty("fileId")] + public string? FileId { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/DeleteResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/DeleteResponse.cs new file mode 100644 index 000000000..7becba930 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/DeleteResponse.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents the response received after deleting a file from Backblaze B2. +/// Inherits all properties from DeleteRequest as the response contains the same fields. +/// +internal class DeleteResponse : DeleteRequest; \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ErrorResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/ErrorResponse.cs new file mode 100644 index 000000000..a534f1263 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ErrorResponse.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Model for JSON parsing of ErrorResponse +/// +internal class ErrorResponse +{ + [JsonProperty("code")] + public string? Code { get; set; } + [JsonProperty("message")] + public string? Message { get; set; } + [JsonProperty("status")] + public long Status { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/FileEntity.cs b/Duplicati/Library/Backend/Backblaze/Model/FileEntity.cs new file mode 100644 index 000000000..8df78cdb1 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/FileEntity.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a file entity in the Backblaze B2 storage system. +/// Contains detailed information about a file including its identifier, name, size, and metadata. +/// +internal class FileEntity +{ + /// + /// Gets or sets the unique identifier assigned to the file by Backblaze B2. + /// This ID is used to reference the file in various API operations. + /// + [JsonProperty("fileId")] + public string? FileID { get; set; } + + /// + /// Gets or sets the name of the file as stored in Backblaze B2. + /// This is the full path of the file within the bucket. + /// + [JsonProperty("fileName")] + public string? FileName { get; set; } + + /// + /// Gets or sets the action performed on the file. + /// Possible values include "upload" for new files, "hide" for hidden files, + /// and "delete" for deleted files. + /// + [JsonProperty("action")] + public string? Action { get; set; } + + /// + /// Gets or sets the size of the file in bytes. + /// Represents the actual content length of the file. + /// + [JsonProperty("size")] + public long Size { get; set; } + + /// + /// Gets or sets the timestamp when the file was uploaded. + /// Value is in milliseconds since epoch (January 1, 1970 00:00:00 UTC). + /// + [JsonProperty("uploadTimestamp")] + public long UploadTimestamp { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ListBucketsRequest.cs b/Duplicati/Library/Backend/Backblaze/Model/ListBucketsRequest.cs new file mode 100644 index 000000000..75c9d7958 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ListBucketsRequest.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a request to list buckets in a Backblaze B2 account. +/// Inherits from AccountIDEntity to include the account identifier. +/// +internal class ListBucketsRequest : AccountIDEntity +{ + /// + /// Initializes a new instance of the ListBucketsRequest class. + /// + /// The account ID for which to list buckets. + public ListBucketsRequest(string? accountId) + { + AccountID = accountId; + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ListBucketsResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/ListBucketsResponse.cs new file mode 100644 index 000000000..58e22d6b6 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ListBucketsResponse.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents the response from a list buckets operation in Backblaze B2. +/// Contains an array of bucket entities returned by the API. +/// +internal class ListBucketsResponse +{ + /// + /// Gets or sets the array of bucket entities returned by the API. + /// Each element contains information about a single bucket in the account. + /// + [JsonProperty("buckets")] + public BucketEntity[]? Buckets { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ListFilesRequest.cs b/Duplicati/Library/Backend/Backblaze/Model/ListFilesRequest.cs new file mode 100644 index 000000000..3f63e829d --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ListFilesRequest.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a request to list files in a Backblaze B2 bucket. +/// Contains parameters for pagination and filtering of the file list. +/// +internal class ListFilesRequest : BucketIDEntity +{ + /// + /// Gets or sets the file name to start listing from. + /// Used for pagination to continue listing from a specific point. + /// + [JsonProperty("startFileName", NullValueHandling = NullValueHandling.Ignore)] + public string? StartFileName { get; set; } + + /// + /// Gets or sets the file ID to start listing from. + /// Used in conjunction with StartFileName for pagination. + /// + [JsonProperty("startFileId", NullValueHandling = NullValueHandling.Ignore)] + public string? StartFileID { get; set; } + + /// + /// Gets or sets the maximum number of files to return in a single request. + /// + [JsonProperty("maxFileCount")] + public long MaxFileCount { get; set; } + + /// + /// Gets or sets the prefix to filter files by. + /// Only files whose names begin with this prefix will be returned. + /// + [JsonProperty("prefix")] + public required string Prefix { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/ListFilesResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/ListFilesResponse.cs new file mode 100644 index 000000000..97d2869e1 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/ListFilesResponse.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents the response from a list files operation in Backblaze B2. +/// Contains a list of files and pagination information for subsequent requests. +/// +internal class ListFilesResponse +{ + /// + /// Gets or sets the name of the next file to use for pagination. + /// This value should be used in the next request to continue listing files. + /// + [JsonProperty("nextFileName")] + public string? NextFileName { get; set; } + + /// + /// Gets or sets the ID of the next file to use for pagination. + /// This value should be used in conjunction with NextFileName for subsequent requests. + /// + [JsonProperty("nextFileId")] + public string? NextFileID { get; set; } + + /// + /// Gets or sets the array of file entities returned by this request. + /// Each element contains information about a single file in the bucket. + /// + [JsonProperty("files")] + public FileEntity[]? Files { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/UploadFileResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/UploadFileResponse.cs new file mode 100644 index 000000000..9e82faaad --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/UploadFileResponse.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents the response received after uploading a file to Backblaze B2. +/// Contains details about the uploaded file including its ID, name, and metadata. +/// +internal class UploadFileResponse : AccountIDEntity +{ + /// + /// Gets or sets the unique identifier of the bucket containing the file. + /// + [JsonProperty("bucketId")] + public string? BucketID { get; set; } + + /// + /// Gets or sets the unique identifier assigned to the uploaded file. + /// + [JsonProperty("fileId")] + public string? FileID { get; set; } + + /// + /// Gets or sets the name of the uploaded file. + /// + [JsonProperty("fileName")] + public string? FileName { get; set; } + + /// + /// Gets or sets the size of the uploaded file in bytes. + /// + [JsonProperty("contentLength")] + public long ContentLength { get; set; } + + /// + /// Gets or sets the SHA1 hash of the file content. + /// + [JsonProperty("contentSha1")] + public string? ContentSha1 { get; set; } + + /// + /// Gets or sets the MIME type of the uploaded file. + /// + [JsonProperty("contentType")] + public string? ContentType { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/UploadUrlRequest.cs b/Duplicati/Library/Backend/Backblaze/Model/UploadUrlRequest.cs new file mode 100644 index 000000000..d04620c52 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/UploadUrlRequest.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents a request to get an upload URL for the Backblaze B2 API. +/// Inherits from BucketIDEntity to include the bucket identifier. +/// +internal class UploadUrlRequest : BucketIDEntity; \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Model/UploadUrlResponse.cs b/Duplicati/Library/Backend/Backblaze/Model/UploadUrlResponse.cs new file mode 100644 index 000000000..42bb641f1 --- /dev/null +++ b/Duplicati/Library/Backend/Backblaze/Model/UploadUrlResponse.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Backblaze.Model; + +/// +/// Represents the response from a get upload URL request in Backblaze B2. +/// Contains the URL and authorization token needed for subsequent file uploads. +/// +internal class UploadUrlResponse : BucketIDEntity +{ + /// + /// Gets or sets the URL to use for uploading files. + /// This URL is specific to the bucket and should be used for subsequent upload requests. + /// + [JsonProperty("uploadUrl")] + public string? UploadUrl { get; set; } + + /// + /// Gets or sets the authorization token required for upload requests. + /// This token must be included in the headers when uploading files using the UploadUrl. + /// + [JsonProperty("authorizationToken")] + public string? AuthorizationToken { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Backblaze/Strings.cs b/Duplicati/Library/Backend/Backblaze/Strings.cs index 17f6f3866..78f16924d 100644 --- a/Duplicati/Library/Backend/Backblaze/Strings.cs +++ b/Duplicati/Library/Backend/Backblaze/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,26 +20,24 @@ // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class B2 { +namespace Duplicati.Library.Backend.Strings +{ + internal static class B2 + { public static string Description { get { return LC.L(@"This backend can read and write data to the Backblaze B2 Cloud Storage. Allowed format is ""b2://bucketname/prefix""."); } } public static string DisplayName { get { return LC.L(@"B2 Cloud Storage"); } } public static string B2applicationkeyDescriptionLong { get { return LC.L(@"B2 Cloud Storage Application Key can be obtained after logging into your Backblaze account. This can also be supplied through the option --{0}.", "auth-password"); } } public static string B2applicationkeyDescriptionShort { get { return LC.L(@"B2 Cloud Storage Application Key"); } } public static string B2accountidDescriptionLong { get { return LC.L(@"B2 Cloud Storage Account ID can be obtained after logging into your Backblaze account. This can also be supplied through the option --{0}.", "auth-username"); } } public static string B2accountidDescriptionShort { get { return LC.L(@"B2 Cloud Storage Account ID"); } } - public static string AuthPasswordDescriptionLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string AuthPasswordDescriptionShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string AuthUsernameDescriptionLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string AuthUsernameDescriptionShort { get { return LC.L(@"Supply the username used to connect to the server"); } } public static string NoB2KeyError { get { return LC.L(@"No B2 Cloud Storage Application Key given"); } } public static string NoB2UserIDError { get { return LC.L(@"No B2 Cloud Storage Account ID given"); } } public static string B2createbuckettypeDescriptionLong { get { return LC.L(@"By default, a private bucket is created. Use this option to set the bucket type. Refer to the B2 documentation for allowed types."); } } public static string B2createbuckettypeDescriptionShort { get { return LC.L(@"The bucket type used when creating a bucket"); } } - public static string B2pagesizeDescriptionLong { get { return LC.L(@"Use this option to set the page size for listing contents of B2 buckets. A lower number means less data, but can increase the number of Class C transaction on B2. Suggested values are between 100 and 1000."); } } - public static string B2pagesizeDescriptionShort { get { return LC.L(@"The size of file-listing pages"); } } + public static string B2pagesizeDescriptionLong { get { return LC.L(@"Use this option to set the page size for listing contents of B2 buckets. A lower number means less data, but can increase the number of Class C transaction on B2. Suggested values are between 100 and 1000."); } } + public static string B2pagesizeDescriptionShort { get { return LC.L(@"The size of file-listing pages"); } } public static string B2downloadurlDescriptionLong { get { return LC.L(@"Change this if you want to use your custom domain to download files, and uploading will not be affected. The default download URL depends on your account and looks like ""https://f00X.backblazeb2.com""."); } } public static string B2downloadurlDescriptionShort { get { return LC.L(@"The base URL to use for downloading files"); } } - public static string InvalidPageSizeError(string argname, string value) { return LC.L(@"The setting ""{0}"" is invalid for ""{1}"". It must be an integer larger than zero.", value, argname); } - } + public static string InvalidPageSizeError(string argname, string? value) { return LC.L(@"The setting ""{0}"" is invalid for ""{1}"". It must be an integer larger than zero.", value, argname); } + } } diff --git a/Duplicati/Library/Backend/Box/BoxBackend.cs b/Duplicati/Library/Backend/Box/BoxBackend.cs index b0ab740bd..5ecbe7ce8 100644 --- a/Duplicati/Library/Backend/Box/BoxBackend.cs +++ b/Duplicati/Library/Backend/Box/BoxBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,25 +19,21 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Net; +using System.Net.Http.Json; +using System.Runtime.CompilerServices; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; +using Uri = Duplicati.Library.Utility.Uri; namespace Duplicati.Library.Backend.Box { - // ReSharper disable once ClassNeverInstantiated.Global - // This class is instantiated dynamically in the BackendLoader. - public class BoxBackend : IBackend, IStreamingBackend + public class BoxBackend : IStreamingBackend { - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("box.com"); private const string AUTHID_OPTION = "authid"; private const string REALLY_DELETE_OPTION = "box-delete-from-trash"; @@ -46,156 +42,149 @@ namespace Duplicati.Library.Backend.Box private const int PAGE_SIZE = 200; - private readonly BoxHelper m_oauth; - private readonly string m_path; - private readonly bool m_deleteFromTrash; + private readonly BoxHelper _oAuthHelper; + private readonly string _path; + private readonly bool _deleteFromTrash; - private string m_currentfolder; - private readonly Dictionary m_filecache = new Dictionary(); + private string? _currentFolder; + private readonly Dictionary _fileCache = new(); + private readonly TimeoutOptionsHelper.Timeouts _timeouts; - private class BoxHelper : OAuthHelper + private class BoxHelper : OAuthHelperHttpClient { - public BoxHelper(string authid) + private readonly TimeoutOptionsHelper.Timeouts _timeouts; + public BoxHelper(string authid, TimeoutOptionsHelper.Timeouts timeouts) : base(authid, "box.com") { AutoAuthHeader = true; + _timeouts = timeouts; } - protected override void ParseException(Exception ex) + public override async Task AttemptParseAndThrowExceptionAsync(Exception ex, HttpResponseMessage responseContext, CancellationToken cancellationToken) { - Exception newex = null; - try - { - if (ex is WebException exception && exception.Response is HttpWebResponse hs) - { - string rawdata = null; - using (var rs = Library.Utility.AsyncHttpRequest.TrySetTimeout(hs.GetResponseStream())) - using (var sr = new System.IO.StreamReader(rs)) - rawdata = sr.ReadToEnd(); + if (ex is not HttpRequestException || responseContext == null) + return; - if (string.IsNullOrWhiteSpace(rawdata)) - return; + if (responseContext is { StatusCode: HttpStatusCode.TooManyRequests }) + throw new TooManyRequestException(responseContext.Headers.RetryAfter); - newex = new Exception("Raw message: " + rawdata); - - var msg = JsonConvert.DeserializeObject(rawdata); - newex = new Exception(string.Format("{0} - {1}: {2}", msg.Status, msg.Code, msg.Message)); - - /*if (msg.ContextInfo != null && msg.ContextInfo.Length > 0) - newex = new Exception(string.Format("{0} - {1}: {2}{3}{4}", msg.Status, msg.Code, msg.Message, Environment.NewLine, string.Join("; ", from n in msg.ContextInfo select n.Message))); - */ - } - } - catch (Exception ex2) - { - Library.Logging.Log.WriteWarningMessage(LOGTAG, "BoxErrorParser", ex2, "Failed to parse error from Box"); - } - - if (newex != null) - throw newex; + await using var stream = await responseContext.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + using var reader = new StreamReader(stream); + var rawData = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, ct => reader.ReadToEndAsync(ct)).ConfigureAwait(false); + var errorResponse = JsonConvert.DeserializeObject(rawData) + ?? new ErrorResponse { Status = (int)responseContext.StatusCode, Code = "Unknown", Message = rawData }; + throw new UserInformationException($"Box.com ErrorResponse: {errorResponse.Status} - {errorResponse.Code}: {errorResponse.Message}", "box.com"); } } - // ReSharper disable once UnusedMember.Global - // This constructor is needed by the BackendLoader. public BoxBackend() { + _oAuthHelper = null!; + _path = null!; + _timeouts = null!; } - // ReSharper disable once UnusedMember.Global - // This constructor is needed by the BackendLoader. - public BoxBackend(string url, Dictionary options) + public BoxBackend(string url, Dictionary options) { - var uri = new Utility.Uri(url); + var uri = new Uri(url); - m_path = Util.AppendDirSeparator(uri.HostAndPath, "/"); + _path = Util.AppendDirSeparator(uri.HostAndPath, "/"); - string authid = null; - if (options.ContainsKey(AUTHID_OPTION)) - authid = options[AUTHID_OPTION]; + var authid = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TOKEN_URL) + .AuthId!; - m_deleteFromTrash = Library.Utility.Utility.ParseBoolOption(options, REALLY_DELETE_OPTION); + _deleteFromTrash = Utility.Utility.ParseBoolOption(options, REALLY_DELETE_OPTION); + _timeouts = TimeoutOptionsHelper.Parse(options); + + _oAuthHelper = new BoxHelper(authid, _timeouts); - m_oauth = new BoxHelper(authid); } private async Task GetCurrentFolderWithCacheAsync(CancellationToken cancelToken) { - if (m_currentfolder == null) - await GetCurrentFolderAsync(false, cancelToken).ConfigureAwait(false); + if (_currentFolder == null) + return await GetCurrentFolderAsync(false, cancelToken).ConfigureAwait(false); - return m_currentfolder; + return _currentFolder; } - private async Task GetCurrentFolderAsync(bool create, CancellationToken cancelToken) + private async Task GetCurrentFolderAsync(bool create, CancellationToken cancelToken) { var parentid = "0"; - foreach (var p in m_path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries)) + foreach (var p in _path.Split(["/"], StringSplitOptions.RemoveEmptyEntries)) { - var el = (MiniFolder)PagedFileListResponse(parentid, true).FirstOrDefault(x => x.Name == p); + var el = (MiniFolder?)await PagedFileListResponse(parentid, true, cancelToken).FirstOrDefaultAsync(x => x.Name == p, cancellationToken: cancelToken).ConfigureAwait(false); if (el == null) { if (!create) throw new FolderMissingException(); - el = await m_oauth.PostAndGetJSONDataAsync( - string.Format("{0}/folders", BOX_API_URL), - cancelToken, - new CreateItemRequest() { Name = p, Parent = new IDReference() { ID = parentid } } - ).ConfigureAwait(false); + el = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => _oAuthHelper.PostAndGetJsonDataAsync( + $"{BOX_API_URL}/folders", + new CreateItemRequest + { + Name = p, + Parent = new IDReference { ID = parentid } + }, + ct + )).ConfigureAwait(false); } parentid = el.ID; + if (string.IsNullOrWhiteSpace(parentid)) + throw new InvalidDataException($"Invalid folder ID for {p} in {_path}"); } - m_currentfolder = parentid; + return _currentFolder = parentid; } - private async Task GetFileIDAsync(string name, CancellationToken cancelToken) + private async Task GetFileIdAsync(string name, CancellationToken cancelToken) { - if (m_filecache.ContainsKey(name)) - return m_filecache[name]; + if (_fileCache.TryGetValue(name, out var async)) + return async; // Make sure we enumerate this, otherwise the m_filecache is empty. var currentFolder = await GetCurrentFolderWithCacheAsync(cancelToken).ConfigureAwait(false); - PagedFileListResponse(currentFolder, false).LastOrDefault(); + await PagedFileListResponse(currentFolder, false, cancelToken).LastOrDefaultAsync(cancellationToken: cancelToken).ConfigureAwait(false); - if (m_filecache.ContainsKey(name)) - return m_filecache[name]; + if (_fileCache.TryGetValue(name, out var idAsync)) + return idAsync; throw new FileMissingException(); } - private IEnumerable PagedFileListResponse(string parentid, bool onlyfolders) + private async IAsyncEnumerable PagedFileListResponse(string parentid, bool onlyfolders, [EnumeratorCancellation] CancellationToken cancelToken) { var offset = 0; var done = false; if (!onlyfolders) - m_filecache.Clear(); + _fileCache.Clear(); do { - var resp = m_oauth.GetJSONData(string.Format("{0}/folders/{1}/items?limit={2}&offset={3}&fields=name,size,modified_at", BOX_API_URL, parentid, PAGE_SIZE, offset)); + var resp = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, ct => _oAuthHelper.GetJsonDataAsync($"{BOX_API_URL}/folders/{parentid}/items?limit={PAGE_SIZE}&offset={offset}&fields=name,size,modified_at", ct)).ConfigureAwait(false); if (resp.Entries == null || resp.Entries.Length == 0) break; foreach (var f in resp.Entries) { + if (string.IsNullOrWhiteSpace(f.Name) || string.IsNullOrWhiteSpace(f.ID)) + continue; + if (onlyfolders && f.Type != "folder") { done = true; break; } - else - { - if (!onlyfolders && f.Type == "file") - m_filecache[f.Name] = f.ID; - yield return f; - } + if (!onlyfolders && f.Type == "file") + _fileCache[f.Name] = f.ID; + + yield return f; } offset = offset + PAGE_SIZE; @@ -206,310 +195,135 @@ namespace Duplicati.Library.Backend.Box } while (!done); } - #region IStreamingBackend implementation - - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { var currentFolder = await GetCurrentFolderWithCacheAsync(cancelToken).ConfigureAwait(false); - var createreq = new CreateItemRequest() + var createreq = new CreateItemRequest { Name = remotename, - Parent = new IDReference() + Parent = new IDReference { ID = currentFolder } }; - if (m_filecache.Count == 0) - PagedFileListResponse(currentFolder, false); + if (_fileCache.Count == 0) + await PagedFileListResponse(currentFolder, false, cancelToken).LastOrDefaultAsync(cancelToken).ConfigureAwait(false); - var existing = m_filecache.ContainsKey(remotename); + var existing = _fileCache.ContainsKey(remotename); + + var multipartForm = new MultipartFormDataContent(); try { string url; - var items = new List(2); if (existing) - url = $"{BOX_UPLOAD_URL}/{m_filecache[remotename]}/content"; + url = $"{BOX_UPLOAD_URL}/{_fileCache[remotename]}/content"; else { url = $"{BOX_UPLOAD_URL}/content"; - items.Add(new MultipartItem(createreq, "attributes")); + multipartForm.Add(JsonContent.Create(createreq), "attributes"); } - items.Add(new MultipartItem(stream, "file", remotename)); + using var timeoutStream = stream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + multipartForm.Add(new StreamContent(timeoutStream), "file", remotename); - var res = (await m_oauth.PostMultipartAndGetJSONDataAsync(url, null, cancelToken, items.ToArray())).Entries.First(); - m_filecache[remotename] = res.ID; + var res = (await _oAuthHelper.PostMultipartAndGetJsonDataAsync(url, cancelToken, multipartForm)).Entries?.FirstOrDefault(); + if (res == null || string.IsNullOrWhiteSpace(res.ID)) + throw new InvalidDataException("No file ID returned after upload"); + _fileCache[remotename] = res.ID; } catch { - m_filecache.Clear(); + _fileCache.Clear(); throw; } } - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { - var fileId = await GetFileIDAsync(remotename, cancelToken).ConfigureAwait(false); - using (var resp = await m_oauth.GetResponseAsync(string.Format("{0}/files/{1}/content", BOX_API_URL, fileId), cancelToken).ConfigureAwait(false)) - using (var rs = Duplicati.Library.Utility.AsyncHttpRequest.TrySetTimeout(resp.GetResponseStream())) - await Library.Utility.Utility.CopyStreamAsync(rs, stream, cancelToken).ConfigureAwait(false); + var fileId = await GetFileIdAsync(remotename, cancelToken).ConfigureAwait(false); + using var request = await _oAuthHelper.CreateRequestAsync($"{BOX_API_URL}/files/{fileId}/content", HttpMethod.Get, cancelToken).ConfigureAwait(false); + using var resp = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => _oAuthHelper.GetResponseAsync(request, HttpCompletionOption.ResponseHeadersRead, ct)).ConfigureAwait(false); + resp.EnsureSuccessStatusCode(); + await using var responseStream = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => resp.Content.ReadAsStreamAsync(ct)).ConfigureAwait(false); + using var ts = responseStream.ObserveReadTimeout(_timeouts.ReadWriteTimeout); + await Utility.Utility.CopyStreamAsync(ts, stream, cancelToken).ConfigureAwait(false); } - #endregion - - #region IBackend implementation - - public System.Collections.Generic.IEnumerable List() + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - var currentFolder = GetCurrentFolderWithCacheAsync(CancellationToken.None).Await(); - return - from n in PagedFileListResponse(currentFolder, false) - select (IFileEntry)new FileEntry(n.Name, n.Size, n.ModifiedAt, n.ModifiedAt) { IsFolder = n.Type == "folder" }; + var currentFolder = await GetCurrentFolderWithCacheAsync(cancelToken).ConfigureAwait(false); + await foreach (var n in PagedFileListResponse(currentFolder, false, cancelToken).ConfigureAwait(false)) + yield return new FileEntry(n.Name, n.Size, n.ModifiedAt, n.ModifiedAt) { IsFolder = n.Type == "folder" }; } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) - await PutAsync(remotename, fs, cancelToken); + await using FileStream fs = File.OpenRead(filename); + await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) - await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); + await using FileStream fs = File.Create(filename); + await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { - var fileid = await GetFileIDAsync(remotename, cancelToken).ConfigureAwait(false); + var fileId = await GetFileIdAsync(remotename, cancelToken).ConfigureAwait(false); try { - using (var r = await m_oauth.GetResponseAsync(string.Format("{0}/files/{1}", BOX_API_URL, fileid), cancelToken, null, "DELETE").ConfigureAwait(false)) + using (var request = await _oAuthHelper.CreateRequestAsync($"{BOX_API_URL}/files/{fileId}", HttpMethod.Delete, cancelToken).ConfigureAwait(false)) + using (var r = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => _oAuthHelper.GetResponseAsync(request, HttpCompletionOption.ResponseContentRead, ct)).ConfigureAwait(false)) { } - if (m_deleteFromTrash) - using (var r = await m_oauth.GetResponseAsync(string.Format("{0}/files/{1}/trash", BOX_API_URL, fileid), cancelToken, null, "DELETE").ConfigureAwait(false)) + if (_deleteFromTrash) + { + using (var request = await _oAuthHelper.CreateRequestAsync($"{BOX_API_URL}/files/{fileId}/trash", HttpMethod.Delete, cancelToken).ConfigureAwait(false)) + using (var r = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => _oAuthHelper.GetResponseAsync(request, HttpCompletionOption.ResponseContentRead, ct)).ConfigureAwait(false)) { } + } } catch { - m_filecache.Clear(); + _fileCache.Clear(); throw; } } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public Task CreateFolderAsync(CancellationToken cancellationToken) { return GetCurrentFolderAsync(true, cancellationToken); } - public string DisplayName - { - get - { - return Strings.Box.DisplayName; - } - } + public string DisplayName => Strings.Box.DisplayName; - public string ProtocolKey - { - get - { - return "box"; - } - } + public string ProtocolKey => "box"; - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Box.AuthidShort, Strings.Box.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("box.com"))), - new CommandLineArgument(REALLY_DELETE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Box.ReallydeleteShort, Strings.Box.ReallydeleteLong), - }); - } - } + public IList SupportedCommands => + [ + .. AuthIdOptionsHelper.GetOptions(TOKEN_URL), + new CommandLineArgument(REALLY_DELETE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Box.ReallydeleteShort, Strings.Box.ReallydeleteLong), + .. TimeoutOptionsHelper.GetOptions() + ]; - public string Description - { - get - { - return Strings.Box.Description; - } - } + public string Description => Strings.Box.Description; - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new string[] { + public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new System.Uri(BOX_API_URL).Host, new System.Uri(BOX_UPLOAD_URL).Host - }); - - #endregion - - #region IDisposable implementation + }.Distinct().WhereNotNullOrWhiteSpace().ToArray()); public void Dispose() { } - - #endregion - - private class MiniUser : IDReference - { - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("login")] - public string Login { get; set; } - } - - private class MiniFolder : IDReference - { - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("etag")] - public string ETag { get; set; } - [JsonProperty("sequence_id")] - public string SequenceID { get; set; } - } - - private class FileEntity : MiniFolder - { - public FileEntity() { Size = -1; } - - [JsonProperty("sha1")] - public string SHA1 { get; set; } - - [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] - public long Size { get; set; } - [JsonProperty("modified_at", NullValueHandling = NullValueHandling.Ignore)] - public DateTime ModifiedAt { get; set; } - } - - private class FolderList - { - [JsonProperty("total_count")] - public long TotalCount { get; set; } - [JsonProperty("entries")] - public MiniFolder[] Entries { get; set; } - } - - private class FileList - { - [JsonProperty("total_count")] - public long TotalCount { get; set; } - [JsonProperty("entries")] - public FileEntity[] Entries { get; set; } - [JsonProperty("offset")] - public long Offset { get; set; } - [JsonProperty("limit")] - public long Limit { get; set; } - } - - private class UploadEmail - { - [JsonProperty("access")] - public string Access { get; set; } - [JsonProperty("email")] - public string Email { get; set; } - } - - private class ListFolderResponse : MiniFolder - { - [JsonProperty("created_at")] - public DateTime CreatedAt { get; set; } - [JsonProperty("modified_at")] - public DateTime ModifiedAt { get; set; } - [JsonProperty("description")] - public string Description { get; set; } - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("path_collection")] - public FolderList PathCollection { get; set; } - - [JsonProperty("created_by")] - public MiniUser CreatedBy { get; set; } - [JsonProperty("modified_by")] - public MiniUser ModifiedBy { get; set; } - [JsonProperty("owned_by")] - public MiniUser OwnedBy { get; set; } - - [JsonProperty("shared_link")] - public MiniUser SharedLink { get; set; } - - [JsonProperty("folder_upload_email")] - public UploadEmail FolderUploadEmail { get; set; } - - [JsonProperty("parent")] - public MiniFolder Parent { get; set; } - - [JsonProperty("item_status")] - public string ItemStatus { get; set; } - - [JsonProperty("item_collection")] - public FileList ItemCollection { get; set; } - - } - - private class OrderEntry - { - [JsonProperty("by")] - public string By { get; set; } - [JsonProperty("direction")] - public string Direction { get; set; } - } - - private class ShortListResponse : FileList - { - [JsonProperty("order")] - public OrderEntry[] Order { get; set; } - - } - - private class IDReference - { - [JsonProperty("id")] - public string ID { get; set; } - } - - private class CreateItemRequest - { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("parent")] - public IDReference Parent { get; set; } - } - - private class ErrorResponse - { - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("status")] - public int Status { get; set; } - [JsonProperty("code")] - public string Code { get; set; } - [JsonProperty("help_url")] - public string HelpUrl { get; set; } - [JsonProperty("message")] - public string Message { get; set; } - [JsonProperty("request_id")] - public string RequestId { get; set; } - - } } } - diff --git a/Duplicati/Library/Backend/Box/Duplicati.Library.Backend.Box.csproj b/Duplicati/Library/Backend/Box/Duplicati.Library.Backend.Box.csproj index b71ec4eb0..983a2765c 100644 --- a/Duplicati/Library/Backend/Box/Duplicati.Library.Backend.Box.csproj +++ b/Duplicati/Library/Backend/Box/Duplicati.Library.Backend.Box.csproj @@ -3,11 +3,15 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.Box + diff --git a/Duplicati/Library/Backend/Box/Model/CreateItemRequest.cs b/Duplicati/Library/Backend/Box/Model/CreateItemRequest.cs new file mode 100644 index 000000000..c37a29db3 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/CreateItemRequest.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class CreateItemRequest +{ + [JsonProperty("name")] public string? Name { get; set; } + [JsonProperty("parent")] public IDReference? Parent { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/ErrorResponse.cs b/Duplicati/Library/Backend/Box/Model/ErrorResponse.cs new file mode 100644 index 000000000..888974aed --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/ErrorResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class ErrorResponse +{ + [JsonProperty("type")] public string? Type { get; set; } + [JsonProperty("status")] public int Status { get; set; } + [JsonProperty("code")] public string? Code { get; set; } + [JsonProperty("help_url")] public string? HelpUrl { get; set; } + [JsonProperty("message")] public string? Message { get; set; } + [JsonProperty("request_id")] public string? RequestId { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/FileEntity.cs b/Duplicati/Library/Backend/Box/Model/FileEntity.cs new file mode 100644 index 000000000..c55b2b15a --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/FileEntity.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class FileEntity : MiniFolder +{ + public FileEntity() + { + Size = -1; + } + + [JsonProperty("sha1")] public string? SHA1 { get; set; } + + [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] + public long Size { get; set; } + + [JsonProperty("modified_at", NullValueHandling = NullValueHandling.Ignore)] + public DateTime ModifiedAt { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/FileList.cs b/Duplicati/Library/Backend/Box/Model/FileList.cs new file mode 100644 index 000000000..5fb514cc9 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/FileList.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class FileList +{ + [JsonProperty("total_count")] public long TotalCount { get; set; } + [JsonProperty("entries")] public FileEntity[]? Entries { get; set; } + [JsonProperty("offset")] public long Offset { get; set; } + [JsonProperty("limit")] public long Limit { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/FolderList.cs b/Duplicati/Library/Backend/Box/Model/FolderList.cs new file mode 100644 index 000000000..778c7091c --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/FolderList.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class FolderList +{ + [JsonProperty("total_count")] public long TotalCount { get; set; } + [JsonProperty("entries")] public MiniFolder[]? Entries { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/IDReference.cs b/Duplicati/Library/Backend/Box/Model/IDReference.cs new file mode 100644 index 000000000..f0e0410c3 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/IDReference.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class IDReference +{ + [JsonProperty("id")] public string? ID { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/ListFolderResponse.cs b/Duplicati/Library/Backend/Box/Model/ListFolderResponse.cs new file mode 100644 index 000000000..6231d2e17 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/ListFolderResponse.cs @@ -0,0 +1,21 @@ +using System; +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class ListFolderResponse : MiniFolder +{ + [JsonProperty("created_at")] public DateTime CreatedAt { get; set; } + [JsonProperty("modified_at")] public DateTime ModifiedAt { get; set; } + [JsonProperty("description")] public string? Description { get; set; } + [JsonProperty("size")] public long Size { get; set; } + [JsonProperty("path_collection")] public FolderList? PathCollection { get; set; } + [JsonProperty("created_by")] public MiniUser? CreatedBy { get; set; } + [JsonProperty("modified_by")] public MiniUser? ModifiedBy { get; set; } + [JsonProperty("owned_by")] public MiniUser? OwnedBy { get; set; } + [JsonProperty("shared_link")] public MiniUser? SharedLink { get; set; } + [JsonProperty("folder_upload_email")] public UploadEmail? FolderUploadEmail { get; set; } + [JsonProperty("parent")] public MiniFolder? Parent { get; set; } + [JsonProperty("item_status")] public string? ItemStatus { get; set; } + [JsonProperty("item_collection")] public FileList? ItemCollection { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/MiniFolder.cs b/Duplicati/Library/Backend/Box/Model/MiniFolder.cs new file mode 100644 index 000000000..5d5f41029 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/MiniFolder.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class MiniFolder : IDReference +{ + [JsonProperty("type")] public string? Type { get; set; } + [JsonProperty("name")] public string? Name { get; set; } + [JsonProperty("etag")] public string? ETag { get; set; } + [JsonProperty("sequence_id")] public string? SequenceID { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/MiniUser.cs b/Duplicati/Library/Backend/Box/Model/MiniUser.cs new file mode 100644 index 000000000..0f96851fc --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/MiniUser.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class MiniUser : IDReference +{ + [JsonProperty("type")] public string? Type { get; set; } + [JsonProperty("name")] public string? Name { get; set; } + [JsonProperty("login")] public string? Login { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/OrderEntry.cs b/Duplicati/Library/Backend/Box/Model/OrderEntry.cs new file mode 100644 index 000000000..16e778ce5 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/OrderEntry.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class OrderEntry +{ + [JsonProperty("by")] public string? By { get; set; } + [JsonProperty("direction")] public string? Direction { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/ShortListResponse.cs b/Duplicati/Library/Backend/Box/Model/ShortListResponse.cs new file mode 100644 index 000000000..9e67c8bc5 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/ShortListResponse.cs @@ -0,0 +1,8 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class ShortListResponse : FileList +{ + [JsonProperty("order")] public OrderEntry[]? Order { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Model/UploadEmail.cs b/Duplicati/Library/Backend/Box/Model/UploadEmail.cs new file mode 100644 index 000000000..2cf749f09 --- /dev/null +++ b/Duplicati/Library/Backend/Box/Model/UploadEmail.cs @@ -0,0 +1,9 @@ +using Newtonsoft.Json; + +namespace Duplicati.Library.Backend.Box; + +public class UploadEmail +{ + [JsonProperty("access")] public string? Access { get; set; } + [JsonProperty("email")] public string? Email { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/Box/Strings.cs b/Duplicati/Library/Backend/Box/Strings.cs index ce2ad2883..2a1fc9b22 100644 --- a/Duplicati/Library/Backend/Box/Strings.cs +++ b/Duplicati/Library/Backend/Box/Strings.cs @@ -1,32 +1,31 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings -{ - internal static class Box { - public static string Description { get { return LC.L(@"This backend can read and write data to Box.com. Allowed format is ""box://folder/subfolder""."); } } - public static string DisplayName { get { return LC.L(@"Box.com"); } } - public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } - public static string AuthidShort { get { return LC.L(@"The authorization code"); } } - public static string ReallydeleteLong { get { return LC.L(@"After deleting a file, it may end up in the trash folder where it will be deleted after a grace period. Use this command to force immediate removal of delete files."); } } - public static string ReallydeleteShort { get { return LC.L(@"Force delete files"); } } - } -} +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Localization.Short; +namespace Duplicati.Library.Backend.Strings +{ + internal static class Box + { + public static string Description { get { return LC.L(@"This backend can read and write data to Box.com. Allowed format is ""box://folder/subfolder""."); } } + public static string DisplayName { get { return LC.L(@"Box.com"); } } + public static string ReallydeleteLong { get { return LC.L(@"After deleting a file, it may end up in the trash folder where it will be deleted after a grace period. Use this command to force immediate removal of delete files."); } } + public static string ReallydeleteShort { get { return LC.L(@"Force delete files"); } } + } +} diff --git a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs index 2be336b1d..956cbd1f3 100644 --- a/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs +++ b/Duplicati/Library/Backend/CloudFiles/CloudFiles.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,13 +22,10 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System; -using System.Collections.Generic; -using System.Linq; +using Duplicati.Library.Utility.Options; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -36,54 +33,57 @@ namespace Duplicati.Library.Backend // This class is instantiated dynamically in the BackendLoader. public class CloudFiles : IBackend, IStreamingBackend { + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + public const string AUTH_URL_US = "https://identity.api.rackspacecloud.com/auth"; public const string AUTH_URL_UK = "https://lon.auth.api.rackspacecloud.com/v1.0"; private const string DUMMY_HOSTNAME = "api.mosso.com"; + private const string AUTH_USERNAME_OPTION = "cloudfiles-username"; + private const string AUTH_PASSWORD_OPTION = "cloudfiles-accesskey"; + private const int ITEM_LIST_LIMIT = 1000; private readonly string m_username; private readonly string m_password; private readonly string m_path; - private string m_storageUrl = null; - private string m_authToken = null; + private string? m_storageUrl = null; + private string? m_authToken = null; private readonly string m_authUrl; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. public CloudFiles() { + m_username = null!; + m_password = null!; + m_path = null!; + m_authUrl = null!; + m_timeouts = null!; } // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. - public CloudFiles(string url, Dictionary options) + public CloudFiles(string url, Dictionary options) { var uri = new Utility.Uri(url); + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, AUTH_USERNAME_OPTION, AUTH_PASSWORD_OPTION); - if (options.ContainsKey("auth-username")) - m_username = options["auth-username"]; - if (options.ContainsKey("auth-password")) - m_password = options["auth-password"]; - - if (options.ContainsKey("cloudfiles-username")) - m_username = options["cloudfiles-username"]; - if (options.ContainsKey("cloudfiles-accesskey")) - m_password = options["cloudfiles-accesskey"]; - - if (!string.IsNullOrEmpty(uri.Username)) - m_username = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - m_password = uri.Password; - - if (string.IsNullOrEmpty(m_username)) + if (!auth.HasUsername) throw new UserInformationException(Strings.CloudFiles.NoUserIDError, "CloudFilesNoUserID"); - if (string.IsNullOrEmpty(m_password)) + if (auth.HasPassword) throw new UserInformationException(Strings.CloudFiles.NoAPIKeyError, "CloudFilesNoApiKey"); + (m_username, m_password) = auth.GetCredentials(); + //Fallback to the previous format if (url.Contains(DUMMY_HOSTNAME)) { + Logging.Log.WriteWarningMessage(LOGTAG, "CloudFilesDeprecatedFormat", null, Strings.CloudFiles.DeprecatedFormatWarning(DUMMY_HOSTNAME)); var u = new System.Uri(url); if (!string.IsNullOrEmpty(u.UserInfo)) @@ -117,8 +117,11 @@ namespace Duplicati.Library.Backend if (!m_path.StartsWith("/", StringComparison.Ordinal)) m_path = "/" + m_path; - if (!options.TryGetValue("cloudfiles-authentication-url", out m_authUrl)) - m_authUrl = Utility.Utility.ParseBoolOption(options, "cloudfiles-uk-account") ? AUTH_URL_UK : AUTH_URL_US; + var authUrl = options.GetValueOrDefault("cloudfiles-authentication-url"); + if (string.IsNullOrEmpty(authUrl)) + authUrl = Utility.Utility.ParseBoolOption(options, "cloudfiles-uk-account") ? AUTH_URL_UK : AUTH_URL_US; + m_authUrl = authUrl; + m_timeouts = TimeoutOptionsHelper.Parse(options); } #region IBackend Members @@ -133,25 +136,26 @@ namespace Duplicati.Library.Backend get { return "cloudfiles"; } } - public IEnumerable List() + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - string extraUrl = "?format=xml&limit=" + ITEM_LIST_LIMIT.ToString(); - string markerUrl = ""; + var extraUrl = "?format=xml&limit=" + ITEM_LIST_LIMIT.ToString(); + var markerUrl = ""; bool repeat; - do { var doc = new System.Xml.XmlDocument(); - - var req = CreateRequest("", extraUrl + markerUrl); + var req = await CreateRequest("", extraUrl + markerUrl, cancelToken).ConfigureAwait(false); try { - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = (HttpWebResponse)areq.GetResponse()) - using (var s = areq.GetResponseStream()) - doc.Load(s); + await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => + { + var areq = new Utility.AsyncHttpRequest(req); + using (var resp = (HttpWebResponse)areq.GetResponse()) + using (var s = areq.GetResponseStream()) + doc.Load(s); + }).ConfigureAwait(false); } catch (WebException wex) { @@ -163,26 +167,26 @@ namespace Duplicati.Library.Backend throw; } - System.Xml.XmlNodeList lst = doc.SelectNodes("container/object"); + var lst = doc.SelectNodes("container/object"); //Perhaps the folder does not exist? //The response should be 404 from the server, but it is not :( - if (lst.Count == 0 && markerUrl == "") //Only on first iteration + if (lst == null || lst.Count == 0 && markerUrl == "") //Only on first iteration { - try { CreateFolderAsync(CancellationToken.None).Await(); } + try { await CreateFolderAsync(cancelToken).ConfigureAwait(false); } catch { } //Ignore } - string lastItemName = ""; + if (lst == null) + yield break; + + var lastItemName = ""; foreach (System.Xml.XmlNode n in lst) { - string name = n["name"].InnerText; - long size; - DateTime mod; - - if (!long.TryParse(n["bytes"].InnerText, out size)) + var name = n["name"]?.InnerText; + if (!long.TryParse(n["bytes"]?.InnerText, out var size)) size = -1; - if (!DateTime.TryParse(n["last_modified"].InnerText, out mod)) + if (!DateTime.TryParse(n["last_modified"]?.InnerText, out var mod)) mod = new DateTime(); lastItemName = name; @@ -199,61 +203,55 @@ namespace Duplicati.Library.Backend public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) + using (var fs = File.OpenRead(filename)) await PutAsync(remotename, fs, cancelToken); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) + using (var fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { - var req = CreateRequest("/" + remotename, ""); - - req.Method = "DELETE"; - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = (HttpWebResponse)areq.GetResponse()) + var req = await CreateRequest("/" + remotename, "", cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => { - if (resp.StatusCode == System.Net.HttpStatusCode.NotFound) - throw new FileMissingException(); + req.Method = "DELETE"; + var areq = new AsyncHttpRequest(req); + using (var resp = (HttpWebResponse)areq.GetResponse()) + { + if (resp.StatusCode == HttpStatusCode.NotFound) + throw new FileMissingException(); - if ((int)resp.StatusCode >= 300) - throw new WebException(Strings.CloudFiles.FileDeleteError, null, WebExceptionStatus.ProtocolError, resp); - else - using (areq.GetResponseStream()) - { } - } + if ((int)resp.StatusCode >= 300) + throw new WebException(Strings.CloudFiles.FileDeleteError, null, WebExceptionStatus.ProtocolError, resp); + else + using (areq.GetResponseStream()) + { } + } + }).ConfigureAwait(false); } - catch (System.Net.WebException wex) + catch (WebException wex) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) + if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) throw new FileMissingException(wex); else throw; } - - return Task.CompletedTask; } - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.CloudFiles.DescriptionAuthPasswordShort, Strings.CloudFiles.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.CloudFiles.DescriptionAuthUsernameShort, Strings.CloudFiles.DescriptionAuthUsernameLong), - new CommandLineArgument("cloudfiles-username", CommandLineArgument.ArgumentType.String, Strings.CloudFiles.DescriptionUsernameShort, Strings.CloudFiles.DescriptionUsernameLong, null, new string[] {"auth-username"} ), - new CommandLineArgument("cloudfiles-accesskey", CommandLineArgument.ArgumentType.Password, Strings.CloudFiles.DescriptionPasswordShort, Strings.CloudFiles.DescriptionPasswordLong, null, new string[] {"auth-password"}), - new CommandLineArgument("cloudfiles-uk-account", CommandLineArgument.ArgumentType.Boolean, Strings.CloudFiles.DescriptionUKAccountShort, Strings.CloudFiles.DescriptionUKAccountLong("cloudfiles-authentication-url", AUTH_URL_UK)), - new CommandLineArgument("cloudfiles-authentication-url", CommandLineArgument.ArgumentType.String, Strings.CloudFiles.DescriptionAuthenticationURLShort, Strings.CloudFiles.DescriptionAuthenticationURLLong_v2("cloudfiles-uk-account"), AUTH_URL_US), - ]); - } - } + public IList SupportedCommands => + [ + new CommandLineArgument(AUTH_USERNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.CloudFiles.DescriptionUsernameShort, Strings.CloudFiles.DescriptionUsernameLong, null, [AuthOptionsHelper.AuthUsernameOption] ), + new CommandLineArgument(AUTH_PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.CloudFiles.DescriptionPasswordShort, Strings.CloudFiles.DescriptionPasswordLong, null, [AuthOptionsHelper.AuthPasswordOption]), + new CommandLineArgument("cloudfiles-uk-account", CommandLineArgument.ArgumentType.Boolean, Strings.CloudFiles.DescriptionUKAccountShort, Strings.CloudFiles.DescriptionUKAccountLong("cloudfiles-authentication-url", AUTH_URL_UK)), + new CommandLineArgument("cloudfiles-authentication-url", CommandLineArgument.ArgumentType.String, Strings.CloudFiles.DescriptionAuthenticationURLShort, Strings.CloudFiles.DescriptionAuthenticationURLLong_v2("cloudfiles-uk-account"), AUTH_URL_US), + .. TimeoutOptionsHelper.GetOptions(), + ]; public string Description { @@ -264,22 +262,20 @@ namespace Duplicati.Library.Backend #region IBackend_v2 Members - public Task TestAsync(CancellationToken cancelToken) - { + public Task TestAsync(CancellationToken cancelToken) => //The "Folder not found" is not detectable :( - this.TestList(); - return Task.CompletedTask; - } + this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { - var createReq = CreateRequest("", ""); - createReq.Method = "PUT"; - var areq = new Utility.AsyncHttpRequest(createReq); - using (var resp = (HttpWebResponse)areq.GetResponse()) - { } - - return Task.CompletedTask; + var createReq = await CreateRequest("", "", cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => + { + createReq.Method = "PUT"; + var areq = new AsyncHttpRequest(createReq); + using (var resp = (HttpWebResponse)areq.GetResponse()) + { } + }).ConfigureAwait(false); } #endregion @@ -295,42 +291,46 @@ namespace Duplicati.Library.Backend #region IStreamingBackend Members public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult( - new string[] { + new string?[] { new System.Uri(m_authUrl).Host, string.IsNullOrWhiteSpace(m_storageUrl) ? null : new System.Uri(m_storageUrl).Host } - .Where(x => !string.IsNullOrWhiteSpace(x)) + .WhereNotNullOrWhiteSpace() .ToArray() ); public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) { - var req = CreateRequest("/" + remotename, ""); + var req = await CreateRequest("/" + remotename, "", cancelToken).ConfigureAwait(false); req.Method = "GET"; - var areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); using (var resp = areq.GetResponse()) - using (var s = areq.GetResponseStream()) + using (var s = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) + using (var timeoutStream = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) using (var hasher = MD5.Create()) - using (var mds = new Utility.HashCalculatingStream(s, hasher)) + using (var mds = new HashCalculatingStream(timeoutStream, hasher)) { - string md5Hash = resp.Headers["ETag"]; + var md5Hash = resp.Headers["ETag"]; await Utility.Utility.CopyStreamAsync(mds, stream, true, cancelToken).ConfigureAwait(false); - if (!String.Equals(mds.GetFinalHashString(), md5Hash, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(mds.GetFinalHashString(), md5Hash, StringComparison.OrdinalIgnoreCase)) throw new Exception(Strings.CloudFiles.ETagVerificationError); } } public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) { - HttpWebRequest req = CreateRequest("/" + remotename, ""); + var req = await CreateRequest("/" + remotename, "", cancelToken).ConfigureAwait(false); req.Method = "PUT"; req.ContentType = "application/octet-stream"; try { req.ContentLength = stream.Length; } catch { } + // TODO: When reviewing this, lets build a common method to unwrap the stream passed from + // BackendManager.PutOperation, so we can use the same logic to compute hashes in all backends + //If we can pre-calculate the MD5 hash before transmission, do so /*if (stream.CanSeek) { @@ -356,22 +356,23 @@ namespace Duplicati.Library.Backend //TODO: We cannot use the local MD5 calculation, because that could involve a throttled read, // and may invoke various events { - string fileHash = null; + string? fileHash = null; long streamLen = -1; try { streamLen = stream.Length; } catch { } - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); - using (System.IO.Stream s = areq.GetRequestStream(streamLen)) + var areq = new AsyncHttpRequest(req); + using (var s = areq.GetRequestStream(streamLen)) + using (var timeoutStream = s.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) using (var hasher = MD5.Create()) - using (var mds = new Utility.HashCalculatingStream(s, hasher)) + using (var mds = new HashCalculatingStream(timeoutStream, hasher)) { await Utility.Utility.CopyStreamAsync(stream, mds, tryRewindSource: true, cancelToken: cancelToken); fileHash = mds.GetFinalHashString(); } - string md5Hash = null; + string? md5Hash = null; //We need to verify the eTag locally try @@ -393,7 +394,7 @@ namespace Duplicati.Library.Backend } - if (md5Hash == null || !String.Equals(md5Hash, fileHash, StringComparison.OrdinalIgnoreCase)) + if (md5Hash == null || !string.Equals(md5Hash, fileHash, StringComparison.OrdinalIgnoreCase)) { //Remove the broken file try { await DeleteAsync(remotename, cancelToken); } @@ -406,29 +407,32 @@ namespace Duplicati.Library.Backend #endregion - private HttpWebRequest CreateRequest(string remotename, string query) + private async Task CreateRequest(string remotename, string query, CancellationToken cancelToken) { //If this is the first call, get an authentication token if (string.IsNullOrEmpty(m_authToken) || string.IsNullOrEmpty(m_storageUrl)) { - var authReq = (HttpWebRequest)HttpWebRequest.Create(m_authUrl); - authReq.Headers.Add("X-Auth-User", m_username); - authReq.Headers.Add("X-Auth-Key", m_password); - authReq.Method = "GET"; - - var areq = new Utility.AsyncHttpRequest(authReq); - using (var resp = areq.GetResponse()) + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => { - m_storageUrl = resp.Headers["X-Storage-Url"]; - m_authToken = resp.Headers["X-Auth-Token"]; - } + var authReq = (HttpWebRequest)HttpWebRequest.Create(m_authUrl); + authReq.Headers.Add("X-Auth-User", m_username); + authReq.Headers.Add("X-Auth-Key", m_password); + authReq.Method = "GET"; - if (string.IsNullOrEmpty(m_authToken) || string.IsNullOrEmpty(m_storageUrl)) - throw new Exception(Strings.CloudFiles.UnexpectedResponseError); + var areq = new AsyncHttpRequest(authReq); + using (var resp = areq.GetResponse()) + { + m_storageUrl = resp.Headers["X-Storage-Url"]; + m_authToken = resp.Headers["X-Auth-Token"]; + } + + if (string.IsNullOrEmpty(m_authToken) || string.IsNullOrEmpty(m_storageUrl)) + throw new Exception(Strings.CloudFiles.UnexpectedResponseError); + }).ConfigureAwait(false); } var req = (HttpWebRequest)HttpWebRequest.Create(m_storageUrl + UrlEncode(m_path + remotename) + query); - req.Headers.Add("X-Auth-Token", UrlEncode(m_authToken)); + req.Headers.Add("X-Auth-Token", UrlEncode(m_authToken!)); req.UserAgent = "Duplicati CloudFiles Backend v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; req.KeepAlive = false; @@ -438,9 +442,9 @@ namespace Duplicati.Library.Backend return req; } - private string UrlEncode(string value) + private static string UrlEncode(string value) { - return Library.Utility.Uri.UrlEncode(value).Replace("+", "%20").Replace("%2f", "/"); + return Utility.Uri.UrlEncode(value).Replace("+", "%20").Replace("%2f", "/"); } } } diff --git a/Duplicati/Library/Backend/CloudFiles/Duplicati.Library.Backend.CloudFiles.csproj b/Duplicati/Library/Backend/CloudFiles/Duplicati.Library.Backend.CloudFiles.csproj index d16657a8d..9f355da46 100644 --- a/Duplicati/Library/Backend/CloudFiles/Duplicati.Library.Backend.CloudFiles.csproj +++ b/Duplicati/Library/Backend/CloudFiles/Duplicati.Library.Backend.CloudFiles.csproj @@ -4,7 +4,9 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/CloudFiles/Strings.cs b/Duplicati/Library/Backend/CloudFiles/Strings.cs index c4d939f06..acb5dc405 100644 --- a/Duplicati/Library/Backend/CloudFiles/Strings.cs +++ b/Duplicati/Library/Backend/CloudFiles/Strings.cs @@ -1,34 +1,32 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class CloudFiles { +namespace Duplicati.Library.Backend.Strings +{ + internal static class CloudFiles + { public static string Description_v2 { get { return LC.L(@"This backend can read and write data to CloudFiles. Allowed format is ""cloudfiles://container/folder""."); } } public static string DisplayName { get { return LC.L(@"Rackspace CloudFiles"); } } public static string DescriptionAuthenticationURLLong_v2(string optionname) { return LC.L(@"CloudFiles use different servers for authentication based on where the account resides. Use this option to set an alternate authentication URL. This option overrides --{0}.", optionname); } public static string DescriptionAuthenticationURLShort { get { return LC.L(@"Provide another authentication URL"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } public static string DescriptionPasswordLong { get { return LC.L(@"The API Access Key used to authenticate with CloudFiles."); } } public static string DescriptionPasswordShort { get { return LC.L(@"Supply the access key used to connect to the server"); } } public static string DescriptionUKAccountLong(string optionname, string optionvalue) { return LC.L(@"Duplicati will assume that the credentials given are for a US account. Use this option if the account is a UK based account. Note that this is equivalent to setting --{0}={1}.", optionname, optionvalue); } @@ -41,5 +39,6 @@ namespace Duplicati.Library.Backend.Strings { public static string NoAPIKeyError { get { return LC.L(@"No CloudFiles API Access Key given"); } } public static string NoUserIDError { get { return LC.L(@"No CloudFiles userID given"); } } public static string UnexpectedResponseError { get { return LC.L(@"Unexpected CloudFiles response. Perhaps the API has changed?"); } } + public static string DeprecatedFormatWarning(string hostname) { return LC.L(@$"The URL is using the marker hostname {hostname} which is no longer supported. Please use the new format ""cloudfiles://container/folder"" instead."); } } } diff --git a/Duplicati/Library/Backend/Dropbox/Dropbox.cs b/Duplicati/Library/Backend/Dropbox/Dropbox.cs index 0a07f143d..18c044dbc 100644 --- a/Duplicati/Library/Backend/Dropbox/Dropbox.cs +++ b/Duplicati/Library/Backend/Dropbox/Dropbox.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,11 +20,8 @@ // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using Duplicati.Library.Utility.Options; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend { @@ -32,9 +29,7 @@ namespace Duplicati.Library.Backend // This class is instantiated dynamically in the BackendLoader. public class Dropbox : IBackend, IStreamingBackend { - private const string AUTHID_OPTION = "authid"; - - private readonly string m_accesToken; + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("dropbox"); private readonly string m_path; private readonly DropboxHelper dbx; @@ -42,25 +37,27 @@ namespace Duplicati.Library.Backend // This constructor is needed by the BackendLoader. public Dropbox() { + m_path = null!; + dbx = null!; } // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. - public Dropbox(string url, Dictionary options) + public Dropbox(string url, Dictionary options) { var uri = new Utility.Uri(url); - m_path = Library.Utility.Uri.UrlDecode(uri.HostAndPath); + m_path = Utility.Uri.UrlDecode(uri.HostAndPath); if (m_path.Length != 0 && !m_path.StartsWith("/", StringComparison.Ordinal)) m_path = "/" + m_path; if (m_path.EndsWith("/", StringComparison.Ordinal)) m_path = m_path.Substring(0, m_path.Length - 1); - if (options.ContainsKey(AUTHID_OPTION)) - m_accesToken = options[AUTHID_OPTION]; + var authId = AuthIdOptionsHelper.Parse(options); + authId.RequireCredentials(TOKEN_URL); - dbx = new DropboxHelper(m_accesToken); + dbx = new DropboxHelper(authId.AuthId!, TimeoutOptionsHelper.Parse(options)); } public void Dispose() @@ -68,15 +65,9 @@ namespace Duplicati.Library.Backend // do nothing } - public string DisplayName - { - get { return Strings.Dropbox.DisplayName; } - } + public string DisplayName => Strings.Dropbox.DisplayName; - public string ProtocolKey - { - get { return "dropbox"; } - } + public string ProtocolKey => "dropbox"; private IFileEntry ParseEntry(MetaData md) { @@ -91,38 +82,42 @@ namespace Duplicati.Library.Backend ife.IsFolder = true; } - try { ife.LastModification = ife.LastAccess = DateTime.Parse(md.server_modified).ToUniversalTime(); } + try + { + if (!string.IsNullOrWhiteSpace(md.server_modified)) + ife.LastModification = ife.LastAccess = DateTime.Parse(md.server_modified).ToUniversalTime(); + } catch { } return ife; } - private T HandleListExceptions(Func func) + private async Task HandleListExceptions(Func> func) { try { - return func(); + return await func().ConfigureAwait(false); } catch (DropboxException de) { - if (de.errorJSON["error"][".tag"].ToString() == "path" && de.errorJSON["error"]["path"][".tag"].ToString() == "not_found") + if (de.errorJSON?["error"]?[".tag"]?.ToString() == "path" && de.errorJSON?["error"]?["path"]?[".tag"]?.ToString() == "not_found") throw new FolderMissingException(); throw; } } - public IEnumerable List() + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - var lfr = HandleListExceptions(() => dbx.ListFiles(m_path)); + var lfr = await HandleListExceptions(() => dbx.ListFiles(m_path, cancelToken)).ConfigureAwait(false); - foreach (var md in lfr.entries) + foreach (var md in lfr.entries ?? []) yield return ParseEntry(md); while (lfr.has_more) { - lfr = HandleListExceptions(() => dbx.ListFilesContinue(lfr.cursor)); - foreach (var md in lfr.entries) + lfr = await HandleListExceptions(() => dbx.ListFilesContinue(lfr.cursor!, cancelToken)).ConfigureAwait(false); + foreach (var md in lfr.entries ?? []) yield return ParseEntry(md); } } @@ -143,7 +138,7 @@ namespace Duplicati.Library.Backend { try { - string path = String.Format("{0}/{1}", m_path, remotename); + var path = $"{m_path}/{remotename}"; await dbx.DeleteAsync(path, cancelToken).ConfigureAwait(false); } catch (DropboxException) @@ -153,25 +148,18 @@ namespace Duplicati.Library.Backend } } - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Dropbox.AuthidShort, Strings.Dropbox.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("dropbox"))), - ]); - } - } + public IList SupportedCommands => + [ + .. AuthIdOptionsHelper.GetOptions(TOKEN_URL), + .. TimeoutOptionsHelper.GetOptions(), + ]; public string Description { get { return Strings.Dropbox.Description; } } public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(WebApi.Dropbox.Hosts()); public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public async Task CreateFolderAsync(CancellationToken cancelToken) { @@ -182,7 +170,7 @@ namespace Duplicati.Library.Backend catch (DropboxException de) { - if (de.errorJSON["error"][".tag"].ToString() == "path" && de.errorJSON["error"]["path"][".tag"].ToString() == "conflict") + if (de.errorJSON?["error"]?[".tag"]?.ToString() == "path" && de.errorJSON["error"]?["path"]?[".tag"]?.ToString() == "conflict") throw new FolderAreadyExistedException(); throw; } diff --git a/Duplicati/Library/Backend/Dropbox/DropboxHelper.cs b/Duplicati/Library/Backend/Dropbox/DropboxHelper.cs index cbecb5fb3..a068df2e5 100644 --- a/Duplicati/Library/Backend/Dropbox/DropboxHelper.cs +++ b/Duplicati/Library/Backend/Dropbox/DropboxHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,14 +18,12 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. + using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.IO; using System.Net; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -34,16 +32,19 @@ namespace Duplicati.Library.Backend private const int DROPBOX_MAX_CHUNK_UPLOAD = 10 * 1024 * 1024; // 10 MB max upload private const string API_ARG_HEADER = "DROPBOX-API-arg"; - public DropboxHelper(string accessToken) + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + + public DropboxHelper(string accessToken, TimeoutOptionsHelper.Timeouts timeouts) : base(accessToken, "dropbox") { + m_timeouts = timeouts; base.AutoAuthHeader = true; // Pre 2022 tokens are direct Dropbox tokens (no ':') // Post 2022-02-21 tokens are regular authid tokens (with a ':') base.AccessTokenOnly = !accessToken.Contains(":"); } - public ListFolderResult ListFiles(string path) + public async Task ListFiles(string path, CancellationToken cancelToken) { var pa = new PathArg { @@ -52,7 +53,9 @@ namespace Duplicati.Library.Backend try { - return PostAndGetJSONData(WebApi.Dropbox.ListFilesUrl(), pa); + return await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => + PostAndGetJSONDataAsync(WebApi.Dropbox.ListFilesUrl(), ct, pa) + ).ConfigureAwait(false); } catch (Exception ex) { @@ -61,13 +64,15 @@ namespace Duplicati.Library.Backend } } - public ListFolderResult ListFilesContinue(string cursor) + public async Task ListFilesContinue(string cursor, CancellationToken cancelToken) { var lfca = new ListFolderContinueArg() { cursor = cursor }; try { - return PostAndGetJSONData(WebApi.Dropbox.ListFilesContinueUrl(), lfca); + return await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => + PostAndGetJSONDataAsync(WebApi.Dropbox.ListFilesContinueUrl(), ct, lfca) + ).ConfigureAwait(false); } catch (Exception ex) { @@ -82,7 +87,9 @@ namespace Duplicati.Library.Backend try { - return await PostAndGetJSONDataAsync(WebApi.Dropbox.CreateFolderUrl(), cancellationToken, pa).ConfigureAwait(false); + return await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, ct => + PostAndGetJSONDataAsync(WebApi.Dropbox.CreateFolderUrl(), ct, pa) + ).ConfigureAwait(false); } catch (Exception ex) { @@ -111,13 +118,14 @@ namespace Duplicati.Library.Backend ulong globalBytesRead = 0; using (var rs = areq.GetRequestStream()) + using (var ts = rs.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) { int bytesRead = 0; do { bytesRead = await stream.ReadAsync(buffer, 0, sizeToRead, cancelToken).ConfigureAwait(false); globalBytesRead += (ulong)bytesRead; - await rs.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); + await ts.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); } while (bytesRead > 0 && globalBytesRead < (ulong)chunksize); } @@ -149,6 +157,7 @@ namespace Duplicati.Library.Backend int bytesReadInRequest = 0; sizeToRead = Math.Min(chunksize, (int)Utility.Utility.DEFAULT_BUFFER_SIZE); using (var rs = areq.GetRequestStream()) + using (var ts = rs.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) { int bytesRead = 0; do @@ -156,15 +165,18 @@ namespace Duplicati.Library.Backend bytesRead = await stream.ReadAsync(buffer, 0, sizeToRead, cancelToken).ConfigureAwait(false); bytesReadInRequest += bytesRead; globalBytesRead += (ulong)bytesRead; - await rs.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); + await ts.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false); } while (bytesRead > 0 && bytesReadInRequest < chunksize); } - using (var response = GetResponse(areq)) - using (var sr = new StreamReader(response.GetResponseStream())) - await sr.ReadToEndAsync().ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + using (var response = GetResponse(areq)) + using (var sr = new StreamReader(response.GetResponseStream())) + await sr.ReadToEndAsync().ConfigureAwait(false); + }).ConfigureAwait(false); } // finish session and commit @@ -180,7 +192,9 @@ namespace Duplicati.Library.Backend req.ContentType = "application/octet-stream"; req.Timeout = 200000; - return ReadJSONResponse(req); + return await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => + ReadJSONResponseAsync(req, ct) + ).ConfigureAwait(false); } catch (Exception ex) { @@ -198,8 +212,10 @@ namespace Duplicati.Library.Backend var req = CreateRequest(WebApi.Dropbox.DownloadFilesUrl(), "POST"); req.Headers[API_ARG_HEADER] = JsonConvert.SerializeObject(pa); - using (var response = GetResponse(req)) - await Utility.Utility.CopyStreamAsync(response.GetResponseStream(), fs, cancelToken).ConfigureAwait(false); + using (var response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => GetResponse(req))) + using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => response.GetResponseStream())) + using (var ts = rs.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(ts, fs, cancelToken).ConfigureAwait(false); } catch (Exception ex) { @@ -212,10 +228,13 @@ namespace Duplicati.Library.Backend { try { - var pa = new PathArg() { path = path }; - using (var response = await GetResponseAsync(WebApi.Dropbox.DeleteUrl(), cancelToken, pa).ConfigureAwait(false)) - using (var sr = new StreamReader(response.GetResponseStream())) - sr.ReadToEnd(); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + var pa = new PathArg() { path = path }; + using (var response = await GetResponseAsync(WebApi.Dropbox.DeleteUrl(), ct, pa).ConfigureAwait(false)) + using (var sr = new StreamReader(response.GetResponseStream())) + await sr.ReadToEndAsync(ct).ConfigureAwait(false); + }).ConfigureAwait(false); } catch (Exception ex) { @@ -232,8 +251,9 @@ namespace Duplicati.Library.Backend try { - using (var sr = new StreamReader(exception.Response.GetResponseStream())) - json = sr.ReadToEnd(); + if (exception.Response != null) + using (var sr = new StreamReader(exception.Response.GetResponseStream())) + json = sr.ReadToEnd(); } catch { } @@ -245,26 +265,27 @@ namespace Duplicati.Library.Backend if (httpResp.StatusCode == HttpStatusCode.NotFound) { if (filerequest) - throw new Duplicati.Library.Interface.FileMissingException(json); + throw new Interface.FileMissingException(json); else - throw new Duplicati.Library.Interface.FolderMissingException(json); + throw new Interface.FolderMissingException(json); } if (httpResp.StatusCode == HttpStatusCode.Conflict) { //TODO: Should actually parse and see if something else happens if (filerequest) - throw new Duplicati.Library.Interface.FileMissingException(json); + throw new Interface.FileMissingException(json); else - throw new Duplicati.Library.Interface.FolderMissingException(json); + throw new Interface.FolderMissingException(json); } if (httpResp.StatusCode == HttpStatusCode.Unauthorized) ThrowAuthException(json, exception); + if ((int)httpResp.StatusCode == 429 || (int)httpResp.StatusCode == 507) - ThrowOverQuotaError(); + throw new Interface.UserInformationException(Strings.Dropbox.OverQuotaError(string.IsNullOrWhiteSpace(json) ? exception.Message : json), "DropboxOverQuotaError", ex); } - JObject errJson = null; + JObject? errJson = null; try { errJson = JObject.Parse(json); @@ -275,20 +296,22 @@ namespace Duplicati.Library.Backend if (errJson != null) throw new DropboxException() { errorJSON = errJson }; + else if (exception.Response is HttpWebResponse wresp) + throw new InvalidDataException($"Non-json response (code: {wresp.StatusCode}, message: {wresp.StatusDescription}): {json}", ex); else - throw new InvalidDataException($"Non-json response: {json}"); + throw new InvalidDataException($"Non-json response: {json}", ex); } } } public class DropboxException : Exception { - public JObject errorJSON { get; set; } + public JObject? errorJSON { get; set; } } public class PathArg { - public string path { get; set; } + public string? path { get; set; } } public class FolderMetadata : MetaData @@ -329,7 +352,7 @@ namespace Duplicati.Library.Backend public class UploadSessionCursor { - public string session_id { get; set; } + public string? session_id { get; set; } public ulong offset { get; set; } } @@ -341,7 +364,7 @@ namespace Duplicati.Library.Backend autorename = false; mute = true; } - public string path { get; set; } + public string? path { get; set; } public string mode { get; set; } public bool autorename { get; set; } public bool mute { get; set; } @@ -350,41 +373,40 @@ namespace Duplicati.Library.Backend public class UploadSessionStartResult { - public string session_id { get; set; } + public string? session_id { get; set; } } public class ListFolderResult { + public MetaData[]? entries { get; set; } - public MetaData[] entries { get; set; } - - public string cursor { get; set; } + public string? cursor { get; set; } public bool has_more { get; set; } } public class ListFolderContinueArg { - public string cursor { get; set; } + public string? cursor { get; set; } } public class MetaData { [JsonProperty(".tag")] - public string tag { get; set; } - public string name { get; set; } - public string server_modified { get; set; } + public string? tag { get; set; } + public string? name { get; set; } + public string? server_modified { get; set; } public ulong size { get; set; } public bool IsFile { get { return tag == "file"; } } // While this is unused, the Dropbox API v2 documentation does not // declare this to be optional. // ReSharper disable once UnusedMember.Global - public string id { get; set; } + public string? id { get; set; } // While this is unused, the Dropbox API v2 documentation does not // declare this to be optional. // ReSharper disable once UnusedMember.Global - public string rev { get; set; } + public string? rev { get; set; } } public class FileMetaData : MetaData diff --git a/Duplicati/Library/Backend/Dropbox/Duplicati.Library.Backend.Dropbox.csproj b/Duplicati/Library/Backend/Dropbox/Duplicati.Library.Backend.Dropbox.csproj index 299f4dba2..84b5f8768 100644 --- a/Duplicati/Library/Backend/Dropbox/Duplicati.Library.Backend.Dropbox.csproj +++ b/Duplicati/Library/Backend/Dropbox/Duplicati.Library.Backend.Dropbox.csproj @@ -3,7 +3,9 @@ net8.0 Dropbox backend for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Dropbox/Strings.cs b/Duplicati/Library/Backend/Dropbox/Strings.cs index e5711bc39..107e56de4 100644 --- a/Duplicati/Library/Backend/Dropbox/Strings.cs +++ b/Duplicati/Library/Backend/Dropbox/Strings.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; @@ -28,5 +28,6 @@ namespace Duplicati.Library.Backend.Strings public static string DisplayName { get { return LC.L(@"Dropbox"); } } public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } public static string AuthidShort { get { return LC.L(@"The authorization code"); } } + public static string OverQuotaError(string message) { return LC.L(@"The Dropbox account is over quota: {0}", message); } } } diff --git a/Duplicati/Library/Backend/Dropbox/WebApi.cs b/Duplicati/Library/Backend/Dropbox/WebApi.cs index 9a9d908d1..acd91bcff 100644 --- a/Duplicati/Library/Backend/Dropbox/WebApi.cs +++ b/Duplicati/Library/Backend/Dropbox/WebApi.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,62 +19,41 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Utility; - namespace Duplicati.Library.Backend.WebApi { public static class Dropbox { public static string CreateFolderUrl() - { - return Uri.UriBuilder(Url.API, Path.CreateFolder); - } + => Utility.Uri.UriBuilder(Url.API, Path.CreateFolder); public static string ListFilesUrl() - { - return Uri.UriBuilder(Url.API, Path.ListFolder); - } + => Utility.Uri.UriBuilder(Url.API, Path.ListFolder); public static string ListFilesContinueUrl() - { - return Uri.UriBuilder(Url.API, Path.ListFolderContinue); - } + => Utility.Uri.UriBuilder(Url.API, Path.ListFolderContinue); public static string DeleteUrl() - { - return Uri.UriBuilder(Url.API, Path.DeleteFolder); - } + => Utility.Uri.UriBuilder(Url.API, Path.DeleteFolder); public static string UploadSessionStartUrl() - { - return Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionStart); - } + => Utility.Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionStart); public static string UploadSessionAppendUrl() - { - return Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionAppend); - } + => Utility.Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionAppend); public static string UploadSessionFinishUrl() - { - return Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionFinish); - } + => Utility.Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionFinish); public static string DownloadFilesUrl() - { - return Uri.UriBuilder(Url.CONTENT_API_URL, Path.DownloadFiles); - } + => Utility.Uri.UriBuilder(Url.CONTENT_API_URL, Path.DownloadFiles); public static string[] Hosts() - { - return new[] { new System.Uri(Url.API).Host, new System.Uri(Url.CONTENT_API_URL).Host }; - - } + => [new Uri(Url.API).Host, new Uri(Url.CONTENT_API_URL).Host]; private static class Url { public const string API = "https://api.dropboxapi.com/2"; - public const string CONTENT_API_URL = "https://content.dropboxapi.com/2"; + public const string CONTENT_API_URL = "https://content.dropboxapi.com/2"; } private static class Path diff --git a/Duplicati/Library/Backend/FTP/AlternateFTPBackend.cs b/Duplicati/Library/Backend/FTP/AlternateFTPBackend.cs index a021dcf4f..26384f5ac 100644 --- a/Duplicati/Library/Backend/FTP/AlternateFTPBackend.cs +++ b/Duplicati/Library/Backend/FTP/AlternateFTPBackend.cs @@ -1,7 +1,26 @@ -using System; -using System.Collections.Generic; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Security.Authentication; using Duplicati.Library.Interface; +using Duplicati.Library.Utility.Options; using FluentFTP; namespace Duplicati.Library.Backend; @@ -13,7 +32,7 @@ namespace Duplicati.Library.Backend; /// /// The functionality is identical to the FTP backend /// -public class AlternateFTPBackend: FTP +public class AlternateFTPBackend : FTP { /* These constants are overriden to provide transparent access to the alternate configuration keys @@ -24,9 +43,11 @@ public class AlternateFTPBackend: FTP protected override string CONFIG_KEY_FTP_UPLOAD_DELAY => "aftp-upload-delay"; protected override string CONFIG_KEY_FTP_LOGTOCONSOLE => "aftp-log-to-console"; protected override string CONFIG_KEY_FTP_LOGPRIVATEINFOTOCONSOLE => "aftp-log-privateinfo-to-console"; - + protected override string CONFIG_KEY_FTP_RELATIVE_PATH => "aftp-relative-path"; + protected override string CONFIG_KEY_FTP_USE_CWD_NAMES => "aftp-use-cwd-names"; + public override string Description => Strings.DescriptionAlternate; - + public override string DisplayName => Strings.DisplayNameAlternate; /* @@ -36,25 +57,29 @@ public class AlternateFTPBackend: FTP public AlternateFTPBackend() { - + } - public AlternateFTPBackend(string url, Dictionary options): base(url, options) + public AlternateFTPBackend(string url, Dictionary options) : base(url, options) { } - + /// /// Overriden so that for the alternate FTP backend, we need to support the alternate config keys starting with "aftp" /// public override IList SupportedCommands => - new List([ - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.DescriptionAuthPasswordShort, Strings.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.DescriptionAuthUsernameShort, Strings.DescriptionAuthUsernameLong), - new CommandLineArgument("disable-upload-verify", CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionDisableUploadVerifyShort, Strings.DescriptionDisableUploadVerifyLong), + [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(CONFIG_KEY_DISABLE_UPLOAD_VERIFY, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionDisableUploadVerifyShort, Strings.DescriptionDisableUploadVerifyLong), + new CommandLineArgument(CONFIG_KEY_FTP_RELATIVE_PATH, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionAbsolutePathShort, Strings.DescriptionAbsolutePathLong), + new CommandLineArgument(CONFIG_KEY_FTP_USE_CWD_NAMES, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionUseCwdNamesShort, Strings.DescriptionUseCwdNamesLong), new CommandLineArgument(CONFIG_KEY_FTP_DATA_CONNECTION_TYPE, CommandLineArgument.ArgumentType.Enumeration, Strings.DescriptionFtpDataConnectionTypeShort, Strings.DescriptionFtpDataConnectionTypeLong, DEFAULT_DATA_CONNECTION_TYPE_STRING, null, Enum.GetNames(typeof(FtpDataConnectionType))), new CommandLineArgument(CONFIG_KEY_FTP_ENCRYPTION_MODE, CommandLineArgument.ArgumentType.Enumeration, Strings.DescriptionFtpEncryptionModeShort, Strings.DescriptionFtpEncryptionModeLong, DEFAULT_ENCRYPTION_MODE_STRING, null, Enum.GetNames(typeof(FtpEncryptionMode))), new CommandLineArgument(CONFIG_KEY_FTP_SSL_PROTOCOLS, CommandLineArgument.ArgumentType.Flags, Strings.DescriptionSslProtocolsShort, Strings.DescriptionSslProtocolsLong, DEFAULT_SSL_PROTOCOLS_STRING, null, Enum.GetNames(typeof(SslProtocols))), new CommandLineArgument(CONFIG_KEY_FTP_UPLOAD_DELAY, CommandLineArgument.ArgumentType.Timespan, Strings.DescriptionUploadDelayShort, Strings.DescriptionUploadDelayLong, DEFAULT_UPLOAD_DELAY_STRING), new CommandLineArgument(CONFIG_KEY_FTP_LOGTOCONSOLE, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionLogToConsoleShort, Strings.DescriptionLogToConsoleLong), new CommandLineArgument(CONFIG_KEY_FTP_LOGPRIVATEINFOTOCONSOLE, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionLogPrivateInfoToConsoleShort, Strings.DescriptionLogPrivateInfoToConsoleLong, "false"), - ]); + new CommandLineArgument(CONFIG_KEY_FTP_IGNORE_PUREFTP, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionIgnorePureFTPShort, Strings.DescriptionIgnorePureFTPLong, "false"), + .. SslOptionsHelper.GetCertOnlyOptions(), + .. TimeoutOptionsHelper.GetOptions(), + ]; } \ No newline at end of file diff --git a/Duplicati/Library/Backend/FTP/Duplicati.Library.Backend.FTP.csproj b/Duplicati/Library/Backend/FTP/Duplicati.Library.Backend.FTP.csproj index 28a2a7698..f526a1c71 100644 --- a/Duplicati/Library/Backend/FTP/Duplicati.Library.Backend.FTP.csproj +++ b/Duplicati/Library/Backend/FTP/Duplicati.Library.Backend.FTP.csproj @@ -3,7 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable @@ -14,11 +16,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Duplicati/Library/Backend/FTP/FTPBackend.cs b/Duplicati/Library/Backend/FTP/FTPBackend.cs index c4bfb03da..096baeacc 100644 --- a/Duplicati/Library/Backend/FTP/FTPBackend.cs +++ b/Duplicati/Library/Backend/FTP/FTPBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,20 +19,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; using System.Net; using System.Net.Security; +using System.Runtime.CompilerServices; using System.Security.Authentication; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; +using Duplicati.Library.Logging; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using FluentFTP; using FluentFTP.Client.BaseClient; using FluentFTP.Exceptions; @@ -41,7 +38,7 @@ using Uri = System.Uri; namespace Duplicati.Library.Backend { - + /// /// The unified FTP backend which uses the FluentFTP library. /// @@ -54,44 +51,164 @@ namespace Duplicati.Library.Backend /// public class FTP : IStreamingBackend { - private NetworkCredential _userInfo; - private const string OPTION_ACCEPT_SPECIFIED_CERTIFICATE = "accept-specified-ssl-hash"; // Global option - private const string OPTION_ACCEPT_ANY_CERTIFICATE = "accept-any-ssl-certificate"; // Global option - + private static readonly string LogTag = Log.LogTagFromType(typeof(FTP)); + /// + /// The credentials used to authenticate with the FTP server + /// + private readonly NetworkCredential? _userInfo; + /// + /// The default data connection type + /// private const FtpDataConnectionType DEFAULT_DATA_CONNECTION_TYPE = FtpDataConnectionType.AutoPassive; + /// + /// The default encryption mode + /// private const FtpEncryptionMode DEFAULT_ENCRYPTION_MODE = FtpEncryptionMode.None; - private static readonly SslProtocols DEFAULT_SSL_PROTOCOLS = SslProtocols.None; // NOTE: None means "use system default" + /// + /// The default SSL protocols + /// + private static readonly SslProtocols DEFAULT_SSL_PROTOCOLS = SslProtocols.None; // NOTE: None means "use system default" + /// + /// Configuration key for the flag to ignore the PureFTPd limit issue, suppressing the exceptions. + /// + /// Chosen not to have ftp prefix to be agnostic between aftp and ftp + /// + protected virtual string CONFIG_KEY_FTP_IGNORE_PUREFTP => "ignore-pureftpd-limit-issue"; + /// + /// The configuration key for the FTP encryption mode + /// protected virtual string CONFIG_KEY_FTP_ENCRYPTION_MODE => "ftp-encryption-mode"; + /// + /// The configuration key for the FTP data connection type + /// protected virtual string CONFIG_KEY_FTP_DATA_CONNECTION_TYPE => "ftp-data-connection-type"; + /// + /// The configuration key for the FTP SSL protocols + /// protected virtual string CONFIG_KEY_FTP_SSL_PROTOCOLS => "ftp-ssl-protocols"; + /// + /// The configuration key for the FTP upload delay + /// protected virtual string CONFIG_KEY_FTP_UPLOAD_DELAY => "ftp-upload-delay"; + /// + /// The configuration key for the FTP log to console + /// protected virtual string CONFIG_KEY_FTP_LOGTOCONSOLE => "ftp-log-to-console"; + /// + /// The configuration key for the FTP log private info to console + /// protected virtual string CONFIG_KEY_FTP_LOGPRIVATEINFOTOCONSOLE => "ftp-log-privateinfo-to-console"; - + /// + /// The configuration key for the FTP log to console + /// + protected virtual string CONFIG_KEY_FTP_LOGDIAGNOSTICS => "ftp-log-diagnostics"; + /// + /// The configuration key for the FTP absolute paths option + /// + protected virtual string CONFIG_KEY_FTP_ABSOLUTE_PATH => "ftp-absolute-path"; + /// + /// The configuration key for the FTP relative path option + /// + protected virtual string CONFIG_KEY_FTP_RELATIVE_PATH => "ftp-relative-path"; + /// + /// The configuration key for the FTP use CWD names option + /// + protected virtual string CONFIG_KEY_FTP_USE_CWD_NAMES => "ftp-use-cwd-names"; + + /// + /// The configuration key for the disable upload verify option + /// + protected virtual string CONFIG_KEY_DISABLE_UPLOAD_VERIFY => "disable-upload-verify"; + // The following keys are private because they are irrelevant for inheritors and are here for backwards compatibility + /// + /// The configuration key for the legacy FTP passive mode + /// private static string CONFIG_KEY_FTP_LEGACY_FTPPASSIVE => "ftp-passive"; + /// + /// The configuration key for the legacy FTP active mode + /// private static string CONFIG_KEY_FTP_LEGACY_FTPREGULAR => "ftp-regular"; + /// + /// The configuration key for the legacy use SSL option + /// private static string CONFIG_KEY_FTP_LEGACY_USESSL => "use-ssl"; + /// + /// The test file name used to test access permissions + /// private const string TEST_FILE_NAME = "duplicati-access-privileges-test.tmp"; + /// + /// The test file content used to test access permissions + /// private const string TEST_FILE_CONTENT = "This file is used by Duplicati to test access permissions and can be safely deleted."; + /// + /// The default data connection type as a string + /// protected static readonly string DEFAULT_DATA_CONNECTION_TYPE_STRING = DEFAULT_DATA_CONNECTION_TYPE.ToString(); + /// + /// The default encryption mode as a string + /// protected static readonly string DEFAULT_ENCRYPTION_MODE_STRING = DEFAULT_ENCRYPTION_MODE.ToString(); + /// + /// The default SSL protocols as a string + /// protected static readonly string DEFAULT_SSL_PROTOCOLS_STRING = DEFAULT_SSL_PROTOCOLS.ToString(); + /// + /// The default upload delay as a string + /// protected static readonly string DEFAULT_UPLOAD_DELAY_STRING = "0s"; - private readonly string _url; + /// + /// The URL of the FTP server + /// + private readonly Uri _url; + /// + /// The flag to indicate if the list verify option is enabled + /// private readonly bool _listVerify = true; + /// + /// The flag to indicate if relative paths are used + /// + private readonly bool _relativePaths = true; + /// + /// The flag to indicate if the CWD strategy is used + /// + private readonly bool _useCwdNames = false; + /// + /// The FTP configuration + /// private readonly FtpConfig _ftpConfig; + /// + /// The wait time after each upload before checking the file size + /// private readonly TimeSpan _uploadWaitTime; - + /// + /// Flag to ignore the PureFTPd limit issue, suppressing the exceptions. + /// + private readonly bool _IgnorePureFTPdLimitIssue; + /// + /// The flag to indicate if the dialog should be logged to the console + /// private readonly bool _logToConsole; + /// + /// The flag to indicate if private information should be logged to the console + /// private readonly bool _logPrivateInfoToConsole; - private readonly bool _accepAllCertificates; - private readonly string[] _validHashes; - + /// + /// The flag to indicate if diagnostics information should be logged + /// + private readonly bool _diagnosticsLog; + /// + /// The ssl certificate options to use + /// + private readonly SslOptionsHelper.SslCertificateOptions _sslOptions; + /// + /// The timeout options to use + /// + private readonly TimeoutOptionsHelper.Timeouts _timeouts; /// /// The localized name to display for this backend /// @@ -102,31 +219,49 @@ namespace Duplicati.Library.Backend /// public virtual string ProtocolKey => "ftp"; - private AsyncFtpClient Client - { get; set; } + /// + /// The client instance + /// + private AsyncFtpClient? _client; + /// + /// The server initial working directory + /// + private string? _initialCwd; + + /// public virtual IList SupportedCommands => - new List([ - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.DescriptionAuthPasswordShort, Strings.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.DescriptionAuthUsernameShort, Strings.DescriptionAuthUsernameLong), - new CommandLineArgument("disable-upload-verify", CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionDisableUploadVerifyShort, Strings.DescriptionDisableUploadVerifyLong), + [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(CONFIG_KEY_DISABLE_UPLOAD_VERIFY, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionDisableUploadVerifyShort, Strings.DescriptionDisableUploadVerifyLong), + new CommandLineArgument(CONFIG_KEY_FTP_ABSOLUTE_PATH, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionAbsolutePathShort, Strings.DescriptionAbsolutePathLong), + new CommandLineArgument(CONFIG_KEY_FTP_USE_CWD_NAMES, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionUseCwdNamesShort, Strings.DescriptionUseCwdNamesLong), new CommandLineArgument(CONFIG_KEY_FTP_DATA_CONNECTION_TYPE, CommandLineArgument.ArgumentType.Enumeration, Strings.DescriptionFtpDataConnectionTypeShort, Strings.DescriptionFtpDataConnectionTypeLong, DEFAULT_DATA_CONNECTION_TYPE_STRING, null, Enum.GetNames(typeof(FtpDataConnectionType))), new CommandLineArgument(CONFIG_KEY_FTP_ENCRYPTION_MODE, CommandLineArgument.ArgumentType.Enumeration, Strings.DescriptionFtpEncryptionModeShort, Strings.DescriptionFtpEncryptionModeLong, DEFAULT_ENCRYPTION_MODE_STRING, null, Enum.GetNames(typeof(FtpEncryptionMode))), new CommandLineArgument(CONFIG_KEY_FTP_SSL_PROTOCOLS, CommandLineArgument.ArgumentType.Flags, Strings.DescriptionSslProtocolsShort, Strings.DescriptionSslProtocolsLong, DEFAULT_SSL_PROTOCOLS_STRING, null, Enum.GetNames(typeof(SslProtocols))), new CommandLineArgument(CONFIG_KEY_FTP_UPLOAD_DELAY, CommandLineArgument.ArgumentType.Timespan, Strings.DescriptionUploadDelayShort, Strings.DescriptionUploadDelayLong, DEFAULT_UPLOAD_DELAY_STRING), new CommandLineArgument(CONFIG_KEY_FTP_LOGTOCONSOLE, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionLogToConsoleShort, Strings.DescriptionLogToConsoleLong), new CommandLineArgument(CONFIG_KEY_FTP_LOGPRIVATEINFOTOCONSOLE, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionLogPrivateInfoToConsoleShort, Strings.DescriptionLogPrivateInfoToConsoleLong, "false"), + new CommandLineArgument(CONFIG_KEY_FTP_LOGDIAGNOSTICS, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionLogDiagnosticsShort, Strings.DescriptionLogDiagnosticsLong), new CommandLineArgument(CONFIG_KEY_FTP_LEGACY_FTPPASSIVE, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionFTPPassiveShort, Strings.DescriptionFTPPassiveLong, "false", null, null, Strings.FtpPassiveDeprecated), new CommandLineArgument(CONFIG_KEY_FTP_LEGACY_FTPREGULAR, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionFTPActiveShort, Strings.DescriptionFTPActiveLong, "true", null, null, Strings.FtpActiveDeprecated), new CommandLineArgument(CONFIG_KEY_FTP_LEGACY_USESSL, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionUseSSLShort, Strings.DescriptionUseSSLLong, "false", null, null, Strings.UseSslDeprecated), - ]); + new CommandLineArgument(CONFIG_KEY_FTP_IGNORE_PUREFTP, CommandLineArgument.ArgumentType.Boolean, Strings.DescriptionIgnorePureFTPShort, Strings.DescriptionIgnorePureFTPLong, "false"), + .. SslOptionsHelper.GetCertOnlyOptions(), + .. TimeoutOptionsHelper.GetOptions(), + ]; /// /// Initialize a new instance. /// public FTP() { - + // TODO: Remove this constructor once static properties are introduced on IBackend + _sslOptions = null!; + _timeouts = null!; + _ftpConfig = null!; + _url = null!; + _client = null!; } /// @@ -135,201 +270,159 @@ namespace Duplicati.Library.Backend /// Configured url. /// Configured options. cannot be null. [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] // The behavior of accessing the virtual properties is as expected - public FTP(string url, Dictionary options) + public FTP(string url, Dictionary options) { - _accepAllCertificates = CoreUtility.ParseBoolOption(options, OPTION_ACCEPT_ANY_CERTIFICATE); - - string certHash; - options.TryGetValue(OPTION_ACCEPT_SPECIFIED_CERTIFICATE, out certHash); - - _validHashes = certHash?.Split([",", ";"], StringSplitOptions.RemoveEmptyEntries); + _sslOptions = SslOptionsHelper.Parse(options); var u = new Utility.Uri(url); u.RequireHost(); - if (!string.IsNullOrEmpty(u.Username)) + var auth = AuthOptionsHelper.Parse(options, u); + if (auth.HasUsername) { - _userInfo = new NetworkCredential + _userInfo = new NetworkCredential() { - UserName = u.Username + UserName = auth.Username, + Domain = "" }; - if (!string.IsNullOrEmpty(u.Password)) - _userInfo.Password = u.Password; - else if (options.ContainsKey("auth-password")) - _userInfo.Password = options["auth-password"]; - } - else - { - if (options.ContainsKey("auth-username")) - { - _userInfo = new NetworkCredential(); - _userInfo.UserName = options["auth-username"]; - if (options.ContainsKey("auth-password")) - _userInfo.Password = options["auth-password"]; - } + + if (auth.HasPassword) + _userInfo.Password = auth.Password; } - //Bugfix, see http://connect.microsoft.com/VisualStudio/feedback/details/695227/networkcredential-default-constructor-leaves-domain-null-leading-to-null-object-reference-exceptions-in-framework-code - if (_userInfo != null) - _userInfo.Domain = ""; + var parsedurl = u.SetScheme("ftp").SetQuery(null).SetCredentials(null, null).ToString(); + parsedurl = Util.AppendDirSeparator(parsedurl, "/"); + _url = new Uri(parsedurl); - _url = u.SetScheme("ftp").SetQuery(null).SetCredentials(null, null).ToString(); - _url = Util.AppendDirSeparator(_url, "/"); - _listVerify = !CoreUtility.ParseBoolOption(options, "disable-upload-verify"); + _listVerify = !CoreUtility.ParseBoolOption(options, CONFIG_KEY_DISABLE_UPLOAD_VERIFY); + _relativePaths = ProtocolKey == "ftp" + ? !CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_ABSOLUTE_PATH) + : CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_RELATIVE_PATH); + + _useCwdNames = CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_USE_CWD_NAMES); if (options.TryGetValue(CONFIG_KEY_FTP_UPLOAD_DELAY, out var uploadWaitTimeString) && !string.IsNullOrWhiteSpace(uploadWaitTimeString)) _uploadWaitTime = Timeparser.ParseTimeSpan(uploadWaitTimeString); - // Process the aftp-data-connection-type option - string dataConnectionTypeString; - FtpDataConnectionType dataConnectionType; - - if (!options.TryGetValue(CONFIG_KEY_FTP_DATA_CONNECTION_TYPE, out dataConnectionTypeString) || string.IsNullOrWhiteSpace(dataConnectionTypeString)) - dataConnectionTypeString = null; - - if (dataConnectionTypeString == null || !Enum.TryParse(dataConnectionTypeString, true, out dataConnectionType)) - dataConnectionType = DEFAULT_DATA_CONNECTION_TYPE; - - // Process the aftp-encryption-mode option - FtpEncryptionMode encryptionMode; - if (!options.TryGetValue(CONFIG_KEY_FTP_ENCRYPTION_MODE, out var encryptionModeString) || string.IsNullOrWhiteSpace(encryptionModeString)) - encryptionModeString = null; - - if (encryptionModeString == null || !Enum.TryParse(encryptionModeString, true, out encryptionMode)) - encryptionMode = DEFAULT_ENCRYPTION_MODE; - - // Process the aftp-ssl-protocols option - SslProtocols sslProtocols; - if (!options.TryGetValue(CONFIG_KEY_FTP_SSL_PROTOCOLS, out var sslProtocolsString) || string.IsNullOrWhiteSpace(sslProtocolsString)) - sslProtocolsString = null; - - if (sslProtocolsString == null || !Enum.TryParse(sslProtocolsString, true, out sslProtocols)) sslProtocols = DEFAULT_SSL_PROTOCOLS; + var dataConnectionType = CoreUtility.ParseEnumOption(options, CONFIG_KEY_FTP_DATA_CONNECTION_TYPE, DEFAULT_DATA_CONNECTION_TYPE); + var encryptionMode = CoreUtility.ParseEnumOption(options, CONFIG_KEY_FTP_ENCRYPTION_MODE, DEFAULT_ENCRYPTION_MODE); + var sslProtocols = CoreUtility.ParseFlagsOption(options, CONFIG_KEY_FTP_SSL_PROTOCOLS, DEFAULT_SSL_PROTOCOLS); // Process options of the legacy FTP backend if (ProtocolKey == "ftp") { // To mirror the behavior of existing backups, we need to check the legacy options - + // This flag takes precedence over ftp-data-connection-type - if (CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LEGACY_FTPPASSIVE)) + if (CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LEGACY_FTPPASSIVE)) dataConnectionType = FtpDataConnectionType.AutoPassive; - + // This flag takes precedence over the ftp-passive flag - if (CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LEGACY_FTPREGULAR)) + if (CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LEGACY_FTPREGULAR)) dataConnectionType = FtpDataConnectionType.AutoActive; - + // When using legacy useSSL option, the encryption is set to automatic and the SSL protocols are set to none // (None meaning the OS will choose the appropriate protocol) if (CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LEGACY_USESSL)) { sslProtocols = SslProtocols.None; - encryptionMode = FtpEncryptionMode.Auto; + encryptionMode = FtpEncryptionMode.Explicit; } } - + _logToConsole = CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LOGTOCONSOLE); _logPrivateInfoToConsole = CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LOGPRIVATEINFOTOCONSOLE); - + _diagnosticsLog = CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_LOGDIAGNOSTICS); + _timeouts = TimeoutOptionsHelper.Parse(options); + _ftpConfig = new FtpConfig { DataConnectionType = dataConnectionType, EncryptionMode = encryptionMode, SslProtocols = sslProtocols, LogToConsole = _logToConsole, + ValidateAnyCertificate = _sslOptions.AcceptAllCertificates, + Noop = true }; + _IgnorePureFTPdLimitIssue = CoreUtility.ParseBoolOption(options, CONFIG_KEY_FTP_IGNORE_PUREFTP); + if (_logPrivateInfoToConsole) _ftpConfig.LogHost = _ftpConfig.LogPassword = _ftpConfig.LogUserName = true; } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - return List(string.Empty); - } - - public IEnumerable List(string filename) - { - return List(filename, false); - } - - private IEnumerable List(string filename, bool stripFile) - { - var list = new List(); - string remotePath = filename; - - var ftpClient = CreateClient(); - - // Get the remote path - var url = new Uri(_url); - remotePath = "/" + GetUnescapedAbsolutePath(url); - - if (!string.IsNullOrEmpty(filename)) + FtpListItem[] items; + try { - if (!stripFile) + var client = await CreateClient(cancelToken).ConfigureAwait(false); + var remotePath = PreparePathForClient(null); + + items = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, ct => + client.GetListing(remotePath, FtpListOption.Modify | FtpListOption.Size, ct) + ).ConfigureAwait(false); + + if (client.ServerType == FtpServer.PureFTPd) { - // Append the filename - remotePath += filename; - } - else if (filename.Contains("/")) - { - remotePath += filename.Substring(0, filename.LastIndexOf("/", StringComparison.Ordinal)); - } - // else: stripping the filename in this case ignoring it + // If the list was truncated an exception has to be raised as the listing is incomplete and can lead to misleading backup/restore results + if( client.LastReplies.Any(x => + x.Code == "226" && + x.Message.Contains("truncated", StringComparison.InvariantCultureIgnoreCase))) + throw new UserInformationException("PureFTPd server effectively truncated the listing due to LimitRecursion parameter - please check documentation for more information", "PureFTPdTruncatedListing"); + + // If no truncation occured and the ignore flag is not set, issue an advisory message + if (!_IgnorePureFTPdLimitIssue) + Log.WriteWarningMessage(LogTag, "PureFTPdIssue", null, Strings.DescriptionIgnorePureFTPLong); + } + } + catch (Exception e) + { + if (TranslateException(null, ref e)) + throw e; + + throw; } - foreach (FtpListItem item in ftpClient.GetListing(remotePath, FtpListOption.Modify | FtpListOption.Size).Await()) + foreach (var item in items) { switch (item.Type) { case FtpObjectType.Directory: + if (item.Name == "." || item.Name == "..") + continue; + + yield return new FileEntry(item.Name, -1, new DateTime(), item.Modified) { - if (item.Name == "." || item.Name == "..") - { - continue; - } + IsFolder = true, + }; + break; - list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified) - { - IsFolder = true, - }); - - break; - } case FtpObjectType.File: - { - list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified)); + yield return new FileEntry(item.Name, item.Size, new DateTime(), item.Modified); + break; - break; - } case FtpObjectType.Link: { if (item.Name == "." || item.Name == "..") - { continue; - } if (item.LinkObject != null) { switch (item.LinkObject.Type) { case FtpObjectType.Directory: + if (item.Name == "." || item.Name == "..") + continue; + + yield return new FileEntry(item.Name, -1, new DateTime(), item.Modified) { - if (item.Name == "." || item.Name == "..") - { - continue; - } + IsFolder = true, + }; + break; - list.Add(new FileEntry(item.Name, -1, new DateTime(), item.Modified) - { - IsFolder = true, - }); - - break; - } case FtpObjectType.File: - { - list.Add(new FileEntry(item.Name, item.Size, new DateTime(), item.Modified)); - - break; - } + yield return new FileEntry(item.Name, item.Size, new DateTime(), item.Modified); + break; } } break; @@ -337,134 +430,165 @@ namespace Duplicati.Library.Backend } } - - return list; } + /// public async Task PutAsync(string remotename, Stream input, CancellationToken cancelToken) { - string remotePath = remotename; - long streamLen; - - var ftpClient = CreateClient(); - try { - streamLen = input.Length; - } - catch (NotSupportedException) { streamLen = -1; } + var streamLen = -1L; + var client = await CreateClient(cancelToken).ConfigureAwait(false); + var clientRemoteName = PreparePathForClient(remotename); - // Get the remote path - remotePath = ""; + try { streamLen = input.Length; } + catch (NotSupportedException) { } - if (!string.IsNullOrEmpty(remotename)) - { - // Append the filename - remotePath += remotename; - } + using var timeoutStream = input.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + var status = await client.UploadStream(timeoutStream, clientRemoteName, createRemoteDir: false, token: cancelToken, progress: null).ConfigureAwait(false); + if (status != FtpStatus.Success) + throw new UserInformationException(Strings.ErrorWriteFile(remotename, $"Status is {status}"), "FtpPutFailure"); - var status = await ftpClient.UploadStream(input, remotePath, createRemoteDir: false, token: cancelToken, progress: null).ConfigureAwait(false); - if (status != FtpStatus.Success) - { - throw new UserInformationException(string.Format(Strings.ErrorWriteFile, remotename), "AftpPutFailure"); - } + // Wait for the upload, if required + if (_uploadWaitTime.Ticks > 0) + Thread.Sleep(_uploadWaitTime); - // Wait for the upload, if required - if (_uploadWaitTime.Ticks > 0) - { - Thread.Sleep(_uploadWaitTime); - } - - if (_listVerify) - { - // check remote file size; matching file size indicates completion - var remoteSize = await ftpClient.GetFileSize(remotePath, -1, cancelToken); - if (streamLen != remoteSize) + if (_listVerify) { - throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, remoteSize, streamLen), "AftpListVerifySizeFailure"); + // check remote file size; matching file size indicates completion + var remoteSize = await client.GetFileSize(clientRemoteName, -1, cancelToken); + if (streamLen != remoteSize) + throw new UserInformationException(Strings.ListVerifySizeFailure(remotename, remoteSize, streamLen), "FtpListVerifySizeFailure"); } } + catch (Exception e) + { + if (TranslateException(remotename, ref e)) + throw e; + + throw; + } } + /// public async Task PutAsync(string remotename, string localname, CancellationToken cancelToken) { - await using FileStream fs = File.Open(localname, FileMode.Open, FileAccess.Read, FileShare.Read); + await using var fs = File.Open(localname, FileMode.Open, FileAccess.Read, FileShare.Read); await PutAsync(remotename, fs, cancelToken); } + /// public async Task GetAsync(string remotename, Stream output, CancellationToken cancelToken) { - var ftpClient = CreateClient(); - - // Get the remote path - var remotePath = ""; - - if (!string.IsNullOrEmpty(remotename)) - { - // Append the filename - remotePath += remotename; - } - - await using var inputStream = await ftpClient.OpenRead(remotePath, token: cancelToken); try { + var client = await CreateClient(cancelToken).ConfigureAwait(false); + var clientRemoteName = PreparePathForClient(remotename); + await using var inputStream = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => client.OpenRead(clientRemoteName, token: ct)).ConfigureAwait(false); + await using var timeoutStream = inputStream.ObserveReadTimeout(_timeouts.ReadWriteTimeout); await CoreUtility.CopyStreamAsync(inputStream, output, false, cancelToken).ConfigureAwait(false); } - finally + catch (Exception e) { - inputStream.Close(); + if (TranslateException(remotename, ref e)) + throw e; + + throw; } } + /// public async Task GetAsync(string remotename, string localname, CancellationToken cancelToken) { await using FileStream fs = File.Open(localname, FileMode.Create, FileAccess.Write, FileShare.None); await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } + /// public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { - var ftpClient = CreateClient(); - - // Get the remote path - var remotePath = ""; - - if (!string.IsNullOrEmpty(remotename)) + try { - // Append the filename - remotePath += remotename; + var client = await CreateClient(cancelToken).ConfigureAwait(false); + var clientRemoteName = PreparePathForClient(remotename); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => + client.DeleteFile(clientRemoteName, ct) + ).ConfigureAwait(false); } + catch (Exception e) + { + if (TranslateException(remotename, ref e)) + throw e; - await ftpClient.DeleteFile(remotePath, cancelToken); + throw; + } } - /// - /// A localized description of the backend, for display in the usage information - /// + /// public virtual string Description => Strings.Description; - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new Uri(_url).Host }); + /// + public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { _url.Host }); - /// - /// Test FTP access permissions. - /// + /// public async Task TestAsync(CancellationToken cancellationToken) { - var list = List(); - - // Delete test file if exists - if (list.Any(entry => entry.Name == TEST_FILE_NAME)) + + // Start with a simple list and pureFTP detection + try { - try + var client = await CreateClient(cancellationToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct => { + await ListAsync(cancellationToken).AnyAsync(cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); + + if (client.ServerType == FtpServer.PureFTPd && !_IgnorePureFTPdLimitIssue) + throw new UserInformationException(Strings.DescriptionIgnorePureFTPLong, "PureFTPdDetected"); + + } + catch (Exception e) + { + if (TranslateException(null, ref e)) + throw e; + + throw; + } + + // Try to set the working directory to trigger a folder-not-found exception + try + { + var client = await CreateClient(cancellationToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct => + { + if (!_useCwdNames) + { + var folderpath = PreparePathForClient(null); + await client.SetWorkingDirectory(folderpath, ct).ConfigureAwait(false); + } + }).ConfigureAwait(false); + } + catch (Exception e) + { + if (TranslateException(null, ref e)) + throw e; + + throw; + } + + // Remove the file if it exists + try + { + if (await ListAsync(cancellationToken).AnyAsync(entry => entry.Name == TEST_FILE_NAME, cancellationToken: cancellationToken).ConfigureAwait(false)) await DeleteAsync(TEST_FILE_NAME, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - if (e.InnerException != null) { e = e.InnerException; } - throw new Exception(string.Format(Strings.ErrorDeleteFile, e.Message), e); - } + } + catch (Exception e) + { + if (TranslateException(TEST_FILE_NAME, ref e)) + throw e; + + throw new UserInformationException(Strings.ErrorDeleteFile(TEST_FILE_NAME, e.Message), "TestPreparationError"); } // Test write permissions @@ -476,8 +600,11 @@ namespace Duplicati.Library.Backend } catch (Exception e) { - if (e.InnerException != null) { e = e.InnerException; } - throw new Exception(string.Format(Strings.ErrorWriteFile, e.Message), e); + // Do not pass the filename here because a not-found should be treated as folder-not-found + if (TranslateException(null, ref e)) + throw e; + + throw new UserInformationException(Strings.ErrorWriteFile(TEST_FILE_NAME, e.Message), "TestWriteError"); } } @@ -493,8 +620,10 @@ namespace Duplicati.Library.Backend } catch (Exception e) { - if (e.InnerException != null) { e = e.InnerException; } - throw new Exception(string.Format(Strings.ErrorReadFile, e.Message), e); + if (TranslateException(TEST_FILE_NAME, ref e)) + throw e; + + throw new UserInformationException(Strings.ErrorReadFile(TEST_FILE_NAME, e.Message), "TestReadError"); } } @@ -505,104 +634,205 @@ namespace Duplicati.Library.Backend } catch (Exception e) { - if (e.InnerException != null) { e = e.InnerException; } - throw new Exception(string.Format(Strings.ErrorDeleteFile, e.Message), e); + if (TranslateException(TEST_FILE_NAME, ref e)) + throw e; + + throw new UserInformationException(Strings.ErrorDeleteFile(TEST_FILE_NAME, e.Message), "TestCleanupError"); } } - public Task CreateFolderAsync(CancellationToken cancellationToken) + /// + public async Task CreateFolderAsync(CancellationToken cancellationToken) { - var client = CreateClient(false); + try + { + var client = await CreateClient(cancellationToken, false).ConfigureAwait(false); + var clientPath = PreparePathForClient(null, false); - var url = new Uri(_url); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct => + { + // Try to create the directory + if (_useCwdNames && clientPath.Contains('/') && clientPath != "/") + { + // Go to the parent folder and create the folder + var parentPath = clientPath.Substring(0, clientPath.LastIndexOf('/')); + var folderName = clientPath.Substring(clientPath.LastIndexOf('/') + 1); + await client.SetWorkingDirectory(parentPath, ct).ConfigureAwait(false); + await client.CreateDirectory(folderName, true, ct).ConfigureAwait(false); - // Get the remote path - var remotePath = GetUnescapedAbsolutePath(url); - - // Try to create the directory - return client.CreateDirectory(remotePath, true, cancellationToken); + // Reset the client and check that it works + _client = null; + client = await CreateClient(ct).ConfigureAwait(false); + var cwd = await client.GetWorkingDirectory(ct).ConfigureAwait(false); + if (!string.Equals(cwd?.TrimEnd('/'), clientPath, StringComparison.OrdinalIgnoreCase)) + throw new UserInformationException(Strings.ErrorCreateFolder(clientPath, cwd), "CreateFolderError"); + } + else + { + await client.CreateDirectory(clientPath, true, ct); + } + }).ConfigureAwait(false); + } + catch (Exception ex) + { + if (TranslateException(null, ref ex)) + throw ex; + throw; + } } + /// public void Dispose() { - if (Client != null) - Client.Dispose(); - - Client = null; - _userInfo = null; + _client?.Dispose(); } - private AsyncFtpClient CreateClient(bool setWorkingDirectory = true) + /// + /// Create a new FTP client, or return the existing one if it already exists. + /// + /// The cancellation token. + /// A flag to override the CWD strategy. + /// The FTP client. + private async Task CreateClient(CancellationToken cancellationToken, bool? cwdFlag = null) { - var uri = new Uri(_url); - - if (Client == null) // Create connection if it doesn't exist yet + if (_client == null) { - var ftpClient = new AsyncFtpClient + _client = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, async ct => { - Host = uri.Host, - Port = uri.Port == -1 ? 21 : uri.Port, - Credentials = _userInfo, - Config = _ftpConfig, - }; - - ftpClient.ValidateCertificate += HandleValidateCertificate; - - Client = ftpClient; - - } // else reuse existing connection - - if (setWorkingDirectory) - { - // Change working directory to the remote path - // Do this every time to prevent issues when FtpClient silently reconnects after failure. - var remotePath = GetUnescapedAbsolutePath(uri); - try - { - Client.SetWorkingDirectory(remotePath).Await(); - } - catch (FtpCommandException ex) - { - if (ex.CompletionCode == "550") + var client = new AsyncFtpClient { - throw new FolderMissingException(Strings.MissingFolderError(remotePath, ex.Message), ex); + Host = _url.Host, + Port = _url.Port == -1 ? 21 : _url.Port, + Credentials = _userInfo, + Config = _ftpConfig, + Logger = _diagnosticsLog ? new DiagnosticsLogger() : null + }; + + client.ValidateCertificate += HandleValidateCertificate; + await client.Connect(ct).ConfigureAwait(false); + + // Set up for relative paths + if (_relativePaths) + { + _initialCwd = await client.GetWorkingDirectory(ct).ConfigureAwait(false); + _initialCwd = _initialCwd?.TrimEnd('/'); } - throw; - } + // Setup the initial working directory, if needed + if (cwdFlag ?? _useCwdNames) + { + var clientPath = PreparePathForClient(null, false, client); + await client.SetWorkingDirectory(clientPath, ct).ConfigureAwait(false); + } + + return client; + }).ConfigureAwait(false); } - - return Client; - } - - private string GetUnescapedAbsolutePath(Uri uri) - { - string absolutePath = Uri.UnescapeDataString(uri.AbsolutePath); - return absolutePath.EndsWith("/", StringComparison.Ordinal) ? absolutePath.Substring(0, absolutePath.Length - 1) : absolutePath; + return _client; } + /// + /// Handle the certificate validation event. + /// + /// The FTP client. + /// The event arguments. private void HandleValidateCertificate(BaseFtpClient control, FtpSslValidationEventArgs e) { - if (e.PolicyErrors == SslPolicyErrors.None || _accepAllCertificates) + if (e.PolicyErrors == SslPolicyErrors.None || _sslOptions.AcceptAllCertificates) { e.Accept = true; return; } + e.Accept = false; try { - var certHash = (_validHashes != null && _validHashes.Length > 0) ? CoreUtility.ByteArrayAsHexString(e.Certificate.GetCertHash()) : null; - if (certHash != null) - { - if (_validHashes.Any(hash => !string.IsNullOrEmpty(hash) && certHash.Equals(hash, StringComparison.OrdinalIgnoreCase))) - { - e.Accept = true; - } - } + var certHash = (_sslOptions.AcceptSpecificCertificateHashes != null && _sslOptions.AcceptSpecificCertificateHashes.Length > 0) ? e.Certificate?.GetCertHashString() : null; + if (certHash != null && _sslOptions.AcceptSpecificCertificateHashes != null && _sslOptions.AcceptSpecificCertificateHashes.Any(hash => !string.IsNullOrEmpty(hash) && certHash.Equals(hash, StringComparison.OrdinalIgnoreCase))) + e.Accept = true; } catch { - e.Accept = false; + } + + if (e.Accept == false && e.Certificate != null) + throw new SslCertificateValidator.InvalidCertificateException(e.Certificate?.GetCertHashString() ?? "", e.PolicyErrors); + } + + /// + /// Prepare the path for the client. + /// + /// The path to prepare. + /// A flag to override the CWD strategy. + /// The FTP client, if not using the class instance. + /// The prepared path. + private string PreparePathForClient(string? path, bool? cwdFlag = null, AsyncFtpClient? client = null) + { + client = client ?? _client; + if (cwdFlag ?? _useCwdNames) + return string.IsNullOrWhiteSpace(path) + ? string.Empty + : Uri.UnescapeDataString(path); + + var remotePath = _url.AbsolutePath.TrimEnd('/'); + if (_relativePaths) + { + if (client == null) + throw new InvalidOperationException("Client not initialized"); + + if (!string.IsNullOrWhiteSpace(_initialCwd)) + remotePath = _initialCwd + "/" + remotePath.TrimStart('/'); + } + + if (string.IsNullOrEmpty(path)) + return Uri.UnescapeDataString(remotePath); + + if (path.StartsWith("/", StringComparison.Ordinal)) + return Uri.UnescapeDataString(path); + + return Uri.UnescapeDataString(remotePath + "/" + path); + } + + /// + /// Translate an exception to a more user-friendly exception. + /// + /// The filename provided. + /// The exception to translate. + /// True if the exception was translated, otherwise false. + private bool TranslateException(string? filename, ref Exception ex) + { + if (ex.InnerException != null && (ex.InnerException is FtpCommandException || ex.InnerException is SslCertificateValidator.InvalidCertificateException)) + ex = ex.InnerException; + + if (ex is FtpCommandException ftpEx && (ftpEx.CompletionCode == "550" || ftpEx.CompletionCode == "450")) + { + ex = string.IsNullOrWhiteSpace(filename) + ? new FolderMissingException(Strings.MissingFolderError(_url.AbsolutePath, ftpEx.Message), ftpEx) + : new FileMissingException(Strings.FileMissingError(filename, ftpEx.Message), ftpEx); + + return true; + } + + if (ex is SslCertificateValidator.InvalidCertificateException) + return true; + + return false; + } + + private sealed class DiagnosticsLogger : IFtpLogger + { + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + public void Log(FtpLogEntry entry) + { + var type = entry.Severity switch + { + FtpTraceLevel.Verbose => Logging.LogMessageType.Verbose, + FtpTraceLevel.Info => Logging.LogMessageType.Information, + FtpTraceLevel.Warn => Logging.LogMessageType.Warning, + FtpTraceLevel.Error => Logging.LogMessageType.Error, + _ => Logging.LogMessageType.Information + }; + Logging.Log.WriteMessage(type, LOGTAG, "FtpLogMessage", entry.Exception, entry.Message); } } } diff --git a/Duplicati/Library/Backend/FTP/Strings.cs b/Duplicati/Library/Backend/FTP/Strings.cs index 95b36b82a..7aa1ffc0f 100644 --- a/Duplicati/Library/Backend/FTP/Strings.cs +++ b/Duplicati/Library/Backend/FTP/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -31,14 +31,12 @@ namespace Duplicati.Library.Backend public static string DescriptionAlternate { get { return LC.L(@"This backend can read and write data to an FTP based backend. Allowed formats are ""aftp://hostname/folder"" and ""aftp://username:password@hostname/folder""."); } } public static string DisplayName { get { return LC.L(@"FTP"); } } public static string DisplayNameAlternate { get { return LC.L(@"Alternative FTP"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } public static string DescriptionLogToConsoleLong { get { return LC.L(@"Use this option to log FTP dialog to terminal console for debugging purposes."); } } public static string DescriptionLogToConsoleShort { get { return LC.L(@"Log FTP dialog to terminal console"); } } public static string DescriptionLogPrivateInfoToConsoleLong { get { return LC.L(@"Use this option to log FTP PRIVATE info (username, password) to console for debugging purposes (DO NOT POST THIS TO THE INTERNET!)"); } } public static string DescriptionLogPrivateInfoToConsoleShort { get { return LC.L(@"Log FTP PRIVATE info to console"); } } + public static string DescriptionLogDiagnosticsShort { get { return LC.L(@"Log diagnostics information"); } } + public static string DescriptionLogDiagnosticsLong { get { return LC.L(@"Use this option to log diagnostics information to the log output. This can be useful for debugging purposes."); } } public static string MissingFolderError(string foldername, string message) { return LC.L(@"The folder {0} was not found. Message: {1}", foldername, message); } public static string ListVerifyFailure(string filename, IEnumerable files) { return LC.L(@"The file {0} was uploaded but not found afterwards. The file listing returned {1}", filename, string.Join(Environment.NewLine, files)); } public static string ListVerifySizeFailure(string filename, long actualsize, long expectedsize) { return LC.L(@"The file {0} was uploaded but the returned size was {1} and it was expected to be {2}", filename, actualsize, expectedsize); } @@ -52,9 +50,9 @@ namespace Duplicati.Library.Backend public static string DescriptionSslProtocolsShort { get { return LC.L(@"Configure the SSL policy to use when encryption is enabled"); } } public static string DescriptionUploadDelayLong { get { return LC.L(@"Some FTP servers need a small delay before reporting the correct file size. The required delay depends on network topology. If you experience errors related to the upload size not matching, try adding a few seconds delay."); } } public static string DescriptionUploadDelayShort { get { return LC.L(@"Add a delay after uploading a file"); } } - public static string ErrorDeleteFile { get { return LC.L(@"Error on deleting file: {0}"); } } - public static string ErrorReadFile { get { return LC.L(@"Error reading file: {0}"); } } - public static string ErrorWriteFile { get { return LC.L(@"Error writing file: {0}"); } } + public static string ErrorDeleteFile(string filename, string message) { return LC.L(@"Error on deleting file: {0}, error: {1}", filename, message); } + public static string ErrorReadFile(string filename, string message) { return LC.L(@"Error reading file: {0}, error: {1}", filename, message); } + public static string ErrorWriteFile(string filename, string message) { return LC.L(@"Error writing file: {0}, error: {1}", filename, message); } public static string DescriptionUseSSLLong { get { return LC.L(@"Use this option to communicate using Secure Socket Layer (SSL) over ftp (ftps)."); } } public static string DescriptionUseSSLShort { get { return LC.L(@"Instruct Duplicati to use an SSL (ftps) connection"); } } public static string DescriptionFTPActiveLong { get { return LC.L(@"Activate this option to make the FTP connection in active mode. Even if the option --{0} is also set, the connection will be made in active mode.", "ftp-passive"); } } @@ -64,6 +62,16 @@ namespace Duplicati.Library.Backend public static string FtpPassiveDeprecated { get { return LC.L(@"The option ftp-passive is deprecated, use ftp-data-connection-type instead."); } } public static string FtpActiveDeprecated { get { return LC.L(@"ftp-regular is deprecated, use ftp-data-connection-type instead."); } } public static string UseSslDeprecated { get { return LC.L(@"use-ssl is deprecated, use ftp-ssl-protocols instead."); } } - + public static string FileMissingError(string filename, string message) { return LC.L(@"The file {0} was not found. Message: {1}", filename, message); } + public static string DescriptionAbsolutePathShort { get { return LC.L(@"Treat the url path as absolute"); } } + public static string DescriptionAbsolutePathLong { get { return LC.L(@"Use this option to interpret the url path as an absolute path. This option only has an effect if the initial starting folder in the FTP server is not the (virtual) root folder. If not set, the path in the url is treated as relative to the initial login folder."); } } + public static string DescriptionRelativePathShort { get { return LC.L(@"Treat the url path as relative"); } } + public static string DescriptionRelativePathLong { get { return LC.L(@"Use this option to interpret the url path as a path that is relative to the initial login folder. This option only has an effect if the initial starting folder in the FTP server is not the (virtual) root folder. If not set, the path in the url is treated as absolute, ignoring the initial login folder."); } } + public static string DescriptionUseCwdNamesShort { get { return LC.L(@"Use CWD instead of absolute paths"); } } + public static string DescriptionUseCwdNamesLong { get { return LC.L(@"Use this option to start the connection with a CWD command instead of an absolute path. This can be useful if the FTP server does not support absolute paths."); } } + public static string ErrorCreateFolder(string targetFolderName, string resultingFolder) { return LC.L(@"Error creating folder {0}, gave folder: {1}", targetFolderName, resultingFolder); } + public static string DescriptionIgnorePureFTPShort => LC.L(@"Ignore PureFTPd limit warnings"); + public static string DescriptionIgnorePureFTPLong => LC.L(@"PureFTPd is known to truncate file listings. If server has been configured to a higher limit or do not expect to store more than 10000 files you can suppress errors and warnings with {0}", "--ignore-pureftpd-limit-issue"); + } } diff --git a/Duplicati/Library/Backend/File/Duplicati.Library.Backend.File.csproj b/Duplicati/Library/Backend/File/Duplicati.Library.Backend.File.csproj index 94448063c..e976a48ba 100644 --- a/Duplicati/Library/Backend/File/Duplicati.Library.Backend.File.csproj +++ b/Duplicati/Library/Backend/File/Duplicati.Library.Backend.File.csproj @@ -4,13 +4,16 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + @@ -18,6 +21,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Duplicati/Library/Backend/File/FileBackend.cs b/Duplicati/Library/Backend/File/FileBackend.cs index 188be8e51..2d3471d1f 100644 --- a/Duplicati/Library/Backend/File/FileBackend.cs +++ b/Duplicati/Library/Backend/File/FileBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,13 +21,11 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using System; -using System.Collections.Generic; -using System.IO; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -42,71 +40,65 @@ namespace Duplicati.Library.Backend private const string OPTION_DISABLE_LENGTH_VERIFICATION = "disable-length-verification"; private readonly string m_path; - private string m_username; - private string m_password; + private string? m_username; + private string? m_password; private readonly bool m_moveFile; private bool m_hasAutenticated; private readonly bool m_forceReauth; private readonly bool m_verifyDestinationLength; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; private static readonly ISystemIO systemIO = SystemIO.IO_OS; public File() { + m_path = null!; + m_timeouts = null!; } - public File(string url, Dictionary options) + public File(string url, Dictionary options) { var uri = new Utility.Uri(url); - m_path = uri.HostAndPath; + var path = uri.HostAndPath; + var auth = AuthOptionsHelper.Parse(options, uri); + m_timeouts = TimeoutOptionsHelper.Parse(options); + m_username = auth.Username; + m_password = auth.Password; - if (options.ContainsKey("auth-username")) - m_username = options["auth-username"]; - if (options.ContainsKey("auth-password")) - m_password = options["auth-password"]; - if (!string.IsNullOrEmpty(uri.Username)) - m_username = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - m_password = uri.Password; + if (!Path.IsPathRooted(path)) + path = systemIO.PathGetFullPath(path); - if (!System.IO.Path.IsPathRooted(m_path)) - m_path = systemIO.PathGetFullPath(m_path); - - if (options.ContainsKey(OPTION_ALTERNATE_PATHS)) + var altPaths = options.GetValueOrDefault(OPTION_ALTERNATE_PATHS); + if (!string.IsNullOrWhiteSpace(altPaths)) { - List paths = new List - { - m_path - }; - paths.AddRange(options[OPTION_ALTERNATE_PATHS].Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); + List paths = + [ + path, + .. altPaths.Split(new string[] {Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries), + ]; //On windows we expand the drive letter * to all drives - if (!OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindows()) { - System.IO.DriveInfo[] drives = System.IO.DriveInfo.GetDrives(); - - for (int i = 0; i < paths.Count; i++) + var drives = DriveInfo.GetDrives(); + for (var i = 0; i < paths.Count; i++) { if (paths[i].StartsWith("*:", StringComparison.Ordinal)) { - string rpl_path = paths[i].Substring(1); + var rpl_path = paths[i].Substring(1); paths.RemoveAt(i); i--; - foreach (System.IO.DriveInfo di in drives) + foreach (var di in drives) paths.Insert(++i, di.Name[0] + rpl_path); } } } - string markerfile = null; - //If there is a marker file, we do not allow the primary target path // to be accepted, unless it contains the marker file - if (options.ContainsKey(OPTION_DESTINATION_MARKER)) - { - markerfile = options[OPTION_DESTINATION_MARKER]; - m_path = null; - } + var markerfile = options.GetValueOrDefault(OPTION_DESTINATION_MARKER); + if (!string.IsNullOrWhiteSpace(markerfile)) + path = null; foreach (string p in paths) { @@ -114,7 +106,7 @@ namespace Duplicati.Library.Backend { if (systemIO.DirectoryExists(p) && (markerfile == null || systemIO.FileExists(systemIO.PathCombine(p, markerfile)))) { - m_path = p; + path = p; break; } } @@ -123,18 +115,22 @@ namespace Duplicati.Library.Backend } } - if (m_path == null) + if (string.IsNullOrWhiteSpace(path)) throw new UserInformationException(Strings.FileBackend.NoDestinationWithMarkerFileError(markerfile, paths.ToArray()), "NoDestinationWithMarker"); } + m_path = path; m_moveFile = Utility.Utility.ParseBoolOption(options, OPTION_MOVE_FILE); m_forceReauth = Utility.Utility.ParseBoolOption(options, OPTION_FORCE_REAUTH); - m_verifyDestinationLength = Utility.Utility.ParseBoolOption(options, OPTION_DISABLE_LENGTH_VERIFICATION); + m_verifyDestinationLength = !Utility.Utility.ParseBoolOption(options, OPTION_DISABLE_LENGTH_VERIFICATION); m_hasAutenticated = false; } private void PreAuthenticate() { + if (!OperatingSystem.IsWindows()) + return; + try { if (!string.IsNullOrEmpty(m_username) && m_password != null && !m_hasAutenticated) @@ -169,14 +165,20 @@ namespace Duplicati.Library.Backend get { return "file"; } } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { PreAuthenticate(); if (!systemIO.DirectoryExists(m_path)) throw new FolderMissingException(Strings.FileBackend.FolderMissingError(m_path)); - return systemIO.EnumerateFileEntries(m_path); + var res = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => + systemIO.EnumerateFileEntries(m_path) + ).ConfigureAwait(false); + + foreach (var entry in res) + yield return entry; } #if DEBUG_RETRY @@ -196,17 +198,19 @@ namespace Duplicati.Library.Backend string targetFilePath = GetRemoteName(targetFilename); long copiedBytes = 0; using (var targetStream = systemIO.FileCreate(targetFilePath)) - copiedBytes = await Utility.Utility.CopyStreamAsync(sourceStream, targetStream, true, cancelToken).ConfigureAwait(false); + using (var timeoutStream = targetStream.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) + copiedBytes = await Utility.Utility.CopyStreamAsync(sourceStream, timeoutStream, true, cancelToken).ConfigureAwait(false); VerifyMatchingSize(targetFilePath, sourceStream, copiedBytes); } #endif - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { // FileOpenRead has flags System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read using (var readstream = systemIO.FileOpenRead(GetRemoteName(remotename))) - await Utility.Utility.CopyStreamAsync(readstream, stream, true, cancelToken).ConfigureAwait(false); + using (var timeoutStream = readstream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(timeoutStream, stream, true, cancelToken).ConfigureAwait(false); } public Task PutAsync(string targetFilename, string sourceFilePath, CancellationToken cancelToken) @@ -240,27 +244,33 @@ namespace Duplicati.Library.Backend return Task.CompletedTask; } - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { - systemIO.FileDelete(GetRemoteName(remotename)); - return Task.CompletedTask; + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ + => systemIO.FileDelete(GetRemoteName(remotename)) + ).ConfigureAwait(false); } public IList SupportedCommands { get { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.FileBackend.DescriptionAuthPasswordShort, Strings.FileBackend.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.FileBackend.DescriptionAuthUsernameShort, Strings.FileBackend.DescriptionAuthUsernameLong), + var lst = new List(); + if (OperatingSystem.IsWindows()) + lst.AddRange([ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(OPTION_FORCE_REAUTH, CommandLineArgument.ArgumentType.Boolean, Strings.FileBackend.ForceReauthShort, Strings.FileBackend.ForceReauthLong) + ]); + + lst.AddRange([ new CommandLineArgument(OPTION_DESTINATION_MARKER, CommandLineArgument.ArgumentType.String, Strings.FileBackend.AlternateDestinationMarkerShort, Strings.FileBackend.AlternateDestinationMarkerLong(OPTION_ALTERNATE_PATHS)), new CommandLineArgument(OPTION_ALTERNATE_PATHS, CommandLineArgument.ArgumentType.Path, Strings.FileBackend.AlternateTargetPathsShort, Strings.FileBackend.AlternateTargetPathsLong(OPTION_DESTINATION_MARKER, System.IO.Path.PathSeparator)), new CommandLineArgument(OPTION_MOVE_FILE, CommandLineArgument.ArgumentType.Boolean, Strings.FileBackend.UseMoveForPutShort, Strings.FileBackend.UseMoveForPutLong), - new CommandLineArgument(OPTION_FORCE_REAUTH, CommandLineArgument.ArgumentType.Boolean, Strings.FileBackend.ForceReauthShort, Strings.FileBackend.ForceReauthLong), new CommandLineArgument(OPTION_DISABLE_LENGTH_VERIFICATION, CommandLineArgument.ArgumentType.Boolean, Strings.FileBackend.DisableLengthVerificationShort, Strings.FileBackend.DisableLengthVerificationLong), + .. TimeoutOptionsHelper.GetOptions() + ]); - }); - + return lst; } } @@ -273,18 +283,16 @@ namespace Duplicati.Library.Backend } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { if (systemIO.DirectoryExists(m_path)) throw new FolderAreadyExistedException(); - systemIO.DirectoryCreate(m_path); - return Task.CompletedTask; + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ + => systemIO.DirectoryCreate(m_path) + ).ConfigureAwait(false); } #endregion @@ -299,18 +307,23 @@ namespace Duplicati.Library.Backend #endregion - private System.IO.DriveInfo GetDrive() + private DriveInfo? GetDrive() { string root; - if (OperatingSystem.IsWindows()) + if (!OperatingSystem.IsWindows()) { string path = Util.AppendDirSeparator(systemIO.PathGetFullPath(m_path)); + + // If the built-in .NET DriveInfo works, use it + try { return new DriveInfo(path); } + catch { } + root = "/"; //Find longest common prefix from mounted devices //TODO: Can trick this with symlinks, where the symlink is on one mounted volume, // and the actual storage is on another - foreach (System.IO.DriveInfo di in System.IO.DriveInfo.GetDrives()) + foreach (var di in DriveInfo.GetDrives()) if (path.StartsWith(Util.AppendDirSeparator(di.Name), StringComparison.Ordinal) && di.Name.Length > root.Length) root = di.Name; } @@ -325,7 +338,7 @@ namespace Duplicati.Library.Backend { try { - return new System.IO.DriveInfo(root); + return new DriveInfo(root); } catch (ArgumentException) { @@ -336,7 +349,7 @@ namespace Duplicati.Library.Backend return null; } - public Task GetQuotaInfoAsync(CancellationToken cancelToken) + public Task GetQuotaInfoAsync(CancellationToken cancelToken) { var driveInfo = this.GetDrive(); if (driveInfo != null) @@ -345,7 +358,7 @@ namespace Duplicati.Library.Backend // If the drive actually has a total size of 0, this should be obvious immediately due to write errors if (driveInfo.TotalSize > 0) { - return Task.FromResult(new QuotaInfo(driveInfo.TotalSize, driveInfo.AvailableFreeSpace)); + return Task.FromResult(new QuotaInfo(driveInfo.TotalSize, driveInfo.AvailableFreeSpace)); } } @@ -353,10 +366,10 @@ namespace Duplicati.Library.Backend { // If we can't get the DriveInfo on Windows, fallback to GetFreeDiskSpaceEx // https://stackoverflow.com/questions/2050343/programmatically-determining-space-available-from-unc-path - return Task.FromResult(GetDiskFreeSpace(m_path)); + return Task.FromResult(GetDiskFreeSpace(m_path)); } - return null; + return Task.FromResult(null); } public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(Array.Empty()); @@ -379,7 +392,7 @@ namespace Duplicati.Library.Backend /// Quota info [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] [SupportedOSPlatform("windows")] - public static QuotaInfo GetDiskFreeSpace(string directory) + public static QuotaInfo? GetDiskFreeSpace(string directory) { ulong available; ulong total; @@ -427,7 +440,7 @@ namespace Duplicati.Library.Backend } } - private static void VerifyMatchingSize(string targetFilePath, Stream sourceStream, long? expectedLength) + private static void VerifyMatchingSize(string targetFilePath, Stream? sourceStream, long? expectedLength) { try { @@ -439,14 +452,15 @@ namespace Duplicati.Library.Backend long? sourceStreamLength = sourceStream == null ? null : Utility.Utility.GetStreamLength(sourceStream, out isStreamPostion); if (sourceStreamLength.HasValue && targetFileInfo.Length != sourceStreamLength.Value) - throw new FileMissingException($"Target file size ({targetFileInfo.Length:n0}) is different from the source length ({sourceStreamLength.Value:n0}){(isStreamPostion ? " - ending stream position)" : "")}. Target: {targetFilePath}"); + throw new FileMissingException($"Target file size ({targetFileInfo.Length:n0}) is different from the source length ({sourceStreamLength.Value:n0}){(isStreamPostion ? " - ending stream position" : "")}. Target: {targetFilePath}"); if (expectedLength.HasValue && targetFileInfo.Length != expectedLength.Value) throw new FileMissingException($"Target file size ({targetFileInfo.Length:n0}) is different from the expected length ({expectedLength.Value:n0}). Target: {targetFilePath}"); } catch { - try { System.IO.File.Delete(targetFilePath); } catch { } + try { System.IO.File.Delete(targetFilePath); } + catch { } throw; } } diff --git a/Duplicati/Library/Backend/File/Strings.cs b/Duplicati/Library/Backend/File/Strings.cs index ff0597da8..47f367db2 100644 --- a/Duplicati/Library/Backend/File/Strings.cs +++ b/Duplicati/Library/Backend/File/Strings.cs @@ -1,38 +1,36 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class FileBackend { +namespace Duplicati.Library.Backend.Strings +{ + internal static class FileBackend + { public static string Description { get { return LC.L(@"This backend can read and write data to an file based backend. Allowed formats are ""file://hostname/folder"" and ""file://username:password@hostname/folder"". You may supply UNC paths (e.g.: ""file://\\server\folder"") or local paths (e.g.: (win) ""file://c:\folder"", (linux) ""file:///usr/pub/files"")"); } } public static string DisplayName { get { return LC.L(@"Local folder or drive"); } } public static string AlternateDestinationMarkerLong(string optionname) { return LC.L(@"This option only works when the option --{0} is also specified. If there are alternate paths specified, this option indicates the name of a marker file that must be present in the folder. This can be used to handle situations where an external drive changes drive letter or mount point. By ensuring that a certain file exists, it is possible to prevent writing data to an unwanted external drive. The contents of the file are never examined, only file existence.", optionname); } public static string AlternateDestinationMarkerShort { get { return LC.L(@"Look for a file in the destination folder"); } } public static string AlternateTargetPathsLong(string optionname, char pathseparator) { return LC.L(@"This option allows multiple targets to be specified. The primary target path is placed before the list of paths supplied with this option. Before starting the backup, each folder in the list is checked for existence and optionally the presence of the marker file supplied by --{0}. The first existing path that optionally contains the marker file is then used as the destination. Multiple destinations are separated with a ""{1}"". On Windows, the path may be a UNC path, and the drive letter may be substituted with an asterisk (*), e.g: ""*:\backup"", which will examine all drive letters. If a username and password is supplied, the same credentials are used for all destinations.", optionname, pathseparator); } public static string AlternateTargetPathsShort { get { return LC.L(@"A list of secondary target paths"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } public static string FolderMissingError(string foldername) { return LC.L(@"The folder {0} does not exist", foldername); } - public static string NoDestinationWithMarkerFileError(string markername, string[] folders) { return LC.L(@"The marker file ""{0}"" was not found in any of the examined destinations: {1}", markername, string.Join(", ", folders)); } + public static string NoDestinationWithMarkerFileError(string? markername, string[] folders) { return LC.L(@"The marker file ""{0}"" was not found in any of the examined destinations: {1}", markername, string.Join(", ", folders)); } public static string UseMoveForPutLong { get { return LC.L(@"When storing the file, the standard operation is to copy the file and delete the original. This sequence ensures that the operation can be retried if something goes wrong. Activating this option may cause the retry operation to fail. This option has no effect unless the option --{0} is activated.", "disable-streaming-transfers"); } } public static string UseMoveForPutShort { get { return LC.L(@"Move the file instead of copying it"); } } public static string ForceReauthLong { get { return LC.L(@"If this option is set, any existing authentication against the remote share is dropped before attempting to authenticate."); } } diff --git a/Duplicati/Library/Backend/File/Win32.cs b/Duplicati/Library/Backend/File/Win32.cs index 44dd83af6..e13d68a9b 100644 --- a/Duplicati/Library/Backend/File/Win32.cs +++ b/Duplicati/Library/Backend/File/Win32.cs @@ -1,31 +1,30 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Text; using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace Duplicati.Library.Backend { + [SupportedOSPlatform("windows")] internal class Win32 { [DllImport("mpr.dll")] @@ -41,7 +40,7 @@ namespace Duplicati.Library.Backend public ResourceType dwType; public ResourceDisplayType dwDisplayType; public ResourceUsage dwUsage; - public string LocalName; + public string? LocalName; public string RemoteName; public string Comment; public string Provider; @@ -111,7 +110,7 @@ namespace Duplicati.Library.Backend try { - NETRESOURCE rsc = new NETRESOURCE(); + var rsc = new NETRESOURCE(); rsc.dwScope = ResourceScope.RESOURCE_GLOBALNET; rsc.dwType = ResourceType.RESOURCETYPE_DISK; diff --git a/Duplicati/Library/Backend/Filen/Duplicati.Library.Backend.Filen.csproj b/Duplicati/Library/Backend/Filen/Duplicati.Library.Backend.Filen.csproj new file mode 100644 index 000000000..de7a4c4e1 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Duplicati.Library.Backend.Filen.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + Library + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.Filen + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Duplicati/Library/Backend/Filen/FilenBackend.cs b/Duplicati/Library/Backend/Filen/FilenBackend.cs new file mode 100644 index 000000000..51e6e5bf6 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/FilenBackend.cs @@ -0,0 +1,252 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The Filen backend +/// +public class FilenBackend : IStreamingBackend +{ + /// + /// The two-factor option name + /// + private const string TwoFactorOption = "two-factor-code"; + /// + /// The move to trash option name + /// + private const string MoveToTrashOption = "move-to-trash"; + /// + /// The Filen client instance + /// + private FilenClient? _client; + /// + /// The authentication options + /// + private readonly AuthOptionsHelper.AuthOptions _auth; + /// + /// The timeout options + /// + private readonly TimeoutOptionsHelper.Timeouts _timeout; + /// + /// The path to the folder + /// + private readonly string _path; + /// + /// The two-factor code, if any + /// + private readonly string? _twoFactorCode; + /// + /// Whether to move files to the trash instead of deleting them + /// + private readonly bool _moveToTrash; + /// + /// The UUID of the folder files are stored in + /// + private string? _folderUuid; + + /// + /// Constructor for reflection based loading + /// + public FilenBackend() + { + _client = null!; + _auth = null!; + _timeout = null!; + _path = null!; + } + + /// + /// Creates a new instance of the Filen backend + /// + /// The connection url + /// The options to use + public FilenBackend(string url, Dictionary options) + { + var uri = new Utility.Uri(url); + _path = uri.HostAndPath; + + _auth = AuthOptionsHelper.Parse(options, uri) + .RequireCredentials(); + + _moveToTrash = Utility.Utility.ParseBoolOption(options, MoveToTrashOption); + _twoFactorCode = options.GetValueOrDefault(TwoFactorOption); + _timeout = TimeoutOptionsHelper.Parse(options); + } + + /// + /// Gets a client + /// + /// The cancellation token to use + /// A client instance + private async Task GetClientAsync(CancellationToken cancellationToken) + { + if (_client == null || DateTime.Now > _client.ValidUntil) + { + _client?.Dispose(); + _client = null; + _client = await FilenClient.CreateClientAsync(HttpClientHelper.CreateClient(), _auth.Username!, _auth.Password!, _twoFactorCode, cancellationToken).ConfigureAwait(false); + } + + return _client; + } + + /// + public string DisplayName => Strings.FilenBackend.DisplayName; + /// + public string ProtocolKey => "filen"; + /// + public string Description => Strings.FilenBackend.Description; + + /// + public IList SupportedCommands => [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(TwoFactorOption, CommandLineArgument.ArgumentType.String, Strings.FilenBackend.TwoFactorShort, Strings.FilenBackend.TwoFactorLong), + new CommandLineArgument(MoveToTrashOption, CommandLineArgument.ArgumentType.Boolean, Strings.FilenBackend.MoveToTrashShort, Strings.FilenBackend.MoveToTrashLong), + .. TimeoutOptionsHelper.GetOptions() + ]; + + /// + /// Gets the folder uuid for the folder this backend is working in + /// + /// The client to use + /// The cancellation token to use + /// The folder UUID + private async Task GetFolderUuid(FilenClient client, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_folderUuid)) + return _folderUuid = await client.ResolveFolderPathAsync(_path, _timeout.ListTimeout, cancellationToken).ConfigureAwait(false); + + return _folderUuid; + } + + /// + public async IAsyncEnumerable ListAsync([System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken) + { + var client = await GetClientAsync(cancellationToken).ConfigureAwait(false); + var folderId = await GetFolderUuid(client, cancellationToken).ConfigureAwait(false); + + await foreach (var entry in client.ListFolderDecryptedAsync(folderId, _timeout.ListTimeout, cancellationToken).ConfigureAwait(false)) + { + yield return new FileEntry(entry.Name, entry.Size, new DateTime(0), entry.LastModified) + { + IsFolder = entry.IsFolder + }; + } + } + + /// + public async Task PutAsync(string remotename, string filename, CancellationToken cancellationToken) + { + using var stream = File.OpenRead(filename); + await PutAsync(remotename, stream, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task GetAsync(string remotename, string filename, CancellationToken cancellationToken) + { + using var stream = File.Create(filename); + await GetAsync(remotename, stream, cancellationToken).ConfigureAwait(false); + } + + /// + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) + { + var client = await GetClientAsync(cancelToken).ConfigureAwait(false); + var folderId = await GetFolderUuid(client, cancelToken).ConfigureAwait(false); + using var timeoutStream = stream.ObserveReadTimeout(_timeout.ReadWriteTimeout, false); + await client.UploadStreamedEncryptedFileAsync(timeoutStream, remotename, folderId, cancelToken).ConfigureAwait(false); + } + + /// + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) + { + var client = await GetClientAsync(cancelToken).ConfigureAwait(false); + var folderId = await GetFolderUuid(client, cancelToken).ConfigureAwait(false); + var file = await client.GetFileEntryAsync(folderId, remotename, _timeout.ListTimeout, cancelToken).ConfigureAwait(false); + + if (file == null) + throw new FileMissingException($"File '{remotename}' not found."); + + using var timeoutStream = stream.ObserveWriteTimeout(_timeout.ReadWriteTimeout, false); + await client.DownloadAndDecryptToStreamAsync(file, timeoutStream, cancelToken).ConfigureAwait(false); + } + + /// + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) + { + var client = await GetClientAsync(cancellationToken).ConfigureAwait(false); + var folderId = await GetFolderUuid(client, cancellationToken).ConfigureAwait(false); + var file = await client.GetFileEntryAsync(folderId, remotename, _timeout.ListTimeout, cancellationToken) + .ConfigureAwait(false); + + if (file == null) + throw new FileMissingException($"File '{remotename}' not found."); + + await Utility.Utility.WithTimeout(_timeout.ShortTimeout, cancellationToken, ct => client.DeleteFileAsync(file.Uuid, !_moveToTrash, ct)).ConfigureAwait(false); + } + + /// + public Task GetDNSNamesAsync(CancellationToken cancelToken) + => Task.FromResult( + FilenClient.GatewayUrls + .Concat(FilenClient.EgestUrls) + .Concat(FilenClient.IngestURLs) + .Select(u => new System.Uri(u).Host) + .Distinct() + .ToArray() + ); + + /// + public Task TestAsync(CancellationToken cancellationToken) + => this.TestListAsync(cancellationToken); + + /// + public async Task CreateFolderAsync(CancellationToken cancellationToken) + { + var client = await GetClientAsync(cancellationToken).ConfigureAwait(false); + var rootFolderId = await client.GetUserBaseFolder(cancellationToken).ConfigureAwait(false); + foreach (var s in _path.Split('/')) + { + if (string.IsNullOrWhiteSpace(s)) + continue; + + var entry = await client.ListFolderDecryptedAsync(rootFolderId, _timeout.ListTimeout, cancellationToken).FirstOrDefaultAsync(f => f.Name == s).ConfigureAwait(false); + if (entry != null) + { + rootFolderId = entry.Uuid; + continue; + } + + rootFolderId = await Utility.Utility.WithTimeout(_timeout.ShortTimeout, cancellationToken, ct => client.CreateFolderAsync(rootFolderId, s, ct)).ConfigureAwait(false); + } + } + + /// + public void Dispose() + { + _client?.Dispose(); + } +} diff --git a/Duplicati/Library/Backend/Filen/FilenClient.cs b/Duplicati/Library/Backend/Filen/FilenClient.cs new file mode 100644 index 000000000..c7c6a54cd --- /dev/null +++ b/Duplicati/Library/Backend/Filen/FilenClient.cs @@ -0,0 +1,666 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The Filen client implementation +/// +public class FilenClient : IDisposable +{ + /// + /// The URLs of the Filen gateways + /// + public static IReadOnlyList GatewayUrls = [ + "https://gateway.filen.io", + "https://gateway.filen.net", + "https://gateway.filen-1.net", + "https://gateway.filen-2.net", + "https://gateway.filen-3.net", + "https://gateway.filen-4.net", + "https://gateway.filen-5.net", + "https://gateway.filen-6.net" + ]; + + /// + /// The URLs of the Filen egest servers + /// + public static IReadOnlyList EgestUrls = [ + "https://egest.filen.io", + "https://egest.filen.net", + "https://egest.filen-1.net", + "https://egest.filen-2.net", + "https://egest.filen-3.net", + "https://egest.filen-4.net", + "https://egest.filen-5.net", + "https://egest.filen-6.net" + ]; + + /// + /// The URLs of the Filen ingest servers + /// + public static IReadOnlyList IngestURLs = [ + "https://ingest.filen.io", + "https://ingest.filen.net", + "https://ingest.filen-1.net", + "https://ingest.filen-2.net", + "https://ingest.filen-3.net", + "https://ingest.filen-4.net", + "https://ingest.filen-5.net", + "https://ingest.filen-6.net" + ]; + + /// + /// The log tag for the Filen client + /// + private static string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The authentication result from the initial login + /// + private sealed record FilenAuthResult + { + /// + /// The API key to use for auntehticated requests + /// + public required string ApiKey { get; init; } + /// + /// The account master key + /// + public required DerivedKey AccountMasterKey { get; init; } + /// + /// The master keys for the account + /// + public required IReadOnlyList MasterKeys { get; init; } + + /// + /// Encrypts metadata with the latest master key + /// + /// The metadata to encrypt + /// The encrypted metadata + public string EncryptMetadata(string data) + => MasterKeys.Last().EncryptMetadata(data); + + /// + /// Decrypts metadata with the correct master key + /// + /// The metadata to decrypt + /// The decrypted metadata + public string DecryptMetadata(string data) + { + Exception? firstExecption = null; + var keyIx = MasterKeys.Count; + foreach (var key in MasterKeys.Reverse()) + { + try + { + return key.DecryptMetadata(data); + } + catch (Exception ex) + { + keyIx--; + firstExecption ??= ex; + Logging.Log.WriteVerboseMessage(LOGTAG, "DecryptMetadataAttemptFailed", ex, "Failed to decrypt metadata with key {0}", keyIx); + } + } + throw new Exception("Failed to decrypt metadata", firstExecption); + } + } + + /// + /// The base url for all requests + /// + private readonly string _baseUrl; + /// + /// The fixed chunk size for uploads + /// + private const int ChunkSize = 1024 * 1024; // 1 MB + /// + /// The HTTP client to use for requests + /// + private readonly HttpClient _httpClient; + /// + /// The UUID of the root folder; null until loaded + /// + private string? _rootFolderUuid; + /// + /// The authentication result from the initial login + /// + private FilenAuthResult _authResult; + + /// + /// The time the auth token is valid until + /// + private readonly DateTime _validUntil; + + /// + /// The cached files to avoid re-fetching + /// + private readonly Dictionary _cachedFiles = new(); + + /// + /// Creates a new Filen client + /// + /// The HTTP client to use for requests + /// The authentication result from the initial login + /// The base url for all requests + private FilenClient(HttpClient httpClient, FilenAuthResult authResult, string baseUrl) + { + _httpClient = httpClient; + _authResult = authResult; + _baseUrl = baseUrl; + _validUntil = DateTime.Now + TimeSpan.FromMinutes(50); + } + + /// + /// The time the client is valid + /// + public DateTime ValidUntil => _validUntil; + + /// + /// Creates a new Filen client and authenticates + /// + /// The HTTP client to use for requests + /// The email address to use for login + /// The password to use for login + /// The two-factor code to use for login + /// The cancellation token to use for the operation + /// The authenticated Filen client + public static async Task CreateClientAsync(HttpClient httpClient, string email, string password, string? twoFactorCode, CancellationToken cancellationToken) + { + var baseUrl = GatewayUrls[Random.Shared.Next(0, GatewayUrls.Count)]; + var authResult = await FilenLogin.AuthenticateAsync(httpClient, baseUrl, email, password, twoFactorCode, cancellationToken).ConfigureAwait(false); + return new FilenClient(httpClient, authResult, baseUrl); + } + + /// + /// Methods used for the initial login + /// + private static class FilenLogin + { + /// + /// Returns the authentication information for the user + /// + /// The HTTP client to use for requests + /// The base url for all requests + /// The email address to use for login + /// The cancellation token to use for the operation + /// The authentication information for the user + private static async Task GetAuthInfoAsync(HttpClient httpClient, string baseUrl, string email, CancellationToken cancellationToken) + { + var loginUrl = $"{baseUrl}/v3/auth/info"; + using var request = new HttpRequestMessage(HttpMethod.Post, loginUrl); + request.Content = new StringContent(JsonSerializer.Serialize(new { email }), Encoding.UTF8, "application/json"); + + var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + return await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + } + + /// + /// Authenticates the user with the Filen API + /// + /// The HTTP client to use for requests + /// The base url for all requests + /// The email address to use for login + /// The password to use for login + /// The two-factor code to use for login + /// The cancellation token to use for the operation + /// The authentication result from the initial login + public static async Task AuthenticateAsync(HttpClient httpClient, string baseUrl, string email, string password, string? twoFactorCode, CancellationToken cancellationToken) + { + var authInfo = await GetAuthInfoAsync(httpClient, baseUrl, email, cancellationToken).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(twoFactorCode)) + twoFactorCode = "XXXXXX"; + + var rootKeys = FilenCrypto.GeneratePasswordAndMasterKeyBasedOnAuthVersion(password, authInfo.AuthVersion, authInfo.Salt); + var loginUrl = $"{baseUrl}/v3/login"; + using var request = new HttpRequestMessage(HttpMethod.Post, loginUrl); + request.Content = new StringContent(JsonSerializer.Serialize(new { email, password = rootKeys.Password, twoFactorCode, authVersion = authInfo.AuthVersion }), Encoding.UTF8, "application/json"); + + using var response = await httpClient.SendAsync(request, cancellationToken); + var result = await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + + // var mk = await GetAllMasterKeys(masterKey1, result.ApiKey, cancellationToken).ConfigureAwait(false); + + var masterKeys = rootKeys.MasterKey.DecryptMetadata(result.MasterKeys); + + return new FilenAuthResult + { + ApiKey = result.ApiKey, + AccountMasterKey = rootKeys.MasterKey, + MasterKeys = masterKeys.Split('|').Select(DerivedKey.Create).ToList() + }; + } + } + + /// + /// Extracts the data from a response or throws an exception + /// + /// The data type to extract + /// The response to extract from + /// The cancellation token to use for the operation + /// The extracted data + private static async Task ExtractDataFromResponse(HttpResponseMessage response, CancellationToken cancellationToken) + { + response.EnsureSuccessStatusCode(); + + // var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + // var result = JsonSerializer.Deserialize>(json); + var result = await response.Content.ReadFromJsonAsync>(cancellationToken).ConfigureAwait(false); + if (result is null) + throw new Exception("Failed to read response"); + if (!result.Status || !string.IsNullOrWhiteSpace(result.Error)) + throw new UserInformationException($"{result.Error ?? result.Message ?? "Unknown"}", "FilenAPIError"); + + return result.Data; + } + + /// + /// Gets the file entry for a file in a folder + /// + /// The UUID of the folder + /// The name of the file + /// The timeout for the operation + /// The cancellation token to use for the operation + /// The file entry for the file, or null if not found + public async Task GetFileEntryAsync(string folderUuid, string filename, TimeSpan timeout, CancellationToken cancellationToken) + { + var f = _cachedFiles.GetValueOrDefault($"{folderUuid}:{filename}"); + if (f is not null) + return f; + + return await ListFolderDecryptedAsync(folderUuid, timeout, cancellationToken) + .FirstOrDefaultAsync(e => e.Name == filename, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Lists the contents of a folder + /// + /// The folder UUID to list + /// + /// + /// + public async IAsyncEnumerable ListFolderDecryptedAsync(string folderUuid, TimeSpan timeout, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var result = await Utility.Utility.WithTimeout(timeout, cancellationToken, async ct => + { + var url = $"{_baseUrl}/v3/dir/content"; + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + request.Content = new StringContent(JsonSerializer.Serialize(new { uuid = folderUuid }), Encoding.UTF8, "application/json"); + + using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + return await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + }).ConfigureAwait(false); + + var entries = new List(); + + foreach (var folder in result.Folders) + { + NameEntry? decryptedName = null; + try + { + decryptedName = JsonSerializer.Deserialize(_authResult.DecryptMetadata(folder.EncryptedName)) + ?? throw new Exception("Failed to decrypt name"); + } + catch (Exception ex) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "SkipFolderDueToDecryptionError", ex, "Failed to decrypt folder name"); + } + + if (!string.IsNullOrWhiteSpace(decryptedName?.Name)) + yield return new FilenFileEntry + { + Uuid = folder.UUID, + Name = decryptedName.Name, + IsFolder = true, + LastModified = DateTimeOffset.FromUnixTimeMilliseconds(folder.LastModified).UtcDateTime, + Size = 0, + Region = string.Empty, + Bucket = string.Empty, + Chunks = 0, + FileKey = string.Empty, + Version = 0 + }; + + } + + foreach (var file in result.Files) + { + FileInfoMetadata? fileInfo = null; + + try + { + fileInfo = JsonSerializer.Deserialize(_authResult.DecryptMetadata(file.MetadataEncrypted)) + ?? throw new Exception("Failed to decrypt metadata"); + } + catch (Exception ex) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "SkipFileDueToDecryptionError", ex, "Failed to decrypt file name"); + } + + if (!string.IsNullOrWhiteSpace(fileInfo?.Name)) + yield return _cachedFiles[$"{folderUuid}:{fileInfo.Name}"] = new FilenFileEntry + { + Uuid = file.Uuid, + Name = fileInfo.Name, + IsFolder = false, + Size = file.Size, + LastModified = DateTimeOffset.FromUnixTimeMilliseconds(fileInfo.LastModified).UtcDateTime, + Region = file.Region, + Bucket = file.Bucket, + Chunks = file.Chunks, + FileKey = fileInfo.Key, + Version = file.Version + }; + } + } + + /// + /// Deletes a file from the Filen API + /// + /// The UUID of the file to delete + /// True to permanently delete the file, false to move it to the trash + /// The cancellation token to use for the operation + /// A task that completes when the file is deleted + public async Task DeleteFileAsync(string fileUuid, bool permanent, CancellationToken cancellationToken) + { + var url = permanent + ? $"{_baseUrl}/v3/file/delete/permanent" + : $"{_baseUrl}/v3/file/trash"; + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + request.Content = new StringContent(JsonSerializer.Serialize(new { uuid = fileUuid }), Encoding.UTF8, "application/json"); + + var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + var key = _cachedFiles.FirstOrDefault(kv => kv.Value.Uuid == fileUuid).Key; + if (string.IsNullOrWhiteSpace(key)) + _cachedFiles.Clear(); + else + _cachedFiles.Remove(key); + } + + /// + /// Uploads a file to the Filen API + /// + /// The stream to upload + /// The name of the file + /// The UUID of the parent folder + /// The cancellation token to use for the operation + /// A task that completes when the file is uploaded + public async Task UploadStreamedEncryptedFileAsync(Stream stream, string remoteName, string parentFolderUuid, CancellationToken cancellationToken) + { + var size = stream.Length; + if (stream.Length == 0) + throw new InvalidOperationException("Cannot upload empty file"); + + var uploadKey = FilenCrypto.GenerateRandomString(32); + var fileKey = DerivedKey.Create(FilenCrypto.GenerateRandomString(32)); + var fileUuid = Guid.NewGuid().ToString(); + + var chunks = await UploadEncryptedChunksAsync(stream, fileUuid, parentFolderUuid, fileKey, uploadKey, cancellationToken).ConfigureAwait(false); + if (chunks == 0) + throw new Exception("Failed to upload any chunks"); + await CompleteChunkedUploadAsync(fileUuid, remoteName, size, chunks, fileKey, uploadKey, cancellationToken).ConfigureAwait(false); + } + + /// + /// Uploads encrypted chunks of a file to the Filen API + /// + /// The stream to read from + /// The UUID of the file + /// The UUID of the parent folder + /// The encryption key for the file + /// The upload key for the file + /// The cancellation token to use for the operation + /// The number of chunks uploaded + private async Task UploadEncryptedChunksAsync(Stream inputStream, string uuid, string parentUuid, DerivedKey fileKey, string uploadKey, CancellationToken cancellationToken) + { + var buffer = new byte[ChunkSize]; + var chunk = 0; + while (true) + { + int bytesRead = 0; + while (bytesRead < ChunkSize) + { + int read = await inputStream.ReadAsync(buffer, bytesRead, ChunkSize - bytesRead, cancellationToken).ConfigureAwait(false); + if (read == 0) break; + bytesRead += read; + } + + Logging.Log.WriteVerboseMessage(LOGTAG, "UploadingChunk", "Uploading chunk {0}, size {1}", chunk, bytesRead); + + // Stream is exhausted + if (bytesRead == 0) break; + + var cipherText = fileKey.EncryptData(buffer.AsSpan().Slice(0, bytesRead)); + var hash = FilenCrypto.HashChunk(cipherText); + + var url = $"{IngestURLs[Random.Shared.Next(0, IngestURLs.Count)]}/v3/upload"; + var qp = new Dictionary + { + { "uuid", uuid }, + { "index", chunk.ToString() }, + { "parent", parentUuid }, + { "uploadKey", uploadKey }, + { "hash", hash } + }; + + url += "?" + string.Join("&", qp.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}")); + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + request.Content = new ByteArrayContent(cipherText); + var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + var res = await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + chunk++; + } + + Logging.Log.WriteVerboseMessage(LOGTAG, "UploadingChunk", "Finished uploading {0} chunks", chunk); + + return chunk; + } + + /// + /// Completes a chunked upload of a file to the Filen API + /// + /// The UUID of the file + /// The name of the file + /// The size of the file + /// The number of chunks + /// The encryption key for the file + /// The upload key for the file + /// The cancellation token to use for the operation + /// A task that completes when the file is uploaded + private async Task CompleteChunkedUploadAsync(string fileUuid, string remoteName, long size, int chunks, DerivedKey fileKey, string uploadKey, CancellationToken cancellationToken) + { + var encryptedName = _authResult.EncryptMetadata(JsonSerializer.Serialize(new NameEntry() { Name = remoteName })); + var nameHashed = FilenCrypto.HashFn(remoteName); + var now = (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds; + + var encryptedMime = fileKey.EncryptMetadata("application/octet-stream"); + var encryptedFilesize = fileKey.EncryptMetadata(JsonSerializer.Serialize(size)); + var encryptedMetadata = _authResult.EncryptMetadata(JsonSerializer.Serialize(new FileInfoMetadata + { + Name = remoteName, + Size = size, + Mime = "application/octet-stream", + Key = fileKey.Key, + LastModified = now, + Create = now + })); + + var completeBody = new + { + uuid = fileUuid, + name = encryptedName, + nameHashed = nameHashed, + size = encryptedFilesize, + chunks = chunks, + mime = encryptedMime, + rm = FilenCrypto.GenerateRandomString(32), + metadata = encryptedMetadata, + version = 2, + uploadKey = uploadKey, + }; + + var url = $"{_baseUrl}/v3/upload/done"; + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + request.Content = new StringContent(JsonSerializer.Serialize(completeBody), Encoding.UTF8, "application/json"); + var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + var res = await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + if (res.Size != size) + throw new Exception($"Failed to upload file, size mismatch. Expected {size}, got {res.Size}"); + } + + /// + /// Downloads and decrypts a file to a stream + /// + /// The file to download + /// The stream to write to + /// The cancellation token to use for the operation + /// A task that completes when the file is downloaded + public async Task DownloadAndDecryptToStreamAsync(FilenFileEntry file, Stream outputStream, CancellationToken cancellationToken) + { + var chunk = 0; + var downloaded = 0L; + var fileKey = DerivedKey.Create(file.FileKey); + + var totalSize = file.Size; + while (downloaded < totalSize) + { + if (chunk >= file.Chunks) + throw new Exception($"Attempted to download more chunks than available, expected {file.Chunks}, got {chunk}. File: {file.Name}, total size: {totalSize}, downloaded: {downloaded}"); + + Logging.Log.WriteVerboseMessage(LOGTAG, "DownloadingChunk", "Downloading chunk {0}, size {1}", chunk, totalSize - downloaded); + var url = $"{EgestUrls[Random.Shared.Next(0, EgestUrls.Count)]}/{file.Region}/{file.Bucket}/{file.Uuid}/{chunk}"; + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + + var response = await _httpClient.SendAsync(request, cancellationToken); + var encrypted = await response.Content.ReadAsByteArrayAsync(cancellationToken); + + var decrypted = fileKey.DecryptData(file.Version, encrypted); + await outputStream.WriteAsync(decrypted, cancellationToken); + downloaded += decrypted.Length; + chunk++; + } + } + + /// + /// Creates a folder in the Filen API + /// + /// The UUID of the parent folder + /// The name of the folder + /// The cancellation token to use for the operation + /// The UUID of the created folder + public async Task CreateFolderAsync(string parentUuid, string folderName, CancellationToken cancellationToken) + { + var self = Guid.NewGuid().ToString(); + var encryptedName = _authResult.EncryptMetadata(JsonSerializer.Serialize(new NameEntry() { Name = folderName })); + var hashedName = FilenCrypto.HashFn(folderName); + + var body = new + { + uuid = self, + name = encryptedName, + nameHashed = hashedName, + parent = parentUuid + }; + + var url = $"{_baseUrl}/v3/dir/create"; + using var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json"); + var response = await _httpClient.SendAsync(request, cancellationToken); + var result = await ExtractDataFromResponse(response, cancellationToken).ConfigureAwait(false); + return result.Uuid; + } + + /// + /// Gets the UUID of the user's base folder + /// + /// The cancellation token to use for the operation + /// The UUID of the user's base folder + public async Task GetUserBaseFolder(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_rootFolderUuid)) + { + using var request = new HttpRequestMessage(HttpMethod.Get, $"{_baseUrl}/v3/user/baseFolder"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _authResult.ApiKey); + var response = await _httpClient.SendAsync(request); + var result = await ExtractDataFromResponse(response, cancellationToken); + return _rootFolderUuid = result.Uuid; + } + return _rootFolderUuid; + } + + /// + /// Resolves a folder path to a UUID + /// + /// The path to resolve + /// The timeout for the operation + /// The cancellation token to use for the operation + /// The UUID of the resolved folder + public async Task ResolveFolderPathAsync(string path, TimeSpan timeout, CancellationToken cancellationToken) + { + var rootFolderUuid = await GetUserBaseFolder(cancellationToken); + + if (string.IsNullOrWhiteSpace(path)) return rootFolderUuid; + + var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + var currentUuid = rootFolderUuid; + var prev = "/"; + + foreach (var segment in segments) + { + var match = await ListFolderDecryptedAsync(currentUuid, timeout, cancellationToken) + .FirstOrDefaultAsync(f => f.Name == segment) + .ConfigureAwait(false); + + if (match == null) + throw new FolderMissingException($"Path segment '{segment}' not found under {prev} {currentUuid}"); + + currentUuid = match.Uuid; + prev += segment + "/"; + } + + return currentUuid; + } + + public void Dispose() + { + _httpClient.Dispose(); + } +} + diff --git a/Duplicati/Library/Backend/Filen/FilenCrypto.cs b/Duplicati/Library/Backend/Filen/FilenCrypto.cs new file mode 100644 index 000000000..add15e35f --- /dev/null +++ b/Duplicati/Library/Backend/Filen/FilenCrypto.cs @@ -0,0 +1,327 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Security.Cryptography; +using System.Text; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Represents the root keys for the account. +/// +/// The master key for encryption. +/// The password for the account. +public sealed record AccountRootKeys(DerivedKey MasterKey, string Password); + +/// +/// Represents a derived key. +/// +/// The source key +/// The derived key. +public sealed record DerivedKey(string Key, byte[] Derived) +{ + /// + /// Creates a new derived key. + /// + /// The key to derive. + /// The derived key. + public static DerivedKey Create(string key) + { + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentException("Key cannot be null or empty.", nameof(key)); + return new DerivedKey(key, FilenCrypto.DeriveKeyFromPassword(key, key, 1, 256)); + } + + /// + /// Decrypts the a metadata string + /// + /// The metadata to decrypt. + /// The dectypred string + public string DecryptMetadata(string metadata) + => FilenCrypto.DecryptMetadata(metadata, this); + + /// + /// Encrypts a metadata string. + /// + /// The metadata to encrypt. + /// The encrypted string. + public string EncryptMetadata(string metadata) + => FilenCrypto.EncryptMetadata(metadata, this); + + /// + /// Encrypts a chunk of data. + /// + /// The data to encrypt + /// The encrypted data. + public byte[] EncryptData(Span data) + => FilenCrypto.EncryptData(data, this); + + /// + /// Decrypts a chunk of data. + /// + /// The version of the encryption. + /// The data to decrypt. + /// The decrypted data. + public byte[] DecryptData(int version, Span data) + => FilenCrypto.DecryptData(version, data, this); +} + +/// +/// Provides cryptographic functions for the Filen backend. +/// +public static class FilenCrypto +{ + /// + /// The size of the GCM tag. + /// + public const int GcmTagSize = 16; + + /// + /// The log tag for the class. + /// + private static string LOGTAG = Logging.Log.LogTagFromType(typeof(FilenCrypto)); + /// + /// The base64 charset used for generating random strings. + /// + private static readonly string Base64Charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + /// + /// Generates/derives the password and master key based on the auth version. Auth Version 1 is deprecated and no longer in use. + /// + /// The raw user password. + /// The authentication version. + /// The salt value. + /// A Task containing the derived password and master keys. + public static AccountRootKeys GeneratePasswordAndMasterKeyBasedOnAuthVersion(string rawPassword, int authVersion, string salt) + { + if (authVersion != 2) + throw new NotSupportedException("Auth version 2 the only supported version."); + + string derivedKey = Convert.ToHexString(DeriveKeyFromPassword(rawPassword, salt, 200000, 512)).ToLowerInvariant(); + string derivedPassword = derivedKey.Substring(derivedKey.Length / 2); + string derivedMasterKey = derivedKey.Substring(0, derivedKey.Length / 2); + + return new AccountRootKeys(DerivedKey.Create(derivedMasterKey), CreateSHA512Hash(derivedPassword)); + } + + /// + /// Creates a SHA512 hash of the input string. + /// + /// The input string. + /// The SHA512 hash as a hexadecimal string. + private static string CreateSHA512Hash(string input) + => Convert.ToHexString( + SHA512.HashData( + Encoding.UTF8.GetBytes(input))) + .ToLowerInvariant(); + + /// + /// Generates a random string (utf-8 compatible). + /// + /// The length of the string. + /// The random string. + public static string GenerateRandomString(int length = 32) + => new(GenerateRandomBytes(length).Select(b => Base64Charset[b % Base64Charset.Length]).ToArray()); + + /// + /// Generates random bytes. + /// + /// The length of the bytes. + /// The random bytes. + public static byte[] GenerateRandomBytes(int length = 32) + { + var buffer = new byte[length]; + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(buffer); + return buffer; + } + + /// + /// Derives a key from a password. + /// + /// The password. + /// The salt. + /// The number of iterations. + /// The bit length of the returned key. + /// The derived key. + public static byte[] DeriveKeyFromPassword(string password, string salt, int iterations, int bitLength) + { + using var deriveBytes = new Rfc2898DeriveBytes(password, Encoding.UTF8.GetBytes(salt), iterations, HashAlgorithmName.SHA512); + return deriveBytes.GetBytes(bitLength / 8); + } + + /// + /// Hashes a chunk of data using SHA512. + /// + /// The input data. + /// The SHA512 hash as a hexadecimal string. + public static string HashChunk(Span input) + => Convert.ToHexString(SHA512.HashData(input)).ToLowerInvariant(); + + /// + /// Hashes a string using SHA1 and SHA512. + /// + /// The input string. + /// The hash as a hexadecimal string. + public static string HashFn(Span input) + => Convert.ToHexString(SHA1.HashData(SHA512.HashData(input))).ToLowerInvariant(); + + /// + /// Hashes a string using SHA1 and SHA512. + /// + /// The input string. + /// The hash as a hexadecimal string. + public static string HashFn(string input) + => HashFn(Encoding.UTF8.GetBytes(input.ToLowerInvariant())); + + /// + /// Encrypts a metadata string. + /// + /// The metadata to encrypt. + /// The key to use for encryption. + /// The encrypted metadata. + public static string EncryptMetadata(string metadata, DerivedKey key) + { + var iv = GenerateRandomString(12); + var combined = AesGcmEncrypt( + key.Derived, + Encoding.UTF8.GetBytes(iv), + Encoding.UTF8.GetBytes(metadata)); + return $"002{iv}{Convert.ToBase64String(combined)}"; + } + + /// + /// Decrypts the a metadata string + /// + /// The metadata to decrypt. + /// The derived key. + /// The dectypred string + public static string DecryptMetadata(string metadata, DerivedKey key) + { + string sliced = metadata.Substring(0, Math.Min(8, metadata.Length)); + + if (sliced == "U2FsdGVk") + throw new NotImplementedException("Version 1 decryption is not implemented."); + + string version = metadata.Substring(0, Math.Min(3, metadata.Length)); + + if (version == "002") + { + var keyBuffer = key.Derived; + var ivBuffer = Encoding.UTF8.GetBytes(metadata.Substring(3, 12)); + var encrypted = Convert.FromBase64String(metadata.Substring(15)); + + return Encoding.UTF8.GetString(AesGcmDecrypt(keyBuffer, ivBuffer, encrypted)); + } + else if (version == "003") + { + Logging.Log.WriteVerboseMessage(LOGTAG, "EncV3NotTested", "Encryption version 3 encoutered for metadata, but this is not tested."); + if (key.Key.Length != 64) + throw new ArgumentException($"Invalid key length {key.Key.Length}. Expected 64 (hex).", nameof(key)); + + var keyBuffer = Convert.FromHexString(key.Key); + var ivBuffer = Convert.FromHexString(metadata.Substring(3, 12)); // Sliced from 3, length 24 + var encrypted = Convert.FromBase64String(metadata.Substring(15)); + + return Encoding.UTF8.GetString(AesGcmDecrypt(keyBuffer, ivBuffer, encrypted)); + } + else + { + throw new ArgumentException($"Invalid metadata version {version}", nameof(metadata)); + } + } + + /// + /// Encrypts a chunk of data. + /// + /// The data to encrypt. + /// The key to use for encryption. + /// The encrypted data. + public static byte[] EncryptData(Span data, DerivedKey key) + { + var tag = new byte[GcmTagSize]; + var iv = GenerateRandomBytes(12); + var combined = new byte[iv.Length + data.Length + tag.Length]; + iv.CopyTo(combined.AsSpan()); + + using var aes = new AesGcm(Encoding.UTF8.GetBytes(key.Key), GcmTagSize); + aes.Encrypt(iv, data, combined.AsSpan().Slice(iv.Length, data.Length), tag); + tag.CopyTo(combined.AsSpan(iv.Length + data.Length)); + return combined; + } + + /// + /// Decrypts a chunk of data. + /// + /// The version of the encryption. + /// The data to decrypt. + /// The key to use for decryption. + /// The decrypted data. + public static byte[] DecryptData(int version, Span data, DerivedKey key) + { + if (version != 2) + throw new NotSupportedException("Only version 2 is supported."); + + var iv = data.Slice(0, 12); + var cipherText = data.Slice(12); + return AesGcmDecrypt(Encoding.UTF8.GetBytes(key.Key), iv, cipherText); + } + + /// + /// Performs AES-GCM decryption. + /// + /// The key. + /// The initialization vector. + /// The encrypted data. + /// The decrypted data. + public static byte[] AesGcmDecrypt(Span key, Span iv, Span encrypted) + { + using (var aesGcm = new AesGcm(key, GcmTagSize)) + { + // The authentication tag is the last 16 bytes of the encrypted data. + var authTag = encrypted.Slice(encrypted.Length - GcmTagSize); + // The ciphertext is the encrypted data without the authentication tag. + var cipherText = encrypted.Slice(0, encrypted.Length - GcmTagSize); + + var plainText = new byte[cipherText.Length]; + aesGcm.Decrypt(iv, cipherText, authTag, plainText); + + return plainText; + } + } + + /// + /// Performs AES-GCM encryption. + /// + /// The key. + /// The initialization vector. + /// The data to encrypt. + /// The encrypted data. + public static byte[] AesGcmEncrypt(Span key, Span iv, Span data) + { + using (var aesGcm = new AesGcm(key, GcmTagSize)) + { + var result = new byte[data.Length + GcmTagSize]; + aesGcm.Encrypt(iv, data, result.AsSpan().Slice(0, data.Length), result.AsSpan().Slice(data.Length)); + return result; + } + } +} diff --git a/Duplicati/Library/Backend/Filen/FilenFileEntry.cs b/Duplicati/Library/Backend/Filen/FilenFileEntry.cs new file mode 100644 index 000000000..fe3720311 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/FilenFileEntry.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Representation of a file or folder as returned by the Filen API +/// +public sealed record FilenFileEntry +{ + /// + /// The UUID of the file or folder + /// + public required string Uuid { get; set; } + /// + /// The name of the file or folder + /// + public required string Name { get; set; } + /// + /// True if the entry is a folder, false if it is a file + /// + public required bool IsFolder { get; set; } + /// + /// The size of the file in bytes, or 0 if the entry is a folder + /// + public required long Size { get; set; } + /// + /// The last modified date of the file + /// + public required DateTime LastModified { get; set; } + /// + /// The region of the file + /// + public required string Region { get; set; } + /// + /// The bucket of the file + /// + public required string Bucket { get; set; } + /// + /// The number of chunks in the file + /// + public required int Chunks { get; set; } + /// + /// The encrypted metadata of the file + /// + public required string FileKey { get; set; } + /// + /// The encryption version + /// + public required int Version; +} + diff --git a/Duplicati/Library/Backend/Filen/Models/AuthInfo.cs b/Duplicati/Library/Backend/Filen/Models/AuthInfo.cs new file mode 100644 index 000000000..031d45be2 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/AuthInfo.cs @@ -0,0 +1,55 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The response from the Filen API auth endpoint +/// +public class AuthInfo +{ + /// + /// The email address of the user + /// + [JsonPropertyName("email")] + public string Email { get; set; } = string.Empty; + + /// + /// The version of the authentication protocol + /// + [JsonPropertyName("authVersion")] + public int AuthVersion { get; set; } + + /// + /// The salt used for the authentication + /// + [JsonPropertyName("salt")] + public string Salt { get; set; } = string.Empty; + + /// + /// An ID value + /// + [JsonPropertyName("id")] + public long Id { get; set; } +} + diff --git a/Duplicati/Library/Backend/Filen/Models/AuthResponse.cs b/Duplicati/Library/Backend/Filen/Models/AuthResponse.cs new file mode 100644 index 000000000..f3943f521 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/AuthResponse.cs @@ -0,0 +1,53 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The authentication response from the initial login +/// +internal class AuthResponse +{ + /// + /// The API key used for all requests + /// + [JsonPropertyName("apiKey")] + public string ApiKey { get; set; } = string.Empty; + + /// + /// The master keys for the account + /// + [JsonPropertyName("masterKeys")] + public string MasterKeys { get; set; } = string.Empty; + /// + /// The account public key + /// + [JsonPropertyName("publicKey")] + public string PublicKey { get; set; } = string.Empty; + /// + /// The account private key + /// + [JsonPropertyName("privateKey")] + public string PrivateKey { get; set; } = string.Empty; +} + diff --git a/Duplicati/Library/Backend/Filen/Models/ChunkUploadResponse.cs b/Duplicati/Library/Backend/Filen/Models/ChunkUploadResponse.cs new file mode 100644 index 000000000..717191b36 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/ChunkUploadResponse.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The response from the Filen API after uploading a chunk +/// +internal class ChunkUploadResponse +{ + [JsonPropertyName("bucket")] + public string Bucket { get; set; } = string.Empty; + + [JsonPropertyName("region")] + public string Region { get; set; } = string.Empty; +} + diff --git a/Duplicati/Library/Backend/Filen/Models/CreateFolderResponse.cs b/Duplicati/Library/Backend/Filen/Models/CreateFolderResponse.cs new file mode 100644 index 000000000..aa0320bfa --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/CreateFolderResponse.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The respons from the Filen API when creating a folder +/// +internal class CreateFolderResponse +{ + /// + /// The UUID of the created folder + /// + [JsonPropertyName("uuid")] + public string Uuid { get; set; } = string.Empty; +} + diff --git a/Duplicati/Library/Backend/Filen/Models/DirListData.cs b/Duplicati/Library/Backend/Filen/Models/DirListData.cs new file mode 100644 index 000000000..799afff20 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/DirListData.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Results from listing a folder in the Filen API +/// +internal class DirListData +{ + /// + /// The folders in the folder + /// + [JsonPropertyName("folders")] + public List Folders { get; set; } = new(); + + /// + /// The files in the folder + /// + [JsonPropertyName("uploads")] + public List Files { get; set; } = new(); +} + diff --git a/Duplicati/Library/Backend/Filen/Models/FileInfoMetadata.cs b/Duplicati/Library/Backend/Filen/Models/FileInfoMetadata.cs new file mode 100644 index 000000000..c0ba68990 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FileInfoMetadata.cs @@ -0,0 +1,62 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The decrypted metadata for a file +/// +internal class FileInfoMetadata +{ + /// + /// The file name + /// + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + /// + /// The file size + /// + [JsonPropertyName("size")] + public long Size { get; set; } + /// + /// The MIME type of the file + /// + [JsonPropertyName("mime")] + public string Mime { get; set; } = string.Empty; + /// + /// The encryption key for the file and metadata + /// + [JsonPropertyName("key")] + public string Key { get; set; } = string.Empty; + /// + /// The last modified date of the file + /// + [JsonPropertyName("lastModified")] + public long LastModified { get; set; } + /// + /// The creation date of the file + /// + [JsonPropertyName("create")] + public long Create { get; set; } +} + diff --git a/Duplicati/Library/Backend/Filen/Models/FileMetadataLong.cs b/Duplicati/Library/Backend/Filen/Models/FileMetadataLong.cs new file mode 100644 index 000000000..83184a109 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FileMetadataLong.cs @@ -0,0 +1,64 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Information returned for a file when listed directly by UUID +/// +internal class FileMetadataLong +{ + [JsonPropertyName("uuid")] + public string Uuid { get; set; } = string.Empty; + + [JsonPropertyName("region")] + public string Region { get; set; } = string.Empty; + + [JsonPropertyName("bucket")] + public string Bucket { get; set; } = string.Empty; + + [JsonPropertyName("nameEncrypted")] + public string NameEncrypted { get; set; } = string.Empty; + + [JsonPropertyName("nameHashed")] + public string NameHashed { get; set; } = string.Empty; + [JsonPropertyName("sizeEncrypted")] + public string SizeEncrypted { get; set; } = string.Empty; + [JsonPropertyName("mimeEncrypted")] + public string MimeEncrypted { get; set; } = string.Empty; + [JsonPropertyName("metadata")] + public string MetadataEncrypted { get; set; } = string.Empty; + [JsonPropertyName("size")] + public long Size { get; set; } + [JsonPropertyName("parent")] + public string Parent { get; set; } = string.Empty; + [JsonPropertyName("versioned")] + public bool Versioned { get; set; } + [JsonPropertyName("trash")] + public bool Thrash { get; set; } + [JsonPropertyName("version")] + public int Version { get; set; } + [JsonPropertyName("chunks")] + public int Chunks { get; set; } +} + diff --git a/Duplicati/Library/Backend/Filen/Models/FileMetadataShort.cs b/Duplicati/Library/Backend/Filen/Models/FileMetadataShort.cs new file mode 100644 index 000000000..73913851c --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FileMetadataShort.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Information returned for a file when listing a folder +/// +internal class FileMetadataShort +{ + [JsonPropertyName("uuid")] + public string Uuid { get; set; } = string.Empty; + [JsonPropertyName("metadata")] + public string MetadataEncrypted { get; set; } = string.Empty; + [JsonPropertyName("rm")] + public string Rm { get; set; } = string.Empty; + [JsonPropertyName("chunks")] + public int Chunks { get; set; } + [JsonPropertyName("size")] + public long Size { get; set; } + [JsonPropertyName("bucket")] + public string Bucket { get; set; } = string.Empty; + [JsonPropertyName("region")] + public string Region { get; set; } = string.Empty; + [JsonPropertyName("parent")] + public string Parent { get; set; } = string.Empty; + [JsonPropertyName("version")] + public int Version { get; set; } +} + diff --git a/Duplicati/Library/Backend/Filen/Models/FileUploadResponse.cs b/Duplicati/Library/Backend/Filen/Models/FileUploadResponse.cs new file mode 100644 index 000000000..bf6b4e837 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FileUploadResponse.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Response after uploading a file +/// +internal class FileUploadResponse +{ + /// + /// The size of the file + /// + [JsonPropertyName("size")] + public long Size { get; set; } + /// + /// The number of chunks in the file + /// + [JsonPropertyName("chuncks")] + public int Chunks { get; set; } +} + diff --git a/Duplicati/Library/Backend/Filen/Models/FilenResponseEnvelope.cs b/Duplicati/Library/Backend/Filen/Models/FilenResponseEnvelope.cs new file mode 100644 index 000000000..4f325d2b9 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FilenResponseEnvelope.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The generic response envelope from the Filen API +/// +internal class FilenResponseEnvelope +{ + /// + /// A message describing the response + /// + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// + /// A code describing the response + /// + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// + /// An error message if the response is an error + /// + [JsonPropertyName("error")] + public string? Error { get; set; } + + /// + /// True if the response is successful + /// + [JsonPropertyName("status")] + public bool Status { get; set; } +} + +/// A typed response envelope with data +/// +/// The data in the message +internal class FilenResponseEnvelope : FilenResponseEnvelope +{ + [JsonPropertyName("data")] + public T Data { get; set; } = default!; +} + + diff --git a/Duplicati/Library/Backend/Filen/Models/FolderListEntry.cs b/Duplicati/Library/Backend/Filen/Models/FolderListEntry.cs new file mode 100644 index 000000000..e25434a67 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/FolderListEntry.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// The raw file entry as returned by the Filen API +/// +internal class FolderListEntry +{ + /// + /// The UUID of the file or folder + /// + [JsonPropertyName("uuid")] + public string UUID { get; set; } = string.Empty; + + /// + /// The encrypted name of the file or folder + /// + [JsonPropertyName("name")] + public string EncryptedName { get; set; } = string.Empty; + + /// + /// The size of the file in bytes, or 0 if the entry is a folder + /// + [JsonPropertyName("size")] + public long Size { get; set; } + + /// + /// The last modified date of the file or folder + /// + [JsonPropertyName("timestamp")] + public long LastModified { get; set; } + + /// + /// The encrypted metadata of the file + /// + [JsonPropertyName("metadata")] + public string EncryptedMetadata { get; set; } = string.Empty; +} + diff --git a/Duplicati/Library/Backend/Filen/Models/NameEntry.cs b/Duplicati/Library/Backend/Filen/Models/NameEntry.cs new file mode 100644 index 000000000..3e5c981c6 --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Models/NameEntry.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json.Serialization; + +namespace Duplicati.Library.Backend.Filen; + +/// +/// Wrapper class for reading or writing a name +/// +internal class NameEntry +{ + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + diff --git a/Duplicati/Library/Backend/Filen/Strings.cs b/Duplicati/Library/Backend/Filen/Strings.cs new file mode 100644 index 000000000..5d787db3e --- /dev/null +++ b/Duplicati/Library/Backend/Filen/Strings.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Localization.Short; +namespace Duplicati.Library.Backend.Strings +{ + internal static class FilenBackend + { + public static string Description => LC.L(@"This backend can read and write data to Filen.io using its REST protocol. Supported format is ""filen://folder/subfolder""."); + public static string DisplayName => LC.L(@"Filen.io"); + public static string TwoFactorShort => LC.L(@"Optional 2-factor code"); + public static string TwoFactorLong => LC.L(@"The 2-factor code to use for authentication, leave empty if the account is not MFA protected. Not that a new code must be provided by the user for each authentication attempt."); + public static string MoveToTrashShort => LC.L(@"Move to trash"); + public static string MoveToTrashLong => LC.L(@"If set, files will be moved to the trash instead of being deleted permanently."); + } +} diff --git a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj index 193c2695f..be6989d62 100644 --- a/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj +++ b/Duplicati/Library/Backend/GoogleServices/Duplicati.Library.Backend.GoogleServices.csproj @@ -3,10 +3,14 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + + diff --git a/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs b/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs index 63f951911..4effd5c35 100644 --- a/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs +++ b/Duplicati/Library/Backend/GoogleServices/GCSConfig.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,11 +19,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using Duplicati.Library.Interface; -using System.Collections.Generic; -using System.Linq; -using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.GoogleServices { @@ -32,7 +28,7 @@ namespace Duplicati.Library.Backend.GoogleServices public class GCSConfig : IWebModule { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Locations; - private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private static readonly string DEFAULT_CONFIG_TYPE_STR = DEFAULT_CONFIG_TYPE.ToString(); private const string KEY_CONFIGTYPE = "gcs-config"; public enum ConfigType @@ -46,15 +42,13 @@ namespace Duplicati.Library.Backend.GoogleServices #region IWebModule implementation - public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) + public IDictionary Execute(IDictionary options) { - string k; - options.TryGetValue(KEY_CONFIGTYPE, out k); + options.TryGetValue(KEY_CONFIGTYPE, out var k); if (string.IsNullOrWhiteSpace(k)) k = DEFAULT_CONFIG_TYPE_STR; - ConfigType ct; - if (!Enum.TryParse(k, true, out ct)) + if (!Enum.TryParse(k, true, out var ct)) ct = DEFAULT_CONFIG_TYPE; switch (ct) @@ -66,23 +60,17 @@ namespace Duplicati.Library.Backend.GoogleServices } } - public string Key { get { return "gcs-getconfig"; } } + public string Key => "gcs-getconfig"; - public string DisplayName { get { return LC.L("Google Cloud Storage configuration module"); } } + public string DisplayName => Strings.GCSConfig.DisplayName; - public string Description { get { return LC.L("Expose Google Cloud Storage configuration as a web module"); } } + public string Description => Strings.GCSConfig.Description; - public System.Collections.Generic.IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, LC.L("The config to get"), LC.L("Provide different config values"), DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) - - ]); - } - } + public IList SupportedCommands => + [ + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, Strings.GCSConfig.ConfigTypeShort, Strings.GCSConfig.ConfigTypeLong, DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + ]; #endregion } diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs index 7dab7738c..d3b8b794c 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,13 +23,11 @@ using Duplicati.Library.Backend.GoogleServices; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; using System.Net; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend.GoogleCloudStorage { @@ -37,7 +35,7 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage // This class is instantiated dynamically in the BackendLoader. public class GoogleCloudStorage : IBackend, IStreamingBackend, IRenameEnabledBackend { - private const string AUTHID_OPTION = "authid"; + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("gcs"); private const string PROJECT_OPTION = "gcs-project"; private const string LOCATION_OPTION = "gcs-location"; @@ -45,17 +43,22 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage private readonly string m_bucket; private readonly string m_prefix; - private readonly string m_project; + private readonly string? m_project; private readonly OAuthHelper m_oauth; - private readonly string m_location; - private readonly string m_storage_class; + private readonly string? m_location; + private readonly string? m_storage_class; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; public GoogleCloudStorage() { + m_bucket = null!; + m_prefix = null!; + m_oauth = null!; + m_timeouts = null!; } - public GoogleCloudStorage(string url, Dictionary options) + public GoogleCloudStorage(string url, Dictionary options) { var uri = new Utility.Uri(url); @@ -66,45 +69,44 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage if (m_prefix.StartsWith("/", StringComparison.Ordinal)) m_prefix = m_prefix.Substring(1); - string authid; - options.TryGetValue(AUTHID_OPTION, out authid); - options.TryGetValue(PROJECT_OPTION, out m_project); - options.TryGetValue(LOCATION_OPTION, out m_location); - options.TryGetValue(STORAGECLASS_OPTION, out m_storage_class); + var authId = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TOKEN_URL); - if (string.IsNullOrEmpty(authid)) - throw new UserInformationException(Strings.GoogleCloudStorage.MissingAuthID(AUTHID_OPTION), "GoogleCloudStorageMissingAuthID"); + m_timeouts = TimeoutOptionsHelper.Parse(options); + m_project = options.GetValueOrDefault(PROJECT_OPTION); + m_location = options.GetValueOrDefault(LOCATION_OPTION); + m_storage_class = options.GetValueOrDefault(STORAGECLASS_OPTION); - m_oauth = new OAuthHelper(authid, this.ProtocolKey); + m_oauth = new OAuthHelper(authId.AuthId!, this.ProtocolKey); m_oauth.AutoAuthHeader = true; } private class ListBucketResponse { - public string nextPageToken { get; set; } - public BucketResourceItem[] items { get; set; } + public string? nextPageToken { get; set; } + public BucketResourceItem[]? items { get; set; } } private class BucketResourceItem { - public string name { get; set; } + public string? name { get; set; } public DateTime? updated { get; set; } public long? size { get; set; } } private class CreateBucketRequest { - public string name { get; set; } - public string location { get; set; } - public string storageClass { get; set; } + public string? name { get; set; } + public string? location { get; set; } + public string? storageClass { get; set; } } - private T HandleListExceptions(Func func) + private async Task HandleListExceptions(Func> func) { try { - return func(); + return await func().ConfigureAwait(false); } catch (WebException wex) { @@ -116,17 +118,21 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage } #region IBackend implementation - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { var url = WebApi.GoogleCloudStorage.ListUrl(m_bucket, Utility.Uri.UrlEncode(m_prefix)); while (true) { - var resp = HandleListExceptions(() => m_oauth.ReadJSONResponse(url)); + var resp = await HandleListExceptions(() => + Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => + m_oauth.ReadJSONResponseAsync(url, ct)) + ).ConfigureAwait(false); if (resp.items != null) foreach (var f in resp.items) { - var name = f.name; + var name = f.name ?? ""; if (name.StartsWith(m_prefix, StringComparison.OrdinalIgnoreCase)) name = name.Substring(m_prefix.Length); if (f.size == null) @@ -146,36 +152,33 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) + using (var fs = File.OpenRead(filename)) await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) + using (var fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public Task DeleteAsync(string remotename, CancellationToken cancelToken) { - var req = m_oauth.CreateRequest(WebApi.GoogleCloudStorage.DeleteUrl(m_bucket, Library.Utility.Uri.UrlPathEncode(m_prefix + remotename))); req.Method = "DELETE"; - return m_oauth.ReadJSONResponseAsync(req, cancelToken); + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_oauth.ReadJSONResponseAsync(req, ct)); } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public async Task CreateFolderAsync(CancellationToken cancelToken) + public Task CreateFolderAsync(CancellationToken cancelToken) { if (string.IsNullOrEmpty(m_project)) throw new UserInformationException(Strings.GoogleCloudStorage.ProjectIDMissingError(PROJECT_OPTION), "GoogleCloudStorageMissingProjectID"); - var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest + var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new CreateBucketRequest { name = m_bucket, location = m_location, @@ -187,23 +190,19 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage req.ContentLength = data.Length; req.ContentType = "application/json; charset=UTF-8"; - var areq = new AsyncHttpRequest(req); + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + var areq = new AsyncHttpRequest(req); + using (var rs = areq.GetRequestStream()) + await rs.WriteAsync(data, 0, data.Length, ct); - using (var rs = areq.GetRequestStream()) - await rs.WriteAsync(data, 0, data.Length, cancelToken); - - await m_oauth.ReadJSONResponseAsync(areq, cancelToken).ConfigureAwait(false); + await m_oauth.ReadJSONResponseAsync(areq, ct).ConfigureAwait(false); + }); } - public string DisplayName - { - get { return Strings.GoogleCloudStorage.DisplayName; } - } + public string DisplayName => Strings.GoogleCloudStorage.DisplayName; - public string ProtocolKey - { - get { return "gcs"; } - } + public string ProtocolKey => "gcs"; public IList SupportedCommands { @@ -212,50 +211,49 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage StringBuilder locations = new StringBuilder(); StringBuilder storageClasses = new StringBuilder(); - foreach (KeyValuePair s in WebApi.GoogleCloudStorage.KNOWN_GCS_LOCATIONS) + foreach (var s in WebApi.GoogleCloudStorage.KNOWN_GCS_LOCATIONS) locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - foreach (KeyValuePair s in WebApi.GoogleCloudStorage.KNOWN_GCS_STORAGE_CLASSES) + foreach (var s in WebApi.GoogleCloudStorage.KNOWN_GCS_STORAGE_CLASSES) storageClasses.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - return new List(new ICommandLineArgument[] { + return [ new CommandLineArgument(LOCATION_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.LocationDescriptionShort, Strings.GoogleCloudStorage.LocationDescriptionLong(locations.ToString())), new CommandLineArgument(STORAGECLASS_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.StorageclassDescriptionShort, Strings.GoogleCloudStorage.StorageclassDescriptionLong(storageClasses.ToString())), - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.GoogleCloudStorage.AuthidShort, Strings.GoogleCloudStorage.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("gcs"))), + .. AuthIdOptionsHelper.GetOptions(TOKEN_URL), new CommandLineArgument(PROJECT_OPTION, CommandLineArgument.ArgumentType.String, Strings.GoogleCloudStorage.ProjectDescriptionShort, Strings.GoogleCloudStorage.ProjectDescriptionLong), - }); + .. TimeoutOptionsHelper.GetOptions(), + ]; } } - public string Description - { - get { return Strings.GoogleCloudStorage.Description; } - } + public string Description => Strings.GoogleCloudStorage.Description; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(WebApi.GoogleCloudStorage.Hosts()); #endregion - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { var item = new BucketResourceItem { name = m_prefix + remotename }; var url = WebApi.GoogleCloudStorage.PutUrl(m_bucket); - var res = await GoogleCommon.ChunkedUploadWithResumeAsync(m_oauth, item, url, stream, cancelToken).ConfigureAwait(false); + var res = await GoogleCommon.ChunkedUploadWithResumeAsync(m_oauth, item, url, stream, m_timeouts.ShortTimeout, m_timeouts.ReadWriteTimeout, cancelToken).ConfigureAwait(false); if (res == null) throw new Exception("Upload succeeded, but no data was returned"); } - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { - var url = WebApi.GoogleCloudStorage.GetUrl(m_bucket, Library.Utility.Uri.UrlPathEncode(m_prefix + remotename)); + var url = WebApi.GoogleCloudStorage.GetUrl(m_bucket, Utility.Uri.UrlPathEncode(m_prefix + remotename)); var req = m_oauth.CreateRequest(url); var areq = new AsyncHttpRequest(req); - using (var resp = areq.GetResponse()) - using (var rs = areq.GetResponseStream()) - await Library.Utility.Utility.CopyStreamAsync(rs, stream, cancelToken).ConfigureAwait(false); + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponse()).ConfigureAwait(false)) + using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) + using (var ts = rs.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(ts, stream, cancelToken).ConfigureAwait(false); } catch (WebException wex) { @@ -266,9 +264,9 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage } } - public async Task RenameAsync(string oldname, string newname, CancellationToken cancelToken) + public Task RenameAsync(string oldname, string newname, CancellationToken cancelToken) { - var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem + var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new BucketResourceItem { name = m_prefix + newname, })); @@ -278,11 +276,14 @@ namespace Duplicati.Library.Backend.GoogleCloudStorage req.ContentLength = data.Length; req.ContentType = "application/json; charset=UTF-8"; - var areq = new AsyncHttpRequest(req); - using (var rs = areq.GetRequestStream()) - await rs.WriteAsync(data, 0, data.Length, cancelToken).ConfigureAwait(false); + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + var areq = new AsyncHttpRequest(req); + using (var rs = areq.GetRequestStream()) + await rs.WriteAsync(data, 0, data.Length, ct).ConfigureAwait(false); - await m_oauth.ReadJSONResponseAsync(req, cancelToken).ConfigureAwait(false); + await m_oauth.ReadJSONResponseAsync(req, ct).ConfigureAwait(false); + }); } #region IDisposable implementation diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs b/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs index 20218c97c..dae8c6731 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleCommon.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,10 +21,7 @@ using Duplicati.Library.Utility; using Newtonsoft.Json; -using System; using System.Net; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend.GoogleServices { @@ -42,7 +39,7 @@ namespace Duplicati.Library.Backend.GoogleServices /// The Oauth instance /// The resumeable uploaduri /// The length of the entire stream - private static long QueryUploadRange(OAuthHelper oauth, string uploaduri, long streamlength, out T response) + private static long QueryUploadRange(OAuthHelper oauth, string uploaduri, long streamlength, out T? response) where T : class { response = null; @@ -51,8 +48,9 @@ namespace Duplicati.Library.Backend.GoogleServices req.ContentLength = 0; req.Headers["Content-Range"] = string.Format("bytes */{0}", streamlength); + // TODO: Apply timeout when this is upgraded to HttpClient var areq = new AsyncHttpRequest(req); - using(var resp = oauth.GetResponseWithoutException(areq)) + using (var resp = oauth.GetResponseWithoutException(areq)) { var code = (int)resp.StatusCode; @@ -62,7 +60,7 @@ namespace Duplicati.Library.Backend.GoogleServices response = oauth.ReadJSONResponse(resp); if (response == null) throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); - + return streamlength; } @@ -73,9 +71,9 @@ namespace Duplicati.Library.Backend.GoogleServices // https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3884 if (resp.Headers["Range"] == null) - return 0; + return 0; else - return long.Parse(resp.Headers["Range"].Split(new char[] { '-' })[1]) + 1; + return long.Parse(resp.Headers["Range"]!.Split(new char[] { '-' })[1]) + 1; } else throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); @@ -90,10 +88,12 @@ namespace Duplicati.Library.Backend.GoogleServices /// The data to submit as JSON metadata. /// The URL to register the upload session against. /// The stream with content data to upload. + /// The short request timeout. + /// The read write timeout. /// The HTTP Method. /// The type of data to upload as metadata. /// The type of data returned from the upload. - public static async Task ChunkedUploadWithResumeAsync(OAuthHelper oauth, TRequest requestdata, string url, System.IO.Stream stream, CancellationToken cancelToken, string method = "POST") + public static async Task ChunkedUploadWithResumeAsync(OAuthHelper oauth, TRequest requestdata, string url, Stream stream, TimeSpan shortTimeout, TimeSpan readWriteTimeout, CancellationToken cancelToken, string method = "POST") where TRequest : class where TResponse : class { @@ -105,25 +105,30 @@ namespace Duplicati.Library.Backend.GoogleServices if (data != null) req.ContentType = "application/json; charset=UTF-8"; - + req.Headers["X-Upload-Content-Type"] = "application/octet-stream"; req.Headers["X-Upload-Content-Length"] = stream.Length.ToString(); - var areq = new AsyncHttpRequest(req); - if (data != null) - using(var rs = areq.GetRequestStream()) - await rs.WriteAsync(data, 0, data.Length, cancelToken).ConfigureAwait(false); - - string uploaduri; - using(var resp = (HttpWebResponse)areq.GetResponse()) + var uploaduri = await Utility.Utility.WithTimeout(shortTimeout, cancelToken, async ct => { - if (resp.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(resp.Headers["Location"])) - throw new WebException("Failed to start upload session", null, WebExceptionStatus.UnknownError, resp); + var areq = new AsyncHttpRequest(req); + if (data != null) + using (var rs = areq.GetRequestStream()) + await rs.WriteAsync(data, 0, data.Length, cancelToken).ConfigureAwait(false); - uploaduri = resp.Headers["Location"]; - } + using (var resp = (HttpWebResponse)areq.GetResponse()) + { + if (resp.StatusCode != HttpStatusCode.OK || string.IsNullOrWhiteSpace(resp.Headers["Location"])) + throw new WebException("Failed to start upload session", null, WebExceptionStatus.UnknownError, resp); - return await ChunkedUploadAsync(oauth, uploaduri, stream, cancelToken).ConfigureAwait(false); + return resp.Headers["Location"]; + } + }).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(uploaduri)) + throw new Exception("Failed to start upload session"); + + return await ChunkedUploadAsync(oauth, uploaduri, stream, shortTimeout, readWriteTimeout, cancelToken).ConfigureAwait(false); } /// @@ -133,24 +138,26 @@ namespace Duplicati.Library.Backend.GoogleServices /// The Oauth instance /// The resumeable uploaduri /// The stream with data to upload. + /// The short request timeout. + /// The read write timeout. /// The type of data in the response. - private static async Task ChunkedUploadAsync(OAuthHelper oauth, string uploaduri, System.IO.Stream stream, CancellationToken cancelToken) + private static async Task ChunkedUploadAsync(OAuthHelper oauth, string uploaduri, Stream stream, TimeSpan shortTimeout, TimeSpan readWriteTimeout, CancellationToken cancelToken) where T : class { var queryRange = false; var retries = 0; var offset = 0L; var buffer = new byte[Library.Utility.Utility.DEFAULT_BUFFER_SIZE]; + HttpWebResponse? resp = null; // Repeatedly try uploading until all retries are done - while(true) + while (true) { try { if (queryRange) { - T re; - offset = GoogleCommon.QueryUploadRange(oauth, uploaduri, stream.Length, out re); + offset = QueryUploadRange(oauth, uploaduri, stream.Length, out var re); queryRange = false; if (re != null) @@ -172,44 +179,43 @@ namespace Duplicati.Library.Backend.GoogleServices // Upload the remaining data var areq = new AsyncHttpRequest(req); - using(var rs = areq.GetRequestStream()) + using (var rs = areq.GetRequestStream()) + using (var ts = rs.ObserveWriteTimeout(readWriteTimeout)) { var remaining = chunkSize; - while(remaining > 0) + while (remaining > 0) { var n = stream.Read(buffer, 0, (int)Math.Min(remaining, Library.Utility.Utility.DEFAULT_BUFFER_SIZE)); - await rs.WriteAsync(buffer, 0, n, cancelToken); + await ts.WriteAsync(buffer, 0, n, cancelToken); remaining -= n; } } // Check the response - using(var resp = oauth.GetResponseWithoutException(areq)) + resp = await Utility.Utility.WithTimeout(shortTimeout, cancelToken, _ => oauth.GetResponseWithoutException(areq)).ConfigureAwait(false); + var code = (int)resp.StatusCode; + + if (code == 308 && resp.Headers["Range"] != null) { - var code = (int)resp.StatusCode; + offset = long.Parse(resp.Headers["Range"]!.Split(new char[] { '-' })[1]) + 1; + retries = 0; + } + else if (code >= 200 && code <= 299) + { + offset += chunkSize; + if (offset != stream.Length) + throw new Exception(string.Format("Upload succeeded prematurely. Uploaded: {0}, total size: {1}", offset, stream.Length)); - if (code == 308 && resp.Headers["Range"] != null) - { - offset = long.Parse(resp.Headers["Range"].Split(new char[] {'-'})[1]) + 1; - retries = 0; - } - else if (code >= 200 && code <= 299) - { - offset += chunkSize; - if (offset != stream.Length) - throw new Exception(string.Format("Upload succeeded prematurely. Uploaded: {0}, total size: {1}", offset, stream.Length)); + //Verify that the response is also valid + var res = oauth.ReadJSONResponse(resp); + if (res == null) + throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); - //Verify that the response is also valid - var res = oauth.ReadJSONResponse(resp); - if (res == null) - throw new Exception(string.Format("Upload succeeded, but no data was returned, status code: {0}", code)); - - return res; - } - else - { - throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); - } + return res; + } + else + { + throw new WebException(string.Format("Unexpected status code: {0}", code), null, WebExceptionStatus.ServerProtocolViolation, resp); } } catch (Exception ex) @@ -219,10 +225,18 @@ namespace Duplicati.Library.Backend.GoogleServices // If we get a 5xx error, or some network issue, we retry if (ex is WebException exception && exception.Response is HttpWebResponse response) { - var code = (int)response.StatusCode; - retry = code >= 500 && code <= 599; + try + { + var code = (int)response.StatusCode; + retry = code >= 500 && code <= 599; + } + catch + { + // Assume this is a transient error + retry = true; + } } - else if (ex is System.Net.Sockets.SocketException || ex is System.IO.IOException || ex.InnerException is System.Net.Sockets.SocketException || ex.InnerException is System.IO.IOException) + else if (ex is System.Net.Sockets.SocketException || ex is IOException || ex.InnerException is System.Net.Sockets.SocketException || ex.InnerException is System.IO.IOException) { retry = true; } @@ -230,7 +244,7 @@ namespace Duplicati.Library.Backend.GoogleServices // Retry with exponential backoff if (retry && retries < 5) { - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); + Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); retries++; // Ask server where we left off @@ -239,6 +253,11 @@ namespace Duplicati.Library.Backend.GoogleServices else throw; } + finally + { + // Wait until the end of the request to dispose of the response, as it may be needed for error handling + resp?.Dispose(); + } } } } diff --git a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs index 967830471..207b15ff9 100644 --- a/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs +++ b/Duplicati/Library/Backend/GoogleServices/GoogleDrive.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,15 +23,11 @@ using Duplicati.Library.Backend.GoogleServices; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Net; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend.GoogleDrive { @@ -39,47 +35,56 @@ namespace Duplicati.Library.Backend.GoogleDrive // This class is instantiated dynamically in the BackendLoader. public class GoogleDrive : IBackend, IStreamingBackend, IQuotaEnabledBackend, IRenameEnabledBackend { - private const string AUTHID_OPTION = "authid"; private const string TEAMDRIVE_ID = "googledrive-teamdrive-id"; private const string FOLDER_MIMETYPE = "application/vnd.google-apps.folder"; + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("googledrive"); + private readonly string m_path; - private readonly string m_teamDriveID; + private readonly string? m_teamDriveID; private readonly OAuthHelper m_oauth; private readonly Dictionary m_filecache; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - private string m_currentFolderId; + private string? m_currentFolderId; public GoogleDrive() { + m_path = null!; + m_oauth = null!; + m_filecache = null!; + m_timeouts = null!; } - public GoogleDrive(string url, Dictionary options) + public GoogleDrive(string url, Dictionary options) { var uri = new Utility.Uri(url); m_path = Util.AppendDirSeparator(uri.HostAndPath, "/"); - string authid = null; - if (options.ContainsKey(AUTHID_OPTION)) - authid = options[AUTHID_OPTION]; + var auth = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TOKEN_URL); - if (options.ContainsKey(TEAMDRIVE_ID)) - m_teamDriveID = options[TEAMDRIVE_ID]; - - m_oauth = new OAuthHelper(authid, this.ProtocolKey) { AutoAuthHeader = true }; + m_timeouts = TimeoutOptionsHelper.Parse(options); + m_teamDriveID = options.GetValueOrDefault(TEAMDRIVE_ID); + m_oauth = new OAuthHelper(auth.AuthId!, this.ProtocolKey) { AutoAuthHeader = true }; m_filecache = new Dictionary(); } private async Task GetFolderIdAsync(string path, bool autocreate, CancellationToken cancelToken) { - var curparent = m_teamDriveID ?? (await GetAboutInfoAsync(cancelToken).ConfigureAwait(false)).rootFolderId; + var curparent = string.IsNullOrWhiteSpace(m_teamDriveID) + ? (await GetAboutInfoAsync(cancelToken).ConfigureAwait(false)).rootFolderId + : m_teamDriveID; + + if (string.IsNullOrEmpty(curparent)) + throw new UserInformationException("Unable to get root folder", "GoogleDriveNoRootFolder"); + var curdisplay = new StringBuilder("/"); foreach (var p in path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries)) { - var res = ListFolder(curparent, true, p).ToArray(); - + var res = await ListFolder(curparent, true, p, cancelToken).ToArrayAsync(cancelToken).ConfigureAwait(false); if (res.Length == 0) { if (!autocreate) @@ -97,6 +102,10 @@ namespace Duplicati.Library.Backend.GoogleDrive } curdisplay.Append(p).Append("/"); + + if (string.IsNullOrWhiteSpace(curparent)) + throw new UserInformationException($"Unable to get folder id for {curdisplay}", "GoogleDriveNoFolderId"); + } return curparent; @@ -110,21 +119,19 @@ namespace Duplicati.Library.Backend.GoogleDrive return m_currentFolderId; } - private async Task GetFileEntriesAsync(string remotename, bool throwMissingException, CancellationToken cancelToken) + private async Task GetFileEntriesAsync(string remotename, bool throwMissingException, CancellationToken cancelToken) { - GoogleDriveFolderItem[] entries; - m_filecache.TryGetValue(remotename, out entries); - + m_filecache.TryGetValue(remotename, out var entries); if (entries != null) return entries; var currentFolderId = await GetCurrentFolderIdAsync(cancelToken).ConfigureAwait(false); - entries = ListFolder(currentFolderId, false, remotename).ToArray(); + entries = await ListFolder(currentFolderId, false, remotename, cancelToken).ToArrayAsync(cancelToken).ConfigureAwait(false); if (entries == null || entries.Length == 0) { if (throwMissingException) - throw new FileMissingException(); + throw new FileMissingException(Strings.GoogleDrive.FilenotFound(remotename)); else return null; } @@ -139,18 +146,17 @@ namespace Duplicati.Library.Backend.GoogleDrive #region IStreamingBackend implementation - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { // Figure out if we update or create the file if (m_filecache.Count == 0) - foreach (var file in List()) { /* Enumerate the full listing */ } + await foreach (var file in ListAsync(cancelToken).ConfigureAwait(false)) { /* Enumerate the full listing */ } - GoogleDriveFolderItem[] files; - m_filecache.TryGetValue(remotename, out files); + m_filecache.TryGetValue(remotename, out var files); - string fileId = null; + string? fileId = null; if (files != null) { if (files.Length == 1) @@ -174,7 +180,7 @@ namespace Duplicati.Library.Backend.GoogleDrive teamDriveId = m_teamDriveID }; - var res = await GoogleCommon.ChunkedUploadWithResumeAsync(m_oauth, item, url, stream, cancelToken, isUpdate ? "PUT" : "POST").ConfigureAwait(false); + var res = await GoogleCommon.ChunkedUploadWithResumeAsync(m_oauth, item, url, stream, m_timeouts.ShortTimeout, m_timeouts.ReadWriteTimeout, cancelToken, isUpdate ? "PUT" : "POST").ConfigureAwait(false); m_filecache[remotename] = [res]; } catch @@ -184,26 +190,31 @@ namespace Duplicati.Library.Backend.GoogleDrive } } - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { // Prevent repeated download url lookups if (m_filecache.Count == 0) - foreach (var file in List()) { /* Enumerate the full listing */ } + await foreach (var file in ListAsync(cancelToken).ConfigureAwait(false)) { /* Enumerate the full listing */ } - var fileId = (await GetFileEntriesAsync(remotename, true, cancelToken).ConfigureAwait(false)).OrderByDescending(x => x.createdDate).First().id; + var fileId = (await GetFileEntriesAsync(remotename, true, cancelToken).ConfigureAwait(false))?.OrderByDescending(x => x.createdDate).FirstOrDefault()?.id; + if (string.IsNullOrEmpty(fileId)) + throw new FileMissingException(Strings.GoogleDrive.FilenotFound(remotename)); var req = m_oauth.CreateRequest(WebApi.GoogleDrive.GetUrl(fileId)); var areq = new AsyncHttpRequest(req); - using (var resp = (HttpWebResponse)areq.GetResponse()) - using (var rs = areq.GetResponseStream()) - await Duplicati.Library.Utility.Utility.CopyStreamAsync(rs, stream, cancelToken); + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) + using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) + using (var ts = rs.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(ts, stream, cancelToken); } #endregion #region IBackend implementation - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) + { bool success = false; try @@ -211,10 +222,10 @@ namespace Duplicati.Library.Backend.GoogleDrive m_filecache.Clear(); // For now, this class assumes that List() fully populates the file cache - var currentFolderId = GetCurrentFolderIdAsync(CancellationToken.None).Await(); - foreach (var n in ListFolder(currentFolderId)) + var currentFolderId = await GetCurrentFolderIdAsync(cancelToken).ConfigureAwait(false); + await foreach (var n in ListFolder(currentFolderId, null, null, cancelToken).ConfigureAwait(false)) { - FileEntry fe = null; + FileEntry? fe = null; if (n.fileSize == null) fe = new FileEntry(n.title); @@ -229,10 +240,9 @@ namespace Duplicati.Library.Backend.GoogleDrive if (!fe.IsFolder) { - GoogleDriveFolderItem[] lst; - if (!m_filecache.TryGetValue(fe.Name, out lst)) + if (!m_filecache.TryGetValue(fe.Name, out var lst)) { - m_filecache[fe.Name] = new GoogleDriveFolderItem[] { n }; + m_filecache[fe.Name] = [n]; } else { @@ -260,13 +270,13 @@ namespace Duplicati.Library.Backend.GoogleDrive public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = System.IO.File.OpenRead(filename)) + using (var fs = File.OpenRead(filename)) await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = System.IO.File.Create(filename)) + using (var fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken); } @@ -274,13 +284,19 @@ namespace Duplicati.Library.Backend.GoogleDrive { try { - foreach (var fileid in from n in await GetFileEntriesAsync(remotename, true, cancelToken).ConfigureAwait(false) select n.id) + var entries = await GetFileEntriesAsync(remotename, true, cancelToken).ConfigureAwait(false); + if (entries == null) + throw new FileMissingException(Strings.GoogleDrive.FilenotFound(remotename)); + + foreach (var fileid in entries.Select(x => x.id).WhereNotNullOrWhiteSpace()) { var url = WebApi.GoogleDrive.DeleteUrl(Library.Utility.Uri.UrlPathEncode(fileid), m_teamDriveID); - await m_oauth.GetJSONDataAsync(url, cancelToken, x => - { - x.Method = "DELETE"; - }); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_oauth.GetJSONDataAsync(url, cancelToken, x => + { + x.Method = "DELETE"; + }) + ).ConfigureAwait(false); } m_filecache.Remove(remotename); @@ -294,10 +310,7 @@ namespace Duplicati.Library.Backend.GoogleDrive } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public async Task CreateFolderAsync(CancellationToken cancelToken) { @@ -305,32 +318,19 @@ namespace Duplicati.Library.Backend.GoogleDrive m_currentFolderId = await GetFolderIdAsync(m_path, true, cancelToken).ConfigureAwait(false); } - public string DisplayName - { - get - { - return Strings.GoogleDrive.DisplayName; - } - } + public string DisplayName => Strings.GoogleDrive.DisplayName; - public string ProtocolKey - { - get - { - return "googledrive"; - } - } + public string ProtocolKey => "googledrive"; - public System.Collections.Generic.IList SupportedCommands => new List(new ICommandLineArgument[] { - new CommandLineArgument(AUTHID_OPTION, - CommandLineArgument.ArgumentType.Password, - Strings.GoogleDrive.AuthidShort, - Strings.GoogleDrive.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("googledrive"))), - new CommandLineArgument(TEAMDRIVE_ID, - CommandLineArgument.ArgumentType.String, - Strings.GoogleDrive.TeamDriveIdShort, - Strings.GoogleDrive.TeamDriveIdLong), - }); + public IList SupportedCommands => + [ + .. AuthIdOptionsHelper.GetOptions(TOKEN_URL), + new CommandLineArgument(TEAMDRIVE_ID, + CommandLineArgument.ArgumentType.String, + Strings.GoogleDrive.TeamDriveIdShort, + Strings.GoogleDrive.TeamDriveIdLong), + .. TimeoutOptionsHelper.GetOptions(), + ]; public string Description { @@ -343,7 +343,7 @@ namespace Duplicati.Library.Backend.GoogleDrive #endregion #region IQuotaEnabledBackend implementation - public async Task GetQuotaInfoAsync(CancellationToken cancelToken) + public async Task GetQuotaInfoAsync(CancellationToken cancelToken) { try { @@ -366,9 +366,10 @@ namespace Duplicati.Library.Backend.GoogleDrive try { var files = await GetFileEntriesAsync(oldname, true, cancellationToken).ConfigureAwait(false); + if (files == null) + throw new FileMissingException(Strings.GoogleDrive.FilenotFound(oldname)); if (files.Length > 1) - throw new UserInformationException(string.Format(Strings.GoogleDrive.MultipleEntries(oldname, m_path)), - "GoogleDriveMultipleEntries"); + throw new UserInformationException(Strings.GoogleDrive.MultipleEntries(oldname, m_path), "GoogleDriveMultipleEntries"); using var stream = new MemoryStream(); await GetAsync(oldname, stream, cancellationToken).ConfigureAwait(false); @@ -397,13 +398,13 @@ namespace Duplicati.Library.Backend.GoogleDrive private class GoogleDriveParentReference { - public string id { get; set; } + public string? id { get; set; } } private class GoogleDriveListResponse { - public string nextPageToken { get; set; } - public GoogleDriveFolderItem[] items { get; set; } + public string? nextPageToken { get; set; } + public GoogleDriveFolderItem[]? items { get; set; } } private class GoogleDriveFolderItemLabels @@ -413,41 +414,44 @@ namespace Duplicati.Library.Backend.GoogleDrive private class GoogleDriveFolderItem { - public string id { get; set; } - public string title { get; set; } - public string description { get; set; } - public string mimeType { get; set; } - public GoogleDriveFolderItemLabels labels { get; set; } + public string? id { get; set; } + public string? title { get; set; } + public string? description { get; set; } + public string? mimeType { get; set; } + public GoogleDriveFolderItemLabels? labels { get; set; } public DateTime? createdDate { get; set; } public DateTime? modifiedDate { get; set; } public long? fileSize { get; set; } - public string teamDriveId { get; set; } - public GoogleDriveParentReference[] parents { get; set; } + public string? teamDriveId { get; set; } + public GoogleDriveParentReference[]? parents { get; set; } } private class GoogleDriveAboutResponse { public long? quotaBytesTotal { get; set; } public long? quotaBytesUsed { get; set; } - public string rootFolderId { get; set; } + public string? rootFolderId { get; set; } } - private IEnumerable ListFolder(string parentfolder, bool? onlyFolders = null, string name = null) + private async IAsyncEnumerable ListFolder(string parentfolder, bool? onlyFolders, string? name, [EnumeratorCancellation] CancellationToken cancelToken) { - var fileQuery = new string[] { + var fileQuery = new string?[] { string.IsNullOrEmpty(name) ? null : string.Format("title = '{0}'", EscapeTitleEntries(name)), onlyFolders == null ? null : string.Format("mimeType {0}= '{1}'", onlyFolders.Value ? "" : "!", FOLDER_MIMETYPE), string.Format("'{0}' in parents", EscapeTitleEntries(parentfolder)), "trashed=false" }; - var encodedFileQuery = Library.Utility.Uri.UrlEncode(string.Join(" and ", fileQuery.Where(x => x != null))); + var encodedFileQuery = Utility.Uri.UrlEncode(string.Join(" and ", fileQuery.Where(x => x != null))); var url = WebApi.GoogleDrive.ListUrl(encodedFileQuery, m_teamDriveID); while (true) { - var res = m_oauth.GetJSONData(url); - foreach (var n in res.items) + var res = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct + => m_oauth.GetJSONDataAsync(url, ct) + ).ConfigureAwait(false); + + foreach (var n in res.items ?? []) yield return n; var token = res.nextPageToken; @@ -459,9 +463,8 @@ namespace Duplicati.Library.Backend.GoogleDrive } private Task GetAboutInfoAsync(CancellationToken cancelToken) - { - return m_oauth.GetJSONDataAsync(WebApi.GoogleDrive.AboutInfoUrl(), cancelToken); - } + => Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_oauth.GetJSONDataAsync(WebApi.GoogleDrive.AboutInfoUrl(), ct)); private Task CreateFolderAsync(string name, string parent, CancellationToken cancelToken) { @@ -471,25 +474,27 @@ namespace Duplicati.Library.Backend.GoogleDrive description = name, mimeType = FOLDER_MIMETYPE, labels = new GoogleDriveFolderItemLabels { hidden = true }, - parents = new GoogleDriveParentReference[] { new GoogleDriveParentReference { id = parent } } + parents = [new GoogleDriveParentReference { id = parent }] }; - var data = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(folder)); + var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(folder)); - return m_oauth.GetJSONDataAsync( - WebApi.GoogleDrive.CreateFolderUrl(m_teamDriveID), - cancelToken, - x => - { - x.Method = "POST"; - x.ContentType = "application/json; charset=UTF-8"; - x.ContentLength = data.Length; + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_oauth.GetJSONDataAsync( + WebApi.GoogleDrive.CreateFolderUrl(m_teamDriveID), + ct, + x => + { + x.Method = "POST"; + x.ContentType = "application/json; charset=UTF-8"; + x.ContentLength = data.Length; - }, async (req, ct) => - { - using (var rs = req.GetRequestStream()) - await rs.WriteAsync(data, 0, data.Length, ct).ConfigureAwait(false); - }); + }, async (req, ct) => + { + using (var rs = req.GetRequestStream()) + await rs.WriteAsync(data, 0, data.Length, ct).ConfigureAwait(false); + }) + ); } } } diff --git a/Duplicati/Library/Backend/GoogleServices/Strings.cs b/Duplicati/Library/Backend/GoogleServices/Strings.cs index 8553baa91..0d4a899f4 100644 --- a/Duplicati/Library/Backend/GoogleServices/Strings.cs +++ b/Duplicati/Library/Backend/GoogleServices/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,13 +23,11 @@ using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { - internal static class GoogleCloudStorage { + internal static class GoogleCloudStorage + { public static string Description { get { return LC.L(@"This backend can read and write data to Google Cloud Storage. Allowed format is ""gcs://bucket/folder""."); } } public static string DisplayName { get { return LC.L(@"Google Cloud Storage"); } } - public static string MissingAuthID(string url) { return LC.L(@"You need an AuthID. You can get it from: {0}", url); } public static string ProjectIDMissingError(string projectoption) { return LC.L(@"You must supply a project ID with --{0} for creating a bucket.", projectoption); } - public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } - public static string AuthidShort { get { return LC.L(@"The authorization code"); } } public static string LocationDescriptionLong(string regions) { return LC.L(@"This option is only used when creating new buckets. Use this option to change what region the data is stored in. Charges vary with bucket location. Known bucket locations: {0}", regions); } public static string LocationDescriptionShort { get { return LC.L(@"Specify location option for creating a bucket"); } } @@ -40,13 +38,21 @@ namespace Duplicati.Library.Backend.Strings public static string ProjectDescriptionShort { get { return LC.L(@"Specify project for creating a bucket"); } } } - internal static class GoogleDrive { + internal static class GoogleDrive + { public static string Description { get { return LC.L(@"This backend can read and write data to Google Drive. Allowed format is ""googledrive://folder/subfolder""."); } } public static string DisplayName { get { return LC.L(@"Google Drive"); } } public static string MultipleEntries(string folder, string parent) { return LC.L(@"There is more than one item named ""{0}"" in the folder ""{1}"".", folder, parent); } - public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } - public static string AuthidShort { get { return LC.L(@"The authorization code"); } } + public static string FilenotFound(string name) { return LC.L(@"File not found: {0}", name); } public static string TeamDriveIdLong { get { return LC.L("This option sets the team drive to use. Leaving it empty uses the personal drive."); } } public static string TeamDriveIdShort { get { return LC.L("Team drive ID"); } } } + + internal static class GCSConfig + { + public static string DisplayName => LC.L("Google Cloud Storage configuration module"); + public static string Description => LC.L("Expose Google Cloud Storage configuration as a web module"); + public static string ConfigTypeShort => LC.L("The config to get"); + public static string ConfigTypeLong => LC.L("Provide different config values"); + } } diff --git a/Duplicati/Library/Backend/GoogleServices/WebApi.cs b/Duplicati/Library/Backend/GoogleServices/WebApi.cs index 8ef8d5b1e..5f488a610 100644 --- a/Duplicati/Library/Backend/GoogleServices/WebApi.cs +++ b/Duplicati/Library/Backend/GoogleServices/WebApi.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; using System.Collections.Specialized; using Duplicati.Library.Utility; @@ -28,41 +27,41 @@ namespace Duplicati.Library.Backend.WebApi public static class GoogleCloudStorage { // From: https://cloud.google.com/storage/docs/bucket-locations - public static readonly KeyValuePair[] KNOWN_GCS_LOCATIONS = { - new KeyValuePair("(default)", null), - new KeyValuePair("Europe", "EU"), - new KeyValuePair("United States", "US"), - new KeyValuePair("Asia", "ASIA"), + public static readonly KeyValuePair[] KNOWN_GCS_LOCATIONS = { + new KeyValuePair("(default)", null), + new KeyValuePair("Europe", "EU"), + new KeyValuePair("United States", "US"), + new KeyValuePair("Asia", "ASIA"), //Regional buckets: https://cloud.google.com/storage/docs/regional-buckets - new KeyValuePair("Eastern Asia-Pacific", "ASIA-EAST1"), - new KeyValuePair("Central United States 1", "US-CENTRAL1"), - new KeyValuePair("Central United States 2", "US-CENTRAL2"), - new KeyValuePair("Eastern United States 1", "US-EAST1"), - new KeyValuePair("Eastern United States 2", "US-EAST2"), - new KeyValuePair("Eastern United States 3", "US-EAST3"), - new KeyValuePair("Western United States", "US-WEST1"), + new KeyValuePair("Eastern Asia-Pacific", "ASIA-EAST1"), + new KeyValuePair("Central United States 1", "US-CENTRAL1"), + new KeyValuePair("Central United States 2", "US-CENTRAL2"), + new KeyValuePair("Eastern United States 1", "US-EAST1"), + new KeyValuePair("Eastern United States 2", "US-EAST2"), + new KeyValuePair("Eastern United States 3", "US-EAST3"), + new KeyValuePair("Western United States", "US-WEST1"), }; - public static readonly KeyValuePair[] KNOWN_GCS_STORAGE_CLASSES = { - new KeyValuePair("(default)", null), - new KeyValuePair("Standard", "STANDARD"), - new KeyValuePair("Durable Reduced Availability (DRA)", "DURABLE_REDUCED_AVAILABILITY"), - new KeyValuePair("Nearline", "NEARLINE"), + public static readonly KeyValuePair[] KNOWN_GCS_STORAGE_CLASSES = { + new KeyValuePair("(default)", null), + new KeyValuePair("Standard", "STANDARD"), + new KeyValuePair("Durable Reduced Availability (DRA)", "DURABLE_REDUCED_AVAILABILITY"), + new KeyValuePair("Nearline", "NEARLINE"), }; - public static string[] Hosts() - { - return new [] { new System.Uri(Url.UPLOAD).Host, - new System.Uri(Url.API).Host }; - } + public static string[] Hosts() => + [ + new System.Uri(Url.UPLOAD).Host, + new System.Uri(Url.API).Host + ]; public static string DeleteUrl(string bucketId, string objectId) { var path = BucketObjectPath(bucketId, objectId); - return Uri.UriBuilder(Url.API, path); + return Utility.Uri.UriBuilder(Url.API, path); } public static string CreateFolderUrl(string projectId) @@ -72,20 +71,16 @@ namespace Duplicati.Library.Backend.WebApi { QueryParam.Project, projectId } }; - return Uri.UriBuilder(Url.API, Path.Bucket, queryParams); + return Utility.Uri.UriBuilder(Url.API, Path.Bucket, queryParams); } public static string RenameUrl(string bucketId, string objectId) - { - return Uri.UriBuilder(Url.API, BucketObjectPath(bucketId, objectId)); - } + => Utility.Uri.UriBuilder(Url.API, BucketObjectPath(bucketId, objectId)); public static string ListUrl(string bucketId, string prefix) - { - return ListUrl(bucketId, prefix, null); - } + => ListUrl(bucketId, prefix, null); - public static string ListUrl(string bucketId, string prefix, string token) + public static string ListUrl(string bucketId, string prefix, string? token) { var queryParams = new NameValueCollection { { QueryParam.Prefix, prefix } @@ -96,7 +91,7 @@ namespace Duplicati.Library.Backend.WebApi queryParams.Set(QueryParam.PageToken, token); } - return Uri.UriBuilder(Url.API, BucketObjectPath(bucketId), queryParams); + return Utility.Uri.UriBuilder(Url.API, BucketObjectPath(bucketId), queryParams); } public static string PutUrl(string bucketId) @@ -106,7 +101,7 @@ namespace Duplicati.Library.Backend.WebApi { QueryParam.UploadType, QueryValue.Resumable } }; var path = UrlPath.Create(Path.Bucket).Append(bucketId).Append(Path.Object).ToString(); - return Uri.UriBuilder(Url.UPLOAD, path, queryParams); + return Utility.Uri.UriBuilder(Url.UPLOAD, path, queryParams); } public static string GetUrl(string bucketId, string objectId) @@ -118,7 +113,7 @@ namespace Duplicati.Library.Backend.WebApi }; var path = BucketObjectPath(bucketId, objectId); - return Uri.UriBuilder(Url.API, path, queryParams); + return Utility.Uri.UriBuilder(Url.API, path, queryParams); } private static class Url @@ -148,36 +143,27 @@ namespace Duplicati.Library.Backend.WebApi public const string Object = "o"; } - private static string BucketObjectPath(string bucketId, string objectId = null) - { - return UrlPath.Create(Path.Bucket) - .Append(bucketId) - .Append(Path.Object) - .Append(objectId).ToString(); - } + private static string BucketObjectPath(string bucketId, string? objectId = null) + => UrlPath.Create(Path.Bucket) + .Append(bucketId) + .Append(Path.Object) + .Append(objectId).ToString(); } public static class GoogleDrive { public static string[] Hosts() - { - return new [] { new System.Uri(Url.DRIVE).Host, new System.Uri(Url.UPLOAD).Host }; - - } + => [new System.Uri(Url.DRIVE).Host, new System.Uri(Url.UPLOAD).Host]; public static string GetUrl(string fileId) - { - return FileQueryUrl(fileId, new NameValueCollection{ + => FileQueryUrl(fileId, new NameValueCollection{ { QueryParam.Alt, QueryValue.Media } }); - } - public static string DeleteUrl(string fileId, string teamDriveId) - { - return FileQueryUrl(Uri.UrlPathEncode(fileId), AddTeamDriveParam(teamDriveId)); - } + public static string DeleteUrl(string fileId, string? teamDriveId) + => FileQueryUrl(Utility.Uri.UrlPathEncode(fileId), AddTeamDriveParam(teamDriveId)); - public static string PutUrl(string fileId, bool useTeamDrive) + public static string PutUrl(string? fileId, bool useTeamDrive) { var queryParams = new NameValueCollection { { QueryParam.UploadType, @@ -189,16 +175,14 @@ namespace Duplicati.Library.Backend.WebApi } return !string.IsNullOrWhiteSpace(fileId) ? - FileUploadUrl(Uri.UrlPathEncode(fileId), queryParams) : + FileUploadUrl(Utility.Uri.UrlPathEncode(fileId), queryParams) : FileUploadUrl(queryParams); } - public static string ListUrl(string fileQuery, string teamDriveId) - { - return ListUrl(fileQuery, teamDriveId, null); - } - - public static string ListUrl(string fileQuery, string teamDriveId, string token) + public static string ListUrl(string fileQuery, string? teamDriveId) + => ListUrl(fileQuery, teamDriveId, null); + + public static string ListUrl(string fileQuery, string? teamDriveId, string? token) { var queryParams = new NameValueCollection { @@ -207,7 +191,7 @@ namespace Duplicati.Library.Backend.WebApi }; queryParams.Add(AddTeamDriveParam(teamDriveId)); - + if (token != null) { queryParams.Set(QueryParam.PageToken, token); @@ -216,15 +200,11 @@ namespace Duplicati.Library.Backend.WebApi return FileQueryUrl(queryParams); } - public static string CreateFolderUrl(string teamDriveId) - { - return FileQueryUrl(AddTeamDriveParam(teamDriveId)); - } + public static string CreateFolderUrl(string? teamDriveId) + => FileQueryUrl(AddTeamDriveParam(teamDriveId)); public static string AboutInfoUrl() - { - return Uri.UriBuilder(Url.DRIVE, Path.About); - } + => Utility.Uri.UriBuilder(Url.DRIVE, Path.About); private static class Url { @@ -259,40 +239,30 @@ namespace Duplicati.Library.Backend.WebApi } private static string FileQueryUrl(NameValueCollection values) - { - return Uri.UriBuilder(Url.DRIVE, Path.File, values); - } + => Utility.Uri.UriBuilder(Url.DRIVE, Path.File, values); - private static string FileQueryUrl(string fileId, NameValueCollection values = null) - { - return Uri.UriBuilder(Url.DRIVE, UrlPath.Create(Path.File).Append(fileId).ToString(), - values); - } + private static string FileQueryUrl(string fileId, NameValueCollection? values = null) + => Utility.Uri.UriBuilder(Url.DRIVE, UrlPath.Create(Path.File).Append(fileId).ToString(), values); - private static string FileUploadUrl(string fileId, NameValueCollection values) - { - return Uri.UriBuilder(Url.UPLOAD, UrlPath.Create(Path.File).Append(fileId).ToString(), - values); - } + private static string FileUploadUrl(string fileId, NameValueCollection? values) + => Utility.Uri.UriBuilder(Url.UPLOAD, UrlPath.Create(Path.File).Append(fileId).ToString(), values); private static string FileUploadUrl(NameValueCollection values) - { - return Uri.UriBuilder(Url.UPLOAD, Path.File, values); - } + => Utility.Uri.UriBuilder(Url.UPLOAD, Path.File, values); - private static NameValueCollection AddTeamDriveParam(string teamDriveId) + private static NameValueCollection AddTeamDriveParam(string? teamDriveId) { return teamDriveId != null ? new NameValueCollection { - { WebApi.GoogleDrive.QueryParam.SupportsTeamDrive, - WebApi.GoogleDrive.QueryValue.True }, - { WebApi.GoogleDrive.QueryParam.TeamDriveId, + { QueryParam.SupportsTeamDrive, + QueryValue.True }, + { QueryParam.TeamDriveId, teamDriveId }, - { WebApi.GoogleDrive.QueryParam.IncludeTeamDrive, - WebApi.GoogleDrive.QueryValue.True }, - { WebApi.GoogleDrive.QueryParam.corpora, - WebApi.GoogleDrive.QueryValue.TeamDrive } + { QueryParam.IncludeTeamDrive, + QueryValue.True }, + { QueryParam.corpora, + QueryValue.TeamDrive } - } : new NameValueCollection( ); + } : new NameValueCollection(); } } } \ No newline at end of file diff --git a/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj b/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj index 14548a943..583dd40a3 100644 --- a/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj +++ b/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj @@ -4,10 +4,13 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable + diff --git a/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs b/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs index 673fb3528..d4f5d7809 100644 --- a/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs +++ b/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,84 +21,71 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend { - public class Idrivee2Backend : IBackend, IStreamingBackend + public class Idrivee2Backend : IBackend, IStreamingBackend, IFolderEnabledBackend { - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - static Idrivee2Backend() - { - - } - + // TODO: Non-standard naming, should be access-key-id and access-key-secret + private const string AUTH_USERNAME_OPTION = "access_key_id"; + private const string AUTH_PASSWORD_OPTION = "access_key_secret"; private readonly string m_prefix; private readonly string m_bucket; - private IS3Client m_s3Client; + private IS3Client? m_s3Client; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + private readonly AuthOptionsHelper.AuthOptions m_auth; + private readonly Dictionary m_options; public Idrivee2Backend() { + m_bucket = null!; + m_prefix = null!; + m_timeouts = null!; + m_auth = null!; + m_options = null!; } - public Idrivee2Backend(string url, Dictionary options) + public Idrivee2Backend(string url, Dictionary options) { var uri = new Utility.Uri(url); m_bucket = uri.Host; m_prefix = uri.Path; m_prefix = m_prefix.Trim(); if (m_prefix.Length != 0) - { m_prefix = Util.AppendDirSeparator(m_prefix, "/"); - } - string accessKeyId = null; - string accessKeySecret = null; - if (options.ContainsKey("auth-username")) - accessKeyId = options["auth-username"]; - if (options.ContainsKey("auth-password")) - accessKeySecret = options["auth-password"]; - - if (options.ContainsKey("access_key_id")) - accessKeyId = options["access_key_id"]; - if (options.ContainsKey("secret_access_key")) - accessKeySecret = options["secret_access_key"]; - - if (string.IsNullOrEmpty(accessKeyId)) + m_timeouts = TimeoutOptionsHelper.Parse(options); + m_auth = AuthOptionsHelper.ParseWithAlias(options, uri, AUTH_USERNAME_OPTION, AUTH_PASSWORD_OPTION); + if (!m_auth.HasUsername) throw new UserInformationException(Strings.Idrivee2Backend.NoKeyIdError, "Idrivee2NoKeyId"); - if (string.IsNullOrEmpty(accessKeySecret)) + if (!m_auth.HasPassword) throw new UserInformationException(Strings.Idrivee2Backend.NoKeySecretError, "Idrivee2NoKeySecret"); - string host = GetRegionEndpoint("https://api.idrivee2.com/api/service/get_region_end_point/" + accessKeyId); - - - m_s3Client = new S3AwsClient(accessKeyId, accessKeySecret, null, host, null, true, false, options); + m_options = options; } - public string GetRegionEndpoint(string url) + public async Task GetRegionEndpointAsync(string url, CancellationToken cancellationToken) { try { - System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); + var req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); req.Method = System.Net.WebRequestMethods.Http.Get; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); + var areq = new Utility.AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => (System.Net.HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { int code = (int)resp.StatusCode; if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically throw new Exception("Failed to fetch region endpoint"); - using (var s = areq.GetResponseStream()) + using (var s = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => areq.GetResponseStream())) + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) { - using (var reader = new StreamReader(s)) + using (var reader = new StreamReader(t)) { string endpoint = reader.ReadToEnd(); return endpoint; @@ -109,32 +96,21 @@ namespace Duplicati.Library.Backend catch (System.Net.WebException wex) { //Convert to better exception - throw new Exception("Failed to fetch region endpoint"); + throw new Exception("Failed to fetch region endpoint", wex); } } #region IBackend Members - public string DisplayName - { - get { return Strings.Idrivee2Backend.DisplayName; } - } + public string DisplayName => Strings.Idrivee2Backend.DisplayName; public string ProtocolKey => "e2"; - public bool SupportsStreaming => true; - - - public IEnumerable List() + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - foreach (IFileEntry file in Connection.ListBucket(m_bucket, m_prefix)) - { - ((FileEntry)file).Name = file.Name.Substring(m_prefix.Length); - if (file.Name.StartsWith("/", StringComparison.Ordinal) && !m_prefix.StartsWith("/", StringComparison.Ordinal)) - ((FileEntry)file).Name = file.Name.Substring(1); - + var con = await GetConnection(cancelToken).ConfigureAwait(false); + await foreach (IFileEntry file in con.ListBucketAsync(m_bucket, m_prefix, false, cancelToken).ConfigureAwait(false)) yield return file; - } } public async Task PutAsync(string remotename, string localname, CancellationToken cancelToken) @@ -145,7 +121,8 @@ namespace Duplicati.Library.Backend public async Task PutAsync(string remotename, Stream input, CancellationToken cancelToken) { - await Connection.AddFileStreamAsync(m_bucket, GetFullKey(remotename), input, cancelToken).ConfigureAwait(false); + var con = await GetConnection(cancelToken).ConfigureAwait(false); + await con.AddFileStreamAsync(m_bucket, GetFullKey(remotename), input, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string localname, CancellationToken cancellationToken) @@ -154,74 +131,46 @@ namespace Duplicati.Library.Backend await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); } - public Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken) + public async Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken) { - return Connection.GetFileStreamAsync(m_bucket, GetFullKey(remotename), output, cancellationToken); + var con = await GetConnection(cancellationToken).ConfigureAwait(false); + await con.GetFileStreamAsync(m_bucket, GetFullKey(remotename), output, cancellationToken).ConfigureAwait(false); } - public Task DeleteAsync(string remotename, CancellationToken cancellationToken) + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) { - return Connection.DeleteObjectAsync(m_bucket, GetFullKey(remotename), cancellationToken); + var con = await GetConnection(cancellationToken).ConfigureAwait(false); + await con.DeleteObjectAsync(m_bucket, GetFullKey(remotename), cancellationToken).ConfigureAwait(false); } - public IList SupportedCommands - { - get - { - var defaults = new Amazon.S3.AmazonS3Config() - { - // If this is not set, accessing the property will trigger an expensive operation (~30 seconds) - // to get the region endpoint. This stalls the supported commands list. - UseArnRegion = false - }; + public IList SupportedCommands => + [ + .. S3AwsClient.GetAwsExtendedOptions(), + new CommandLineArgument(AUTH_USERNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.Idrivee2Backend.KeyIDDescriptionShort, Strings.Idrivee2Backend.KeyIDDescriptionLong,null, [AuthOptionsHelper.AuthUsernameOption], null), + new CommandLineArgument(AUTH_PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Idrivee2Backend.KeySecretDescriptionShort, Strings.Idrivee2Backend.KeySecretDescriptionLong, null, [AuthOptionsHelper.AuthPasswordOption ], null), + .. TimeoutOptionsHelper.GetOptions(), + ]; - var exts = - typeof(Amazon.S3.AmazonS3Config).GetProperties().Where(x => x.CanRead && x.CanWrite && (x.PropertyType == typeof(string) || x.PropertyType == typeof(bool) || x.PropertyType == typeof(int) || x.PropertyType == typeof(long) || x.PropertyType.IsEnum)) - .Select(x => (ICommandLineArgument)new CommandLineArgument( - "s3-ext-" + x.Name.ToLowerInvariant(), - x.PropertyType == typeof(bool) ? CommandLineArgument.ArgumentType.Boolean : x.PropertyType.IsEnum ? CommandLineArgument.ArgumentType.Enumeration : CommandLineArgument.ArgumentType.String, - x.Name, - string.Format("Extended option {0}", x.Name), - string.Format("{0}", x.GetValue(defaults)), - null, - x.PropertyType.IsEnum ? Enum.GetNames(x.PropertyType) : null)); - - var normal = new ICommandLineArgument[] { - new CommandLineArgument("access_key_secret", CommandLineArgument.ArgumentType.Password, Strings.Idrivee2Backend.KeySecretDescriptionShort, Strings.Idrivee2Backend.KeySecretDescriptionLong, null, new[]{"auth-password"}, null), - new CommandLineArgument("access_key_id", CommandLineArgument.ArgumentType.String, Strings.Idrivee2Backend.KeyIDDescriptionShort, Strings.Idrivee2Backend.KeyIDDescriptionLong,null, new[]{"auth-username"}, null) - }; - - return normal.Union(exts).ToList(); - } - } - - public string Description - { - get - { - return Strings.Idrivee2Backend.Description; - } - } + public string Description => Strings.Idrivee2Backend.Description; public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { + var con = await GetConnection(cancelToken).ConfigureAwait(false); //S3 does not complain if the bucket already exists - return Connection.AddBucketAsync(m_bucket, cancelToken); + await con.AddBucketAsync(m_bucket, cancelToken).ConfigureAwait(false); } #endregion #region IRenameEnabledBackend Members - public Task Rename(string source, string target, CancellationToken cancelToken) + public async Task Rename(string source, string target, CancellationToken cancelToken) { - return Connection.RenameFileAsync(m_bucket, GetFullKey(source), GetFullKey(target), cancelToken); + var con = await GetConnection(cancelToken).ConfigureAwait(false); + await con.RenameFileAsync(m_bucket, GetFullKey(source), GetFullKey(target), cancelToken); } #endregion @@ -236,14 +185,46 @@ namespace Duplicati.Library.Backend #endregion - private IS3Client Connection => m_s3Client; - - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { m_s3Client.GetDnsHost() }); - - private string GetFullKey(string name) + private async Task GetConnection(CancellationToken cancellationToken) { - //AWS SDK encodes the filenames correctly - return m_prefix + name; + if (m_s3Client == null) + { + (var accessKeyId, var accessKeySecret) = m_auth.GetCredentials(); + // TODO: Do not make blocking calls in the constructor + var host = await GetRegionEndpointAsync("https://api.idrivee2.com/api/service/get_region_end_point/" + accessKeyId, cancellationToken).ConfigureAwait(false); + m_s3Client = new S3AwsClient(accessKeyId, accessKeySecret, null, host, null, true, false, m_timeouts, m_options); + } + + return m_s3Client; } + + public async Task GetDNSNamesAsync(CancellationToken cancelToken) + { + var con = await GetConnection(cancelToken).ConfigureAwait(false); + var dnshost = con.GetDnsHost(); + return string.IsNullOrWhiteSpace(dnshost) + ? [] + : [dnshost]; + } + + private string GetFullKey(string? name) + //AWS SDK encodes the filenames correctly + => $"{m_prefix}{name}"; + + /// + public async IAsyncEnumerable ListAsync(string? path, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var filterPath = GetFullKey(path); + if (!string.IsNullOrWhiteSpace(filterPath)) + filterPath = Util.AppendDirSeparator(filterPath, "/"); + + var con = await GetConnection(cancellationToken).ConfigureAwait(false); + await foreach (var files in con.ListBucketAsync(m_bucket, filterPath, true, cancellationToken).ConfigureAwait(false)) + yield return files; + } + + /// + public Task GetEntryAsync(string path, CancellationToken cancellationToken) + => Task.FromResult(null); } } diff --git a/Duplicati/Library/Backend/Idrivee2/Strings.cs b/Duplicati/Library/Backend/Idrivee2/Strings.cs index 032e95d36..ece2080a9 100644 --- a/Duplicati/Library/Backend/Idrivee2/Strings.cs +++ b/Duplicati/Library/Backend/Idrivee2/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,9 +19,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class Idrivee2Backend { - public static string Description { get { return LC.L(@"This backend can read and write data to IDrive e2."); } } +namespace Duplicati.Library.Backend.Strings +{ + internal static class Idrivee2Backend + { + public static string Description { get { return LC.L(@"This backend can read and write data to IDrive e2. Allowed format is ""e2://bucket/folder""."); } } public static string DisplayName { get { return LC.L(@"IDrive e2"); } } public static string KeySecretDescriptionLong { get { return LC.L(@"Access Key Secret can be obtained after logging into your IDrive e2 account. This can also be supplied through the option --{0}.", "auth-password"); } } public static string KeySecretDescriptionShort { get { return LC.L(@"Access Key Secret"); } } diff --git a/Duplicati/Library/Backend/Jottacloud/Duplicati.Library.Backend.Jottacloud.csproj b/Duplicati/Library/Backend/Jottacloud/Duplicati.Library.Backend.Jottacloud.csproj index 01fe1b339..3cba2fed0 100644 --- a/Duplicati/Library/Backend/Jottacloud/Duplicati.Library.Backend.Jottacloud.csproj +++ b/Duplicati/Library/Backend/Jottacloud/Duplicati.Library.Backend.Jottacloud.csproj @@ -4,7 +4,9 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs index ef8eea6a1..ec4e5f30f 100644 --- a/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs +++ b/Duplicati/Library/Backend/Jottacloud/Jottacloud.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,11 +22,11 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Localization.Short; -using System; -using System.Collections.Generic; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Cryptography; -using System.Threading; using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -34,7 +34,7 @@ namespace Duplicati.Library.Backend // This class is instantiated dynamically in the BackendLoader. public class Jottacloud : IBackend, IStreamingBackend { - private const string AUTHID_OPTION = "authid"; + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("jottacloud"); private const string JFS_ROOT = "https://jfs.jottacloud.com/jfs"; private const string JFS_ROOT_UPLOAD = "https://up.jottacloud.com/jfs"; // Separate host for uploading files private const string JFS_BUILTIN_DEVICE = "Jotta"; // The built-in device used for the built-in Sync and Archive mount points. @@ -59,6 +59,7 @@ namespace Duplicati.Library.Backend private static readonly string JFS_DEFAULT_THREADS = "4"; private readonly int m_threads; private readonly long m_chunksize; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; private readonly JottacloudAuthHelper m_oauth; @@ -69,23 +70,33 @@ namespace Duplicati.Library.Backend /// static Jottacloud() { - System.Net.ServicePointManager.DefaultConnectionLimit = 1000; + // TODO: Remove this when HttpClient migration is done + ServicePointManager.DefaultConnectionLimit = 1000; } public Jottacloud() { + m_device = null!; + m_mountPoint = null!; + m_path = null!; + m_url_device = null!; + m_url = null!; + m_url_upload = null!; + m_timeouts = null!; + m_oauth = null!; } - public Jottacloud(string url, Dictionary options) + public Jottacloud(string url, Dictionary options) { // Duplicati back-end url for Jottacloud is in format "jottacloud://folder/subfolder", we transform them to // the Jottacloud REST API (JFS) url format "https://jfs.jottacloud.com/jfs/[username]/[device]/[mountpoint]/[folder]/[subfolder]". // Find out what JFS device to use. - if (options.ContainsKey(JFS_DEVICE_OPTION)) + var device = options.GetValueOrDefault(JFS_DEVICE_OPTION); + if (!string.IsNullOrWhiteSpace(device)) { // Custom device specified. - m_device = options[JFS_DEVICE_OPTION]; + m_device = device; if (string.Equals(m_device, JFS_BUILTIN_DEVICE, StringComparison.OrdinalIgnoreCase)) { m_device_builtin = true; // Device is configured, but value set to the built-in device! @@ -104,10 +115,11 @@ namespace Duplicati.Library.Backend } // Find out what JFS mount point to use on the device. - if (options.ContainsKey(JFS_MOUNT_POINT_OPTION)) + var mountPoint = options.GetValueOrDefault(JFS_MOUNT_POINT_OPTION); + if (!string.IsNullOrWhiteSpace(mountPoint)) { // Custom mount point specified. - m_mountPoint = options[JFS_MOUNT_POINT_OPTION]; + m_mountPoint = mountPoint; // If we are using the built-in device make sure we have picked a mount point that we can use. if (m_device_builtin) @@ -135,10 +147,10 @@ namespace Duplicati.Library.Backend m_mountPoint = JFS_DEFAULT_CUSTOM_MOUNT_POINT; // Set a suitable default mount point for custom (backup) devices. } - string authid = null; - if (options.ContainsKey(AUTHID_OPTION)) - authid = options[AUTHID_OPTION]; - m_oauth = new JottacloudAuthHelper(authid); + var authId = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TOKEN_URL); + + m_oauth = new JottacloudAuthHelper(authId.AuthId!); // Build URL var u = new Utility.Uri(url); @@ -151,51 +163,54 @@ namespace Duplicati.Library.Backend m_url = m_url_device + "/" + m_mountPoint + "/" + m_path; m_url_upload = JFS_ROOT_UPLOAD + "/" + m_oauth.Username + "/" + m_device + "/" + m_mountPoint + "/" + m_path; // Different hostname, else identical to m_url. - m_threads = int.Parse(options.ContainsKey(JFS_THREADS) ? options[JFS_THREADS] : JFS_DEFAULT_THREADS); + var jfsThreads = options.GetValueOrDefault(JFS_THREADS); + if (string.IsNullOrWhiteSpace(jfsThreads)) + jfsThreads = JFS_DEFAULT_THREADS; - if (!options.TryGetValue(JFS_CHUNKSIZE, out var tmp)) - { - tmp = JFS_DEFAULT_CHUNKSIZE; - } + m_threads = int.Parse(jfsThreads); - var chunksize = Utility.Sizeparser.ParseSize(tmp, "mb"); + var jfsChunksize = options.GetValueOrDefault(JFS_CHUNKSIZE); + if (string.IsNullOrWhiteSpace(jfsChunksize)) + jfsChunksize = JFS_DEFAULT_CHUNKSIZE; + + var chunksize = Sizeparser.ParseSize(jfsChunksize, "mb"); // Chunk size is bound by BinaryReader.ReadBytes(length) where length is an int. if (chunksize > int.MaxValue || chunksize < 1024) - { - throw new ArgumentOutOfRangeException(nameof(chunksize), string.Format("The chunk size cannot be less than {0}, nor larger than {1}", Utility.Utility.FormatSizeString(1024), Utility.Utility.FormatSizeString(int.MaxValue))); - } + throw new ArgumentOutOfRangeException(nameof(chunksize), $"The chunk size cannot be less than {Utility.Utility.FormatSizeString(1024)}, nor larger than {Utility.Utility.FormatSizeString(int.MaxValue)}"); m_chunksize = chunksize; + m_timeouts = TimeoutOptionsHelper.Parse(options); } #region IBackend Members - public string DisplayName - { - get { return Strings.Jottacloud.DisplayName; } - } + public string DisplayName => Strings.Jottacloud.DisplayName; - public string ProtocolKey - { - get { return "jottacloud"; } - } + public string ProtocolKey => "jottacloud"; - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { + // Remove warning until this backend is rewritten with HttpClient + await Task.CompletedTask; + var doc = new System.Xml.XmlDocument(); try { // Send request and load XML response. - var req = CreateRequest(System.Net.WebRequestMethods.Http.Get, "", "", false); - var areq = new Utility.AsyncHttpRequest(req); - using (var rs = areq.GetResponseStream()) - doc.Load(rs); + var req = CreateRequest(WebRequestMethods.Http.Get, "", "", false); + var areq = new AsyncHttpRequest(req); + await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => + { + using (var rs = areq.GetResponseStream()) + doc.Load(rs); + }).ConfigureAwait(false); } - catch (System.Net.WebException wex) + catch (WebException wex) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) + if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) throw new FolderMissingException(wex); throw; } @@ -204,50 +219,61 @@ namespace Duplicati.Library.Backend // We must check for "deleted" attribute, because files/folders which has it is deleted (attribute contains the timestamp of deletion) // so we treat them as non-existent here. var xRoot = doc.DocumentElement; - if (xRoot.Attributes["deleted"] != null) - { + if (xRoot == null) throw new FolderMissingException(); - } - foreach (System.Xml.XmlNode xFolder in xRoot.SelectNodes("folders/folder[not(@deleted)]")) - { - // Subfolders are only listed with name. We can get a timestamp by sending a request for each folder, but that is probably not necessary? - FileEntry fe = new FileEntry(xFolder.Attributes["name"].Value); - fe.IsFolder = true; - yield return fe; - } - foreach (System.Xml.XmlNode xFile in xRoot.SelectNodes("files/file[not(@deleted)]")) - { - var fe = ToFileEntry(xFile); - if (fe != null) + if (xRoot.Attributes["deleted"] != null) + throw new FolderMissingException(); + + var folders = xRoot.SelectNodes("folders/folder[not(@deleted)]"); + if (folders != null) + foreach (System.Xml.XmlNode xFolder in folders) { - yield return fe; + // Subfolders are only listed with name. We can get a timestamp by sending a request for each folder, but that is probably not necessary? + var name = xFolder.Attributes?["name"]?.Value; + if (string.IsNullOrEmpty(name)) + continue; + + yield return new FileEntry(name) { IsFolder = true }; + } + + var files = xRoot.SelectNodes("files/file[not(@deleted)]"); + if (files != null) + foreach (System.Xml.XmlNode xFile in files) + { + var fe = ToFileEntry(xFile); + if (fe != null) + yield return fe; } - } } - public static IFileEntry ToFileEntry(System.Xml.XmlNode xFile) + public static IFileEntry? ToFileEntry(System.Xml.XmlNode? xFile) { - string name = xFile.Attributes["name"].Value; + if (xFile == null) + return null; + + var name = xFile.Attributes?["name"]?.Value; + if (string.IsNullOrWhiteSpace(name)) + return null; + // Normal files have an "currentRevision", which represent the most recent successfully upload // (could also checked that currentRevision/state is "COMPLETED", but should not be necessary). // There might also be a newer "latestRevision" coming from an incomplete or corrupt upload, // but we ignore that here and use the information about the last valid version. - System.Xml.XmlNode xRevision = xFile.SelectSingleNode("currentRevision"); + var xRevision = xFile.SelectSingleNode("currentRevision"); if (xRevision != null) { - System.Xml.XmlNode xState = xRevision.SelectSingleNode("state"); + var xState = xRevision.SelectSingleNode("state"); if (xState != null && xState.InnerText == "COMPLETED") // Think "currentRevision" always is a complete version, but just to be on the safe side.. { - System.Xml.XmlNode xSize = xRevision.SelectSingleNode("size"); + var xSize = xRevision.SelectSingleNode("size"); long size; if (xSize == null || !long.TryParse(xSize.InnerText, out size)) size = -1; - DateTime lastModified; - System.Xml.XmlNode xModified = xRevision.SelectSingleNode("modified"); // There is created, modified and updated time stamps, but not last accessed. - if (xModified == null || !DateTime.TryParseExact(xModified.InnerText, JFS_DATE_FORMAT, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal, out lastModified)) + + var xModified = xRevision.SelectSingleNode("modified"); // There is created, modified and updated time stamps, but not last accessed. + if (xModified == null || !DateTime.TryParseExact(xModified.InnerText, JFS_DATE_FORMAT, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal, out var lastModified)) lastModified = new DateTime(); - FileEntry fe = new FileEntry(name, size, lastModified, lastModified); - return fe; + return new FileEntry(name, size, lastModified, lastModified); } } return null; @@ -256,22 +282,26 @@ namespace Duplicati.Library.Backend /// /// Retrieves info for a single file (used to determine file size for chunking) /// - /// - /// - public IFileEntry Info(string remotename) + /// The remote file name + /// The cancellation token + /// The file entry + public async Task Info(string remotename, CancellationToken cancelToken) { var doc = new System.Xml.XmlDocument(); try { // Send request and load XML response. - var req = CreateRequest(System.Net.WebRequestMethods.Http.Get, remotename, "", false); + var req = CreateRequest(WebRequestMethods.Http.Get, remotename, "", false); var areq = new Utility.AsyncHttpRequest(req); - using (var rs = areq.GetResponseStream()) - doc.Load(rs); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => + { + using (var rs = areq.GetResponseStream()) + doc.Load(rs); + }).ConfigureAwait(false); } - catch (System.Net.WebException wex) + catch (WebException wex) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) + if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) throw new FileMissingException(wex); throw; } @@ -280,80 +310,74 @@ namespace Duplicati.Library.Backend // We must check for "deleted" attribute, because files/folders which has it is deleted (attribute contains the timestamp of deletion) // so we treat them as non-existent here. var xFile = doc.DocumentElement; - if (xFile.Attributes["deleted"] != null) - { + if (xFile?.Attributes["deleted"] != null) throw new FileMissingException(string.Format("{0}: {1}", LC.L("The requested file does not exist"), remotename)); - } return ToFileEntry(xFile); } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) + using (var fs = File.OpenRead(filename)) await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) + using (var fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public Task DeleteAsync(string remotename, CancellationToken cancelToken) { - System.Net.HttpWebRequest req = CreateRequest(System.Net.WebRequestMethods.Http.Post, remotename, "rm=true", false); // rm=true means permanent delete, dl=true would be move to trash. - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) - { } + var req = CreateRequest(WebRequestMethods.Http.Post, remotename, "rm=true", false); // rm=true means permanent delete, dl=true would be move to trash. + var areq = new Utility.AsyncHttpRequest(req); - return Task.CompletedTask; - } - - public IList SupportedCommands - { - get + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => { - return new List([ - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Jottacloud.AuthidShort, Strings.Jottacloud.AuthidLong(OAuthHelper.OAUTH_LOGIN_URL("jottacloud"))), - new CommandLineArgument(JFS_DEVICE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Jottacloud.DescriptionDeviceShort, Strings.Jottacloud.DescriptionDeviceLong(JFS_MOUNT_POINT_OPTION)), - new CommandLineArgument(JFS_MOUNT_POINT_OPTION, CommandLineArgument.ArgumentType.String, Strings.Jottacloud.DescriptionMountPointShort, Strings.Jottacloud.DescriptionMountPointLong(JFS_DEVICE_OPTION)), - new CommandLineArgument(JFS_THREADS, CommandLineArgument.ArgumentType.Integer, Strings.Jottacloud.ThreadsShort, Strings.Jottacloud.ThreadsLong, JFS_DEFAULT_THREADS), - new CommandLineArgument(JFS_CHUNKSIZE, CommandLineArgument.ArgumentType.Size, Strings.Jottacloud.ChunksizeShort, Strings.Jottacloud.ChunksizeLong, JFS_DEFAULT_CHUNKSIZE), - ]); - } + using (var resp = (HttpWebResponse)areq.GetResponse()) + { } + }); } - public string Description - { - get { return Strings.Jottacloud.Description; } - } + public IList SupportedCommands => [ + .. AuthIdOptionsHelper.GetOptions(TOKEN_URL), + new CommandLineArgument(JFS_DEVICE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Jottacloud.DescriptionDeviceShort, Strings.Jottacloud.DescriptionDeviceLong(JFS_MOUNT_POINT_OPTION)), + new CommandLineArgument(JFS_MOUNT_POINT_OPTION, CommandLineArgument.ArgumentType.String, Strings.Jottacloud.DescriptionMountPointShort, Strings.Jottacloud.DescriptionMountPointLong(JFS_DEVICE_OPTION)), + new CommandLineArgument(JFS_THREADS, CommandLineArgument.ArgumentType.Integer, Strings.Jottacloud.ThreadsShort, Strings.Jottacloud.ThreadsLong, JFS_DEFAULT_THREADS), + new CommandLineArgument(JFS_CHUNKSIZE, CommandLineArgument.ArgumentType.Size, Strings.Jottacloud.ChunksizeShort, Strings.Jottacloud.ChunksizeLong, JFS_DEFAULT_CHUNKSIZE), + .. TimeoutOptionsHelper.GetOptions(), + ]; + + public string Description => Strings.Jottacloud.Description; public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { // When using custom (backup) device we must create the device first (if not already exists). if (!m_device_builtin) { - var req = CreateRequest(System.Net.WebRequestMethods.Http.Post, m_url_device, "type=WORKSTATION"); // Hard-coding device type. Must be one of "WORKSTATION", "LAPTOP", "IMAC", "MACBOOK", "IPAD", "ANDROID", "IPHONE" or "WINDOWS_PHONE". - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) - { } + var req = CreateRequest(WebRequestMethods.Http.Post, m_url_device, "type=WORKSTATION"); // Hard-coding device type. Must be one of "WORKSTATION", "LAPTOP", "IMAC", "MACBOOK", "IPAD", "ANDROID", "IPHONE" or "WINDOWS_PHONE". + var areq = new AsyncHttpRequest(req); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => + { + using (var resp = (HttpWebResponse)areq.GetResponse()) + { } + }).ConfigureAwait(false); } // Create the folder path, and if using custom mount point it will be created as well in the same operation. { - var req = CreateRequest(System.Net.WebRequestMethods.Http.Post, "", "mkDir=true", false); - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) - { } + var req = CreateRequest(WebRequestMethods.Http.Post, "", "mkDir=true", false); + var areq = new AsyncHttpRequest(req); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => + { + using (var resp = (HttpWebResponse)areq.GetResponse()) + { } + }).ConfigureAwait(false); } - return Task.CompletedTask; } #endregion @@ -366,44 +390,49 @@ namespace Duplicati.Library.Backend #endregion - private System.Net.HttpWebRequest CreateRequest(string method, string url, string queryparams) + private HttpWebRequest CreateRequest(string method, string url, string queryparams) { return m_oauth.CreateRequest(url + (string.IsNullOrEmpty(queryparams) || queryparams.Trim().Length == 0 ? "" : "?" + queryparams), method); } - private System.Net.HttpWebRequest CreateRequest(string method, string remotename, string queryparams, bool upload) + private HttpWebRequest CreateRequest(string method, string remotename, string queryparams, bool upload) { var url = (upload ? m_url_upload : m_url) + Library.Utility.Uri.UrlEncode(remotename).Replace("+", "%20"); return CreateRequest(method, url, queryparams); } public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new string[] { - new Uri(JFS_ROOT).Host, - new Uri(JFS_ROOT_UPLOAD).Host + new System.Uri(JFS_ROOT).Host, + new System.Uri(JFS_ROOT_UPLOAD).Host }); - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { if (m_threads > 1) { - await ParallelGetAsync(remotename, stream, cancelToken).ConfigureAwait(false); + using (var timeoutStream = stream.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout, false)) + await ParallelGetAsync(remotename, stream, cancelToken).ConfigureAwait(false); return; } // Downloading from Jottacloud: Will only succeed if the file has a completed revision, // and if there are multiple versions of the file we will only get the latest completed version, // ignoring any incomplete or corrupt versions. - var req = CreateRequest(System.Net.WebRequestMethods.Http.Get, remotename, "mode=bin", false); - var areq = new Utility.AsyncHttpRequest(req); - using (var s = areq.GetResponseStream()) - await Utility.Utility.CopyStreamAsync(s, stream, true, cancelToken).ConfigureAwait(false); + var req = CreateRequest(WebRequestMethods.Http.Get, remotename, "mode=bin", false); + var areq = new AsyncHttpRequest(req); + using (var s = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(t, stream, true, cancelToken).ConfigureAwait(false); } /// /// Fetches the file in chunks (parallelized) /// - public async Task ParallelGetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task ParallelGetAsync(string remotename, Stream stream, CancellationToken cancelToken) { - var size = Info(remotename).Size; + var info = await Info(remotename, cancelToken); + if (info == null) + throw new FileMissingException(remotename); + var size = info.Size; var chunks = new Queue>(); // Tuple => Position (from), Position (to) @@ -426,11 +455,11 @@ namespace Duplicati.Library.Backend var item = chunks.Dequeue(); tasks.Enqueue(Task.Run(() => { - var req = CreateRequest(System.Net.WebRequestMethods.Http.Get, remotename, "mode=bin", false); + var req = CreateRequest(WebRequestMethods.Http.Get, remotename, "mode=bin", false); req.AddRange(item.Item1, item.Item2 - 1); - var areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); using (var s = areq.GetResponseStream()) - using (var reader = new System.IO.BinaryReader(s)) + using (var reader = new BinaryReader(s)) { var length = item.Item2 - item.Item1; return reader.ReadBytes((int)length); @@ -442,7 +471,7 @@ namespace Duplicati.Library.Backend } } - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { // Some challenges with uploading to Jottacloud: // - Jottacloud supports use of a custom header where we can tell the server the MD5 hash of the file @@ -459,32 +488,34 @@ namespace Duplicati.Library.Backend // request the server to remove the file again and throw an exception. But there is a requirement that // we specify the file size in a custom header. And if the stream is not seek-able we are not able // to use stream.Length, so we are back at square one. - Duplicati.Library.Utility.TempFile tmpFile = null; - var baseStream = stream; - while (baseStream is Duplicati.Library.Utility.OverrideableStream) - baseStream = typeof(Duplicati.Library.Utility.OverrideableStream).GetField("m_basestream", System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(baseStream) as System.IO.Stream; + + // TODO: This unwrapping code is shared with CloudFiles + TempFile? tmpFile = null; + Stream? baseStream = stream; + while (baseStream is OverrideableStream) + baseStream = typeof(OverrideableStream).GetField("m_basestream", System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)?.GetValue(baseStream) as System.IO.Stream; if (baseStream == null) throw new Exception(string.Format("Unable to unwrap stream from: {0}", stream.GetType())); string md5Hash; if (baseStream.CanSeek) { var originalPosition = baseStream.Position; - using (var md5 = System.Security.Cryptography.MD5.Create()) - md5Hash = Library.Utility.Utility.ByteArrayAsHexString(md5.ComputeHash(baseStream)); + using (var md5 = MD5.Create()) + md5Hash = Utility.Utility.ByteArrayAsHexString(md5.ComputeHash(baseStream)); baseStream.Position = originalPosition; } else { // No seeking possible, use a temp file - tmpFile = new Duplicati.Library.Utility.TempFile(); - using (var os = System.IO.File.OpenWrite(tmpFile)) + tmpFile = new TempFile(); + using (var os = File.OpenWrite(tmpFile)) using (var hasher = MD5.Create()) - using (var md5 = new Utility.HashCalculatingStream(baseStream, hasher)) + using (var md5 = new HashCalculatingStream(baseStream, hasher)) { await Utility.Utility.CopyStreamAsync(md5, os, true, cancelToken).ConfigureAwait(false); md5Hash = md5.GetFinalHashString(); } - stream = System.IO.File.OpenRead(tmpFile); + stream = File.OpenRead(tmpFile); } try { @@ -495,7 +526,7 @@ namespace Duplicati.Library.Backend // HTTP 404 (Not Found) if file does not exists or it exists with a different hash, in which // case we must send a new request to upload the new content. var fileSize = stream.Length; - var req = CreateRequest(System.Net.WebRequestMethods.Http.Post, remotename, "umode=nomultipart", true); + var req = CreateRequest(WebRequestMethods.Http.Post, remotename, "umode=nomultipart", true); req.Headers.Add("JMd5", md5Hash); // Not required, but it will make the server verify the content and mark the file as corrupt if there is a mismatch. req.Headers.Add("JSize", fileSize.ToString()); // Required, and used to mark file as incomplete if we upload something be the total size of the original file! // File time stamp headers: Since we are working with a stream here we do not know the local file's timestamps, @@ -506,14 +537,15 @@ namespace Duplicati.Library.Backend req.ContentLength = fileSize; // Write post data request - var areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); using (var rs = areq.GetRequestStream()) + using (var ts = rs.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) await Utility.Utility.CopyStreamAsync(stream, rs, true, cancelToken).ConfigureAwait(false); // Send request, and check response - using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = (HttpWebResponse)areq.GetResponse()) { - if (resp.StatusCode != System.Net.HttpStatusCode.Created) - throw new System.Net.WebException(Strings.Jottacloud.FileUploadError, null, System.Net.WebExceptionStatus.ProtocolError, resp); + if (resp.StatusCode != HttpStatusCode.Created) + throw new WebException(Strings.Jottacloud.FileUploadError, null, WebExceptionStatus.ProtocolError, resp); // Request seems to be successful, but we must verify the response XML content to be sure that the file // was correctly uploaded: The server will verify the JSize header and mark the file as incomplete if @@ -531,7 +563,7 @@ namespace Duplicati.Library.Backend try { doc.Load(rs); } catch (System.Xml.XmlException) { - throw new System.Net.WebException(Strings.Jottacloud.FileUploadError, System.Net.WebExceptionStatus.ProtocolError); + throw new WebException(Strings.Jottacloud.FileUploadError, WebExceptionStatus.ProtocolError); } bool uploadCompletedSuccessfully = false; var xFile = doc["file"]; @@ -546,7 +578,7 @@ namespace Duplicati.Library.Backend } } if (!uploadCompletedSuccessfully) // Report error (and we just let the incomplete/corrupt file revision stay on the server..) - throw new System.Net.WebException(Strings.Jottacloud.FileUploadError, System.Net.WebExceptionStatus.ProtocolError); + throw new WebException(Strings.Jottacloud.FileUploadError, WebExceptionStatus.ProtocolError); } } } diff --git a/Duplicati/Library/Backend/Jottacloud/JottacloudAuthHelper.cs b/Duplicati/Library/Backend/Jottacloud/JottacloudAuthHelper.cs index 01d8be506..1f24f6450 100644 --- a/Duplicati/Library/Backend/Jottacloud/JottacloudAuthHelper.cs +++ b/Duplicati/Library/Backend/Jottacloud/JottacloudAuthHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -52,23 +52,23 @@ namespace Duplicati.Library.Backend private class UserInfo { [JsonProperty("sub")] - public string Subject { get; set; } + public string? Subject { get; set; } [JsonProperty("email_verified")] public bool EmailVerified { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("realm")] - public string Realm { get; set; } + public string? Realm { get; set; } [JsonProperty("preferred_username")] - public string PreferredUsername { get; set; } // The numeric internal username, same as Username + public string? PreferredUsername { get; set; } // The numeric internal username, same as Username [JsonProperty("given_name")] - public string GivenName { get; set; } + public string? GivenName { get; set; } [JsonProperty("family_name")] - public string FamilyName { get; set; } + public string? FamilyName { get; set; } [JsonProperty("email")] - public string Email { get; set; } + public string? Email { get; set; } [JsonProperty("username")] - public string Username { get; set; } // The numeric internal username, same as PreferredUsername + public string? Username { get; set; } // The numeric internal username, same as PreferredUsername } } diff --git a/Duplicati/Library/Backend/Jottacloud/Strings.cs b/Duplicati/Library/Backend/Jottacloud/Strings.cs index de4dc957d..163519c79 100644 --- a/Duplicati/Library/Backend/Jottacloud/Strings.cs +++ b/Duplicati/Library/Backend/Jottacloud/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { +namespace Duplicati.Library.Backend.Strings +{ internal static class Jottacloud { public static string Description { get { return LC.L(@"This backend can read and write data to Jottacloud using its REST protocol. Allowed format is ""jottacloud://folder/subfolder""."); } } public static string DisplayName { get { return LC.L(@"Jottacloud"); } } - public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } - public static string AuthidShort { get { return LC.L(@"The authorization code"); } } public static string NoUsernameError { get { return LC.L(@"No username found"); } } public static string NoPathError { get { return LC.L(@"No path given. Files cannot be uploaded to the root folder"); } } public static string IllegalMountPoint { get { return LC.L(@"Illegal mount point given."); } } diff --git a/Duplicati/Library/Backend/Mega/Duplicati.Library.Backend.Mega.csproj b/Duplicati/Library/Backend/Mega/Duplicati.Library.Backend.Mega.csproj index b9cc93edd..c74f20ae6 100644 --- a/Duplicati/Library/Backend/Mega/Duplicati.Library.Backend.Mega.csproj +++ b/Duplicati/Library/Backend/Mega/Duplicati.Library.Backend.Mega.csproj @@ -3,7 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Mega/MegaBackend.cs b/Duplicati/Library/Backend/Mega/MegaBackend.cs index 7336071bc..56cb2ef8e 100644 --- a/Duplicati/Library/Backend/Mega/MegaBackend.cs +++ b/Duplicati/Library/Backend/Mega/MegaBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,13 +22,10 @@ using CG.Web.MegaApiClient; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using OtpNet; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; +using OtpNet; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend.Mega { @@ -36,68 +33,67 @@ namespace Duplicati.Library.Backend.Mega // This class is instantiated dynamically in the BackendLoader. public class MegaBackend : IBackend, IStreamingBackend { - private readonly string m_username = null; - private readonly string m_password = null; - private readonly string m_twoFactorKey = null; - private Dictionary> m_filecache; - private INode m_currentFolder = null; - private readonly string m_prefix = null; + private readonly string m_username; + private readonly string m_password; + private readonly string? m_twoFactorKey; + private Dictionary>? m_filecache; + private INode? m_currentFolder = null; + private readonly string m_prefix; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - private MegaApiClient m_client; + private MegaApiClient? m_client; public MegaBackend() { + m_username = null!; + m_password = null!; + m_twoFactorKey = null; + m_prefix = null!; + m_timeouts = null!; } - private MegaApiClient Client + private async Task GetClient(CancellationToken cancelToken) { - get - { - if (m_client == null) + if (m_client == null) + m_client = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => { var cl = new MegaApiClient(); if (m_twoFactorKey == null) - cl.Login(m_username, m_password); + await cl.LoginAsync(m_username, m_password).ConfigureAwait(false); else { var totp = new Totp(Base32Encoding.ToBytes(m_twoFactorKey)).ComputeTotp(); - cl.Login(m_username, m_password, totp); + await cl.LoginAsync(m_username, m_password, totp).ConfigureAwait(false); } - m_client = cl; - } + return cl; + }).ConfigureAwait(false); - return m_client; - } + return m_client; } - public MegaBackend(string url, Dictionary options) + public MegaBackend(string url, Dictionary options) { var uri = new Utility.Uri(url); - if (options.ContainsKey("auth-username")) - m_username = options["auth-username"]; - if (options.ContainsKey("auth-password")) - m_password = options["auth-password"]; + var auth = AuthOptionsHelper.Parse(options, uri); if (options.ContainsKey("auth-two-factor-key")) m_twoFactorKey = options["auth-two-factor-key"]; - if (!string.IsNullOrEmpty(uri.Username)) - m_username = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - m_password = uri.Password; - - if (string.IsNullOrEmpty(m_username)) + if (string.IsNullOrWhiteSpace(auth.Username)) throw new UserInformationException(Strings.MegaBackend.NoUsernameError, "MegaNoUsername"); - if (string.IsNullOrEmpty(m_password)) + if (string.IsNullOrWhiteSpace(auth.Password)) throw new UserInformationException(Strings.MegaBackend.NoPasswordError, "MegaNoPassword"); + (m_username, m_password) = auth.GetCredentials(); m_prefix = uri.HostAndPath ?? ""; + m_timeouts = TimeoutOptionsHelper.Parse(options); } - private async Task FetchCurrentFolderAsync(bool autocreate, CancellationToken cancelToken) + private async Task FetchCurrentFolderAsync(bool autocreate, CancellationToken cancelToken) { + var client = await GetClient(cancelToken).ConfigureAwait(false); var parts = m_prefix.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries); - var nodes = Client.GetNodes(); + var nodes = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => client.GetNodes()).ConfigureAwait(false); INode parent = nodes.First(x => x.Type == NodeType.Root); foreach (var n in parts) @@ -108,7 +104,7 @@ namespace Duplicati.Library.Backend.Mega if (!autocreate) throw new FolderMissingException(); - item = await Client.CreateFolderAsync(n, parent).ConfigureAwait(false); + item = await client.CreateFolderAsync(n, parent).ConfigureAwait(false); } parent = item; @@ -117,14 +113,17 @@ namespace Duplicati.Library.Backend.Mega m_currentFolder = parent; await ResetFileCacheAsync(nodes, cancelToken).ConfigureAwait(false); + + return m_currentFolder; } private async Task GetCurrentFolderAsync(CancellationToken cancelToken) { - if (m_currentFolder == null) - await FetchCurrentFolderAsync(false, cancelToken).ConfigureAwait(false); + var folder = m_currentFolder; + if (folder == null) + folder = await FetchCurrentFolderAsync(false, cancelToken).ConfigureAwait(false); - return m_currentFolder; + return folder; } private async Task GetFileNodeAsync(string name, CancellationToken cancelToken) @@ -140,17 +139,23 @@ namespace Duplicati.Library.Backend.Mega throw new FileMissingException(); } - private async Task ResetFileCacheAsync(IEnumerable list, CancellationToken cancelToken) + private async Task>> ResetFileCacheAsync(IEnumerable? list, CancellationToken cancelToken) { if (m_currentFolder == null) { await FetchCurrentFolderAsync(false, cancelToken).ConfigureAwait(false); + // We build it as part of the fetch step + return m_filecache!; } else { + var client = await GetClient(cancelToken).ConfigureAwait(false); + var currentFolder = await GetCurrentFolderAsync(cancelToken).ConfigureAwait(false); - m_filecache = - (list ?? Client.GetNodes()).Where(x => x.Type == NodeType.File && x.ParentId == currentFolder.Id) + if (list == null) + list = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => client.GetNodes()).ConfigureAwait(false); + return m_filecache = + list.Where(x => x.Type == NodeType.File && x.ParentId == currentFolder.Id) .GroupBy(x => x.Name, x => x, (k, g) => new KeyValuePair>(k, g.ToList())) .ToDictionary(x => x.Key, x => x.Value); } @@ -158,19 +163,22 @@ namespace Duplicati.Library.Backend.Mega #region IStreamingBackend implementation - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { - if (m_filecache == null) - await ResetFileCacheAsync(null, cancelToken).ConfigureAwait(false); + var filecache = m_filecache; + if (filecache == null) + filecache = await ResetFileCacheAsync(null, cancelToken).ConfigureAwait(false); + var client = await GetClient(cancelToken).ConfigureAwait(false); var currentFolder = await GetCurrentFolderAsync(cancelToken).ConfigureAwait(false); - var el = await Client.UploadAsync(stream, remotename, currentFolder, new Progress(), null, cancelToken).ConfigureAwait(false); - if (m_filecache.ContainsKey(remotename)) + using var ts = stream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); + var el = await client.UploadAsync(ts, remotename, currentFolder, new Progress(), null, cancelToken).ConfigureAwait(false); + if (filecache.ContainsKey(remotename)) await DeleteAsync(remotename, cancelToken).ConfigureAwait(false); - m_filecache[remotename] = [el]; + filecache[remotename] = [el]; } catch { @@ -181,24 +189,29 @@ namespace Duplicati.Library.Backend.Mega public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) { + var client = await GetClient(cancelToken).ConfigureAwait(false); var node = await GetFileNodeAsync(remotename, cancelToken).ConfigureAwait(false); - using (var s = Client.Download(node)) - await Library.Utility.Utility.CopyStreamAsync(s, stream, cancelToken).ConfigureAwait(false); + using (var s = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => client.Download(node)).ConfigureAwait(false)) + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(t, stream, cancelToken).ConfigureAwait(false); } #endregion #region IBackend implementation - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - if (m_filecache == null) - ResetFileCacheAsync(null, CancellationToken.None).Await(); + var filecache = m_filecache; + if (filecache == null) + filecache = await ResetFileCacheAsync(null, cancelToken).ConfigureAwait(false); - return - from n in m_filecache.Values - let item = n.OrderByDescending(x => x.ModificationDate).First() - select new FileEntry(item.Name, item.Size, item.ModificationDate ?? new DateTime(0), item.ModificationDate ?? new DateTime(0)); + foreach (var n in filecache.Values) + { + var item = n.OrderByDescending(x => x.ModificationDate).First(); + yield return new FileEntry(item.Name, item.Size, item.ModificationDate ?? new DateTime(0), item.ModificationDate ?? new DateTime(0)); + } } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) @@ -217,16 +230,18 @@ namespace Duplicati.Library.Backend.Mega { try { - if (m_filecache == null || !m_filecache.ContainsKey(remotename)) - await ResetFileCacheAsync(null, cancelToken).ConfigureAwait(false); + var filecache = m_filecache; + if (filecache == null || !filecache.ContainsKey(remotename)) + filecache = await ResetFileCacheAsync(null, cancelToken).ConfigureAwait(false); - if (!m_filecache.ContainsKey(remotename)) + if (!filecache.ContainsKey(remotename)) throw new FileMissingException(); - foreach (var n in m_filecache[remotename]) - await Client.DeleteAsync(n, false).ConfigureAwait(false); + var client = await GetClient(cancelToken).ConfigureAwait(false); + foreach (var n in filecache[remotename]) + await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => client.DeleteAsync(n, false)).ConfigureAwait(false); - m_filecache.Remove(remotename); + filecache.Remove(remotename); } catch { @@ -236,51 +251,24 @@ namespace Duplicati.Library.Backend.Mega } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public Task CreateFolderAsync(CancellationToken cancelToken) { return FetchCurrentFolderAsync(true, cancelToken); } - public string DisplayName - { - get - { - return Strings.MegaBackend.DisplayName; - } - } + public string DisplayName => Strings.MegaBackend.DisplayName; - public string ProtocolKey - { - get - { - return "mega"; - } - } + public string ProtocolKey => "mega"; - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.MegaBackend.AuthPasswordDescriptionShort, Strings.MegaBackend.AuthPasswordDescriptionLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.MegaBackend.AuthUsernameDescriptionShort, Strings.MegaBackend.AuthUsernameDescriptionLong), - new CommandLineArgument("auth-two-factor-key", CommandLineArgument.ArgumentType.Password, Strings.MegaBackend.AuthTwoFactorKeyDescriptionShort, Strings.MegaBackend.AuthTwoFactorKeyDescriptionLong), - }); - } - } + public IList SupportedCommands => [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument("auth-two-factor-key", CommandLineArgument.ArgumentType.Password, Strings.MegaBackend.AuthTwoFactorKeyDescriptionShort, Strings.MegaBackend.AuthTwoFactorKeyDescriptionLong), + .. TimeoutOptionsHelper.GetOptions() + ]; - public string Description - { - get - { - return Strings.MegaBackend.Description; - } - } + public string Description => Strings.MegaBackend.Description; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(Array.Empty()); diff --git a/Duplicati/Library/Backend/Mega/Strings.cs b/Duplicati/Library/Backend/Mega/Strings.cs index 7880a01ac..5baf89d10 100644 --- a/Duplicati/Library/Backend/Mega/Strings.cs +++ b/Duplicati/Library/Backend/Mega/Strings.cs @@ -1,6 +1,5 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// Copyright (C) 2024, Suguru Hirahara // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), diff --git a/Duplicati/Library/Backend/OAuthHelper/Duplicati.Library.OAuthHelper.csproj b/Duplicati/Library/Backend/OAuthHelper/Duplicati.Library.OAuthHelper.csproj index a60255472..358479946 100644 --- a/Duplicati/Library/Backend/OAuthHelper/Duplicati.Library.OAuthHelper.csproj +++ b/Duplicati/Library/Backend/OAuthHelper/Duplicati.Library.OAuthHelper.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs index 66e8b6558..5551a903f 100644 --- a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs +++ b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelper.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Utility; diff --git a/Duplicati/Library/Backend/OAuthHelper/JSONWebHelperHttpClient.cs b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelperHttpClient.cs new file mode 100644 index 000000000..325bcbcb3 --- /dev/null +++ b/Duplicati/Library/Backend/OAuthHelper/JSONWebHelperHttpClient.cs @@ -0,0 +1,301 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using HttpMethod = System.Net.Http.HttpMethod; + +namespace Duplicati.Library; + +/// +/// Minimalist version of the JSONWebHelper to be used with HttpClient HttpRequestMessage/HttpResponseMessage types +/// +/// HttpClient reference +public class JsonWebHelperHttpClient(HttpClient httpClient) +{ + /// + /// HttpClient reference for inheritors + /// + protected readonly HttpClient _httpClient = httpClient; + + /// + /// Useragent string building method + /// + protected string UserAgent => $"Duplicati v{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}"; + + /// + /// Centralized method to prepare a request with the given URL and method setting useragent + /// + /// Url + /// Method + public virtual Task CreateRequestAsync(string url, HttpMethod method, CancellationToken cancelToken) + { + var request = new HttpRequestMessage(method, url); + request.Headers.Add("User-Agent", UserAgent); + + return Task.FromResult(request); + } + + /// + /// Performs a multipart post and parses the response as JSON + /// + /// The parsed JSON item. + /// The url to post to. + /// Token to cancel the operation. + /// The multipart items. + /// The return type parameter. + public virtual async Task PostMultipartAndGetJsonDataAsync(string url, CancellationToken cancellationToken, MultipartContent parts) + { + using var response = await PostMultipartAsync(url, cancellationToken, parts).ConfigureAwait(false); + return await ReadJsonResponseAsync(response, cancellationToken).ConfigureAwait(false); + } + + + /// + /// Performs a multipart post + /// + /// The response. + /// The url to post to. + /// Token to cancel the operation. + /// The multipart items. + protected virtual async Task PostMultipartAsync(string url, CancellationToken cancellationToken, MultipartContent parts) + { + using var req = await CreateRequestAsync(url, HttpMethod.Post, cancellationToken).ConfigureAwait(false); + req.Content = parts; + return await _httpClient.SendAsync(req, cancellationToken).ConfigureAwait(false); + } + + /// + /// Execute Get request and return response and deserializes JSON response into the given type + /// + /// Url + /// Cancellation Token + /// Setup Actions for customizing the request + /// Destination Type + protected virtual async Task GetJsonDataAsync(string url, CancellationToken cancellationToken, Action setup = null) + { + using var req = await CreateRequestAsync(url, HttpMethod.Get, cancellationToken).ConfigureAwait(false); + + if (setup != null) + setup(req); + + return await ReadJsonResponseAsync(req, cancellationToken).ConfigureAwait(false); + } + + /// + /// Execute Post and return response and deserializes JSON response into the given type + /// + /// Url + /// Cancellation Token + /// The item to be serialized into a json and added to the body + /// Destination Type + public virtual async Task PostAndGetJsonDataAsync(string url, object item, CancellationToken cancellationToken) + { + var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(item)); + + return await GetJsonDataAsync( + url, + cancellationToken, + request => + { + request.Method = HttpMethod.Post; + request.Content = new ByteArrayContent(data); + request.Content.Headers.Add("Content-Length", data.Length.ToString()); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + ).ConfigureAwait(false); + } + + /// + /// Executes a web request and json-deserializes the results as the specified type + /// + /// The deserialized JSON data. + /// The remote URL + /// Token to cancel the operation. + /// The type of data to return. + public virtual async Task GetJsonDataAsync(string url, CancellationToken cancellationToken) + { + + return await GetJsonDataAsync( + url, + cancellationToken, + request => + { + request.Method = HttpMethod.Get; + } + ).ConfigureAwait(false); + } + + /// + /// Reads the JSON response from the server and deserializes it into the given type + /// + /// Request object + /// Cancellation Token + /// Destination Type + protected virtual async Task ReadJsonResponseAsync(HttpRequestMessage req, CancellationToken cancellationToken) + { + using var resp = await GetResponseAsync(req, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + return await ReadJsonResponseAsync(resp, cancellationToken).ConfigureAwait(false); + } + + /// + /// Read the JSON response from the server and deserialize it into the given type asynchronously + /// + /// Response object + /// + /// Type to cast to + /// + /// Exception when failing to deserialize the JSON to the Type + protected virtual async Task ReadJsonResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken) + { + await using var rs = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using var ps = new StreamPeekReader(rs); + try + { + using var tr = new StreamReader(ps); + await using var jr = new JsonTextReader(tr); + return new JsonSerializer().Deserialize(jr); + } + catch (Exception ex) + { + // If we get invalid JSON, report the peek value + if (ex is JsonReaderException) + throw new IOException($"Invalid JSON data: \"{ps.PeekData()}\"", ex); + // Otherwise, we have no additional help to offer + throw; + } + } + + /// + /// Use this method to register an exception handler, + /// which can throw another, more meaningful exception + /// + public virtual Task AttemptParseAndThrowExceptionAsync(Exception ex, HttpResponseMessage responseContext, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + /// + /// Execute request and return response + /// + /// Request object + /// Cancellation Token + /// + public async Task GetResponseAsync(HttpRequestMessage req, HttpCompletionOption httpCompletionOption, CancellationToken cancellationToken) + { + HttpResponseMessage response = null; + try + { + response = await _httpClient.SendAsync(req, httpCompletionOption, cancellationToken).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + return response; + } + catch (Exception ex) + { + await AttemptParseAndThrowExceptionAsync(ex, response, cancellationToken).ConfigureAwait(false); + throw; + } + } + + /// + /// A utility class that shadows the real stream but provides access + /// to the first 2kb of the stream to use in error reporting + /// + private class StreamPeekReader(Stream source) : Stream + { + private readonly byte[] m_peekbuffer = new byte[1024 * 2]; + private int m_peekbytes = 0; + + public string PeekData() + { + if (m_peekbuffer.Length == 0) + return string.Empty; + + return Encoding.UTF8.GetString(m_peekbuffer, 0, m_peekbytes); + } + + public override bool CanRead => source.CanRead; + public override bool CanSeek => source.CanSeek; + public override bool CanWrite => source.CanWrite; + public override long Length => source.Length; + public override long Position { get => source.Position; set => source.Position = value; } + public override void Flush() => source.Flush(); + public override long Seek(long offset, SeekOrigin origin) => source.Seek(offset, origin); + public override void SetLength(long value) => source.SetLength(value); + public override void Write(byte[] buffer, int offset, int count) => source.Write(buffer, offset, count); + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => source.BeginRead(buffer, offset, count, callback, state); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) => source.BeginWrite(buffer, offset, count, callback, state); + public override bool CanTimeout => source.CanTimeout; + public override void Close() => source.Close(); + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => source.CopyToAsync(destination, bufferSize, cancellationToken); + protected override void Dispose(bool disposing) => base.Dispose(disposing); + public override int EndRead(IAsyncResult asyncResult) => source.EndRead(asyncResult); + public override void EndWrite(IAsyncResult asyncResult) => source.EndWrite(asyncResult); + public override Task FlushAsync(CancellationToken cancellationToken) => source.FlushAsync(cancellationToken); + public override int ReadTimeout { get => source.ReadTimeout; set => source.ReadTimeout = value; } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => source.WriteAsync(buffer, offset, count, cancellationToken); + public override int WriteTimeout { get => source.WriteTimeout; set => source.WriteTimeout = value; } + + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var br = 0; + if (m_peekbytes < m_peekbuffer.Length - 1) + { + var maxb = Math.Min(count, m_peekbuffer.Length - m_peekbytes); + br = await source.ReadAsync(m_peekbuffer, m_peekbytes, maxb, cancellationToken); + Array.Copy(m_peekbuffer, m_peekbytes, buffer, offset, br); + m_peekbytes += br; + offset += br; + count -= br; + if (count == 0 || br < maxb) + return br; + } + + return await source.ReadAsync(buffer, offset, count, cancellationToken) + br; + } + + public override int Read(byte[] buffer, int offset, int count) + { + var br = 0; + if (m_peekbytes < m_peekbuffer.Length - 1) + { + var maxb = Math.Min(count, m_peekbuffer.Length - m_peekbytes); + br = source.Read(m_peekbuffer, m_peekbytes, maxb); + Array.Copy(m_peekbuffer, m_peekbytes, buffer, offset, br); + m_peekbytes += br; + offset += br; + count -= br; + + if (count == 0 || br < maxb) + return br; + } + + return source.Read(buffer, offset, count) + br; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/OAuthHelper/MultipartItem.cs b/Duplicati/Library/Backend/OAuthHelper/MultipartItem.cs index d826a8a57..02c410a72 100644 --- a/Duplicati/Library/Backend/OAuthHelper/MultipartItem.cs +++ b/Duplicati/Library/Backend/OAuthHelper/MultipartItem.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Backend/OAuthHelper/OAuthHelper.cs b/Duplicati/Library/Backend/OAuthHelper/OAuthHelper.cs index 9e604e5ce..f25d400de 100644 --- a/Duplicati/Library/Backend/OAuthHelper/OAuthHelper.cs +++ b/Duplicati/Library/Backend/OAuthHelper/OAuthHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,39 +22,37 @@ using System; using System.Net; using Duplicati.Library.Utility; -using System.Collections.Generic; -using System.Web; namespace Duplicati.Library { - /// - /// Class for providing call-context access to http settings - /// - public static class OAuthContextSettings - { - /// - /// The struct wrapping the OAuth settings - /// - private struct OAuthSettings - { - /// - /// The server url - /// - public string ServerURL; - } + /// + /// Class for providing call-context access to http settings + /// + public static class OAuthContextSettings + { + /// + /// The struct wrapping the OAuth settings + /// + private struct OAuthSettings + { + /// + /// The server url + /// + public string ServerURL; + } - /// - /// Starts the session. - /// - /// The session. - /// The url to use for the server. - public static IDisposable StartSession(string serverurl) - { + /// + /// Starts the session. + /// + /// The session. + /// The url to use for the server. + public static IDisposable StartSession(string serverurl) + { return CallContextSettings.StartContext(new OAuthSettings { ServerURL = serverurl }); - } + } - /// - /// Gets the server URL to use for OAuth. - /// + /// + /// Gets the server URL to use for OAuth. + /// public static string ServerURL { get @@ -63,7 +61,7 @@ namespace Duplicati.Library return string.IsNullOrWhiteSpace(r) ? OAuthHelper.DUPLICATI_OAUTH_SERVICE : r; } } - } + } public class OAuthHelper : JSONWebHelper { @@ -73,11 +71,11 @@ namespace Duplicati.Library public const string DUPLICATI_OAUTH_SERVICE = "https://duplicati-oauth-handler.appspot.com/refresh"; - public static string OAUTH_LOGIN_URL(string modulename) + public static string OAUTH_LOGIN_URL(string modulename) { var u = new Library.Utility.Uri(OAuthContextSettings.ServerURL); var addr = u.SetPath("").SetQuery((u.Query ?? "") + (string.IsNullOrWhiteSpace(u.Query) ? "" : "&") + "type={0}"); - return string.Format(addr.ToString(), modulename); + return string.Format(addr.ToString(), modulename); } /// @@ -106,7 +104,7 @@ namespace Duplicati.Library OAuthLoginUrl = OAUTH_LOGIN_URL(servicename); if (string.IsNullOrEmpty(authid)) - throw new Duplicati.Library.Interface.UserInformationException(Strings.OAuthHelper.MissingAuthID(OAuthLoginUrl), "MissingAuthID"); + throw new Interface.UserInformationException(Strings.OAuthHelper.MissingAuthID(OAuthLoginUrl), "MissingAuthID"); } public T GetTokenResponse() @@ -129,7 +127,7 @@ namespace Duplicati.Library if (!noAuthorization && AutoAuthHeader && !string.Equals(OAuthContextSettings.ServerURL, url)) r.Headers["Authorization"] = string.Format("Bearer {0}", AccessToken); return r; - } + } public string AccessToken { @@ -142,7 +140,7 @@ namespace Duplicati.Library { var retries = 0; - while(true) + while (true) { try { diff --git a/Duplicati/Library/Backend/OAuthHelper/OAuthHelperHttpClient.cs b/Duplicati/Library/Backend/OAuthHelper/OAuthHelperHttpClient.cs new file mode 100644 index 000000000..622c819ed --- /dev/null +++ b/Duplicati/Library/Backend/OAuthHelper/OAuthHelperHttpClient.cs @@ -0,0 +1,196 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Utility; + +namespace Duplicati.Library; + +public class OAuthHelperHttpClient : JsonWebHelperHttpClient +{ + private string _Token; + private string _Authid; + private DateTime _mTokenExpires = DateTime.UtcNow; + private string OAuthLoginUrl { get; } + + /// + /// Timeout for authentication requests + /// + private static readonly TimeSpan AUTHENTICATION_TIMEOUT = TimeSpan.FromSeconds(25); + + /// + /// Maximum number of retries for authorization + /// + private const int MAX_AUTHORIZATION_RETRIES = 5; + + /// + /// URL to use for OAuth login + /// + /// The name of the module + /// The URL to use for OAuth login + private static string OAUTH_LOGIN_URL(string modulename) + { + var u = new Utility.Uri(OAuthContextSettings.ServerURL); + var addr = u.SetPath("") + .SetQuery((u.Query ?? "") + (string.IsNullOrWhiteSpace(u.Query) ? "" : "&") + "type={0}"); + return string.Format(addr.ToString(), modulename); + } + + /// + /// Set to true to automatically add the Authorization header to requests + /// + public bool AutoAuthHeader { get; set; } + + /// + /// Set to true if the provider does not use refresh tokens, but only access tokens + /// + public bool AccessTokenOnly { get; set; } + + /// + /// If true (the default), when a v1 authid is being used it will be swapped + /// with a v2 authid, when the OAuth service returns one (which it dypically + /// does after a provider token refresh has been performed). Some providers + /// are not compatible with v2 authid, tyically because they generate a new + /// refresh token with every access token refresh and invalidates the old. + /// If the oauth service still returns a v2 authid for such a provider, + /// set this property to false to make Duplicati ignore it. + /// + public bool AutoV2 { get; set; } = true; + + public OAuthHelperHttpClient(string authid, string servicename, HttpClient httpClient = null, string useragent = null) + : base(httpClient ?? HttpClientHelper.CreateClient()) + { + _Authid = authid; + OAuthLoginUrl = OAUTH_LOGIN_URL(servicename); + + if (string.IsNullOrEmpty(authid)) + throw new Interface.UserInformationException( + Strings.OAuthHelper.MissingAuthID(OAuthLoginUrl), "MissingAuthID"); + } + + private async Task CreateRequestAsync(string url, HttpMethod method, bool noAuthorization, CancellationToken cancellationToken) + { + var request = new HttpRequestMessage(method, url); + request.Headers.Add("User-Agent", UserAgent); + if (!noAuthorization && AutoAuthHeader && !string.Equals(OAuthContextSettings.ServerURL, url)) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", + await GetAccessTokenAsync(cancellationToken).ConfigureAwait(false)); + return request; + } + + public override Task CreateRequestAsync(string url, HttpMethod method, CancellationToken cancellationToken) + => CreateRequestAsync(url, method, false, cancellationToken); + + private async Task GetAccessTokenAsync(CancellationToken cancellationToken) + { + if (AccessTokenOnly) + return _Authid; + + if (_Token == null || _mTokenExpires < DateTime.UtcNow) + { + var retries = 0; + + while (true) + { + HttpResponseMessage response = null; + try + { + using var request = await CreateRequestAsync(OAuthContextSettings.ServerURL, HttpMethod.Get, false, cancellationToken).ConfigureAwait(false); + + return await Utility.Utility.WithTimeout(AUTHENTICATION_TIMEOUT, cancellationToken, async ct => + { + request.Headers.TryAddWithoutValidation("X-AuthID", _Authid); + response = await _httpClient.SendAsync(request, ct).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + var res = await ReadJsonResponseAsync(response, ct).ConfigureAwait(false); + + _mTokenExpires = DateTime.UtcNow.AddSeconds(res.expires - 30); + if (AutoV2 && !string.IsNullOrWhiteSpace(res.v2_authid)) + _Authid = res.v2_authid; + return _Token = res.access_token; + }).ConfigureAwait(false); + } + catch (Exception ex) + { + var clientError = false; + + try + { + // Only retry once on client errors + if (ex is HttpRequestException { StatusCode: not null } exception) + { + var sc = (int)exception.StatusCode; + clientError = sc is >= 400 and <= 499; + } + } + catch + { + // ignored + } + + if (response != null && response.Headers.Contains("X-Reason")) + { + var msg = response.Headers.GetValues("X-Reason").FirstOrDefault(); + + if (string.IsNullOrWhiteSpace(msg)) + msg = response.StatusCode.ToString(); + + if (response.StatusCode == HttpStatusCode.ServiceUnavailable) + { + string errorKey = msg == response.StatusCode.ToString() + ? "OAuthOverQuotaError" + : "OAuthLoginError"; + + string errorMessage = errorKey == "OAuthOverQuotaError" + ? Strings.OAuthHelper.OverQuotaError + : Strings.OAuthHelper.AuthorizationFailure(msg, OAuthLoginUrl); + + throw new Interface.UserInformationException(errorMessage, errorKey, + errorKey == "OAuthLoginError" ? ex : null); + } + } + + if (retries >= (clientError ? 1 : MAX_AUTHORIZATION_RETRIES)) + { + await AttemptParseAndThrowExceptionAsync(ex, response, cancellationToken).ConfigureAwait(false); + throw; + } + + Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, retries))); + retries++; + } + finally + { + response?.Dispose(); + } + + } + } + + return _Token; + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/OAuthHelper/OAuthHttpClient.cs b/Duplicati/Library/Backend/OAuthHelper/OAuthHttpClient.cs index f3cd300e3..5a5e0d2d5 100644 --- a/Duplicati/Library/Backend/OAuthHelper/OAuthHttpClient.cs +++ b/Duplicati/Library/Backend/OAuthHelper/OAuthHttpClient.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Backend/OAuthHelper/OAuthHttpMessageHandler.cs b/Duplicati/Library/Backend/OAuthHelper/OAuthHttpMessageHandler.cs index 5ce4632ed..90b0445cc 100644 --- a/Duplicati/Library/Backend/OAuthHelper/OAuthHttpMessageHandler.cs +++ b/Duplicati/Library/Backend/OAuthHelper/OAuthHttpMessageHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Backend/OAuthHelper/OAuthServiceResponse.cs b/Duplicati/Library/Backend/OAuthHelper/OAuthServiceResponse.cs new file mode 100644 index 000000000..ee721cac9 --- /dev/null +++ b/Duplicati/Library/Backend/OAuthHelper/OAuthServiceResponse.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library; + +public class OAuthServiceResponse +{ + public string access_token { get; set; } + + [Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int expires { get; set; } + + [Newtonsoft.Json.JsonProperty(NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string v2_authid { get; set; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/OAuthHelper/RetryAfterHelper.cs b/Duplicati/Library/Backend/OAuthHelper/RetryAfterHelper.cs index f9ea955a8..1519cee61 100644 --- a/Duplicati/Library/Backend/OAuthHelper/RetryAfterHelper.cs +++ b/Duplicati/Library/Backend/OAuthHelper/RetryAfterHelper.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Logging; @@ -26,21 +26,70 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace Duplicati.Library { + /// + /// Helper class to manage the Retry-After header for a given URL. + /// public class RetryAfterHelper { + /// + /// The log tag for this class. + /// private static readonly string LOGTAG = Log.LogTagFromType(); + /// + /// Initializes a new instance of the class. + /// + private RetryAfterHelper() + { + } + // Whenever a response includes a Retry-After header, we'll update this timestamp with when we can next // send a request. And before sending any requests, we'll make sure to wait until at least this time. // Since this may be read and written by multiple threads, it is stored as a long and updated using Interlocked.Exchange. private long retryAfter = DateTimeOffset.MinValue.UtcTicks; + /// + /// The lock object to ensure thread safety for accessing the retry after helpers. + /// + private static object _lock = new object(); + /// + /// Lookup table that maps the URL to the RetryAfterHelper for that URL. + /// + private static Dictionary _retryAfterHelpers = new Dictionary(); + + /// + /// Backends generally do not keep any state, but the retry after header is stateful, + /// as it depends on the last request. This method obtains a helper object for the url, + /// so a small amount of state can be shared between instances. + /// + /// The URL to get the RetryAfterHelper for. + /// The RetryAfterHelper for the given URL. + public static RetryAfterHelper CreateOrGetRetryAfterHelper(string url) + { + lock (_lock) + { + // Remove any expired entries + foreach (var (k, h) in _retryAfterHelpers.ToArray()) + if (h.retryAfter < DateTimeOffset.UtcNow.UtcTicks) + _retryAfterHelpers.Remove(k); + + // Get the RetryAfterHelper for the given URL, or create a new one if it doesn't exist + if (!_retryAfterHelpers.TryGetValue(url, out var retryAfterHelper)) + _retryAfterHelpers[url] = retryAfterHelper = new RetryAfterHelper(); + + return retryAfterHelper; + } + } + + /// + /// Sets the Retry-After header value for the next request. + /// + /// The Retry-After header value to set. public void SetRetryAfter(RetryConditionHeaderValue retryAfter) { if (retryAfter != null) @@ -82,27 +131,39 @@ namespace Duplicati.Library } } + /// + /// Waits for the time specified in the Retry-After header before returning. + /// public void WaitForRetryAfter() { this.WaitForRetryAfterAsync(CancellationToken.None).Await(); } + /// + /// Waits for the time specified in the Retry-After header before continuing. + /// + /// The cancellation token to cancel the wait. + /// A task that completes when the wait is done. public async Task WaitForRetryAfterAsync(CancellationToken cancelToken) { TimeSpan delay = this.GetDelayTime(); if (delay > TimeSpan.Zero) { - Log.WriteProfilingMessage( + Log.WriteInformationMessage( LOGTAG, "RetryAfterWait", "Waiting for {0} to respect Retry-After header", delay); - await Task.Delay(delay).ConfigureAwait(false); + await Task.Delay(delay, cancelToken).ConfigureAwait(false); } } + /// + /// Gets the delay time until the next request can be made. + /// + /// The delay time until the next request can be made. private TimeSpan GetDelayTime() { // Make sure this is thread safe in case multiple calls are made concurrently to this backend diff --git a/Duplicati/Library/Backend/OAuthHelper/Strings.cs b/Duplicati/Library/Backend/OAuthHelper/Strings.cs index 597563abd..4ab39fb0d 100644 --- a/Duplicati/Library/Backend/OAuthHelper/Strings.cs +++ b/Duplicati/Library/Backend/OAuthHelper/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Backend/OneDrive/Duplicati.Library.Backend.OneDrive.csproj b/Duplicati/Library/Backend/OneDrive/Duplicati.Library.Backend.OneDrive.csproj index 13796498a..515043adb 100644 --- a/Duplicati/Library/Backend/OneDrive/Duplicati.Library.Backend.OneDrive.csproj +++ b/Duplicati/Library/Backend/OneDrive/Duplicati.Library.Backend.OneDrive.csproj @@ -4,7 +4,9 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/OneDrive/Exceptions.cs b/Duplicati/Library/Backend/OneDrive/Exceptions.cs index c8b685767..bb542ab94 100644 --- a/Duplicati/Library/Backend/OneDrive/Exceptions.cs +++ b/Duplicati/Library/Backend/OneDrive/Exceptions.cs @@ -1,30 +1,25 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.IO; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Duplicati.Library.Utility; @@ -36,11 +31,11 @@ namespace Duplicati.Library.Backend.MicrosoftGraph { private static readonly Regex authorizationHeaderRemover = new Regex(@"Authorization:\s*Bearer\s+\S+", RegexOptions.IgnoreCase); - private readonly HttpResponseMessage responseMessage; - private readonly HttpWebResponse webResponse; + private readonly HttpResponseMessage? responseMessage; + private readonly HttpWebResponse? webResponse; public MicrosoftGraphException(HttpResponseMessage response) - : this(string.Format("{0}: {1} error from request {2}", response.StatusCode, response.ReasonPhrase, response.RequestMessage.RequestUri), response) + : this(string.Format("{0}: {1} error from request {2}", response.StatusCode, response.ReasonPhrase, response.RequestMessage?.RequestUri), response) { } @@ -49,7 +44,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph { } - public MicrosoftGraphException(string message, HttpResponseMessage response, Exception innerException) + public MicrosoftGraphException(string message, HttpResponseMessage response, Exception? innerException) : base(BuildFullMessage(message, response), innerException) { this.responseMessage = response; @@ -65,59 +60,40 @@ namespace Duplicati.Library.Backend.MicrosoftGraph { } - public MicrosoftGraphException(string message, HttpWebResponse response, Exception innerException) + public MicrosoftGraphException(string message, HttpWebResponse response, Exception? innerException) : base(BuildFullMessage(message, response), innerException) { this.webResponse = response; } - public string RequestUrl - { - get - { - if (this.responseMessage != null) - { - return this.responseMessage.RequestMessage.RequestUri.ToString(); - } - else - { - return this.webResponse.ResponseUri.ToString(); - } - } - } - public HttpStatusCode StatusCode { get { - if (this.responseMessage != null) - { - return this.responseMessage.StatusCode; - } - else - { - return this.webResponse.StatusCode; - } + if (responseMessage != null) + return responseMessage.StatusCode; + if (webResponse != null) + return webResponse.StatusCode; + return HttpStatusCode.InternalServerError; } } - protected static string ResponseToString(HttpResponseMessage response) + protected static string? ResponseToString(HttpResponseMessage response) { - if (response != null) - { - // Start to read the content - using (Task content = response.Content.ReadAsStringAsync()) - { - // Since the exception message may be saved / sent in logs, we want to prevent the authorization header from being included. - // it wouldn't be as bad as recording the username/password in logs, since the token will expire, but it doesn't hurt to be safe. - // So we replace anything in the request that looks like the auth header with a safe version. - string requestMessage = authorizationHeaderRemover.Replace(response.RequestMessage.ToString(), "Authorization: Bearer ABC...XYZ"); - return string.Format("{0}\n{1}\n{2}", requestMessage, response, PrettifyJson(content.Await())); - } - } - else - { + if (response == null) return null; + + // Start to read the content + using (Task content = response.Content.ReadAsStringAsync()) + { + // Since the exception message may be saved / sent in logs, we want to prevent the authorization header from being included. + // it wouldn't be as bad as recording the username/password in logs, since the token will expire, but it doesn't hurt to be safe. + // So we replace anything in the request that looks like the auth header with a safe version. + var requestMessage = response.RequestMessage == null + ? "" + : authorizationHeaderRemover.Replace(response.RequestMessage.ToString(), "Authorization: Bearer ABC...XYZ"); + + return string.Format("{0}\n{1}\n{2}", requestMessage, response, PrettifyJson(content.Await())); } } @@ -133,41 +109,28 @@ namespace Duplicati.Library.Backend.MicrosoftGraph } } - protected static string ResponseToString(HttpWebResponse response) + protected static string? ResponseToString(HttpWebResponse response) { - if (response != null) - { - // Start to read the content - using (var responseStream = response.GetResponseStream()) - using (var textReader = new StreamReader(responseStream)) - { - return string.Format("{0}\n{1}", response, PrettifyJson(textReader.ReadToEnd())); - } - } - else - { + if (response == null) return null; - } + + // Start to read the content + using (var responseStream = response.GetResponseStream()) + using (var textReader = new StreamReader(responseStream)) + return string.Format("{0}\n{1}", response, PrettifyJson(textReader.ReadToEnd())); } private static string BuildFullMessage(string message, HttpWebResponse response) { - if (response != null) - { - return string.Format("{0}\n{1}", message, ResponseToString(response)); - } - else - { + if (response == null) return message; - } + return string.Format("{0}\n{1}", message, ResponseToString(response)); } private static string PrettifyJson(string json) { if (string.IsNullOrWhiteSpace(json)) - { return json; - } if (json[0] == '<') { @@ -191,12 +154,12 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class DriveItemNotFoundException : MicrosoftGraphException { public DriveItemNotFoundException(HttpResponseMessage response) - : base(string.Format("Item at {0} was not found", response?.RequestMessage?.RequestUri?.ToString() ?? ""), response) + : base(string.Format("Item at {0} was not found", response.RequestMessage?.RequestUri?.ToString() ?? ""), response) { } public DriveItemNotFoundException(HttpWebResponse response) - : base(string.Format("Item at {0} was not found", response?.ResponseUri?.ToString() ?? ""), response) + : base(string.Format("Item at {0} was not found", response.ResponseUri?.ToString() ?? ""), response) { } } @@ -209,7 +172,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph int fragmentCount, Exception fragmentException) : base( - string.Format("Error uploading fragment {0} of {1} for {2}", fragment, fragmentCount, originalResponse?.RequestMessage?.RequestUri?.ToString() ?? ""), + string.Format("Error uploading fragment {0} of {1} for {2}", fragment, fragmentCount, originalResponse.RequestMessage?.RequestUri?.ToString() ?? ""), originalResponse, fragmentException) { @@ -223,7 +186,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph int fragmentCount, Exception fragmentException) : base( - string.Format("Error uploading fragment {0} of {1} for {2}", fragment, fragmentCount, originalResponse?.ResponseUri?.ToString() ?? ""), + string.Format("Error uploading fragment {0} of {1} for {2}", fragment, fragmentCount, originalResponse.ResponseUri?.ToString() ?? ""), originalResponse, fragmentException) { diff --git a/Duplicati/Library/Backend/OneDrive/MicrosoftGraphBackend.cs b/Duplicati/Library/Backend/OneDrive/MicrosoftGraphBackend.cs index a454e2ed7..bcb244c2b 100644 --- a/Duplicati/Library/Backend/OneDrive/MicrosoftGraphBackend.cs +++ b/Duplicati/Library/Backend/OneDrive/MicrosoftGraphBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,17 +23,12 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Logging; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Net; -using System.Net.Http; using System.Net.Http.Headers; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -58,11 +53,9 @@ namespace Duplicati.Library.Backend private const string BASE_ADDRESS = "https://graph.microsoft.com"; - private const string AUTHID_OPTION = "authid"; private const string UPLOAD_SESSION_FRAGMENT_SIZE_OPTION = "fragment-size"; private const string UPLOAD_SESSION_FRAGMENT_RETRY_COUNT_OPTION = "fragment-retry-count"; private const string UPLOAD_SESSION_FRAGMENT_RETRY_DELAY_OPTION = "fragment-retry-delay"; - private const string USE_HTTP_CLIENT = "use-http-client"; private const int UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_COUNT = 5; private const int UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_DELAY = 1000; @@ -92,11 +85,6 @@ namespace Duplicati.Library.Backend /// private const int UPLOAD_SESSION_FRAGMENT_MULTIPLE_SIZE = 320 * 1024; - /// - /// Whether to use the HttpClient class for HTTP requests as default. - /// - private static readonly bool USE_HTTP_CLIENT_DEFAULT = true; - /// /// Cached copy of the PATH method /// @@ -113,7 +101,6 @@ namespace Duplicati.Library.Backend private readonly JsonSerializer m_serializer = new JsonSerializer(); private readonly OAuthHttpClient m_client; - private readonly OAuthHelper m_oAuthHelper; private readonly int fragmentSize; private readonly int fragmentRetryCount; private readonly int fragmentRetryDelay; // In milliseconds @@ -122,23 +109,29 @@ namespace Duplicati.Library.Backend // send a request. And before sending any requests, we'll make sure to wait until at least this time. // Since this may be read and written by multiple threads, it is stored as a long and updated using Interlocked.Exchange. private readonly RetryAfterHelper m_retryAfter; + protected readonly TimeoutOptionsHelper.Timeouts m_timeouts; - private string[] dnsNames = null; + private string[]? dnsNames = null; private readonly Lazy rootPathFromURL; private string RootPath => this.rootPathFromURL.Value; + private string TokenUrl => OAuthHelper.OAUTH_LOGIN_URL(ProtocolKey); - protected MicrosoftGraphBackend() { } // Constructor needed for dynamic loading to find it - - protected MicrosoftGraphBackend(string url, string protocolKey, Dictionary options) + protected MicrosoftGraphBackend() { - string authid; - options.TryGetValue(AUTHID_OPTION, out authid); - if (string.IsNullOrEmpty(authid)) - throw new UserInformationException(Strings.MicrosoftGraph.MissingAuthId(OAuthHelper.OAUTH_LOGIN_URL(protocolKey)), "MicrosoftGraphBackendMissingAuthId"); + // Constructor needed for dynamic loading to find it + m_client = null!; + m_retryAfter = null!; + m_timeouts = null!; + rootPathFromURL = null!; + } - string fragmentSizeStr; - if (options.TryGetValue(UPLOAD_SESSION_FRAGMENT_SIZE_OPTION, out fragmentSizeStr) && int.TryParse(fragmentSizeStr, out this.fragmentSize)) + protected MicrosoftGraphBackend(string url, string protocolKey, Dictionary options) + { + var authId = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TokenUrl); + + if (options.TryGetValue(UPLOAD_SESSION_FRAGMENT_SIZE_OPTION, out var fragmentSizeStr) && int.TryParse(fragmentSizeStr, out this.fragmentSize)) { // Make sure the fragment size is a multiple of the desired multiple size. // If it isn't, we round down to the nearest multiple below it. @@ -152,41 +145,17 @@ namespace Duplicati.Library.Backend this.fragmentSize = UPLOAD_SESSION_FRAGMENT_DEFAULT_SIZE; } - string fragmentRetryCountStr; - if (!(options.TryGetValue(UPLOAD_SESSION_FRAGMENT_RETRY_COUNT_OPTION, out fragmentRetryCountStr) && int.TryParse(fragmentRetryCountStr, out this.fragmentRetryCount))) - { + if (!(options.TryGetValue(UPLOAD_SESSION_FRAGMENT_RETRY_COUNT_OPTION, out var fragmentRetryCountStr) && int.TryParse(fragmentRetryCountStr, out this.fragmentRetryCount))) this.fragmentRetryCount = UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_COUNT; - } - string fragmentRetryDelayStr; - if (!(options.TryGetValue(UPLOAD_SESSION_FRAGMENT_RETRY_DELAY_OPTION, out fragmentRetryDelayStr) && int.TryParse(fragmentRetryDelayStr, out this.fragmentRetryDelay))) - { + if (!(options.TryGetValue(UPLOAD_SESSION_FRAGMENT_RETRY_DELAY_OPTION, out var fragmentRetryDelayStr) && int.TryParse(fragmentRetryDelayStr, out this.fragmentRetryDelay))) this.fragmentRetryDelay = UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_DELAY; - } - bool useHttpClient; - string useHttpClientStr; - if (options.TryGetValue(USE_HTTP_CLIENT, out useHttpClientStr)) - { - useHttpClient = Utility.Utility.ParseBool(useHttpClientStr, USE_HTTP_CLIENT_DEFAULT); - } - else - { - useHttpClient = USE_HTTP_CLIENT_DEFAULT; - } + this.m_client = new OAuthHttpClient(authId.AuthId!, protocolKey); + this.m_client.BaseAddress = new System.Uri(BASE_ADDRESS); - if (useHttpClient) - { - this.m_client = new OAuthHttpClient(authid, protocolKey); - this.m_client.BaseAddress = new System.Uri(BASE_ADDRESS); - } - else - { - this.m_oAuthHelper = new OAuthHelper(authid, protocolKey); - this.m_oAuthHelper.AutoAuthHeader = true; - } - - this.m_retryAfter = new RetryAfterHelper(); + this.m_retryAfter = RetryAfterHelper.CreateOrGetRetryAfterHelper(url); + this.m_timeouts = TimeoutOptionsHelper.Parse(options); // Extract out the path to the backup root folder from the given URI. Since this can be an expensive operation, // we will cache the value using a lazy initializer. @@ -198,88 +167,68 @@ namespace Duplicati.Library.Backend public abstract string DisplayName { get; } - public string Description - { - get - { - return this.DescriptionTemplate( + public string Description => this.DescriptionTemplate( "Microsoft Service Agreement", SERVICES_AGREEMENT, "Microsoft Online Privacy Statement", PRIVACY_STATEMENT); - } - } - public IList SupportedCommands - { - get - { - return new[] - { - new CommandLineArgument(AUTHID_OPTION, CommandLineArgument.ArgumentType.Password, Strings.MicrosoftGraph.AuthIdShort, Strings.MicrosoftGraph.AuthIdLong(OAuthHelper.OAUTH_LOGIN_URL(this.ProtocolKey))), - new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_SIZE_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentSizeShort, Strings.MicrosoftGraph.FragmentSizeLong, Library.Utility.Utility.FormatSizeString(UPLOAD_SESSION_FRAGMENT_DEFAULT_SIZE)), - new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_RETRY_COUNT_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentRetryCountShort, Strings.MicrosoftGraph.FragmentRetryCountLong, UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_COUNT.ToString()), - new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_RETRY_DELAY_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentRetryDelayShort, Strings.MicrosoftGraph.FragmentRetryDelayLong, UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_DELAY.ToString()), - new CommandLineArgument(USE_HTTP_CLIENT, CommandLineArgument.ArgumentType.Boolean, Strings.MicrosoftGraph.UseHttpClientShort, Strings.MicrosoftGraph.UseHttpClientLong, USE_HTTP_CLIENT_DEFAULT.ToString()), - } - .Concat(this.AdditionalSupportedCommands).ToList(); - } - } + public IList SupportedCommands => [ + .. AuthIdOptionsHelper.GetOptions(TokenUrl), + new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_SIZE_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentSizeShort, Strings.MicrosoftGraph.FragmentSizeLong, Library.Utility.Utility.FormatSizeString(UPLOAD_SESSION_FRAGMENT_DEFAULT_SIZE)), + new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_RETRY_COUNT_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentRetryCountShort, Strings.MicrosoftGraph.FragmentRetryCountLong, UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_COUNT.ToString()), + new CommandLineArgument(UPLOAD_SESSION_FRAGMENT_RETRY_DELAY_OPTION, CommandLineArgument.ArgumentType.Integer, Strings.MicrosoftGraph.FragmentRetryDelayShort, Strings.MicrosoftGraph.FragmentRetryDelayLong, UPLOAD_SESSION_FRAGMENT_DEFAULT_RETRY_DELAY.ToString()), + new CommandLineArgument("use-http-client", CommandLineArgument.ArgumentType.Boolean, Strings.MicrosoftGraph.UseHttpClientShort, Strings.MicrosoftGraph.UseHttpClientLong, "true", null, null, Strings.MicrosoftGraph.UseHttpClientDeprecated), + .. AdditionalSupportedCommands, + .. TimeoutOptionsHelper.GetOptions() + ]; public async Task GetDNSNamesAsync(CancellationToken cancelToken) { - if (this.dnsNames == null) + if (dnsNames == null) { // The DNS names that this instance may need to access include: // - Core graph API endpoint // - Upload session endpoint (which seems to be different depending on the drive being accessed - not sure if it can vary for a single drive) // To get the upload session endpoint, we can start an upload session and then immediately cancel it. // We pick a random file name (using a guid) to make sure we don't conflict with an existing file - string dnsTestFile = string.Format("DNSNameTest-{0}", Guid.NewGuid()); - var uploadSession = await this.PostAsync(string.Format("{0}/root:{1}{2}:/createUploadSession", this.DrivePrefix, this.RootPath, NormalizeSlashes(dnsTestFile)), MicrosoftGraphBackend.dummyUploadSession, cancelToken).ConfigureAwait(false); + var dnsTestFile = string.Format("DNSNameTest-{0}", Guid.NewGuid()); + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + var uploadSession = await this.PostAsync(string.Format("{0}/root:{1}{2}:/createUploadSession", drivePrefix, this.RootPath, NormalizeSlashes(dnsTestFile)), dummyUploadSession, cancelToken).ConfigureAwait(false); // Canceling an upload session is done by sending a DELETE to the upload URL await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - if (this.m_client != null) - { - using (var request = new HttpRequestMessage(HttpMethod.Delete, uploadSession.UploadUrl)) - using (var response = await this.m_client.SendAsync(request, cancelToken).ConfigureAwait(false)) - { - this.CheckResponse(response); - } - } - else - { - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(uploadSession.UploadUrl, cancelToken, MicrosoftGraphBackend.dummyUploadSession, HttpMethod.Delete.ToString()).ConfigureAwait(false)) - { - this.CheckResponse(response); - } - } + using (var request = new HttpRequestMessage(HttpMethod.Delete, uploadSession.UploadUrl)) + using (var response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => m_client.SendAsync(request, false, ct)).ConfigureAwait(false)) + CheckResponse(response); - this.dnsNames = new[] - { - new System.Uri(BASE_ADDRESS).Host, - new System.Uri(uploadSession.UploadUrl).Host, - } + dnsNames = new[] { + new System.Uri(BASE_ADDRESS).Host, + string.IsNullOrWhiteSpace(uploadSession.UploadUrl) ? null : new System.Uri(uploadSession.UploadUrl).Host, + } + .WhereNotNullOrWhiteSpace() .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); } - return this.dnsNames; + return dnsNames; } - public async Task GetQuotaInfoAsync(CancellationToken cancelToken) + public async Task GetQuotaInfoAsync(CancellationToken cancelToken) { - Drive driveInfo = await this.GetAsync(this.DrivePrefix, cancelToken).ConfigureAwait(false); - if (driveInfo.Quota != null) + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + var driveInfo = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => GetAsync(drivePrefix, ct) + ).ConfigureAwait(false); + + if (driveInfo?.Quota != null) { // Some sources (SharePoint for example) seem to return 0 for these values even when the quota isn't exceeded.. // As a special test, if all the returned values are 0, we pretend that no quota was reported. // This way we don't send spurious warnings because the quota looks like it is exceeded. if (driveInfo.Quota.Total != 0 || driveInfo.Quota.Remaining != 0 || driveInfo.Quota.Used != 0) - { return new QuotaInfo(driveInfo.Quota.Total, driveInfo.Quota.Remaining); - } } return null; @@ -289,54 +238,36 @@ namespace Duplicati.Library.Backend /// /// Override-able fragment indicating the API version to use each query /// - protected virtual string ApiVersion - { - get { return "/v1.0"; } - } + protected virtual string ApiVersion => "/v1.0"; /// /// Normalized fragment (starting with a slash and ending without one) for the path to the drive to be used. /// For example: "/me/drive" for the default drive for a user. /// - protected abstract string DrivePath { get; } + protected abstract Task GetDrivePath(CancellationToken cancelToken); protected abstract DescriptionTemplateDelegate DescriptionTemplate { get; } - protected virtual IList AdditionalSupportedCommands - { - get - { - return new ICommandLineArgument[0]; - } - } + protected virtual IList AdditionalSupportedCommands => []; - private string DrivePrefix - { - get - { - if (this.m_client != null) - { - return this.ApiVersion + this.DrivePath; - } - else - { - // When not using the HttpClient for requests, the base address needs to be included in this prefix - return BASE_ADDRESS + this.ApiVersion + this.DrivePath; - } - } - } + private async Task GetDrivePrefix(CancellationToken cancelToken) + => $"{ApiVersion}{await GetDrivePath(cancelToken).ConfigureAwait(false)}"; public async Task CreateFolderAsync(CancellationToken cancelToken) { - string parentFolder = "root"; - string parentFolderPath = string.Empty; + var parentFolder = "root"; + var parentFolderPath = string.Empty; + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + foreach (string folder in this.RootPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)) { string nextPath = parentFolderPath + "/" + folder; DriveItem folderItem; try { - folderItem = await this.GetAsync(string.Format("{0}/root:{1}", this.DrivePrefix, NormalizeSlashes(nextPath)), cancelToken).ConfigureAwait(false); + folderItem = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => GetAsync(string.Format("{0}/root:{1}", drivePrefix, NormalizeSlashes(nextPath)), ct) + ).ConfigureAwait(false); } catch (DriveItemNotFoundException) { @@ -346,7 +277,9 @@ namespace Duplicati.Library.Backend Folder = new FolderFacet(), }; - folderItem = await this.PostAsync(string.Format("{0}/items/{1}/children", this.DrivePrefix, parentFolder), newFolder, cancelToken).ConfigureAwait(false); + folderItem = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => PostAsync(string.Format("{0}/items/{1}/children", drivePrefix, parentFolder), newFolder, ct) + ).ConfigureAwait(false); } parentFolder = folderItem.Id; @@ -354,9 +287,11 @@ namespace Duplicati.Library.Backend } } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - foreach (DriveItem item in this.Enumerate(string.Format("{0}/root:{1}:/children", this.DrivePrefix, this.RootPath))) + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + await foreach (var item in this.Enumerate(string.Format("{0}/root:{1}:/children", drivePrefix, this.RootPath), cancelToken).ConfigureAwait(false)) { // Exclude non-files and deleted items (not sure if they show up in this listing, but make sure anyway) if (item.IsFile && !item.IsDeleted) @@ -380,25 +315,18 @@ namespace Duplicati.Library.Backend { try { + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); m_retryAfter.WaitForRetryAfter(); - string getUrl = string.Format("{0}/root:{1}{2}:/content", this.DrivePrefix, this.RootPath, NormalizeSlashes(remotename)); - if (this.m_client != null) + string getUrl = string.Format("{0}/root:{1}{2}:/content", drivePrefix, this.RootPath, NormalizeSlashes(remotename)); + using (var response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => m_client.GetAsync(getUrl, HttpCompletionOption.ResponseHeadersRead, ct) + ).ConfigureAwait(false)) { - using (var response = await this.m_client.GetAsync(getUrl, cancelToken).ConfigureAwait(false)) - { - this.CheckResponse(response); - using (var responseStream = await response.Content.ReadAsStreamAsync(cancelToken).ConfigureAwait(false)) - await Library.Utility.Utility.CopyStreamAsync(responseStream, stream, cancelToken).ConfigureAwait(false); - } - } - else - { - using (var response = this.m_oAuthHelper.GetResponseWithoutException(getUrl)) - { - this.CheckResponse(response); - using (var responseStream = response.GetResponseStream()) - await Library.Utility.Utility.CopyStreamAsync(responseStream, stream, cancelToken).ConfigureAwait(false); - } + CheckResponse(response); + using (var responseStream = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => response.Content.ReadAsStreamAsync(ct)).ConfigureAwait(false)) + using (var timeoutSream = responseStream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(responseStream, stream, cancelToken).ConfigureAwait(false); } } catch (DriveItemNotFoundException ex) @@ -412,7 +340,10 @@ namespace Duplicati.Library.Backend { try { - await this.PatchAsync(string.Format("{0}/root:{1}{2}", this.DrivePrefix, this.RootPath, NormalizeSlashes(oldname)), new DriveItem() { Name = newname }, cancelToken).ConfigureAwait(false); + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, + ct => PatchAsync(string.Format("{0}/root:{1}{2}", drivePrefix, this.RootPath, NormalizeSlashes(oldname)), new DriveItem() { Name = newname }, ct) + ).ConfigureAwait(false); } catch (DriveItemNotFoundException ex) { @@ -424,33 +355,23 @@ namespace Duplicati.Library.Backend public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { using (FileStream fileStream = File.OpenRead(filename)) - { await PutAsync(remotename, fileStream, cancelToken).ConfigureAwait(false); - } } public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + // PUT only supports up to 4 MB file uploads. There's a separate process for larger files. if (stream.Length < PUT_MAX_SIZE) { await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - string putUrl = string.Format("{0}/root:{1}{2}:/content", this.DrivePrefix, this.RootPath, NormalizeSlashes(remotename)); - if (this.m_client != null) + string putUrl = string.Format("{0}/root:{1}{2}:/content", drivePrefix, this.RootPath, NormalizeSlashes(remotename)); + using (var timeoutStream = stream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false)) + using (var streamContent = new StreamContent(timeoutStream)) { - using (StreamContent streamContent = new StreamContent(stream)) - { - streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); - using (var response = await this.m_client.PutAsync(putUrl, streamContent, cancelToken).ConfigureAwait(false)) - { - // Make sure this response is a valid drive item, though we don't actually use it for anything currently. - await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); - } - } - } - else - { - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(putUrl, cancelToken, stream, HttpMethod.Put.ToString()).ConfigureAwait(false)) + streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + using (var response = await this.m_client.PutAsync(putUrl, streamContent, cancelToken).ConfigureAwait(false)) { // Make sure this response is a valid drive item, though we don't actually use it for anything currently. await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); @@ -464,232 +385,58 @@ namespace Duplicati.Library.Backend // The documentation seems somewhat contradictory - it states that uploads must be done sequentially, // but also states that the nextExpectedRanges value returned may indicate multiple ranges... // For now, this plays it safe and does a sequential upload. - string createSessionUrl = string.Format("{0}/root:{1}{2}:/createUploadSession", this.DrivePrefix, this.RootPath, NormalizeSlashes(remotename)); - if (this.m_client != null) + string createSessionUrl = string.Format("{0}/root:{1}{2}:/createUploadSession", drivePrefix, this.RootPath, NormalizeSlashes(remotename)); + await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); + using (var createSessionRequest = new HttpRequestMessage(HttpMethod.Post, createSessionUrl)) + using (var createSessionResponse = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.m_client.SendAsync(createSessionRequest, ct)).ConfigureAwait(false)) { - await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - using (HttpRequestMessage createSessionRequest = new HttpRequestMessage(HttpMethod.Post, createSessionUrl)) - using (HttpResponseMessage createSessionResponse = await this.m_client.SendAsync(createSessionRequest, cancelToken).ConfigureAwait(false)) + var uploadSession = await this.ParseResponseAsync(createSessionResponse, cancelToken).ConfigureAwait(false); + + // If the stream's total length is less than the chosen fragment size, then we should make the buffer only as large as the stream. + int bufferSize = (int)Math.Min(this.fragmentSize, stream.Length); + + long read = 0; + for (long offset = 0; offset < stream.Length; offset += read) { - var uploadSession = await this.ParseResponseAsync(createSessionResponse, cancelToken).ConfigureAwait(false); - - // If the stream's total length is less than the chosen fragment size, then we should make the buffer only as large as the stream. - int bufferSize = (int)Math.Min(this.fragmentSize, stream.Length); - - long read = 0; - for (long offset = 0; offset < stream.Length; offset += read) + // If the stream isn't long enough for this to be a full buffer, then limit the length + long currentBufferSize = bufferSize; + if (stream.Length < offset + bufferSize) { - // If the stream isn't long enough for this to be a full buffer, then limit the length - long currentBufferSize = bufferSize; - if (stream.Length < offset + bufferSize) - { - currentBufferSize = stream.Length - offset; - } - - using (Stream subStream = new ReadLimitLengthStream(stream, offset, currentBufferSize)) - { - read = subStream.Length; - - int fragmentCount = (int)Math.Ceiling((double)stream.Length / bufferSize); - int retryCount = this.fragmentRetryCount; - for (int attempt = 0; attempt < retryCount; attempt++) - { - await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - - int fragmentNumber = (int)(offset / bufferSize); - Log.WriteVerboseMessage( - LOGTAG, - "MicrosoftGraphFragmentUpload", - "Uploading fragment {0}/{1} of remote file {2}", - fragmentNumber, - fragmentCount, - remotename); - - using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Put, uploadSession.UploadUrl)) - using (StreamContent fragmentContent = new StreamContent(subStream)) - { - fragmentContent.Headers.ContentLength = read; - fragmentContent.Headers.ContentRange = new ContentRangeHeaderValue(offset, offset + read - 1, stream.Length); - - request.Content = fragmentContent; - - try - { - // The uploaded put requests will error if they are authenticated - using (HttpResponseMessage response = await this.m_client.SendAsync(request, false, cancelToken).ConfigureAwait(false)) - { - // Note: On the last request, the json result includes the default properties of the item that was uploaded - await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); - } - } - catch (MicrosoftGraphException ex) - { - if (subStream.Position != 0) - { - if (subStream.CanSeek) - { - // Make sure to reset the substream to its start in case this is a retry - subStream.Seek(0, SeekOrigin.Begin); - } - else - { - // If any of the source stream was read and the substream can't be seeked back to the beginning, - // then the internal retry mechanism can't be used and the caller will have to retry this whole file. - // Should we consider signaling to the graph API that we're abandoning this upload session? - await this.ThrowUploadSessionException( - uploadSession, - createSessionResponse, - fragmentNumber, - fragmentCount, - ex, - cancelToken).ConfigureAwait(false); - } - } - - // Error handling based on recommendations here: - // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_createuploadsession#best-practices - if (attempt >= retryCount - 1) - { - // We've used up all our retry attempts - await this.ThrowUploadSessionException( - uploadSession, - createSessionResponse, - fragmentNumber, - fragmentCount, - ex, - cancelToken).ConfigureAwait(false); - } - else if ((int)ex.StatusCode >= 500 && (int)ex.StatusCode < 600) - { - // If a 5xx error code is hit, we should use an exponential backoff strategy before retrying. - // To make things simpler, we just use the current attempt number as the exponential factor. - // If there was a Retry-After header, we'll wait for that right before sending the next request as well. - TimeSpan delay = TimeSpan.FromMilliseconds((int)Math.Pow(2, attempt) * this.fragmentRetryDelay); - - Log.WriteRetryMessage( - LOGTAG, - "MicrosoftGraphFragmentRetryIn", - ex, - "Uploading fragment {0}/{1} of remote file {2} failed and will be retried in {3}", - fragmentNumber, - fragmentCount, - remotename, - delay); - - await Task.Delay(delay).ConfigureAwait(false); - continue; - } - else if (ex.StatusCode == HttpStatusCode.NotFound) - { - // 404 is a special case indicating the upload session no longer exists, so the fragment shouldn't be retried. - // Instead we'll let the caller re-attempt the whole file. - await this.ThrowUploadSessionException( - uploadSession, - createSessionResponse, - fragmentNumber, - fragmentCount, - ex, - cancelToken).ConfigureAwait(false); - } - else if ((int)ex.StatusCode >= 400 && (int)ex.StatusCode < 500) - { - // If a 4xx error code is hit, we should retry without the exponential backoff attempt. - Log.WriteRetryMessage( - LOGTAG, - "MicrosoftGraphFragmentRetry", - ex, - "Uploading fragment {0}/{1} of remote file {2} failed and will be retried", - fragmentNumber, - fragmentCount, - remotename); - - continue; - } - else - { - // Other errors should be rethrown - await this.ThrowUploadSessionException( - uploadSession, - createSessionResponse, - fragmentNumber, - fragmentCount, - ex, - cancelToken).ConfigureAwait(false); - } - } - catch (Exception ex) - { - // Any other exceptions should also cause the upload session to be canceled. - await this.ThrowUploadSessionException( - uploadSession, - createSessionResponse, - fragmentNumber, - fragmentCount, - ex, - cancelToken).ConfigureAwait(false); - } - - // If we successfully sent this piece, then we can break out of the retry loop - break; - } - } - } + currentBufferSize = stream.Length - offset; } - } - } - else - { - await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - using (var createSessionResponse = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(createSessionUrl, cancelToken, MicrosoftGraphBackend.dummyUploadSession, HttpMethod.Post.ToString()).ConfigureAwait(false)) - { - var uploadSession = await this.ParseResponseAsync(createSessionResponse, cancelToken).ConfigureAwait(false); - // If the stream's total length is less than the chosen fragment size, then we should make the buffer only as large as the stream. - int bufferSize = (int)Math.Min(this.fragmentSize, stream.Length); - - long read = 0; - for (long offset = 0; offset < stream.Length; offset += read) + using (var subStream = new ReadLimitLengthStream(stream, offset, currentBufferSize)) { - // If the stream isn't long enough for this to be a full buffer, then limit the length - long currentBufferSize = bufferSize; - if (stream.Length < offset + bufferSize) - { - currentBufferSize = stream.Length - offset; - } + read = subStream.Length; - using (Stream subStream = new ReadLimitLengthStream(stream, offset, currentBufferSize)) + int fragmentCount = (int)Math.Ceiling((double)stream.Length / bufferSize); + int retryCount = this.fragmentRetryCount; + for (int attempt = 0; attempt < retryCount; attempt++) { - read = subStream.Length; + await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - int fragmentCount = (int)Math.Ceiling((double)stream.Length / bufferSize); - int retryCount = this.fragmentRetryCount; - for (int attempt = 0; attempt < retryCount; attempt++) + int fragmentNumber = (int)(offset / bufferSize); + Log.WriteVerboseMessage( + LOGTAG, + "MicrosoftGraphFragmentUpload", + "Uploading fragment {0}/{1} of remote file {2}", + fragmentNumber, + fragmentCount, + remotename); + + using (var request = new HttpRequestMessage(HttpMethod.Put, uploadSession.UploadUrl)) + using (var timeoutStream = subStream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false)) + using (var fragmentContent = new StreamContent(timeoutStream)) { - await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); + fragmentContent.Headers.ContentLength = read; + fragmentContent.Headers.ContentRange = new ContentRangeHeaderValue(offset, offset + read - 1, stream.Length); - int fragmentNumber = (int)(offset / bufferSize); - Log.WriteVerboseMessage( - LOGTAG, - "MicrosoftGraphFragmentUpload", - "Uploading fragment {0}/{1} of remote file {2}", - fragmentNumber, - fragmentCount, - remotename); - - // The uploaded put requests will error if they are authenticated - var request = new AsyncHttpRequest(this.m_oAuthHelper.CreateRequest(uploadSession.UploadUrl, HttpMethod.Put.ToString(), true)); - request.Request.ContentLength = read; - request.Request.Headers.Set(HttpRequestHeader.ContentRange, new ContentRangeHeaderValue(offset, offset + read - 1, stream.Length).ToString()); - request.Request.ContentType = "application/octet-stream"; - - using (var requestStream = request.GetRequestStream(read)) - { - await Utility.Utility.CopyStreamAsync(subStream, requestStream, cancelToken).ConfigureAwait(false); - } + request.Content = fragmentContent; try { - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(request, cancelToken).ConfigureAwait(false)) + // The uploaded put requests will error if they are authenticated + using (var response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.m_client.SendAsync(request, false, ct)).ConfigureAwait(false)) { // Note: On the last request, the json result includes the default properties of the item that was uploaded await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); @@ -816,22 +563,11 @@ namespace Duplicati.Library.Backend { try { + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - string deleteUrl = string.Format("{0}/root:{1}{2}", this.DrivePrefix, this.RootPath, NormalizeSlashes(remotename)); - if (this.m_client != null) - { - using (var response = await this.m_client.DeleteAsync(deleteUrl, cancelToken).ConfigureAwait(false)) - { - this.CheckResponse(response); - } - } - else - { - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(deleteUrl, cancelToken, null, HttpMethod.Delete.ToString()).ConfigureAwait(false)) - { - this.CheckResponse(response); - } - } + string deleteUrl = string.Format("{0}/root:{1}{2}", drivePrefix, this.RootPath, NormalizeSlashes(remotename)); + using (var response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.m_client.DeleteAsync(deleteUrl, ct)).ConfigureAwait(false)) + this.CheckResponse(response); } catch (DriveItemNotFoundException ex) { @@ -844,7 +580,8 @@ namespace Duplicati.Library.Backend { try { - string rootPath = string.Format("{0}/root:{1}", this.DrivePrefix, this.RootPath); + var drivePrefix = await GetDrivePrefix(cancelToken).ConfigureAwait(false); + string rootPath = string.Format("{0}/root:{1}", drivePrefix, this.RootPath); await this.GetAsync(rootPath, cancelToken).ConfigureAwait(false); } catch (DriveItemNotFoundException ex) @@ -887,41 +624,18 @@ namespace Duplicati.Library.Backend private async Task SendRequestAsync(HttpMethod method, string url, CancellationToken cancelToken) { - if (this.m_client != null) + using (var request = new HttpRequestMessage(method, url)) { - using (var request = new HttpRequestMessage(method, url)) - { - return await this.SendRequestAsync(request, cancelToken).ConfigureAwait(false); - } - } - else - { - await m_retryAfter.WaitForRetryAfterAsync(cancelToken).ConfigureAwait(false); - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(url, cancelToken, null, method.ToString()).ConfigureAwait(false)) - - { - return await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); - } + return await this.SendRequestAsync(request, cancelToken).ConfigureAwait(false); } } private async Task SendRequestAsync(HttpMethod method, string url, T body, CancellationToken cancelToken) where T : class { - if (this.m_client != null) + using (var request = new HttpRequestMessage(method, url)) + using (request.Content = this.PrepareContent(body)) { - using (var request = new HttpRequestMessage(method, url)) - using (request.Content = this.PrepareContent(body)) - { - return await this.SendRequestAsync(request, cancelToken).ConfigureAwait(false); - } - } - else - { - m_retryAfter.WaitForRetryAfter(); - using (var response = this.m_oAuthHelper.GetResponseWithoutException(url, body, method.ToString())) - { - return await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); - } + return await this.SendRequestAsync(request, cancelToken).ConfigureAwait(false); } } @@ -934,15 +648,15 @@ namespace Duplicati.Library.Backend } } - private IEnumerable Enumerate(string url) + private async IAsyncEnumerable Enumerate(string url, [EnumeratorCancellation] CancellationToken cancelToken) { - string nextUrl = url; + var nextUrl = url; while (!string.IsNullOrEmpty(nextUrl)) { GraphCollection results; try { - results = this.GetAsync>(nextUrl, CancellationToken.None).Await(); + results = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => this.GetAsync>(nextUrl, ct)).ConfigureAwait(false); } catch (DriveItemNotFoundException ex) { @@ -950,10 +664,8 @@ namespace Duplicati.Library.Backend throw new FolderMissingException(ex); } - foreach (T result in results.Value) - { + foreach (T result in results.Value ?? []) yield return result; - } nextUrl = results.ODataNextLink; } @@ -978,49 +690,15 @@ namespace Duplicati.Library.Backend } } - private void CheckResponse(HttpWebResponse response) - { - string retryAfterHeader = response.Headers[HttpResponseHeader.RetryAfter]; - if (retryAfterHeader != null && RetryConditionHeaderValue.TryParse(retryAfterHeader, out RetryConditionHeaderValue retryAfter)) - { - m_retryAfter.SetRetryAfter(retryAfter); - } - - if (!((int)response.StatusCode >= 200 && (int)response.StatusCode < 300)) - { - if (response.StatusCode == HttpStatusCode.NotFound) - { - // It looks like this is an 'item not found' exception, so wrap it in a new exception class to make it easier to pick things out. - throw new DriveItemNotFoundException(response); - } - else - { - // Throw a wrapper exception to make it easier for the caller to look at specific status codes, etc. - throw new MicrosoftGraphException(response); - } - } - } - private async Task ParseResponseAsync(HttpResponseMessage response, CancellationToken cancelToken) { - this.CheckResponse(response); - using (var responseStream = await response.Content.ReadAsStreamAsync(cancelToken).ConfigureAwait(false)) - using (var reader = new StreamReader(responseStream)) + CheckResponse(response); + using (var responseStream = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => response.Content.ReadAsStreamAsync(ct)).ConfigureAwait(false)) + using (var timeoutStream = responseStream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + using (var reader = new StreamReader(timeoutStream)) using (var jsonReader = new JsonTextReader(reader)) - { - return this.m_serializer.Deserialize(jsonReader); - } - } - - private Task ParseResponseAsync(HttpWebResponse response, CancellationToken cancelToken) - { - this.CheckResponse(response); - using (Stream responseStream = response.GetResponseStream()) - using (StreamReader reader = new StreamReader(responseStream)) - using (JsonTextReader jsonReader = new JsonTextReader(reader)) - { - return Task.FromResult(this.m_serializer.Deserialize(jsonReader)); - } + return m_serializer.Deserialize(jsonReader) + ?? throw new MicrosoftGraphException("Failed to parse response", response); } private async Task ThrowUploadSessionException( @@ -1043,26 +721,6 @@ namespace Duplicati.Library.Backend throw new UploadSessionException(createSessionResponse, fragment, fragmentCount, ex); } - private async Task ThrowUploadSessionException( - UploadSession uploadSession, - HttpWebResponse createSessionResponse, - int fragment, - int fragmentCount, - Exception ex, - CancellationToken cancelToken) - { - // Before throwing the exception, cancel the upload session - // The uploaded delete request will error if it is authenticated - var request = new AsyncHttpRequest(this.m_oAuthHelper.CreateRequest(uploadSession.UploadUrl, HttpMethod.Delete.ToString(), true)); - using (var response = await this.m_oAuthHelper.GetResponseWithoutExceptionAsync(request, cancelToken).ConfigureAwait(false)) - { - // Note that the response body should always be empty in this case. - await this.ParseResponseAsync(response, cancelToken).ConfigureAwait(false); - } - - throw new UploadSessionException(createSessionResponse, fragment, fragmentCount, ex); - } - /// /// Normalizes the slashes in a url fragment. For example: /// "" => "" @@ -1085,16 +743,11 @@ namespace Duplicati.Library.Backend return url; } - private StringContent PrepareContent(T body) + private StringContent? PrepareContent(T body) { - if (body != null) - { - return new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); - } - else - { + if (body == null) return null; - } + return new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json"); } } } diff --git a/Duplicati/Library/Backend/OneDrive/MicrosoftGraphTypes.cs b/Duplicati/Library/Backend/OneDrive/MicrosoftGraphTypes.cs index 5f661b067..50100a8d4 100644 --- a/Duplicati/Library/Backend/OneDrive/MicrosoftGraphTypes.cs +++ b/Duplicati/Library/Backend/OneDrive/MicrosoftGraphTypes.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,9 +18,6 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Linq; - using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -34,10 +31,10 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class Identity { [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("displayName", NullValueHandling = NullValueHandling.Ignore)] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } } public class IdentitySet @@ -46,37 +43,37 @@ namespace Duplicati.Library.Backend.MicrosoftGraph /// The optional application associated with this action /// [JsonProperty("application", NullValueHandling = NullValueHandling.Ignore)] - public Identity Application { get; set; } + public Identity? Application { get; set; } /// /// The optional device associated with this action /// [JsonProperty("device", NullValueHandling = NullValueHandling.Ignore)] - public Identity Device { get; set; } + public Identity? Device { get; set; } /// /// The optional user associated with this action /// [JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)] - public Identity User { get; set; } + public Identity? User { get; set; } } public class BaseItem { [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("createdBy", NullValueHandling = NullValueHandling.Ignore)] - public IdentitySet CreatedBy { get; set; } + public IdentitySet? CreatedBy { get; set; } [JsonProperty("createdDateTime", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? CreatedDateTime { get; set; } [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] - public string Description { get; set; } + public string? Description { get; set; } [JsonProperty("lastModifiedBy", NullValueHandling = NullValueHandling.Ignore)] - public IdentitySet LastModifiedBy { get; set; } + public IdentitySet? LastModifiedBy { get; set; } [JsonProperty("lastModifiedDateTime", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? LastModifiedDateTime { get; set; } @@ -90,10 +87,10 @@ namespace Duplicati.Library.Backend.MicrosoftGraph /// https://support.microsoft.com/en-us/help/2829981/support-for-whitespace-characters-in-file-and-folder-names-for-windows /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("webUrl", NullValueHandling = NullValueHandling.Ignore)] - public string WebUrl { get; set; } + public string? WebUrl { get; set; } } public enum QuotaState @@ -176,10 +173,10 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public DriveType DriveType { get; set; } [JsonProperty("owner", NullValueHandling = NullValueHandling.Ignore)] - public IdentitySet Owner { get; set; } + public IdentitySet? Owner { get; set; } [JsonProperty("quota", NullValueHandling = NullValueHandling.Ignore)] - public Quota Quota { get; set; } + public Quota? Quota { get; set; } } public class HashesType @@ -189,30 +186,30 @@ namespace Duplicati.Library.Backend.MicrosoftGraph /// Available only in OneDrive personal /// [JsonProperty("sha1Hash", NullValueHandling = NullValueHandling.Ignore)] - public string Sha1Hash { get; set; } + public string? Sha1Hash { get; set; } /// /// CRC32 hash /// Available only in OneDrive personal /// [JsonProperty("crc32Hash", NullValueHandling = NullValueHandling.Ignore)] - public string Crc32Hash { get; set; } + public string? Crc32Hash { get; set; } /// /// A proprietary hash of the file that can be used to determine if the file's contents have changed. /// Available on in OneDrive for Business and SharePoint Server 2016. /// [JsonProperty("quickXorHash", NullValueHandling = NullValueHandling.Ignore)] - public string QuickXorHash { get; set; } + public string? QuickXorHash { get; set; } } public class FileFacet { [JsonProperty("hashes", NullValueHandling = NullValueHandling.Ignore)] - public HashesType Hashes { get; set; } + public HashesType? Hashes { get; set; } [JsonProperty("mimeType", NullValueHandling = NullValueHandling.Ignore)] - public string MimeType { get; set; } + public string? MimeType { get; set; } } public enum SortBy @@ -307,7 +304,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public long? ChildCount { get; set; } [JsonProperty("view", NullValueHandling = NullValueHandling.Ignore)] - public FolderView View { get; set; } + public FolderView? View { get; set; } } /// @@ -337,19 +334,19 @@ namespace Duplicati.Library.Backend.MicrosoftGraph /// Package type - currently only oneNote is defined, but others could be used. /// [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] - public string Type { get; set; } + public string? Type { get; set; } } public class DeletedFacet { [JsonProperty("state", NullValueHandling = NullValueHandling.Ignore)] - public string State { get; set; } + public string? State { get; set; } } public class RemoteItemFacet { [JsonProperty("remoteItem", NullValueHandling = NullValueHandling.Ignore)] - public RemoteItemFacet RemoteItem { get; set; } + public RemoteItemFacet? RemoteItem { get; set; } } public class RootFacet @@ -362,7 +359,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph /// The unique identifier which can be used under /drive/special/ /// [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } + public string? Name { get; set; } } /// @@ -372,19 +369,19 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class ItemReference { [JsonProperty("driveId", NullValueHandling = NullValueHandling.Ignore)] - public string DriveId { get; set; } + public string? DriveId { get; set; } [JsonProperty("driveType", NullValueHandling = NullValueHandling.Ignore)] public DriveType DriveType { get; set; } [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)] - public string Path { get; set; } + public string? Path { get; set; } } public enum ConflictBehavior @@ -403,41 +400,41 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class DriveItem : BaseItem { [JsonProperty("parentReference", NullValueHandling = NullValueHandling.Ignore)] - public ItemReference ParentReference { get; set; } + public ItemReference? ParentReference { get; set; } [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] public long? Size { get; set; } [JsonProperty("file", NullValueHandling = NullValueHandling.Ignore)] - public FileFacet File { get; set; } + public FileFacet? File { get; set; } [JsonProperty("folder", NullValueHandling = NullValueHandling.Ignore)] - public FolderFacet Folder { get; set; } + public FolderFacet? Folder { get; set; } [JsonProperty("fileSystemInfo", NullValueHandling = NullValueHandling.Ignore)] - public FileSystemInfoFacet FileSystemInfo { get; set; } + public FileSystemInfoFacet? FileSystemInfo { get; set; } [JsonProperty("package", NullValueHandling = NullValueHandling.Ignore)] - public PackageFacet Package { get; set; } + public PackageFacet? Package { get; set; } [JsonProperty("deleted", NullValueHandling = NullValueHandling.Ignore)] - public DeletedFacet Deleted { get; set; } + public DeletedFacet? Deleted { get; set; } [JsonProperty("remoteItem", NullValueHandling = NullValueHandling.Ignore)] - public RemoteItemFacet RemoteItem { get; set; } + public RemoteItemFacet? RemoteItem { get; set; } [JsonProperty("root", NullValueHandling = NullValueHandling.Ignore)] - public RootFacet Root { get; set; } + public RootFacet? Root { get; set; } [JsonProperty("specialFolder", NullValueHandling = NullValueHandling.Ignore)] - public SpecialFolderFacet SpecialFolder { get; set; } + public SpecialFolderFacet? SpecialFolder { get; set; } [JsonProperty("@microsoft.graph.conflictBehavior", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringEnumConverter))] public ConflictBehavior? ConflictBehavior { get; set; } [JsonProperty("@microsoft.graph.downloadUrl", NullValueHandling = NullValueHandling.Ignore)] - public string DownloadUrl { get; set; } + public string? DownloadUrl { get; set; } [JsonIgnore] public bool IsFile @@ -515,25 +512,25 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class GraphCollection { [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] - public T[] Value { get; set; } + public T[]? Value { get; set; } [JsonProperty("@odata.nextLink", NullValueHandling = NullValueHandling.Ignore)] - public string ODataNextLink { get; set; } + public string? ODataNextLink { get; set; } } public class UploadSession { [JsonProperty("uploadUrl", NullValueHandling = NullValueHandling.Ignore)] - public string UploadUrl { get; set; } + public string? UploadUrl { get; set; } [JsonProperty("expirationDateTime", NullValueHandling = NullValueHandling.Ignore)] public DateTimeOffset? ExpirationDateTime { get; set; } [JsonProperty("nextExpectedRanges", NullValueHandling = NullValueHandling.Ignore)] - public string[] NextExpectedRanges { get; set; } + public string[]? NextExpectedRanges { get; set; } [JsonProperty("item", NullValueHandling = NullValueHandling.Ignore)] - public DriveItem Item { get; set; } + public DriveItem? Item { get; set; } } public class SharePointSite : BaseItem @@ -543,16 +540,16 @@ namespace Duplicati.Library.Backend.MicrosoftGraph public class Group : BaseItem { [JsonProperty("mail", NullValueHandling = NullValueHandling.Ignore)] - public string Mail { get; set; } + public string? Mail { get; set; } [JsonProperty("proxyAddresses", NullValueHandling = NullValueHandling.Ignore)] - public string[] ProxyAddresses { get; set; } + public string[]? ProxyAddresses { get; set; } [JsonProperty("mailNickname", NullValueHandling = NullValueHandling.Ignore)] - public string MailNickname { get; set; } + public string? MailNickname { get; set; } [JsonProperty("groupTypes", NullValueHandling = NullValueHandling.Ignore)] - public string[] GroupTypes { get; set; } + public string[]? GroupTypes { get; set; } [JsonIgnore] public bool IsUnifiedGroup diff --git a/Duplicati/Library/Backend/OneDrive/MicrosoftGroup.cs b/Duplicati/Library/Backend/OneDrive/MicrosoftGroup.cs index c6e1f39cd..f5bca8593 100644 --- a/Duplicati/Library/Backend/OneDrive/MicrosoftGroup.cs +++ b/Duplicati/Library/Backend/OneDrive/MicrosoftGroup.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,13 +18,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; + using Duplicati.Library.Backend.MicrosoftGraph; using Duplicati.Library.Interface; -using Duplicati.Library.Utility; namespace Duplicati.Library.Backend { @@ -34,93 +30,76 @@ namespace Duplicati.Library.Backend private const string GROUP_ID_OPTION = "group-id"; private const string PROTOCOL_KEY = "msgroup"; - private readonly string drivePath; + private readonly Dictionary options; + private string? drivePath; - public MicrosoftGroup() { } // Constructor needed for dynamic loading to find it - - public MicrosoftGroup(string url, Dictionary options) - : base(url, MicrosoftGroup.PROTOCOL_KEY, options) + public MicrosoftGroup() { - string groupId = null; - string groupEmail; - if (options.TryGetValue(GROUP_EMAIL_OPTION, out groupEmail)) - { - // TODO: Should not make network requests in constructor - groupId = this.GetGroupIdFromEmailAsync(groupEmail, CancellationToken.None).Await(); - } + // Constructor needed for dynamic loading to find it + options = null!; + } - string groupIdOption; - if (options.TryGetValue(GROUP_ID_OPTION, out groupIdOption)) + public MicrosoftGroup(string url, Dictionary options) + : base(url, PROTOCOL_KEY, options) + { + this.options = options; + } + + public override string ProtocolKey => PROTOCOL_KEY; + + public override string DisplayName => Strings.MicrosoftGroup.DisplayName; + + protected override async Task GetDrivePath(CancellationToken cancelToken) + { + if (string.IsNullOrEmpty(drivePath)) { - if (!string.IsNullOrEmpty(groupId) && !string.Equals(groupId, groupIdOption)) + string? groupId = null; + var groupEmail = options.GetValueOrDefault(GROUP_EMAIL_OPTION); + if (!string.IsNullOrWhiteSpace(groupEmail)) + groupId = await this.GetGroupIdFromEmailAsync(groupEmail, cancelToken).ConfigureAwait(false); + + var groupIdOption = options.GetValueOrDefault(GROUP_ID_OPTION); + if (!string.IsNullOrWhiteSpace(groupIdOption)) { - throw new UserInformationException(Strings.MicrosoftGroup.ConflictingGroupId(groupIdOption, groupId), "MicrosoftGroupConflictingGroupId"); + if (!string.IsNullOrEmpty(groupId) && !string.Equals(groupId, groupIdOption)) + throw new UserInformationException(Strings.MicrosoftGroup.ConflictingGroupId(groupIdOption, groupId), "MicrosoftGroupConflictingGroupId"); + + groupId = groupIdOption; } - groupId = groupIdOption; + if (string.IsNullOrWhiteSpace(groupId)) + throw new UserInformationException(Strings.MicrosoftGroup.MissingGroupIdAndEmailAddress, "MicrosoftGroupMissingGroupIdAndEmailAddress"); + + drivePath = string.Format("/groups/{0}/drive", groupId); } - if (string.IsNullOrEmpty(groupId)) - { - throw new UserInformationException(Strings.MicrosoftGroup.MissingGroupIdAndEmailAddress, "MicrosoftGroupMissingGroupIdAndEmailAddress"); - } - - this.drivePath = string.Format("/groups/{0}/drive", groupId); + return drivePath; } - public override string ProtocolKey - { - get { return MicrosoftGroup.PROTOCOL_KEY; } - } + protected override DescriptionTemplateDelegate DescriptionTemplate => Strings.MicrosoftGroup.Description; - public override string DisplayName - { - get { return Strings.MicrosoftGroup.DisplayName; } - } - - protected override string DrivePath - { - get { return this.drivePath; } - } - - protected override DescriptionTemplateDelegate DescriptionTemplate - { - get - { - return Strings.MicrosoftGroup.Description; - } - } - - protected override IList AdditionalSupportedCommands - { - get - { - return - [ + protected override IList AdditionalSupportedCommands => [ new CommandLineArgument(GROUP_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.MicrosoftGroup.GroupIdShort, Strings.MicrosoftGroup.GroupIdLong), new CommandLineArgument(GROUP_EMAIL_OPTION, CommandLineArgument.ArgumentType.String, Strings.MicrosoftGroup.GroupEmailShort, Strings.MicrosoftGroup.GroupEmailLong), ]; - } - } private async Task GetGroupIdFromEmailAsync(string email, CancellationToken cancelToken) { // We can get all groups that have the given email as one of their addresses with: // https://graph.microsoft.com/v1.0/groups?$filter=mail eq '{email}' or proxyAddresses/any(x:x eq 'smtp:{email}') string request = string.Format("{0}/groups?$filter=mail eq '{1}' or proxyAddresses/any(x:x eq 'smtp:{1}')", this.ApiVersion, email); - var groups = await this.GetAsync>(request, cancelToken).ConfigureAwait(false); - if (groups.Value.Length == 0) - { + var groups = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => GetAsync>(request, ct)).ConfigureAwait(false); + if (groups.Value == null || groups.Value.Length == 0) throw new UserInformationException(Strings.MicrosoftGroup.NoGroupsWithEmail(email), "MicrosoftGroupNoGroupsWithEmail"); - } - else if (groups.Value.Length > 1) - { + + if (groups.Value.Length > 1) throw new UserInformationException(Strings.MicrosoftGroup.MultipleGroupsWithEmail(email), "MicrosoftGroupMultipleGroupsWithEmail"); - } - else - { - return groups.Value.Single().Id; - } + + var id = groups.Value.Single().Id; + if (string.IsNullOrEmpty(id)) + throw new UserInformationException(Strings.MicrosoftGroup.NoGroupsWithEmail(email), "MicrosoftGroupNoGroupsWithEmail"); + + return id; } } } diff --git a/Duplicati/Library/Backend/OneDrive/OneDriveV2.cs b/Duplicati/Library/Backend/OneDrive/OneDrive.cs similarity index 51% rename from Duplicati/Library/Backend/OneDrive/OneDriveV2.cs rename to Duplicati/Library/Backend/OneDrive/OneDrive.cs index b0d4ff5cd..c263e422d 100644 --- a/Duplicati/Library/Backend/OneDrive/OneDriveV2.cs +++ b/Duplicati/Library/Backend/OneDrive/OneDrive.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,68 +18,44 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; using Duplicati.Library.Interface; namespace Duplicati.Library.Backend { - public class OneDriveV2 : MicrosoftGraphBackend + public class OneDrive : MicrosoftGraphBackend { private const string DRIVE_ID_OPTION = "drive-id"; private const string DEFAULT_DRIVE_PATH = "/me/drive"; private const string PROTOCOL_KEY = "onedrivev2"; - private readonly string drivePath; + private readonly Task drivePath; - public OneDriveV2() { } // Constructor needed for dynamic loading to find it - - public OneDriveV2(string url, Dictionary options) - : base(url, OneDriveV2.PROTOCOL_KEY, options) + public OneDrive() { - string driveId; - if (options.TryGetValue(DRIVE_ID_OPTION, out driveId)) - { - this.drivePath = string.Format("/drives/{0}", driveId); - } - else - { - this.drivePath = DEFAULT_DRIVE_PATH; - } + // Constructor needed for dynamic loading to find it + drivePath = null!; } - public override string ProtocolKey + public OneDrive(string url, Dictionary options) + : base(url, PROTOCOL_KEY, options) { - get { return OneDriveV2.PROTOCOL_KEY; } + var driveId = options.GetValueOrDefault(DRIVE_ID_OPTION); + drivePath = Task.FromResult(string.IsNullOrWhiteSpace(driveId) + ? DEFAULT_DRIVE_PATH + : string.Format("/drives/{0}", driveId)); } - public override string DisplayName - { - get { return Strings.OneDriveV2.DisplayName; } - } + public override string ProtocolKey => PROTOCOL_KEY; - protected override string DrivePath - { - get { return this.drivePath; } - } + public override string DisplayName => Strings.OneDrive.DisplayName; - protected override DescriptionTemplateDelegate DescriptionTemplate - { - get - { - return Strings.OneDriveV2.Description; - } - } + protected override Task GetDrivePath(CancellationToken cancelToken) => drivePath; - protected override IList AdditionalSupportedCommands - { - get - { - return new ICommandLineArgument[] - { - new CommandLineArgument(DRIVE_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.OneDriveV2.DriveIdShort, Strings.OneDriveV2.DriveIdLong(DEFAULT_DRIVE_PATH)), - }; - } - } + protected override DescriptionTemplateDelegate DescriptionTemplate => Strings.OneDrive.Description; + + protected override IList AdditionalSupportedCommands => [ + new CommandLineArgument(DRIVE_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.OneDrive.DriveIdShort, Strings.OneDrive.DriveIdLong(DEFAULT_DRIVE_PATH)), + ]; } } diff --git a/Duplicati/Library/Backend/OneDrive/SharePointV2.cs b/Duplicati/Library/Backend/OneDrive/SharePointV2.cs index 2d1c62059..859ce18ca 100644 --- a/Duplicati/Library/Backend/OneDrive/SharePointV2.cs +++ b/Duplicati/Library/Backend/OneDrive/SharePointV2.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,12 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; + using Duplicati.Library.Backend.MicrosoftGraph; using Duplicati.Library.Interface; @@ -34,67 +29,40 @@ namespace Duplicati.Library.Backend private const string SITE_ID_OPTION = "site-id"; private const string PROTOCOL_KEY = "sharepoint"; - private readonly string drivePath; - private string siteId = null; + private readonly Task drivePath; + private string siteId; - public SharePointV2() { } // Constructor needed for dynamic loading to find it + public SharePointV2() + { + // Constructor needed for dynamic loading to find it + drivePath = null!; + siteId = null!; + } - public SharePointV2(string url, Dictionary options) - : base(url, SharePointV2.PROTOCOL_KEY, options) + public SharePointV2(string url, Dictionary options) + : base(url, PROTOCOL_KEY, options) { // Check to see if a site ID was explicitly provided - string siteIdOption; - if (options.TryGetValue(SITE_ID_OPTION, out siteIdOption)) - { - if (!string.IsNullOrEmpty(this.siteId) && !string.Equals(this.siteId, siteIdOption)) - { - throw new UserInformationException(Strings.SharePointV2.ConflictingSiteId(siteIdOption, this.siteId), "SharePointConflictingSiteId"); - } - - this.siteId = siteIdOption; - } - - if (string.IsNullOrEmpty(this.siteId)) - { + var siteIdOption = options.GetValueOrDefault(SITE_ID_OPTION); + if (!string.IsNullOrWhiteSpace(siteIdOption)) + siteId = siteIdOption; + else throw new UserInformationException(Strings.SharePointV2.MissingSiteId, "SharePointMissingSiteId"); - } - this.drivePath = string.Format("/sites/{0}/drive", this.siteId); + drivePath = Task.FromResult(string.Format("/sites/{0}/drive", siteId)); } - public override string ProtocolKey - { - get { return SharePointV2.PROTOCOL_KEY; } - } + public override string ProtocolKey => PROTOCOL_KEY; - public override string DisplayName - { - get { return Strings.SharePointV2.DisplayName; } - } + public override string DisplayName => Strings.SharePointV2.DisplayName; - protected override string DrivePath - { - get { return this.drivePath; } - } + protected override Task GetDrivePath(CancellationToken cancelToken) => drivePath; - protected override DescriptionTemplateDelegate DescriptionTemplate - { - get - { - return Strings.SharePointV2.Description; - } - } + protected override DescriptionTemplateDelegate DescriptionTemplate => Strings.SharePointV2.Description; - protected override IList AdditionalSupportedCommands - { - get - { - return new ICommandLineArgument[] - { - new CommandLineArgument(SITE_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.SharePointV2.SiteIdShort, Strings.SharePointV2.SiteIdLong), - }; - } - } + protected override IList AdditionalSupportedCommands => [ + new CommandLineArgument(SITE_ID_OPTION, CommandLineArgument.ArgumentType.String, Strings.SharePointV2.SiteIdShort, Strings.SharePointV2.SiteIdLong), + ]; /// /// This method takes an input URL, which could be either in the format that the old SharePoint backend accepted or @@ -107,7 +75,7 @@ namespace Duplicati.Library.Backend /// Path within the drive protected override async Task GetRootPathFromUrlAsync(string url, CancellationToken cancelToken) { - Uri uri = new Uri(url); + var uri = new Uri(url); // If the user gave a URL like "https://{tenant}.sharepoint.com/path" in the UI, // it might appear here as "sharepoint://https://{tenant}.sharepoint.com/path". @@ -118,19 +86,22 @@ namespace Duplicati.Library.Backend uri = new Uri(string.Format("{0}:{1}", uri.Scheme, uri.LocalPath)); } - var site = await this.GetSharePointSite(uri, cancelToken).ConfigureAwait(false); + var site = await GetSharePointSite(uri, cancelToken).ConfigureAwait(false); if (site != null) { // Get the web URL of the site's main drive try { - var drive = await this.GetAsync(string.Format("{0}/sites/{1}/drive", this.ApiVersion, site.Id), cancelToken).ConfigureAwait(false); + var drive = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.GetAsync(string.Format("{0}/sites/{1}/drive", this.ApiVersion, site.Id), ct)).ConfigureAwait(false); - this.siteId = site.Id; - var driveWebUrl = new Uri(drive.WebUrl); + if (drive != null && !string.IsNullOrEmpty(drive.WebUrl) && !string.IsNullOrWhiteSpace(site.Id)) + { + siteId = site.Id; + var driveWebUrl = new Uri(drive.WebUrl); - // Make sure to replace any "//" in the original path with "/", so the substrings line up. - return uri.LocalPath.Replace("//", "/").Substring(driveWebUrl.LocalPath.Length); + // Make sure to replace any "//" in the original path with "/", so the substrings line up. + return uri.LocalPath.Replace("//", "/").Substring(driveWebUrl.LocalPath.Length); + } } catch (MicrosoftGraphException) { @@ -141,7 +112,7 @@ namespace Duplicati.Library.Backend return await base.GetRootPathFromUrlAsync(url, cancelToken).ConfigureAwait(false); } - private async Task GetSharePointSite(Uri url, CancellationToken cancelToken) + private async Task GetSharePointSite(Uri url, CancellationToken cancelToken) { var uri = new UriBuilder(url); @@ -157,7 +128,7 @@ namespace Duplicati.Library.Backend try { var request = string.Format("{0}:/{1}", requestBase, uri.Path.Substring(0, siteHint)); - return await this.GetAsync(request, cancelToken).ConfigureAwait(false); + return await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.GetAsync(request, ct)).ConfigureAwait(false); } catch (MicrosoftGraphException) { @@ -171,7 +142,7 @@ namespace Duplicati.Library.Backend try { string request = string.Format("{0}:/{1}", requestBase, string.Join("/", pathPieces.Take(i))); - return await this.GetAsync(request, cancelToken).ConfigureAwait(false); + return await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => this.GetAsync(request, ct)).ConfigureAwait(false); } catch (MicrosoftGraphException) { diff --git a/Duplicati/Library/Backend/OneDrive/Strings.cs b/Duplicati/Library/Backend/OneDrive/Strings.cs index ad17d0e3a..7a7c3ea2f 100644 --- a/Duplicati/Library/Backend/OneDrive/Strings.cs +++ b/Duplicati/Library/Backend/OneDrive/Strings.cs @@ -1,31 +1,28 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { internal static class MicrosoftGraph { - public static string AuthIdLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } - public static string AuthIdShort { get { return LC.L(@"The authorization code"); } } - public static string MissingAuthId(string url) { return LC.L(@"No Auth-ID was provided - you can get one from {0}", url); } public static string FragmentSizeLong { get { return LC.L(@"Size of individual fragments which are uploaded separately for large files. It is recommended to be between 5-10 MiB (though a smaller value may work better on a slower or less reliable connection), and to be a multiple of 320 KiB."); } } public static string FragmentSizeShort { get { return LC.L(@"Fragment size for large uploads"); } } public static string FragmentRetryCountLong { get { return LC.L(@"Number of retry attempts made for each fragment before failing the overall file upload."); } } @@ -34,12 +31,13 @@ namespace Duplicati.Library.Backend.Strings public static string FragmentRetryDelayShort { get { return LC.L(@"Millisecond delay between fragment errors"); } } public static string UseHttpClientLong { get { return LC.L(@"Use this option to set HttpClient class to perform HTTP requests."); } } public static string UseHttpClientShort { get { return LC.L(@"Whether the HttpClient class should be used"); } } + public static string UseHttpClientDeprecated { get { return LC.L(@"The option --use-http-client is deprecated and has no effect."); } } } - internal static class OneDriveV2 + internal static class OneDrive { public static string Description(string mssadescription, string mssalink, string msopdescription, string msoplink) { return LC.L(@"Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the Microsoft Graph API. Usage of this backend requires that you agree to the terms in {0} ({1}) and {2} ({3}).", mssadescription, mssalink, msopdescription, msoplink); } - public static string DisplayName { get { return LC.L(@"Microsoft OneDrive v2"); } } + public static string DisplayName { get { return LC.L(@"Microsoft OneDrive"); } } public static string DriveIdLong(string defaultDrive) { return LC.L(@"ID of the drive to store data in. If no drive is specified, the default OneDrive or OneDrive for Business drive will be used via '{0}'.", defaultDrive); } public static string DriveIdShort { get { return LC.L(@"Optional ID of the drive"); } } } @@ -51,7 +49,7 @@ namespace Duplicati.Library.Backend.Strings public static string SiteIdLong { get { return LC.L(@"ID of the site to store data in."); } } public static string SiteIdShort { get { return LC.L(@"ID of the site"); } } public static string MissingSiteId { get { return LC.L(@"No site ID was provided"); } } - public static string ConflictingSiteId(string given, string found) { return LC.L(@"Conflicting site IDs used: given {0} but found {1}", given, found); } + public static string MissingDriveInfo { get { return LC.L(@"No drive information was returned"); } } } internal static class MicrosoftGroup diff --git a/Duplicati/Library/Backend/OpenStack/Duplicati.Library.Backend.OpenStack.csproj b/Duplicati/Library/Backend/OpenStack/Duplicati.Library.Backend.OpenStack.csproj index 193c2695f..7de58c33f 100644 --- a/Duplicati/Library/Backend/OpenStack/Duplicati.Library.Backend.OpenStack.csproj +++ b/Duplicati/Library/Backend/OpenStack/Duplicati.Library.Backend.OpenStack.csproj @@ -3,7 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs b/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs index 214cbd40a..280d22cbf 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackConfig.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,18 +19,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using Duplicati.Library.Interface; -using System.Collections.Generic; -using System.Linq; -using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.OpenStack { public class SwiftConfig : IWebModule { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Providers; - private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private static readonly string DEFAULT_CONFIG_TYPE_STR = DEFAULT_CONFIG_TYPE.ToString(); private const string KEY_CONFIGTYPE = "openstack-config"; public enum ConfigType @@ -42,42 +38,32 @@ namespace Duplicati.Library.Backend.OpenStack { } - public System.Collections.Generic.IDictionary Execute(System.Collections.Generic.IDictionary options) + public IDictionary Execute(IDictionary options) { - string k; - options.TryGetValue(KEY_CONFIGTYPE, out k); + options.TryGetValue(KEY_CONFIGTYPE, out var k); if (string.IsNullOrWhiteSpace(k)) k = DEFAULT_CONFIG_TYPE_STR; - ConfigType ct; - if (!Enum.TryParse(k, true, out ct)) + if (!Enum.TryParse(k, true, out var ct)) ct = DEFAULT_CONFIG_TYPE; switch (ct) { case ConfigType.Versions: - return OpenStack.OpenStackStorage.OPENSTACK_VERSIONS.ToDictionary((x) => x.Key, (y) => y.Value); + return OpenStackStorage.OPENSTACK_VERSIONS.ToDictionary((x) => x.Key, (y) => y.Value); default: - return OpenStack.OpenStackStorage.KNOWN_OPENSTACK_PROVIDERS.ToDictionary((x) => x.Key, (y) => y.Value); + return OpenStackStorage.KNOWN_OPENSTACK_PROVIDERS.ToDictionary((x) => x.Key, (y) => y.Value); } } - public string Key { get { return "openstack-getconfig"; } } + public string Key => "openstack-getconfig"; - public string DisplayName { get { return LC.L("OpenStack configuration module"); } } + public string DisplayName => Strings.SwiftConfig.DisplayName; - public string Description { get { return LC.L("Expose OpenStack configuration as a web module"); } } + public string Description => Strings.SwiftConfig.Description; - - public System.Collections.Generic.IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, LC.L("The config to get"), LC.L("Provide different config values"), DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) - - ]); - } - } + public IList SupportedCommands => [ + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, Strings.SwiftConfig.ConfigTypeOptionShort, Strings.SwiftConfig.ConfigTypeOptionLong, DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + ]; } } diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs index a77647b59..7ea70dc4d 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,18 +19,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; using Duplicati.Library.Interface; -using System.Linq; -using System.IO; using Duplicati.Library.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System.Net; using System.Text; using Duplicati.Library.Common.IO; -using System.Threading; +using System.Runtime.CompilerServices; +using Duplicati.Library.Utility.Options; using System.Threading.Tasks; namespace Duplicati.Library.Backend.OpenStack @@ -38,8 +35,6 @@ namespace Duplicati.Library.Backend.OpenStack public class OpenStackStorage : IBackend, IStreamingBackend { private const string DOMAINNAME_OPTION = "openstack-domain-name"; - private const string USERNAME_OPTION = "auth-username"; - private const string PASSWORD_OPTION = "auth-password"; private const string TENANTNAME_OPTION = "openstack-tenant-name"; private const string AUTHURI_OPTION = "openstack-authuri"; private const string VERSION_OPTION = "openstack-version"; @@ -52,19 +47,20 @@ namespace Duplicati.Library.Backend.OpenStack private readonly string m_container; private readonly string m_prefix; - private readonly string m_domainName; + private readonly string? m_domainName; private readonly string m_username; - private readonly string m_password; + private readonly string? m_password; private readonly string m_authUri; - private readonly string m_version; - private readonly string m_tenantName; - private readonly string m_apikey; - private readonly string m_region; + private readonly string? m_version; + private readonly string? m_tenantName; + private readonly string? m_apikey; + private readonly string? m_region; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - protected string m_simplestorageendpoint; + protected string? m_simplestorageendpoint; private readonly WebHelper m_helper; - private OpenStackAuthResponse.TokenClass m_accessToken; + private OpenStackAuthResponse.TokenClass? m_accessToken; public static readonly KeyValuePair[] KNOWN_OPENSTACK_PROVIDERS = { new KeyValuePair("Rackspace US", "https://identity.api.rackspacecloud.com/v2.0"), @@ -75,7 +71,9 @@ namespace Duplicati.Library.Backend.OpenStack new KeyValuePair("Infomaniak Swiss Backup cluster 1", "https://swiss-backup.infomaniak.com/identity/v3"), new KeyValuePair("Infomaniak Swiss Backup cluster 2", "https://swiss-backup02.infomaniak.com/identity/v3"), new KeyValuePair("Infomaniak Swiss Backup cluster 3", "https://swiss-backup03.infomaniak.com/identity/v3"), + new KeyValuePair("Infomaniak Swiss Backup cluster 4", "https://swiss-backup04.infomaniak.com/identity/v3"), new KeyValuePair("Infomaniak Public Cloud 1", "https://api.pub1.infomaniak.cloud/identity/v3"), + new KeyValuePair("Infomaniak Public Cloud 2", "https://api.pub2.infomaniak.cloud/identity/v3"), new KeyValuePair("Catalyst Cloud - nz-hlz-1 (NZ)", "https://api.nz-hlz-1.catalystcloud.io:5000/v3"), new KeyValuePair("Catalyst Cloud - nz-por-1 (NZ)", "https://api.nz-por-1.catalystcloud.io:5000/v3"), }; @@ -90,8 +88,8 @@ namespace Duplicati.Library.Backend.OpenStack { public class AuthContainer { - public Identity identity { get; set; } - public Scope scope { get; set; } + public Identity? identity { get; set; } + public Scope? scope { get; set; } } public class Identity @@ -100,7 +98,7 @@ namespace Duplicati.Library.Backend.OpenStack public IdentityMethods[] methods { get; set; } [JsonProperty("password", NullValueHandling = NullValueHandling.Ignore)] - public PasswordBasedRequest PasswordCredentials { get; set; } + public PasswordBasedRequest? PasswordCredentials { get; set; } public Identity() { @@ -110,7 +108,7 @@ namespace Duplicati.Library.Backend.OpenStack public class Scope { - public Project project; + public Project? project; } public enum IdentityMethods @@ -120,7 +118,7 @@ namespace Duplicati.Library.Backend.OpenStack public class PasswordBasedRequest { - public UserCredentials user { get; set; } + public UserCredentials? user { get; set; } } public class UserCredentials @@ -131,6 +129,9 @@ namespace Duplicati.Library.Backend.OpenStack public UserCredentials() { + domain = null!; + name = null!; + password = null!; } public UserCredentials(Domain domain, string name, string password) { @@ -143,9 +144,9 @@ namespace Duplicati.Library.Backend.OpenStack public class Domain { - public string name { get; set; } + public string? name { get; set; } - public Domain(string name) + public Domain(string? name) { this.name = name; } @@ -165,16 +166,16 @@ namespace Duplicati.Library.Backend.OpenStack public AuthContainer auth { get; set; } - public Keystone3AuthRequest(string domain_name, string username, string password, string project_name) + public Keystone3AuthRequest(string? domain_name, string username, string? password, string? project_name) { Domain domain = new Domain(domain_name); this.auth = new AuthContainer(); this.auth.identity = new Identity(); this.auth.identity.PasswordCredentials = new PasswordBasedRequest(); - this.auth.identity.PasswordCredentials.user = new UserCredentials(domain, username, password); + this.auth.identity.PasswordCredentials.user = new UserCredentials(domain, username, password!); this.auth.scope = new Scope(); - this.auth.scope.project = new Project(domain, project_name); + this.auth.scope.project = new Project(domain, project_name!); } } @@ -183,41 +184,41 @@ namespace Duplicati.Library.Backend.OpenStack public class AuthContainer { [JsonProperty("RAX-KSKEY:apiKeyCredentials", NullValueHandling = NullValueHandling.Ignore)] - public ApiKeyBasedRequest ApiCredentials { get; set; } + public ApiKeyBasedRequest? ApiCredentials { get; set; } [JsonProperty("passwordCredentials", NullValueHandling = NullValueHandling.Ignore)] - public PasswordBasedRequest PasswordCredentials { get; set; } + public PasswordBasedRequest? PasswordCredentials { get; set; } [JsonProperty("tenantName", NullValueHandling = NullValueHandling.Ignore)] - public string TenantName { get; set; } + public string? TenantName { get; set; } [JsonProperty("token", NullValueHandling = NullValueHandling.Ignore)] - public TokenBasedRequest Token { get; set; } + public TokenBasedRequest? Token { get; set; } } public class ApiKeyBasedRequest { - public string username { get; set; } - public string apiKey { get; set; } + public string? username { get; set; } + public string? apiKey { get; set; } } public class PasswordBasedRequest { - public string username { get; set; } - public string password { get; set; } - public string tenantName { get; set; } + public string? username { get; set; } + public string? password { get; set; } + public string? tenantName { get; set; } } public class TokenBasedRequest { - public string id { get; set; } + public string? id { get; set; } } public AuthContainer auth { get; set; } - public OpenStackAuthRequest(string tenantname, string username, string password, string apikey) + public OpenStackAuthRequest(string? tenantname, string username, string? password, string? apikey) { this.auth = new AuthContainer(); this.auth.TenantName = tenantname; @@ -244,70 +245,70 @@ namespace Duplicati.Library.Backend.OpenStack private class Keystone3AuthResponse { - public TokenClass token { get; set; } + public TokenClass? token { get; set; } public class EndpointItem { // 'interface' is a reserved keyword, so we need this decorator to map it [JsonProperty(PropertyName = "interface")] - public string interface_name { get; set; } - public string region { get; set; } - public string url { get; set; } + public string? interface_name { get; set; } + public string? region { get; set; } + public string? url { get; set; } } public class CatalogItem { - public EndpointItem[] endpoints { get; set; } - public string name { get; set; } - public string type { get; set; } + public EndpointItem[]? endpoints { get; set; } + public string? name { get; set; } + public string? type { get; set; } } public class TokenClass { - public CatalogItem[] catalog { get; set; } + public CatalogItem[]? catalog { get; set; } public DateTime? expires_at { get; set; } } } private class OpenStackAuthResponse { - public AccessClass access { get; set; } + public AccessClass? access { get; set; } public class TokenClass { - public string id { get; set; } + public string? id { get; set; } public DateTime? expires { get; set; } } public class EndpointItem { - public string region { get; set; } - public string tenantId { get; set; } - public string publicURL { get; set; } - public string internalURL { get; set; } + public string? region { get; set; } + public string? tenantId { get; set; } + public string? publicURL { get; set; } + public string? internalURL { get; set; } } public class ServiceItem { - public string name { get; set; } - public string type { get; set; } - public EndpointItem[] endpoints { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public EndpointItem[]? endpoints { get; set; } } public class AccessClass { - public TokenClass token { get; set; } - public ServiceItem[] serviceCatalog { get; set; } + public TokenClass? token { get; set; } + public ServiceItem[]? serviceCatalog { get; set; } } } private class OpenStackStorageItem { - public string name { get; set; } + public string? name { get; set; } public DateTime? last_modified { get; set; } public long? bytes { get; set; } - public string content_type { get; set; } - public string subdir { get; set; } + public string? content_type { get; set; } + public string? subdir { get; set; } } private class WebHelper : JSONWebHelper @@ -316,48 +317,62 @@ namespace Duplicati.Library.Backend.OpenStack public WebHelper(OpenStackStorage parent) { m_parent = parent; } - public override HttpWebRequest CreateRequest(string url, string method = null) + public override HttpWebRequest CreateRequest(string url, string? method = null) { var req = base.CreateRequest(url, method); - req.Headers["X-Auth-Token"] = m_parent.AccessToken; + req.Headers["X-Auth-Token"] = m_parent.GetAccessToken(CancellationToken.None).Await(); return req; } } public OpenStackStorage() { + m_container = null!; + m_prefix = null!; + m_timeouts = null!; + m_username = null!; + m_authUri = null!; + m_helper = null!; } - public OpenStackStorage(string url, Dictionary options) + public OpenStackStorage(string url, Dictionary options) { var uri = new Utility.Uri(url); m_container = uri.Host; m_prefix = Util.AppendDirSeparator("/" + uri.Path, "/"); + m_timeouts = TimeoutOptionsHelper.Parse(options); // For OpenStack we do not use a leading slash if (m_prefix.StartsWith("/", StringComparison.Ordinal)) m_prefix = m_prefix.Substring(1); + var auth = AuthOptionsHelper.Parse(options, uri); + options.TryGetValue(DOMAINNAME_OPTION, out m_domainName); - options.TryGetValue(USERNAME_OPTION, out m_username); - options.TryGetValue(PASSWORD_OPTION, out m_password); options.TryGetValue(TENANTNAME_OPTION, out m_tenantName); - options.TryGetValue(AUTHURI_OPTION, out m_authUri); options.TryGetValue(VERSION_OPTION, out m_version); options.TryGetValue(APIKEY_OPTION, out m_apikey); options.TryGetValue(REGION_OPTION, out m_region); - if (string.IsNullOrWhiteSpace(m_username)) - throw new UserInformationException(Strings.OpenStack.MissingOptionError(USERNAME_OPTION), "OpenStackMissingUsername"); - if (string.IsNullOrWhiteSpace(m_authUri)) + if (!auth.HasUsername) + throw new UserInformationException(Strings.OpenStack.MissingOptionError(AuthOptionsHelper.AuthUsernameOption), "OpenStackMissingUsername"); + + m_username = auth.Username!; + m_password = auth.Password; + var authUri = options.GetValueOrDefault(AUTHURI_OPTION); + if (string.IsNullOrWhiteSpace(authUri)) throw new UserInformationException(Strings.OpenStack.MissingOptionError(AUTHURI_OPTION), "OpenStackMissingAuthUri"); + m_authUri = authUri; + if (string.IsNullOrWhiteSpace(m_version) && m_authUri.EndsWith("/v3", StringComparison.OrdinalIgnoreCase)) + m_version = "v3"; + switch (m_version) { case "v3": if (string.IsNullOrWhiteSpace(m_password)) - throw new UserInformationException(Strings.OpenStack.MissingOptionError(PASSWORD_OPTION), "OpenStackMissingPassword"); + throw new UserInformationException(Strings.OpenStack.MissingOptionError(AuthOptionsHelper.AuthPasswordOption), "OpenStackMissingPassword"); if (string.IsNullOrWhiteSpace(m_domainName)) throw new UserInformationException(Strings.OpenStack.MissingOptionError(DOMAINNAME_OPTION), "OpenStackMissingDomainName"); if (string.IsNullOrWhiteSpace(m_tenantName)) @@ -368,7 +383,7 @@ namespace Duplicati.Library.Backend.OpenStack if (string.IsNullOrWhiteSpace(m_apikey)) { if (string.IsNullOrWhiteSpace(m_password)) - throw new UserInformationException(Strings.OpenStack.MissingOptionError(PASSWORD_OPTION), "OpenStackMissingPassword"); + throw new UserInformationException(Strings.OpenStack.MissingOptionError(AuthOptionsHelper.AuthPasswordOption), "OpenStackMissingPassword"); if (string.IsNullOrWhiteSpace(m_tenantName)) throw new UserInformationException(Strings.OpenStack.MissingOptionError(TENANTNAME_OPTION), "OpenStackMissingTenantName"); } @@ -378,15 +393,12 @@ namespace Duplicati.Library.Backend.OpenStack m_helper = new WebHelper(this); } - protected virtual string AccessToken + protected async Task GetAccessToken(CancellationToken cancelToken) { - get - { - if (m_accessToken == null || (m_accessToken.expires.HasValue && (m_accessToken.expires.Value - DateTime.UtcNow).TotalSeconds < 30)) - GetAuthResponse(); + if (m_accessToken == null || (m_accessToken.expires.HasValue && (m_accessToken.expires.Value - DateTime.UtcNow).TotalSeconds < 30)) + await GetAuthResponse(cancelToken).ConfigureAwait(false); - return m_accessToken.id; - } + return m_accessToken!.id!; } private static string JoinUrls(string uri, string fragment) @@ -399,21 +411,19 @@ namespace Duplicati.Library.Backend.OpenStack return JoinUrls(JoinUrls(uri, fragment1), fragment2); } - private void GetAuthResponse() + private Task GetAuthResponse(CancellationToken cancellationToken) { switch (this.m_version) { case "v3": - GetKeystone3AuthResponse(); - break; + return GetKeystone3AuthResponse(cancellationToken); case "v2": default: - GetOpenstackAuthResponse(); - break; + return GetOpenstackAuthResponse(cancellationToken); } } - private Keystone3AuthResponse GetKeystone3AuthResponse() + private async Task GetKeystone3AuthResponse(CancellationToken cancellationToken) { var helper = new JSONWebHelper(); @@ -429,35 +439,50 @@ namespace Duplicati.Library.Backend.OpenStack req.ContentLength = data.Length; req.ContentType = "application/json; charset=UTF-8"; - using (var rs = req.GetRequestStream()) - rs.Write(data, 0, data.Length); - - WebResponse http_response = req.GetResponse(); - - Keystone3AuthResponse resp; - using (var reader = new StreamReader(http_response.GetResponseStream())) + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, async ct => { - resp = Newtonsoft.Json.JsonConvert.DeserializeObject( - reader.ReadToEnd()); - } + using (var rs = req.GetRequestStream()) + await rs.WriteAsync(data, 0, data.Length, ct); + }).ConfigureAwait(false); - string token = http_response.Headers["X-Subject-Token"]; - this.m_accessToken = new OpenStackAuthResponse.TokenClass(); - this.m_accessToken.id = token; - this.m_accessToken.expires = resp.token.expires_at; + + var http_response = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, ct => req.GetResponse()).ConfigureAwait(false); + + var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, ct => + { + using (var reader = new StreamReader(http_response.GetResponseStream())) + return JsonConvert.DeserializeObject(reader.ReadToEnd()) + ?? throw new Exception("Failed to parse response"); + }).ConfigureAwait(false); + + if (resp.token == null) + throw new Exception("No token received"); + + var token = http_response.Headers["X-Subject-Token"]; + if (string.IsNullOrWhiteSpace(token)) + throw new Exception("No token received"); + + this.m_accessToken = new OpenStackAuthResponse.TokenClass() + { + id = token, + expires = resp.token.expires_at + }; // Grab the endpoint now that we have received it anyway - var fileservice = resp.token.catalog.FirstOrDefault(x => string.Equals(x.type, "object-store", StringComparison.OrdinalIgnoreCase)); + var fileservice = (resp.token.catalog ?? []).FirstOrDefault(x => string.Equals(x.type, "object-store", StringComparison.OrdinalIgnoreCase)); if (fileservice == null) throw new Exception("No object-store service found, is this service supported by the provider?"); + if (fileservice.endpoints == null || fileservice.endpoints.Length == 0) + throw new Exception("No endpoints found for object-store service"); + var endpoint = fileservice.endpoints.FirstOrDefault(x => (string.Equals(m_region, x.region) && string.Equals(x.interface_name, "public", StringComparison.OrdinalIgnoreCase))) ?? fileservice.endpoints.First(); m_simplestorageendpoint = endpoint.url; return resp; } - private OpenStackAuthResponse GetOpenstackAuthResponse() + private async Task GetOpenstackAuthResponse(CancellationToken cancellationToken) { var helper = new JSONWebHelper(); @@ -465,53 +490,60 @@ namespace Duplicati.Library.Backend.OpenStack req.Accept = "application/json"; req.Method = "POST"; - var resp = helper.ReadJSONResponse( + var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, ct => helper.ReadJSONResponse( req, new OpenStackAuthRequest(m_tenantName, m_username, m_password, m_apikey) - ); + )).ConfigureAwait(false); + + if (resp.access?.token == null) + throw new Exception("No token received"); m_accessToken = resp.access.token; // Grab the endpoint now that we have received it anyway - var fileservice = resp.access.serviceCatalog.FirstOrDefault(x => string.Equals(x.type, "object-store", StringComparison.OrdinalIgnoreCase)); + var fileservice = (resp.access.serviceCatalog ?? []).FirstOrDefault(x => string.Equals(x.type, "object-store", StringComparison.OrdinalIgnoreCase)); if (fileservice == null) throw new Exception("No object-store service found, is this service supported by the provider?"); - var endpoint = fileservice.endpoints.FirstOrDefault(x => string.Equals(m_region, x.region)) ?? fileservice.endpoints.First(); + var fileserviceendpoints = fileservice.endpoints ?? []; + var endpoint = fileserviceendpoints.FirstOrDefault(x => string.Equals(m_region, x.region)) + ?? fileserviceendpoints.FirstOrDefault(); + + if (endpoint == null) + throw new Exception("No endpoint found for object-store service"); m_simplestorageendpoint = endpoint.publicURL; return resp; } - protected virtual string SimpleStorageEndPoint + protected async Task GetSimpleStorageEndPoint(CancellationToken cancelToken) { - get - { - if (m_simplestorageendpoint == null) - GetAuthResponse(); + if (m_simplestorageendpoint == null) + await GetAuthResponse(cancelToken); - return m_simplestorageendpoint; - } + return m_simplestorageendpoint!; } #region IStreamingBackend implementation public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { - var url = JoinUrls(SimpleStorageEndPoint, m_container, Utility.Uri.UrlPathEncode(m_prefix + remotename)); - using (await m_helper.GetResponseAsync(url, cancelToken, stream, "PUT").ConfigureAwait(false)) + var url = JoinUrls(await GetSimpleStorageEndPoint(cancelToken).ConfigureAwait(false), m_container, Utility.Uri.UrlPathEncode(m_prefix + remotename)); + using (var ts = stream.ObserveReadTimeout(m_timeouts.ShortTimeout, false)) + using (await m_helper.GetResponseAsync(url, cancelToken, ts, "PUT").ConfigureAwait(false)) { } } public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { - var url = JoinUrls(SimpleStorageEndPoint, m_container, Utility.Uri.UrlPathEncode(m_prefix + remotename)); + var url = JoinUrls(await GetSimpleStorageEndPoint(cancelToken).ConfigureAwait(false), m_container, Utility.Uri.UrlPathEncode(m_prefix + remotename)); try { - using (var resp = m_helper.GetResponse(url)) - using (var rs = AsyncHttpRequest.TrySetTimeout(resp.GetResponseStream())) - await Library.Utility.Utility.CopyStreamAsync(rs, stream, cancelToken).ConfigureAwait(false); + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_helper.GetResponse(url)).ConfigureAwait(false)) + using (var rs = AsyncHttpRequest.TrySetTimeout(await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => resp.GetResponseStream()).ConfigureAwait(false))) + using (var ts = rs.ObserveReadTimeout(m_timeouts.ShortTimeout)) + await Utility.Utility.CopyStreamAsync(ts, stream, cancelToken).ConfigureAwait(false); } catch (WebException wex) { @@ -525,11 +557,11 @@ namespace Duplicati.Library.Backend.OpenStack #endregion #region IBackend implementation - private T HandleListExceptions(Func func) + private async Task HandleListExceptions(Func> func) { try { - return func(); + return await func().ConfigureAwait(false); } catch (WebException wex) { @@ -540,11 +572,12 @@ namespace Duplicati.Library.Backend.OpenStack } } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - var plainurl = JoinUrls(SimpleStorageEndPoint, m_container) + string.Format("?format=json&delimiter=/&limit={0}", PAGE_LIMIT); + var plainurl = JoinUrls(await GetSimpleStorageEndPoint(cancelToken).ConfigureAwait(false), m_container) + string.Format("?format=json&delimiter=/&limit={0}", PAGE_LIMIT); if (!string.IsNullOrEmpty(m_prefix)) - plainurl += "&prefix=" + Library.Utility.Uri.UrlEncode(m_prefix); + plainurl += "&prefix=" + Utility.Uri.UrlEncode(m_prefix); var url = plainurl; @@ -553,10 +586,13 @@ namespace Duplicati.Library.Backend.OpenStack var req = m_helper.CreateRequest(url); req.Accept = "application/json"; - var items = HandleListExceptions(() => m_helper.ReadJSONResponse(req)); + var items = await HandleListExceptions(async () => await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => m_helper.ReadJSONResponseAsync(req, ct))).ConfigureAwait(false); foreach (var n in items) { var name = n.name; + if (string.IsNullOrWhiteSpace(name)) + continue; + if (name.StartsWith(m_prefix, StringComparison.Ordinal)) name = name.Substring(m_prefix.Length); @@ -587,36 +623,22 @@ namespace Duplicati.Library.Backend.OpenStack using (FileStream fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { - var url = JoinUrls(SimpleStorageEndPoint, m_container, Library.Utility.Uri.UrlPathEncode(m_prefix + remotename)); - return m_helper.ReadJSONResponseAsync(url, cancelToken, null, "DELETE"); + var url = JoinUrls(await GetSimpleStorageEndPoint(cancelToken).ConfigureAwait(false), m_container, Library.Utility.Uri.UrlPathEncode(m_prefix + remotename)); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_helper.ReadJSONResponseAsync(url, ct, null, "DELETE")).ConfigureAwait(false); } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); + public async Task CreateFolderAsync(CancellationToken cancelToken) { - var url = JoinUrls(SimpleStorageEndPoint, m_container); - using (await m_helper.GetResponseAsync(url, cancelToken, null, "PUT").ConfigureAwait(false)) + var url = JoinUrls(await GetSimpleStorageEndPoint(cancelToken).ConfigureAwait(false), m_container); + using (await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_helper.GetResponseAsync(url, cancelToken, null, "PUT")).ConfigureAwait(false)) { } } - public string DisplayName - { - get - { - return Strings.OpenStack.DisplayName; - } - } - public string ProtocolKey - { - get - { - return "openstack"; - } - } + public string DisplayName => Strings.OpenStack.DisplayName; + public string ProtocolKey => "openstack"; public IList SupportedCommands { get @@ -625,30 +647,27 @@ namespace Duplicati.Library.Backend.OpenStack foreach (var s in KNOWN_OPENSTACK_PROVIDERS) authuris.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - return new List(new[] { + return [ new CommandLineArgument(DOMAINNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.DomainnameOptionShort, Strings.OpenStack.DomainnameOptionLong), - new CommandLineArgument(USERNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.UsernameOptionShort, Strings.OpenStack.UsernameOptionLong), - new CommandLineArgument(PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.OpenStack.PasswordOptionShort, Strings.OpenStack.PasswordOptionLong(TENANTNAME_OPTION)), + new CommandLineArgument(AuthOptionsHelper.AuthUsernameOption, CommandLineArgument.ArgumentType.String, Strings.OpenStack.UsernameOptionShort, Strings.OpenStack.UsernameOptionLong), + new CommandLineArgument(AuthOptionsHelper.AuthPasswordOption, CommandLineArgument.ArgumentType.Password, Strings.OpenStack.PasswordOptionShort, Strings.OpenStack.PasswordOptionLong(TENANTNAME_OPTION)), new CommandLineArgument(TENANTNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.TenantnameOptionShort, Strings.OpenStack.TenantnameOptionLong), new CommandLineArgument(APIKEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.OpenStack.ApikeyOptionShort, Strings.OpenStack.ApikeyOptionLong), new CommandLineArgument(AUTHURI_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.AuthuriOptionShort, Strings.OpenStack.AuthuriOptionLong(authuris.ToString())), new CommandLineArgument(VERSION_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.VersionOptionShort, Strings.OpenStack.VersionOptionLong), new CommandLineArgument(REGION_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.RegionOptionShort, Strings.OpenStack.RegionOptionLong), - }); - } - } - public string Description - { - get - { - return Strings.OpenStack.Description; + .. TimeoutOptionsHelper.GetOptions(), + ]; } } + public string Description => Strings.OpenStack.Description; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new System.Uri(m_authUri).Host, string.IsNullOrWhiteSpace(m_simplestorageendpoint) ? null : new System.Uri(m_simplestorageendpoint).Host - }); + } + .WhereNotNullOrWhiteSpace() + .ToArray()); #endregion #region IDisposable implementation diff --git a/Duplicati/Library/Backend/OpenStack/Strings.cs b/Duplicati/Library/Backend/OpenStack/Strings.cs index 67cd636ab..6abff1fff 100644 --- a/Duplicati/Library/Backend/OpenStack/Strings.cs +++ b/Duplicati/Library/Backend/OpenStack/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,7 +23,8 @@ using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { - internal static class OpenStack { + internal static class OpenStack + { public static string Description { get { return LC.L(@"This backend can read and write data to Swift (OpenStack Object Storage). Allowed format is ""openstack://container/folder""."); } } public static string DisplayName { get { return LC.L(@"OpenStack Simple Storage"); } } public static string MissingOptionError(string optionname) { return LC.L(@"Missing required option: {0}", optionname); } @@ -37,12 +38,20 @@ namespace Duplicati.Library.Backend.Strings public static string TenantnameOptionShort { get { return LC.L(@"Supply the Tenant Name used to connect to the server"); } } public static string ApikeyOptionLong { get { return LC.L(@"The API key can be used to connect without supplying a password and tenant ID with some providers."); } } public static string ApikeyOptionShort { get { return LC.L(@"Supply the API key used to connect to the server"); } } - public static string AuthuriOptionLong(string providers) { return LC.L(@"The authentication URL is used to authenticate the user and find the storage service. The URL commonly ends with ""/v2.0"". Known providers are: {0}{1}", System.Environment.NewLine, providers); } + public static string AuthuriOptionLong(string providers) { return LC.L(@"The authentication URL is used to authenticate the user and find the storage service. The URL commonly ends with ""/v2.0"" for v2 and ""/v3"" for v3. Known providers are: {0}{1}", System.Environment.NewLine, providers); } public static string AuthuriOptionShort { get { return LC.L(@"Supply the authentication URL"); } } public static string VersionOptionLong { get { return LC.L(@"The keystone API version to use. Valid values are 'v2' and 'v3'."); } } public static string VersionOptionShort { get { return LC.L(@"The keystone API version to use"); } } - public static string RegionOptionLong { get { return LC.L(@"This option is only used when creating a container, and is used to indicate where the container should be placed. Consult your provider for a list of valid regions, or leave empty for the default region."); } } - public static string RegionOptionShort { get { return LC.L(@"Supply the region used for creating a container"); } } + public static string RegionOptionLong { get { return LC.L(@"By default, the first reported endpoint will be used for file transfers. To select a specific region, provide the region name. If no such region is supported, the default (first reported) endpoint is used."); } } + public static string RegionOptionShort { get { return LC.L(@"Supply the prefered region for endpoints"); } } + } + + internal static class SwiftConfig + { + public static string Description { get { return LC.L(@"Expose OpenStack configuration as a web module"); } } + public static string DisplayName { get { return LC.L(@"OpenStack configuration module"); } } + public static string ConfigTypeOptionLong { get { return LC.L(@"Provide different config values"); } } + public static string ConfigTypeOptionShort { get { return LC.L(@"The config to get"); } } } } diff --git a/Duplicati/Library/Backend/Rclone/Duplicati.Library.Backend.Rclone.csproj b/Duplicati/Library/Backend/Rclone/Duplicati.Library.Backend.Rclone.csproj index 4b7b509a9..4e9b541f8 100644 --- a/Duplicati/Library/Backend/Rclone/Duplicati.Library.Backend.Rclone.csproj +++ b/Duplicati/Library/Backend/Rclone/Duplicati.Library.Backend.Rclone.csproj @@ -3,7 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Rclone/Rclone.cs b/Duplicati/Library/Backend/Rclone/Rclone.cs index a5bc4bde5..99f1873ff 100644 --- a/Duplicati/Library/Backend/Rclone/Rclone.cs +++ b/Duplicati/Library/Backend/Rclone/Rclone.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,16 +21,12 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { @@ -51,22 +47,28 @@ namespace Duplicati.Library.Backend private readonly string remote_path; private readonly string opt_rclone; private readonly string rclone_executable; + private readonly TimeoutOptionsHelper.Timeouts timeouts; public Rclone() { - + local_repo = null!; + remote_repo = null!; + remote_path = null!; + opt_rclone = null!; + rclone_executable = null!; + timeouts = null!; } - public Rclone(string url, Dictionary options) + public Rclone(string url, Dictionary options) { var uri = new Utility.Uri(url); /*should check here if program is installed */ - local_repo = options.GetValueOrDefault(OPTION_LOCAL_REPO, local_repo); - remote_repo = options.GetValueOrDefault(OPTION_REMOTE_REPO, remote_repo); - remote_path = options.GetValueOrDefault(OPTION_REMOTE_PATH, remote_path); - opt_rclone = options.GetValueOrDefault(OPTION_RCLONE, opt_rclone) ?? ""; - rclone_executable = options.GetValueOrDefault(OPTION_RCLONE_EXECUTABLE, rclone_executable); + local_repo = options.GetValueOrDefault(OPTION_LOCAL_REPO) ?? ""; + remote_repo = options.GetValueOrDefault(OPTION_REMOTE_REPO) ?? ""; + remote_path = options.GetValueOrDefault(OPTION_REMOTE_PATH) ?? ""; + opt_rclone = options.GetValueOrDefault(OPTION_RCLONE) ?? ""; + rclone_executable = options.GetValueOrDefault(OPTION_RCLONE_EXECUTABLE) ?? ""; if (string.IsNullOrWhiteSpace(local_repo)) local_repo = "local"; @@ -77,6 +79,8 @@ namespace Duplicati.Library.Backend if (string.IsNullOrWhiteSpace(rclone_executable)) rclone_executable = "rclone"; + timeouts = TimeoutOptionsHelper.Parse(options); + #if DEBUG Console.WriteLine("Constructor {0}: {1}:{2} {3}", local_repo, remote_repo, remote_path, opt_rclone); #endif @@ -84,17 +88,11 @@ namespace Duplicati.Library.Backend #region IBackendInterface Members - public string DisplayName - { - get { return Strings.Rclone.DisplayName; } - } + public string DisplayName => Strings.Rclone.DisplayName; - public string ProtocolKey - { - get { return "rclone"; } - } + public string ProtocolKey => "rclone"; - private async Task RcloneCommandExecuter(String command, String arguments, CancellationToken cancelToken) + private async Task RcloneCommandExecuter(string command, string arguments, TimeSpan timeout, CancellationToken cancelToken) { StringBuilder outputBuilder = new StringBuilder(); StringBuilder errorBuilder = new StringBuilder(); @@ -122,11 +120,11 @@ namespace Duplicati.Library.Backend EnableRaisingEvents = true }; // attach the event handler for OutputDataReceived before starting the process - process.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler + process.OutputDataReceived += new DataReceivedEventHandler ( - delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) + delegate (object sender, DataReceivedEventArgs e) { - if (!String.IsNullOrEmpty(e.Data)) + if (!string.IsNullOrEmpty(e.Data)) { #if DEBUG // Console.Error.WriteLine(String.Format("output {0}", e.Data)); @@ -137,12 +135,12 @@ namespace Duplicati.Library.Backend } ); - process.ErrorDataReceived += new System.Diagnostics.DataReceivedEventHandler + process.ErrorDataReceived += new DataReceivedEventHandler ( - delegate (object sender, System.Diagnostics.DataReceivedEventArgs e) + delegate (object sender, DataReceivedEventArgs e) { - if (!String.IsNullOrEmpty(e.Data)) + if (!string.IsNullOrEmpty(e.Data)) { #if DEBUG Console.Error.WriteLine("error {0}", e.Data); @@ -160,11 +158,17 @@ namespace Duplicati.Library.Backend process.BeginOutputReadLine(); process.BeginErrorReadLine(); + using var timer = timeout == Timeout.InfiniteTimeSpan + ? new TaskCompletionSource().Task + : Task.Delay(timeout); + + var killed = false; while (!process.HasExited) { await Task.Delay(500).ConfigureAwait(false); - if (cancelToken.IsCancellationRequested) + if (cancelToken.IsCancellationRequested || timer.IsCompleted) { + killed = true; process.Kill(); process.WaitForExit(); } @@ -174,41 +178,43 @@ namespace Duplicati.Library.Backend process.CancelErrorRead(); if (errorBuilder.ToString().Contains(RCLONE_ERROR_DIRECTORY_NOT_FOUND)) - { throw new FolderMissingException(errorBuilder.ToString()); - } if (errorBuilder.ToString().Contains(RCLONE_ERROR_CONFIG_NOT_FOUND)) - { throw new Exception($"Missing config file? {errorBuilder}"); - } if (errorBuilder.Length > 0) - { throw new Exception(errorBuilder.ToString()); + + if (killed) + { + if (timer.IsCompleted) + throw new TimeoutException(); + + throw new TaskCanceledException(); } return outputBuilder.ToString(); } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - String str_result; + string str_result; try { - str_result = RcloneCommandExecuter(rclone_executable, $"lsjson {remote_repo}:{remote_path}", CancellationToken.None).Await(); + str_result = await RcloneCommandExecuter(rclone_executable, $"lsjson {remote_repo}:{remote_path}", timeouts.ListTimeout, cancelToken).ConfigureAwait(false); // this will give an error if the executable does not exist. } - catch (FolderMissingException ex) { throw new FolderMissingException(ex); } - using (JsonReader jsonReader = new JsonTextReader(new StringReader(str_result))) + using (var jsonReader = new JsonTextReader(new StringReader(str_result))) { //no date parsing by JArray needed, will be parsed later jsonReader.DateParseHandling = DateParseHandling.None; @@ -219,14 +225,19 @@ namespace Duplicati.Library.Backend #if DEBUG Console.Error.WriteLine(item); #endif - FileEntry fe = new FileEntry( - item.GetValue("Name").Value(), - item.GetValue("Size").Value(), - DateTime.Parse(item.GetValue("ModTime").Value()), - DateTime.Parse(item.GetValue("ModTime").Value()) + var modTimeString = item.GetValue("ModTime")?.Value(); + var modTime = string.IsNullOrWhiteSpace(modTimeString) + ? new DateTime(0) + : DateTime.Parse(modTimeString); + + var fe = new FileEntry( + item.GetValue("Name")?.Value(), + item.GetValue("Size")?.Value() ?? -1, + modTime, + modTime ) { - IsFolder = item.GetValue("IsDir").Value() + IsFolder = item.GetValue("IsDir")?.Value() ?? false }; yield return fe; } @@ -237,7 +248,7 @@ namespace Duplicati.Library.Backend { try { - await RcloneCommandExecuter(rclone_executable, $"copyto {local_repo}:{filename} {remote_repo}:{remote_path}/{remotename}", cancelToken).ConfigureAwait(false); + await RcloneCommandExecuter(rclone_executable, $"copyto {local_repo}:{filename} {remote_repo}:{remote_path}/{remotename}", Timeout.InfiniteTimeSpan, cancelToken).ConfigureAwait(false); } catch (FolderMissingException ex) { @@ -249,7 +260,7 @@ namespace Duplicati.Library.Backend { try { - await RcloneCommandExecuter(rclone_executable, $"copyto {remote_repo}:{Path.Combine(this.remote_path, remotename)} {local_repo}:{filename}", cancelToken).ConfigureAwait(false); + await RcloneCommandExecuter(rclone_executable, $"copyto {remote_repo}:{Path.Combine(this.remote_path, remotename)} {local_repo}:{filename}", Timeout.InfiniteTimeSpan, cancelToken).ConfigureAwait(false); } catch (FolderMissingException ex) { @@ -263,7 +274,7 @@ namespace Duplicati.Library.Backend // Will give a "directory not found" error if the file does not exist, need to change that to a missing file exception try { - await RcloneCommandExecuter(rclone_executable, $"delete {remote_repo}:{Path.Combine(remote_path, remotename)}", cancelToken).ConfigureAwait(false); + await RcloneCommandExecuter(rclone_executable, $"delete {remote_repo}:{Path.Combine(remote_path, remotename)}", timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); } catch (FolderMissingException ex) { @@ -271,40 +282,26 @@ namespace Duplicati.Library.Backend } } - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(OPTION_LOCAL_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneLocalRepoShort, Strings.Rclone.RcloneLocalRepoLong, "local"), - new CommandLineArgument(OPTION_REMOTE_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemoteRepoShort, Strings.Rclone.RcloneRemoteRepoLong, "remote"), - new CommandLineArgument(OPTION_REMOTE_PATH, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemotePathShort, Strings.Rclone.RcloneRemotePathLong, "backup"), - new CommandLineArgument(OPTION_RCLONE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneOptionRcloneShort, Strings.Rclone.RcloneOptionRcloneLong, ""), - new CommandLineArgument(OPTION_RCLONE_EXECUTABLE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneExecutableShort, Strings.Rclone.RcloneExecutableLong, "rclone") - ]); + public IList SupportedCommands => [ + new CommandLineArgument(OPTION_LOCAL_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneLocalRepoShort, Strings.Rclone.RcloneLocalRepoLong, "local"), + new CommandLineArgument(OPTION_REMOTE_REPO, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemoteRepoShort, Strings.Rclone.RcloneRemoteRepoLong, "remote"), + new CommandLineArgument(OPTION_REMOTE_PATH, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneRemotePathShort, Strings.Rclone.RcloneRemotePathLong, "backup"), + new CommandLineArgument(OPTION_RCLONE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneOptionRcloneShort, Strings.Rclone.RcloneOptionRcloneLong, ""), + new CommandLineArgument(OPTION_RCLONE_EXECUTABLE, CommandLineArgument.ArgumentType.String, Strings.Rclone.RcloneExecutableShort, Strings.Rclone.RcloneExecutableLong, "rclone"), + .. TimeoutOptionsHelper.GetOptions() + .Where(x => x.Name != TimeoutOptionsHelper.ReadWriteTimeoutOption) + ]; - } - } - - public string Description - { - get - { - return Strings.Rclone.Description; - } - } + public string Description => Strings.Rclone.Description; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { remote_repo }); public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public Task CreateFolderAsync(CancellationToken cancelToken) { - return RcloneCommandExecuter(rclone_executable, $"mkdir {remote_repo}:{remote_path}", cancelToken); + return RcloneCommandExecuter(rclone_executable, $"mkdir {remote_repo}:{remote_path}", timeouts.ShortTimeout, cancelToken); } #endregion diff --git a/Duplicati/Library/Backend/Rclone/Strings.cs b/Duplicati/Library/Backend/Rclone/Strings.cs index 2b41a5eab..242343429 100644 --- a/Duplicati/Library/Backend/Rclone/Strings.cs +++ b/Duplicati/Library/Backend/Rclone/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Backend/S3/Duplicati.Library.Backend.S3.csproj b/Duplicati/Library/Backend/S3/Duplicati.Library.Backend.S3.csproj index bf248e538..70e6d0648 100644 --- a/Duplicati/Library/Backend/S3/Duplicati.Library.Backend.S3.csproj +++ b/Duplicati/Library/Backend/S3/Duplicati.Library.Backend.S3.csproj @@ -4,18 +4,20 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable - - - + + + - + diff --git a/Duplicati/Library/Backend/S3/IS3Client.cs b/Duplicati/Library/Backend/S3/IS3Client.cs index b85c458dc..d71c7ccee 100644 --- a/Duplicati/Library/Backend/S3/IS3Client.cs +++ b/Duplicati/Library/Backend/S3/IS3Client.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,17 +18,13 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Duplicati.Library.Interface; namespace Duplicati.Library.Backend { public interface IS3Client : IDisposable { - IEnumerable ListBucket(string bucketName, string prefix); + IAsyncEnumerable ListBucketAsync(string bucketName, string prefix, bool recursive, CancellationToken cancellationToken); Task AddBucketAsync(string bucketName, CancellationToken cancelToken); @@ -36,10 +32,10 @@ namespace Duplicati.Library.Backend Task RenameFileAsync(string bucketName, string source, string target, CancellationToken cancelToken); - Task GetFileStreamAsync(string bucketName, string keyName, System.IO.Stream target, CancellationToken cancelToken); + Task GetFileStreamAsync(string bucketName, string keyName, Stream target, CancellationToken cancelToken); - string GetDnsHost(); + string? GetDnsHost(); - Task AddFileStreamAsync(string bucketName, string keyName, System.IO.Stream source, CancellationToken cancelToken); + Task AddFileStreamAsync(string bucketName, string keyName, Stream source, CancellationToken cancelToken); } } \ No newline at end of file diff --git a/Duplicati/Library/Backend/S3/S3AwsClient.cs b/Duplicati/Library/Backend/S3/S3AwsClient.cs index 023147be9..b044c732d 100644 --- a/Duplicati/Library/Backend/S3/S3AwsClient.cs +++ b/Duplicati/Library/Backend/S3/S3AwsClient.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,13 +22,11 @@ using Amazon.S3; using Amazon.S3.Model; +using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using Duplicati.Library.Utility.Options; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend { @@ -37,47 +35,32 @@ namespace Duplicati.Library.Backend /// public class S3AwsClient : IS3Client { - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private const int ITEM_LIST_LIMIT = 1000; - private readonly string m_locationConstraint; - private readonly string m_storageClass; - private AmazonS3Client m_client; + private const string EXT_OPTION_PREFIX = "s3-ext-"; + + private readonly string? m_locationConstraint; + private readonly string? m_storageClass; + private readonly AmazonS3Client m_client; private readonly bool m_useChunkEncoding; - private readonly string m_dnsHost; + private readonly string? m_dnsHost; + private readonly bool m_useV2ListApi; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - public S3AwsClient(string awsID, string awsKey, string locationConstraint, string servername, - string storageClass, bool useSSL, bool disableChunkEncoding, Dictionary options) + public S3AwsClient(string awsID, string awsKey, string? locationConstraint, string servername, + string? storageClass, bool useSSL, bool disableChunkEncoding, TimeoutOptionsHelper.Timeouts timeouts, Dictionary options) { - var cfg = S3AwsClient.GetDefaultAmazonS3Config(); + var cfg = GetDefaultAmazonS3Config(); cfg.UseHttp = !useSSL; cfg.ServiceURL = (useSSL ? "https://" : "http://") + servername; - foreach (var opt in options.Keys.Where(x => x.StartsWith("s3-ext-", StringComparison.OrdinalIgnoreCase))) - { - var prop = cfg.GetType().GetProperties().FirstOrDefault(x => - string.Equals(x.Name, opt.Substring("s3-ext-".Length), StringComparison.OrdinalIgnoreCase)); - if (prop != null && prop.CanWrite) - { - if (prop.PropertyType == typeof(bool)) - prop.SetValue(cfg, Utility.Utility.ParseBoolOption(options, opt)); - else if (prop.PropertyType.IsEnum) - prop.SetValue(cfg, Enum.Parse(prop.PropertyType, options[opt], true)); - else if (prop.PropertyType == typeof(int)) - prop.SetValue(cfg, int.Parse(options[opt])); - else if (prop.PropertyType == typeof(long)) - prop.SetValue(cfg, long.Parse(options[opt])); - else if (prop.PropertyType == typeof(string)) - prop.SetValue(cfg, options[opt]); - } - - if (prop == null) - Logging.Log.WriteWarningMessage(LOGTAG, "UnsupportedOption", null, "Unsupported option: {0}", opt); - } + CommandLineArgumentMapper.ApplyArguments(cfg, options, EXT_OPTION_PREFIX); m_client = new AmazonS3Client(awsID, awsKey, cfg); + m_timeouts = timeouts; + m_useV2ListApi = string.Equals(options.GetValueOrDefault("list-api-version", "v1"), "v2", StringComparison.OrdinalIgnoreCase); m_locationConstraint = locationConstraint; m_storageClass = storageClass; m_dnsHost = string.IsNullOrWhiteSpace(cfg.ServiceURL) ? null : new System.Uri(cfg.ServiceURL).Host; @@ -94,10 +77,10 @@ namespace Duplicati.Library.Backend if (!string.IsNullOrEmpty(m_locationConstraint)) request.BucketRegionName = m_locationConstraint; - return m_client.PutBucketAsync(request, cancelToken); + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_client.PutBucketAsync(request, ct)); } - internal static AmazonS3Config GetDefaultAmazonS3Config() + public static AmazonS3Config GetDefaultAmazonS3Config() { return new AmazonS3Config() { @@ -111,7 +94,33 @@ namespace Duplicati.Library.Backend }; } - public virtual async Task GetFileStreamAsync(string bucketName, string keyName, System.IO.Stream target, CancellationToken cancelToken) + private static readonly HashSet EXCLUDED_EXTENDED_OPTIONS = new HashSet([ + nameof(AmazonS3Config.USEast1RegionalEndpointValue) + ]); + + /// + /// List of properties that are slow to read the default value from + /// + /// Changes in this list will likely need to be reflected in AWSSecretProvider.cs + private static readonly HashSet SLOW_LOADING_PROPERTIES = new[] { + nameof(AmazonS3Config.RegionEndpoint), + nameof(AmazonS3Config.ServiceURL), + nameof(AmazonS3Config.MaxErrorRetry), + nameof(AmazonS3Config.DefaultConfigurationMode), + nameof(AmazonS3Config.Timeout), + nameof(AmazonS3Config.RetryMode), + }.ToHashSet(); + + public static IEnumerable GetAwsExtendedOptions() + => CommandLineArgumentMapper.MapArguments(GetDefaultAmazonS3Config(), prefix: EXT_OPTION_PREFIX, exclude: EXCLUDED_EXTENDED_OPTIONS, excludeDefaultValue: SLOW_LOADING_PROPERTIES) + .Cast() + .Select(x => + { + x.LongDescription = $"Extended option {x.LongDescription}"; + return x; + }); + + public virtual async Task GetFileStreamAsync(string bucketName, string keyName, Stream target, CancellationToken cancelToken) { try { @@ -121,15 +130,10 @@ namespace Duplicati.Library.Backend Key = keyName }; - using (var objectGetResponse = await m_client.GetObjectAsync(objectGetRequest).ConfigureAwait(false)) + using (var objectGetResponse = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_client.GetObjectAsync(objectGetRequest, ct)).ConfigureAwait(false)) using (var s = objectGetResponse.ResponseStream) - { - // TODO: This does not work and throws InvalidOperationException() - try { s.ReadTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; } - catch { } - - await Utility.Utility.CopyStreamAsync(s, target, cancelToken).ConfigureAwait(false); - } + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(t, target, cancelToken).ConfigureAwait(false); } catch (AmazonS3Exception s3Ex) { @@ -139,19 +143,20 @@ namespace Duplicati.Library.Backend } - public string GetDnsHost() + public string? GetDnsHost() { return m_dnsHost; } - public virtual async Task AddFileStreamAsync(string bucketName, string keyName, System.IO.Stream source, + public virtual async Task AddFileStreamAsync(string bucketName, string keyName, Stream source, CancellationToken cancelToken) { + using var ts = source.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); var objectAddRequest = new PutObjectRequest { BucketName = bucketName, Key = keyName, - InputStream = source, + InputStream = ts, UseChunkEncoding = m_useChunkEncoding }; if (!string.IsNullOrWhiteSpace(m_storageClass)) @@ -180,35 +185,70 @@ namespace Duplicati.Library.Backend Key = keyName }; - return m_client.DeleteObjectAsync(objectDeleteRequest, cancellationToken); + return Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, ct => m_client.DeleteObjectAsync(objectDeleteRequest, ct)); } - public virtual IEnumerable ListBucket(string bucketName, string prefix) + /// + /// Lists the contents of a bucket, using a plugable API call + /// + /// The bucket to list + /// The prefix to list + /// If true, the list is recursive + /// The cancellation token + /// The list of files + public async IAsyncEnumerable ListBucketAsync(string bucketName, string prefix, bool recursive, [EnumeratorCancellation] CancellationToken cancellationToken) { - bool isTruncated = true; - string filename = null; + var isTruncated = true; + string? filename = null; + var delimiter = recursive ? "" : "/"; + if (!string.IsNullOrWhiteSpace(prefix)) + prefix = Util.AppendDirSeparator(prefix, "/"); //TODO: Figure out if this is the case with AWSSDK too //Unfortunately S3 sometimes reports duplicate values when requesting more than one page of results //So, track the files that have already been returned and skip any duplicates. - HashSet alreadyReturned = new HashSet(); + var alreadyReturned = new HashSet(); //We truncate after ITEM_LIST_LIMIT elements, and then repeat while (isTruncated) { - var listRequest = new ListObjectsRequest { BucketName = bucketName }; - - if (!string.IsNullOrEmpty(filename)) - listRequest.Marker = filename; - - listRequest.MaxKeys = ITEM_LIST_LIMIT; - if (!string.IsNullOrEmpty(prefix)) - listRequest.Prefix = prefix; - - ListObjectsResponse listResponse; + ListObjectsV2Response listResponse; try { - listResponse = m_client.ListObjectsAsync(listRequest).Await(); + if (m_useV2ListApi) + { + var listRequest = new ListObjectsV2Request + { + BucketName = bucketName, + Prefix = prefix, + ContinuationToken = filename, + MaxKeys = ITEM_LIST_LIMIT, + Delimiter = delimiter + }; + listResponse = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancellationToken, ct => m_client.ListObjectsV2Async(listRequest, ct)).ConfigureAwait(false); + } + else + { + // Use the V1 API + var listRequest = new ListObjectsRequest + { + BucketName = bucketName, + Prefix = prefix, + Marker = filename, + MaxKeys = ITEM_LIST_LIMIT, + Delimiter = delimiter + }; + var listResponsev1 = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancellationToken, ct => m_client.ListObjectsAsync(listRequest, ct)).ConfigureAwait(false); + + // Map the V1 response to the V2 response + listResponse = new ListObjectsV2Response + { + CommonPrefixes = listResponsev1.CommonPrefixes, + IsTruncated = listResponsev1.IsTruncated, + NextContinuationToken = listResponsev1.NextMarker, + S3Objects = listResponsev1.S3Objects + }; + } } catch (AmazonS3Exception e) { @@ -222,16 +262,37 @@ namespace Duplicati.Library.Backend } isTruncated = listResponse.IsTruncated; - filename = listResponse.NextMarker; + filename = listResponse.NextContinuationToken; + + foreach (var obj in listResponse.CommonPrefixes) + { + if (obj == prefix || !obj.StartsWith(prefix)) + continue; + + // Because the prefixes are returned, and not the folder objects + // we do not get the folder modification date + yield return new FileEntry( + obj.Substring(prefix.Length), + -1, + new DateTime(0), + new DateTime(0) + ) + { IsFolder = true }; + } foreach (var obj in listResponse.S3Objects.Where(obj => alreadyReturned.Add(obj.Key))) { - yield return new Common.IO.FileEntry( - obj.Key, + // Skip self-prefix, this discards the folder modification date :/ + if (obj.Key == prefix || !obj.Key.StartsWith(prefix)) + continue; + + yield return new FileEntry( + obj.Key.Substring(prefix.Length), obj.Size, obj.LastModified, obj.LastModified - ); + ) + { IsFolder = obj.Key.EndsWith("/") }; } } } @@ -246,7 +307,7 @@ namespace Duplicati.Library.Backend DestinationKey = target }; - await m_client.CopyObjectAsync(copyObjectRequest, cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => m_client.CopyObjectAsync(copyObjectRequest, ct)).ConfigureAwait(false); await DeleteObjectAsync(bucketName, source, cancelToken).ConfigureAwait(false); } @@ -256,7 +317,6 @@ namespace Duplicati.Library.Backend { if (m_client != null) m_client.Dispose(); - m_client = null; } #endregion diff --git a/Duplicati/Library/Backend/S3/S3Backend.cs b/Duplicati/Library/Backend/S3/S3Backend.cs index 7d236ffcc..0e0f7c440 100644 --- a/Duplicati/Library/Backend/S3/S3Backend.cs +++ b/Duplicati/Library/Backend/S3/S3Backend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,27 +21,27 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using Duplicati.Library.Utility.Options; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; namespace Duplicati.Library.Backend { - public class S3 : IBackend, IStreamingBackend, IRenameEnabledBackend + public class S3 : IBackend, IStreamingBackend, IRenameEnabledBackend, IFolderEnabledBackend { private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + private const string AUTH_USERNAME_OPTION = "aws-access-key-id"; + private const string AUTH_PASSWORD_OPTION = "aws-secret-access-key"; + private const string STORAGECLASS_OPTION = "s3-storage-class"; private const string SERVER_NAME = "s3-server-name"; private const string LOCATION_OPTION = "s3-location-constraint"; private const string SSL_OPTION = "use-ssl"; private const string S3_CLIENT_OPTION = "s3-client"; private const string S3_DISABLE_CHUNK_ENCODING_OPTION = "s3-disable-chunk-encoding"; + private const string S3_LIST_API_VERSION_OPTION = "s3-list-api-version"; + private const string S3_RECURSIVE_LIST = "s3-recursive-list"; public static readonly Dictionary KNOWN_S3_PROVIDERS = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Amazon S3", "s3.amazonaws.com" }, @@ -65,7 +65,9 @@ namespace Duplicati.Library.Backend { "Infomaniak Swiss Backup cluster 1", "s3.swiss-backup.infomaniak.com" }, { "Infomaniak Swiss Backup cluster 2", "s3.swiss-backup02.infomaniak.com" }, { "Infomaniak Swiss Backup cluster 3", "s3.swiss-backup03.infomaniak.com" }, + { "Infomaniak Swiss Backup cluster 4", "s3.swiss-backup04.infomaniak.com" }, { "Infomaniak Public Cloud 1", "s3.pub1.infomaniak.cloud" }, + { "Infomaniak Public Cloud 2", "s3.pub2.infomaniak.cloud" }, { "さくらのクラウド (Sakura Cloud)", "s3.isk01.sakurastorage.jp" }, { "Seagate Lyve - US-East-1", "https://s3.us-east-1.lyvecloud.seagate.com" }, { "Seagate Lyve - US-West-1", "https://s3.us-west-1.lyvecloud.seagate.com" }, @@ -172,35 +174,32 @@ namespace Duplicati.Library.Backend private readonly string m_bucket; private readonly string m_prefix; + private readonly bool m_recurseLists; private const string DEFAULT_S3_HOST = "s3.amazonaws.com"; - private IS3Client s3Client; + private readonly IS3Client m_s3Client; public S3() { + m_bucket = null!; + m_prefix = null!; + m_s3Client = null!; } - public S3(string url, Dictionary options) + public S3(string url, Dictionary options) { var uri = new Utility.Uri(url); uri.RequireHost(); m_bucket = uri.Host; m_prefix = uri.Path; + var timeout = TimeoutOptionsHelper.Parse(options); - if (!options.TryGetValue("aws-access-key-id", out var awsID)) - options.TryGetValue("auth-username", out awsID); - if (!options.TryGetValue("aws-secret-access-key", out var awsKey)) - options.TryGetValue("auth-password", out awsKey); + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, AUTH_USERNAME_OPTION, AUTH_PASSWORD_OPTION); - if (!string.IsNullOrEmpty(uri.Username)) - awsID = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - awsKey = uri.Password; - - if (string.IsNullOrEmpty(awsID)) + if (!auth.HasUsername) throw new UserInformationException(Strings.S3Backend.NoAMZUserIDError, "S3NoAmzUserID"); - if (string.IsNullOrEmpty(awsKey)) + if (!auth.HasPassword) throw new UserInformationException(Strings.S3Backend.NoAMZKeyError, "S3NoAmzKey"); var useSSL = Utility.Utility.ParseBoolOption(options, SSL_OPTION); @@ -224,21 +223,28 @@ namespace Duplicati.Library.Backend if (m_prefix.Length != 0) m_prefix = Util.AppendDirSeparator(m_prefix, "/"); + m_recurseLists = Utility.Utility.ParseBoolOption(options, S3_RECURSIVE_LIST); + // Auto-disable DNS lookup for non-AWS configurations if (!options.ContainsKey("s3-ext-forcepathstyle") && !hostname.EndsWith(".amazonaws.com", StringComparison.OrdinalIgnoreCase)) options["s3-ext-forcepathstyle"] = "true"; var disableChunkEncoding = Utility.Utility.ParseBoolOption(options, S3_DISABLE_CHUNK_ENCODING_OPTION); - options.TryGetValue(S3_CLIENT_OPTION, out var s3ClientOptionValue); + var s3ClientOptionValue = options.GetValueOrDefault(S3_CLIENT_OPTION); - if (s3ClientOptionValue == "aws" || s3ClientOptionValue == null) + (var awsID, var awsKey) = auth.GetCredentials(); + if (string.IsNullOrWhiteSpace(s3ClientOptionValue) || string.Equals(s3ClientOptionValue, "aws", StringComparison.OrdinalIgnoreCase)) { - s3Client = new S3AwsClient(awsID, awsKey, locationConstraint, hostname, storageClass, useSSL, disableChunkEncoding, options); + m_s3Client = new S3AwsClient(awsID, awsKey, locationConstraint, hostname, storageClass, useSSL, disableChunkEncoding, timeout, options); + } + else if (string.Equals(s3ClientOptionValue, "minio", StringComparison.OrdinalIgnoreCase)) + { + m_s3Client = new S3MinioClient(awsID, awsKey, locationConstraint, hostname, storageClass, useSSL, timeout, options); } else { - s3Client = new S3MinioClient(awsID, awsKey, locationConstraint, hostname, storageClass, useSSL, options); + throw new UserInformationException(Strings.S3Backend.UnknownS3ClientError(s3ClientOptionValue), "UnknownS3Client"); } } @@ -264,20 +270,8 @@ namespace Duplicati.Library.Backend get { return true; } } - - public IEnumerable List() - { - foreach (IFileEntry file in Connection.ListBucket(m_bucket, m_prefix)) - { - ((FileEntry)file).Name = file.Name.Substring(m_prefix.Length); - - //Fix for a bug in Duplicati 1.0 beta 3 and earlier, where filenames are incorrectly prefixed with a slash - if (file.Name.StartsWith("/", StringComparison.Ordinal) && !m_prefix.StartsWith("/", StringComparison.Ordinal)) - ((FileEntry)file).Name = file.Name.Substring(1); - - yield return file; - } - } + public IAsyncEnumerable ListAsync(CancellationToken cancelToken) + => Connection.ListBucketAsync(m_bucket, m_prefix, m_recurseLists, cancelToken); public async Task PutAsync(string remotename, string localname, CancellationToken cancelToken) { @@ -310,43 +304,30 @@ namespace Duplicati.Library.Backend { get { - StringBuilder hostnames = new StringBuilder(); - StringBuilder locations = new StringBuilder(); + var hostnames = new StringBuilder(); + var locations = new StringBuilder(); foreach (var s in KNOWN_S3_PROVIDERS) hostnames.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); foreach (var s in KNOWN_S3_LOCATIONS) locations.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - var defaults = S3AwsClient.GetDefaultAmazonS3Config(); + var exts = S3AwsClient.GetAwsExtendedOptions(); - var exts = - typeof(Amazon.S3.AmazonS3Config).GetProperties().Where(x => x.CanRead && x.CanWrite && (x.PropertyType == typeof(string) || x.PropertyType == typeof(bool) || x.PropertyType == typeof(int) || x.PropertyType == typeof(long) || x.PropertyType.IsEnum)) - .Select(x => (ICommandLineArgument)new CommandLineArgument( - "s3-ext-" + x.Name.ToLowerInvariant(), - x.PropertyType == typeof(bool) ? CommandLineArgument.ArgumentType.Boolean : x.PropertyType.IsEnum ? CommandLineArgument.ArgumentType.Enumeration : CommandLineArgument.ArgumentType.String, - x.Name, - string.Format("Extended option {0}", x.Name), - string.Format("{0}", x.GetValue(defaults)), - null, - x.PropertyType.IsEnum ? Enum.GetNames(x.PropertyType) : null)); - - - var normal = new ICommandLineArgument[] { - new CommandLineArgument("aws-secret-access-key", CommandLineArgument.ArgumentType.Password, Strings.S3Backend.AMZKeyDescriptionShort, Strings.S3Backend.AMZKeyDescriptionLong,null, new string[] {"auth-password"}, null ), - new CommandLineArgument("aws-access-key-id", CommandLineArgument.ArgumentType.String, Strings.S3Backend.AMZUserIDDescriptionShort, Strings.S3Backend.AMZUserIDDescriptionLong, null, new string[] {"auth-username"}, null), + return [ + new CommandLineArgument("aws-access-key-id", CommandLineArgument.ArgumentType.String, Strings.S3Backend.AMZUserIDDescriptionShort, Strings.S3Backend.AMZUserIDDescriptionLong, null, [AuthOptionsHelper.AuthUsernameOption], null), + new CommandLineArgument("aws-secret-access-key", CommandLineArgument.ArgumentType.Password, Strings.S3Backend.AMZKeyDescriptionShort, Strings.S3Backend.AMZKeyDescriptionLong,null, [AuthOptionsHelper.AuthPasswordOption], null ), new CommandLineArgument(STORAGECLASS_OPTION, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3StorageclassDescriptionShort, Strings.S3Backend.S3StorageclassDescriptionLong), new CommandLineArgument(SERVER_NAME, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3ServerNameDescriptionShort, Strings.S3Backend.S3ServerNameDescriptionLong(hostnames.ToString()), DEFAULT_S3_HOST), new CommandLineArgument(LOCATION_OPTION, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3LocationDescriptionShort, Strings.S3Backend.S3LocationDescriptionLong(locations.ToString())), new CommandLineArgument(SSL_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.DescriptionUseSSLShort, Strings.S3Backend.DescriptionUseSSLLong), - new CommandLineArgument(S3_CLIENT_OPTION, CommandLineArgument.ArgumentType.String, Strings.S3Backend.S3ClientDescriptionShort, Strings.S3Backend.S3ClientDescriptionLong), + new CommandLineArgument(S3_CLIENT_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.S3Backend.S3ClientDescriptionShort, Strings.S3Backend.S3ClientDescriptionLong, "aws", null, new string[] { "aws", "minio" }), new CommandLineArgument(S3_DISABLE_CHUNK_ENCODING_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.DescriptionDisableChunkEncodingShort, Strings.S3Backend.DescriptionDisableChunkEncodingLong, "false"), - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.S3Backend.AuthPasswordDescriptionShort, Strings.S3Backend.AuthPasswordDescriptionLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.S3Backend.AuthUsernameDescriptionShort, Strings.S3Backend.AuthUsernameDescriptionLong), - }; - - return normal.Union(exts).ToList(); - + new CommandLineArgument(S3_LIST_API_VERSION_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.S3Backend.DescriptionListApiVersionShort, Strings.S3Backend.DescriptionListApiVersionLong, "v1", null, ["v1", "v2"]), + new CommandLineArgument(S3_RECURSIVE_LIST, CommandLineArgument.ArgumentType.Boolean, Strings.S3Backend.DescriptionRecursiveListShort, Strings.S3Backend.DescriptionRecursiveListLong, "false"), + .. TimeoutOptionsHelper.GetOptions(), + .. exts + ]; } } @@ -359,10 +340,7 @@ namespace Duplicati.Library.Backend } public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public Task CreateFolderAsync(CancellationToken cancelToken) { @@ -385,23 +363,42 @@ namespace Duplicati.Library.Backend public void Dispose() { - s3Client?.Dispose(); - s3Client = null; + m_s3Client?.Dispose(); } #endregion private IS3Client Connection { - get { return s3Client; } + get { return m_s3Client; } } - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { s3Client.GetDnsHost() }); - - private string GetFullKey(string name) + public Task GetDNSNamesAsync(CancellationToken cancelToken) { - //AWS SDK encodes the filenames correctly - return m_prefix + name; + var host = m_s3Client.GetDnsHost(); + return Task.FromResult( + string.IsNullOrWhiteSpace(host) + ? [] + : [host] + ); } + + private string GetFullKey(string? name) + //AWS SDK encodes the filenames correctly + => $"{m_prefix}{name}"; + + /// + public IAsyncEnumerable ListAsync(string? path, CancellationToken cancellationToken) + { + var filterPath = GetFullKey(path); + if (!string.IsNullOrWhiteSpace(filterPath)) + filterPath = Util.AppendDirSeparator(filterPath, "/"); + + return m_s3Client.ListBucketAsync(m_bucket, filterPath, m_recurseLists, cancellationToken); + } + + /// + public Task GetEntryAsync(string path, CancellationToken cancellationToken) + => Task.FromResult(null); } } diff --git a/Duplicati/Library/Backend/S3/S3Config.cs b/Duplicati/Library/Backend/S3/S3Config.cs index f2ef0dec6..03777b5df 100644 --- a/Duplicati/Library/Backend/S3/S3Config.cs +++ b/Duplicati/Library/Backend/S3/S3Config.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,17 +19,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; using Duplicati.Library.Interface; -using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend { public class S3Config : IWebModule { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Providers; - private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private static readonly string DEFAULT_CONFIG_TYPE_STR = DEFAULT_CONFIG_TYPE.ToString(); private const string KEY_CONFIGTYPE = "s3-config"; public enum ConfigType @@ -48,13 +45,11 @@ namespace Duplicati.Library.Backend public IDictionary Execute(IDictionary options) { - string k; - options.TryGetValue(KEY_CONFIGTYPE, out k); + options.TryGetValue(KEY_CONFIGTYPE, out var k); if (string.IsNullOrWhiteSpace(k)) k = DEFAULT_CONFIG_TYPE_STR; - ConfigType ct; - if (!Enum.TryParse(k, true, out ct)) + if (!Enum.TryParse(k, true, out var ct)) ct = DEFAULT_CONFIG_TYPE; switch (ct) @@ -70,22 +65,16 @@ namespace Duplicati.Library.Backend } } - public string Key { get { return "s3-getconfig"; } } + public string Key => "s3-getconfig"; - public string DisplayName { get { return LC.L("S3 configuration module"); } } + public string DisplayName => Strings.S3Config.DisplayName; - public string Description { get { return LC.L("Expose S3 configuration as a web module"); } } + public string Description => Strings.S3Config.Description; - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, LC.L("The config to get"), LC.L("Provide different config values"), DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) - - ]); - } - } + public IList SupportedCommands => + [ + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, Strings.S3Config.ConfigTypeShort, Strings.S3Config.ConfigTypeLong, DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + ]; #endregion } diff --git a/Duplicati/Library/Backend/S3/S3IAM.cs b/Duplicati/Library/Backend/S3/S3IAM.cs index 9ee0bc56e..5c6b6d09e 100644 --- a/Duplicati/Library/Backend/S3/S3IAM.cs +++ b/Duplicati/Library/Backend/S3/S3IAM.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; using Duplicati.Library.Interface; using Amazon.IdentityManagement; using Amazon.IdentityManagement.Model; -using Duplicati.Library.Localization.Short; +using Duplicati.Library.Utility; namespace Duplicati.Library.Backend { @@ -66,71 +63,61 @@ namespace Duplicati.Library.Backend } "; - public string Key { get { return "s3-iamconfig"; } } + public string Key => "s3-iamconfig"; - public string DisplayName { get { return LC.L("S3 IAM support module"); } } + public string DisplayName => Strings.S3IAM.DisplayName; - public string Description { get { return LC.L("Expose S3 IAM manipulation as a web module"); } } + public string Description => Strings.S3IAM.Description; - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(KEY_OPERATION, CommandLineArgument.ArgumentType.Enumeration, LC.L("The operation to perform"), LC.L("Select the operation to perform"), null, Enum.GetNames(typeof(Operation))), - new CommandLineArgument(KEY_USERNAME, CommandLineArgument.ArgumentType.String, LC.L("The username"), LC.L("The Amazon Access Key ID")), - new CommandLineArgument(KEY_PASSWORD, CommandLineArgument.ArgumentType.String, LC.L("The password"), LC.L("The Amazon Secret Key")), + public IList SupportedCommands => new List([ + new CommandLineArgument(KEY_OPERATION, CommandLineArgument.ArgumentType.Enumeration, Strings.S3IAM.OperationShort, Strings.S3IAM.OperationLong, null, Enum.GetNames(typeof(Operation))), + new CommandLineArgument(KEY_USERNAME, CommandLineArgument.ArgumentType.String, Strings.S3IAM.UsernameShort, Strings.S3IAM.UsernameLong), + new CommandLineArgument(KEY_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.S3IAM.PasswordShort, Strings.S3IAM.PasswordLong) ]); - } - } public IDictionary Execute(IDictionary options) { - options.TryGetValue(KEY_OPERATION, out string operationstring); - options.TryGetValue(KEY_USERNAME, out string username); - options.TryGetValue(KEY_PASSWORD, out string password); - options.TryGetValue(KEY_PATH, out string path); + options.TryGetValue(KEY_OPERATION, out var operationstring); + options.TryGetValue(KEY_USERNAME, out var username); + options.TryGetValue(KEY_PASSWORD, out var password); + options.TryGetValue(KEY_PATH, out var path); ValidateArgument(operationstring, KEY_OPERATION); - if (!Enum.TryParse(operationstring, true, out Operation operation)) + if (!Enum.TryParse(operationstring, true, out var operation)) throw new ArgumentException(string.Format("Unable to parse {0} as an operation", operationstring)); switch (operation) { case Operation.GetPolicyDoc: ValidateArgument(path, KEY_PATH); - return GetPolicyDoc(path); + return GetPolicyDoc(path!); case Operation.CreateIAMUser: ValidateArgument(username, KEY_USERNAME); ValidateArgument(password, KEY_PASSWORD); ValidateArgument(path, KEY_PATH); - return CreateUnprivilegedUser(username, password, path); + return CreateUnprivilegedUser(username!, password!, path!); default: ValidateArgument(username, KEY_USERNAME); ValidateArgument(password, KEY_PASSWORD); - return CanCreateUser(username, password); + return CanCreateUser(username!, password!); } } - private static void ValidateArgument(string arg, string type) + private static void ValidateArgument(string? arg, string type) { if (string.IsNullOrWhiteSpace(arg)) - { throw new ArgumentNullException(type); - } } private static IDictionary GetPolicyDoc(string path) - { - return new Dictionary + => new Dictionary { ["doc"] = GeneratePolicyDoc(path) }; - } private static string GeneratePolicyDoc(string path) { @@ -148,7 +135,7 @@ namespace Duplicati.Library.Backend } - private static Boolean DetermineIfCreateUserIsAllowed(User user, AmazonIdentityManagementServiceClient cl) + private static bool DetermineIfCreateUserIsAllowed(User user, AmazonIdentityManagementServiceClient cl) { var simulatePrincipalPolicy = new SimulatePrincipalPolicyRequest { @@ -196,12 +183,13 @@ namespace Duplicati.Library.Backend User user; try { - user = cl.GetUserAsync().GetAwaiter().GetResult().User; + user = cl.GetUserAsync().Await().User; } - catch (Exception ex) when (ex is NoSuchEntityException || ex is ServiceFailureException) + catch (Exception ex) { return new Dictionary { + ["isroot"] = false.ToString(), ["ex"] = ex.ToString(), ["error"] = $"Exception occurred while retrieving user {awsid} : {ex.Message}" }; diff --git a/Duplicati/Library/Backend/S3/S3MinioClient.cs b/Duplicati/Library/Backend/S3/S3MinioClient.cs index 0240dab8d..086704209 100644 --- a/Duplicati/Library/Backend/S3/S3MinioClient.cs +++ b/Duplicati/Library/Backend/S3/S3MinioClient.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,14 +18,13 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.IO; using System.Reactive.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; +using System.Threading.Channels; +using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Minio; using Minio.Exceptions; @@ -35,13 +34,15 @@ namespace Duplicati.Library.Backend { private static readonly string Logtag = Logging.Log.LogTagFromType(); - private MinioClient m_client; - private readonly string m_locationConstraint; + private readonly MinioClient m_client; + private readonly string? m_locationConstraint; private readonly string m_dnsHost; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - public S3MinioClient(string awsID, string awsKey, string locationConstraint, - string servername, string storageClass, bool useSSL, Dictionary options) + public S3MinioClient(string awsID, string awsKey, string? locationConstraint, + string servername, string? storageClass, bool useSSL, TimeoutOptionsHelper.Timeouts timeouts, Dictionary options) { + m_timeouts = timeouts; m_locationConstraint = locationConstraint; m_client = new MinioClient( servername, @@ -58,20 +59,50 @@ namespace Duplicati.Library.Backend m_dnsHost = servername; } - public IEnumerable ListBucket(string bucketName, string prefix) + private static async IAsyncEnumerable ToAsyncEnumerable( + IObservable observable, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { - ThrowExceptionIfBucketDoesNotExist(bucketName); + var channel = Channel.CreateUnbounded(); // Buffered channel for async iteration - var observable = m_client.ListObjectsAsync(bucketName, prefix, true); + using var subscription = observable.Subscribe( + item => channel.Writer.TryWrite(item), + ex => channel.Writer.TryComplete(ex), + () => channel.Writer.TryComplete() + ); - foreach (var obj in observable.ToEnumerable()) + while (await channel.Reader.WaitToReadAsync(cancellationToken)) { - yield return new Common.IO.FileEntry( - obj.Key, + while (channel.Reader.TryRead(out var item)) + { + yield return item; + } + } + } + + public async IAsyncEnumerable ListBucketAsync(string bucketName, string prefix, bool recursive, [EnumeratorCancellation] CancellationToken cancellationToken) + { + await ThrowExceptionIfBucketDoesNotExist(bucketName, cancellationToken).ConfigureAwait(false); + + if (!string.IsNullOrWhiteSpace(prefix)) + prefix = Util.AppendDirSeparator(prefix, "/"); + + var observable = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancellationToken, ct + => m_client.ListObjectsAsync(bucketName, prefix, recursive, ct) + ).ConfigureAwait(false); + + await foreach (var obj in ToAsyncEnumerable(observable, cancellationToken).ConfigureAwait(false)) + { + if (obj.Key == prefix || !obj.Key.StartsWith(prefix)) + continue; + + yield return new FileEntry( + obj.Key.Substring(prefix.Length), (long)obj.Size, Convert.ToDateTime(obj.LastModified), Convert.ToDateTime(obj.LastModified) - ); + ) + { IsFolder = obj.Key.EndsWith("/") }; } } @@ -79,12 +110,15 @@ namespace Duplicati.Library.Backend { try { - await m_client.MakeBucketAsync(bucketName, m_locationConstraint, cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_client.MakeBucketAsync(bucketName, m_locationConstraint, cancelToken) + ).ConfigureAwait(false); } catch (MinioException e) { Logging.Log.WriteErrorMessage(Logtag, "ErrorMakingBucketMinio", null, "Error making bucket {0} using Minio: {1}", bucketName, e.ToString()); + throw; } } @@ -92,13 +126,18 @@ namespace Duplicati.Library.Backend { try { - await m_client.RemoveObjectAsync(bucketName, keyName, cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_client.RemoveObjectAsync(bucketName, keyName, ct) + ).ConfigureAwait(false); } catch (MinioException e) { Logging.Log.WriteErrorMessage(Logtag, "ErrorRemovingObjectMinio", null, "Error removing from bucket {0} object {1} using Minio: {1}", bucketName, keyName, e.ToString()); + + ParseAndThrowNotFoundException(e, keyName, bucketName); + throw; } } @@ -106,14 +145,17 @@ namespace Duplicati.Library.Backend { try { - await m_client.CopyObjectAsync(bucketName, source, - bucketName, target, cancellationToken: cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_client.CopyObjectAsync(bucketName, source, bucketName, target, cancellationToken: ct) + ).ConfigureAwait(false); } catch (MinioException e) { Logging.Log.WriteErrorMessage(Logtag, "ErrorCopyingObjectMinio", null, "Error copying object {0} to {1} in bucket {2} using Minio: {3}", source, target, bucketName, e.ToString()); + + throw; } await DeleteObjectAsync(bucketName, source, cancelToken).ConfigureAwait(false); @@ -127,21 +169,30 @@ namespace Duplicati.Library.Backend // If the object is not found, statObject() throws an exception, // else it means that the object exists. // Execution is successful. - await m_client.StatObjectAsync(bucketName, keyName, cancellationToken: cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct + => m_client.StatObjectAsync(bucketName, keyName, cancellationToken: cancelToken) + ).ConfigureAwait(false); // Get input stream to have content of 'my-objectname' from 'my-bucketname' await m_client.GetObjectAsync(bucketName, keyName, - (stream) => { Utility.Utility.CopyStream(stream, target); }).ConfigureAwait(false); + (stream) => + { + using var t = stream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout); + Utility.Utility.CopyStream(t, target); + }).ConfigureAwait(false); } catch (MinioException e) { Logging.Log.WriteErrorMessage(Logtag, "ErrorGettingObjectMinio", null, "Error getting object {0} to {1} using Minio: {2}", keyName, bucketName, e.ToString()); + + ParseAndThrowNotFoundException(e, keyName, bucketName); + throw; } } - public string GetDnsHost() + public string? GetDnsHost() { return m_dnsHost; } @@ -149,14 +200,15 @@ namespace Duplicati.Library.Backend public virtual async Task AddFileStreamAsync(string bucketName, string keyName, Stream source, CancellationToken cancelToken) { - ThrowExceptionIfBucketDoesNotExist(bucketName); + await ThrowExceptionIfBucketDoesNotExist(bucketName, cancelToken).ConfigureAwait(false); try { + using var t = source.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); await m_client.PutObjectAsync(bucketName, keyName, - source, - source.Length, + t, + t.Length, "application/octet-stream", cancellationToken: cancelToken); } catch (MinioException e) @@ -167,20 +219,23 @@ namespace Duplicati.Library.Backend } } - private void ThrowExceptionIfBucketDoesNotExist(string bucketName) + private void ParseAndThrowNotFoundException(MinioException e, string keyName, string bucketName) { - if (!m_client.BucketExistsAsync(bucketName).Await()) - { - throw new FolderMissingException($"Bucket {bucketName} does not exist."); - } + if (e.ServerResponse?.StatusCode == System.Net.HttpStatusCode.NotFound || e.Response?.Code == "NoSuchKey") + throw new FileMissingException($"File {keyName} not found in bucket {bucketName}"); } + private Task ThrowExceptionIfBucketDoesNotExist(string bucketName, CancellationToken cancelToken) + => Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + if (!await m_client.BucketExistsAsync(bucketName, ct)) + throw new FolderMissingException($"Bucket {bucketName} does not exist."); + }); #region IDisposable Members public void Dispose() { - m_client = null; } #endregion diff --git a/Duplicati/Library/Backend/S3/Strings.cs b/Duplicati/Library/Backend/S3/Strings.cs index 7e4e90ce7..0daf410fd 100644 --- a/Duplicati/Library/Backend/S3/Strings.cs +++ b/Duplicati/Library/Backend/S3/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -49,5 +49,31 @@ namespace Duplicati.Library.Backend.Strings public static string DescriptionDisableChunkEncodingShort { get { return LC.L(@"Disable chunk encoding (aws client only)"); } } public static string S3StorageclassDescriptionLong { get { return LC.L(@"Use this option to specify a storage class. If this option is not used, the server will choose a default storage class."); } } public static string S3StorageclassDescriptionShort { get { return LC.L(@"Specify storage class"); } } + public static string DescriptionListApiVersionShort { get { return LC.L(@"Specify the S3 list API version to use"); } } + public static string DescriptionListApiVersionLong { get { return LC.L(@"Use this option to specify the S3 list API version to use. This can be used to work around issues with some S3 providers."); } } + public static string DescriptionRecursiveListShort { get { return LC.L(@"Use this option to list all files in the bucket"); } } + public static string DescriptionRecursiveListLong { get { return LC.L(@"To reduce the number of objects listed, the default is to only list the first level of objects. Use this option to list all objects in the bucket."); } } + public static string UnknownS3ClientError(string client) { return LC.L(@"Unknown S3 client: {0}", client); } } + + internal static class S3Config + { + public static string DisplayName => LC.L("S3 configuration module"); + public static string Description => LC.L("Expose S3 configuration as a web module"); + public static string ConfigTypeShort => LC.L("The config to get"); + public static string ConfigTypeLong => LC.L("Provide different config values"); + } + + internal static class S3IAM + { + public static string DisplayName => LC.L("S3 IAM support module"); + public static string Description => LC.L("Expose S3 IAM manipulation as a web module"); + public static string OperationShort => LC.L("The operation to perform"); + public static string OperationLong => LC.L("Select the operation to perform"); + public static string UsernameShort => LC.L("The username to use"); + public static string UsernameLong => LC.L("The Amazon Access Key ID"); + public static string PasswordShort => LC.L("The password to use"); + public static string PasswordLong => LC.L("The Amazon Secret Key"); + } + } diff --git a/Duplicati/Library/Backend/SMB/CIFSBackend.cs b/Duplicati/Library/Backend/SMB/CIFSBackend.cs new file mode 100644 index 000000000..425b35fcf --- /dev/null +++ b/Duplicati/Library/Backend/SMB/CIFSBackend.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend; + +/// +/// Native CIFS/SMB Backend implementation +/// +public class CIFSBackend : SMBBackend +{ + /// + /// Log tag for the backend + /// + public static readonly string LOGTAG = Logging.Log.LogTagFromType(); + /// + /// Gets the protocol key for the backend + /// + public override string ProtocolKey => "cifs"; + + /// + /// Gets the display name for the backend + /// + public override string DisplayName => "CIFS (deprecated)"; + + /// + /// Gets the description for the backend + /// + public override string Description => "Same as SMB backend, but with a different name. Use SMB instead."; + + /// + /// Empty constructor is required for the backend to be loaded by the backend factory + /// + public CIFSBackend() : base() + { + } + + /// + /// Actual constructor for the backend that accepts the url and options + /// + /// URL in Duplicati Uri format + /// options to be used in the backend + public CIFSBackend(string url, Dictionary options) + : base(url, options) + { + Logging.Log.WriteWarningMessage(LOGTAG, "DeprecatedCIFSBackend", null, "The CIFS backend is deprecated, please use the SMB backend instead."); + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/SMB/Duplicati.Library.Backend.SMB.csproj b/Duplicati/Library/Backend/SMB/Duplicati.Library.Backend.SMB.csproj new file mode 100644 index 000000000..41f97d11f --- /dev/null +++ b/Duplicati/Library/Backend/SMB/Duplicati.Library.Backend.SMB.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + Library + Copyright © 2025 Team Duplicati, MIT license + enable + enable + Duplicati.Library.Backend.SMB + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/Duplicati/Library/Backend/SMB/Model/SMBConnectionParameters.cs b/Duplicati/Library/Backend/SMB/Model/SMBConnectionParameters.cs new file mode 100644 index 000000000..85a66c43a --- /dev/null +++ b/Duplicati/Library/Backend/SMB/Model/SMBConnectionParameters.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using SMBLibrary; + +namespace Duplicati.Library.Backend.SMB.Model; + +/// +/// Connection parameters for establishing an SMB connection. +/// +/// The name or IP address of the SMB server +/// The transport protocol type used for SMB communication +/// The name of the network share to connect to +/// The path within the share to access +/// The authentication domain name +/// The username for authentication +/// The password for authentication +/// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values) +/// Write buffer size for SMB operations (will be capped automatically by SMB negotiated values) +public sealed record SMBConnectionParameters( + string ServerName, + SMBTransportType TransportType, + string ShareName, + string Path, + string? AuthDomain, + string? AuthUser, + string? AuthPassword, + int? ReadBufferSize, + int? WriteBufferSize); \ No newline at end of file diff --git a/Duplicati/Library/Backend/SMB/SMBBackend.cs b/Duplicati/Library/Backend/SMB/SMBBackend.cs new file mode 100644 index 000000000..4c8cfadac --- /dev/null +++ b/Duplicati/Library/Backend/SMB/SMBBackend.cs @@ -0,0 +1,356 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using Duplicati.Library.Backend.SMB; +using Duplicati.Library.Backend.SMB.Model; +using SMBLibrary; +using Duplicati.Library.SourceProvider; +using System.Runtime.CompilerServices; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Utility.Options; + +namespace Duplicati.Library.Backend; + +/// +/// Native CIFS/SMB Backend implementation +/// +public class SMBBackend : IStreamingBackend, IFolderEnabledBackend +{ + /// + /// Implementation of interface property for the backend key + /// + public virtual string ProtocolKey => "smb"; + + /// + /// Implementation of interface property for the backend display name + /// + public virtual string DisplayName => Strings.SMBBackend.DisplayName; + + /// + /// Implementation of interface property for the backend description + /// + public virtual string Description => Strings.SMBBackend.Description; + + /// + /// Hostname only (no ports or paths) to be used on DNS resolutions. + /// + private string _DnsName; + + /// + /// Path separators (both Windows \ and unix /) to be used in path manipulation + /// + private static readonly char[] PATH_SEPARATORS = ['/', '\\']; + + /// + /// Cache of parsed connection parameters + /// + private readonly SMBConnectionParameters _connectionParameters; + + /// + /// Timeout options to be used in the backend + /// + private readonly TimeoutOptionsHelper.Timeouts _timeouts; + + /// + /// Shared connection between all methods to avoid re-authentication + /// + private SMBShareConnection? _sharedConnection; + + /// + /// Read buffer size for SMB operations (will be capped automatically by SMB negotiated values) + /// + private const string READ_BUFFER_SIZE_OPTION = "read-buffer-size"; + + /// + /// Write buffer size for SMB operations (will be capped automatically by SMB negotiated values) + /// + private const string WRITE_BUFFER_SIZE_OPTION = "write-buffer-size"; + + /// + /// Backend option for controlling the transport (directtcp or netbios) + /// + private const string TRANSPORT_OPTION = "transport"; + + /// + /// Domain (complementary part of authentication) option + /// + private const string AUTH_DOMAIN_OPTION = "auth-domain"; + + /// + /// Defines the default transport to be used in CIFS connection + /// + private const string DEFAULT_TRANSPORT = "directtcp"; + + /// + /// Mapping of transport string to SMBTransportType enum to be used in parsing the option string + /// + private readonly Dictionary _transportMap = new() + { + ["directtcp"] = SMBTransportType.DirectTCPTransport, + ["netbios"] = SMBTransportType.NetBiosOverTCP + }; + + /// + /// Empty constructor is required for the backend to be loaded by the backend factory + /// + public SMBBackend() + { + _DnsName = null!; + _connectionParameters = null!; + _timeouts = null!; + } + + /// + /// Actual constructor for the backend that accepts the url and options + /// + /// URL in Duplicati Uri format + /// options to be used in the backend + public SMBBackend(string url, Dictionary options) + { + if (string.IsNullOrEmpty(url)) + throw new ArgumentNullException(nameof(url)); + if (options == null) + throw new ArgumentNullException(nameof(options)); + + var uri = new Utility.Uri(url); + uri.RequireHost(); + _DnsName = uri.Host; + + var input = uri.Path.TrimEnd('/'); + var slashIndex = input.IndexOf('/'); // Find first slash to separate server and share if present. + + var auth = AuthOptionsHelper.Parse(options, uri); + var authDomain = options.GetValueOrDefault(AUTH_DOMAIN_OPTION); + var transport = options.GetValueOrDefault(TRANSPORT_OPTION); + + int? readBufferSize = null, writeBufferSize = null; + + var readBufferSizeConfig = options.GetValueOrDefault(READ_BUFFER_SIZE_OPTION); + if (!string.IsNullOrWhiteSpace(readBufferSizeConfig)) + readBufferSize = int.TryParse(readBufferSizeConfig, out int value) ? value : null; + + var writeBufferSizeConfig = options.GetValueOrDefault(WRITE_BUFFER_SIZE_OPTION); + if (!string.IsNullOrWhiteSpace(writeBufferSizeConfig)) + writeBufferSize = int.TryParse(readBufferSizeConfig, out int value) ? value : null; + + // Normalize to 10KB minimum buffers size + readBufferSize = readBufferSize < 1024 * 10 ? null : readBufferSize; + writeBufferSize = writeBufferSize < 1024 * 10 ? null : writeBufferSize; + + var transportType = _transportMap.TryGetValue( + string.IsNullOrEmpty(transport) ? DEFAULT_TRANSPORT : transport.ToLower(), + out SMBTransportType type) + ? type + : throw new UserInformationException($"Transport must be one of: {string.Join(", ", _transportMap.Keys)}", "CIFSConfig"); + + _timeouts = TimeoutOptionsHelper.Parse(options); + _connectionParameters = new SMBConnectionParameters( + uri.Host, + transportType, + slashIndex >= 0 ? input[..slashIndex] : input, + slashIndex >= 0 ? input[(slashIndex + 1)..] : "", + authDomain, + auth.Username, + auth.Password, + readBufferSize, + writeBufferSize + ); + } + + /// + /// Implementation of interface property to return supported command parameters + /// + public IList SupportedCommands => + [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(AUTH_DOMAIN_OPTION, CommandLineArgument.ArgumentType.String, Strings.SMBBackend.DescriptionAuthDomainShort, Strings.SMBBackend.DescriptionAuthDomainLong), + new CommandLineArgument(TRANSPORT_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Options.TransportShort, Strings.Options.TransportLong, DEFAULT_TRANSPORT, null, _transportMap.Keys.ToArray()), + new CommandLineArgument(READ_BUFFER_SIZE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Options.DescriptionReadBufferSizeShort, Strings.Options.DescriptionReadBufferSizeLong), + new CommandLineArgument(WRITE_BUFFER_SIZE_OPTION, CommandLineArgument.ArgumentType.String, Strings.Options.DescriptionWriteBufferSizeShort, Strings.Options.DescriptionWriteBufferSizeLong), + .. TimeoutOptionsHelper.GetOptions() + ]; + + /// + /// Implementation of interface method for listing remote folder contents + /// + /// List of IFileEntry with directory listing result + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + foreach (var v in await con.ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false)) + yield return v; + } + + /// + /// Upload files to remote location + /// + /// Filename at remote location + /// Filename to read from + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + public async Task PutAsync(string remotename, string localname, CancellationToken cancellationToken) + { + await using var fs = File.Open(localname, + FileMode.Open, FileAccess.Read, FileShare.Read); + await PutAsync(remotename, fs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Upload files to remote location + /// + /// Filename at remote location + /// Stream to read from + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + public async Task PutAsync(string remotename, Stream input, CancellationToken cancellationToken) + { + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + await con.PutAsync(remotename, input, cancellationToken).ConfigureAwait(false); + } + + /// + /// Download files from remote + /// + /// Filename at remote location + /// Local filename to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, string localname, CancellationToken cancellationToken) + { + await using var fs = File.Open(localname, + FileMode.Create, FileAccess.Write, + FileShare.None); + await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Download files from remote + /// + /// Filename at remote location + /// Destination stream to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken) + { + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + await con.GetAsync(remotename, output, cancellationToken).ConfigureAwait(false); + } + + /// + /// Delete remote file if it exists, if not, throws FileMissingException + /// + /// filename to be deleted on the remote + /// CancellationToken that is combined with internal timeout token + /// + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or business logic errors + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) + { + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + await con.DeleteAsync(remotename, cancellationToken).ConfigureAwait(false); + } + + /// + /// Implementation of interface function to return hosnames used by the backend + /// + /// CancellationToken, in this call not used. + /// + public Task GetDNSNamesAsync(CancellationToken cancellationToken) => + Task.FromResult(new[] { _DnsName ?? string.Empty }); + + /// + /// Tests backend connectivity by verifying the configured path exists + /// + /// The cancellation token (not used) + /// Thrown when configured path does not exist + public async Task TestAsync(CancellationToken cancellationToken) + { + // This will throw an exception if the folder is missing + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + await con.ListAsync(_connectionParameters.Path, cancellationToken).ConfigureAwait(false); + } + + /// + /// Creates the configured remote folder path if it doesn't exist + /// + /// CancellationToken that will be combined with internal timeout token + /// Task representing the asynchronous operation + /// Thrown when folder creation fails + public async Task CreateFolderAsync(CancellationToken cancellationToken) + { + var pathParts = _connectionParameters.Path? + .Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries); + + if (pathParts == null || pathParts.Length == 0) + return; + + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + await con.CreateFolderAsync(_connectionParameters.Path ?? "", cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Gets or creates a shared SMB connection + /// + /// An SMB connection that can be used for file operations + private async Task GetConnectionAsync(CancellationToken cancellationToken) + => _sharedConnection ??= await SMBShareConnection.CreateAsync(_connectionParameters, _timeouts, cancellationToken).ConfigureAwait(false); + + + + /// + /// Implementation of Dispose pattern enforced by interface + /// + public void Dispose() + { + try + { + _sharedConnection?.Dispose(); + } + catch (Exception ex) + { + // Log the exception but don't rethrow since we're in Dispose + System.Diagnostics.Debug.WriteLine($"Error disposing CIFS connection: {ex.Message}"); + } + } + + /// + public async IAsyncEnumerable ListAsync(string? path, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var sourcePath = _connectionParameters.Path; + if (!string.IsNullOrWhiteSpace(sourcePath)) + sourcePath = Util.AppendDirSeparator(sourcePath, "/"); + + var con = await GetConnectionAsync(cancellationToken).ConfigureAwait(false); + foreach (var v in await con.ListAsync(sourcePath + BackendSourceFileEntry.NormalizePathTo(path, '/'), cancellationToken).ConfigureAwait(false)) + if (v.Name != "." && v.Name != "..") + yield return v; + } + + /// + public Task GetEntryAsync(string path, CancellationToken cancellationToken) + => Task.FromResult(null); +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/SMB/SMBShareConnection.cs b/Duplicati/Library/Backend/SMB/SMBShareConnection.cs new file mode 100644 index 000000000..a77bafd12 --- /dev/null +++ b/Duplicati/Library/Backend/SMB/SMBShareConnection.cs @@ -0,0 +1,503 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Backend.SMB.Model; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using Duplicati.Library.Localization.Short; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; +using SMBLibrary; +using SMBLibrary.Client; +using FileAttributes = SMBLibrary.FileAttributes; + +namespace Duplicati.Library.Backend.SMB; + +/// +/// Class the wraps the SMB connection and file store objects, handling the connection, +/// logon and logoff operations as well as safely disposing resources. +/// +public class SMBShareConnection : IDisposable, IAsyncDisposable +{ + /// + /// SMBConnection client + /// + private readonly SMB2Client _smb2Client; + + /// + /// Shared fileStore object. + /// + private readonly ISMBFileStore _smbFileStore; + + /// + /// Connection parameters + /// + private readonly SMBConnectionParameters _connectionParameters; + + /// + /// The timeouts to use for operations. + /// + private readonly TimeoutOptionsHelper.Timeouts _timeouts; + + /// + /// The semaphore to ensure that only one operation is performed at a time. + /// + private readonly SemaphoreSlim _semaphore = new(1, 1); + + /// + /// Flag to indicate if the object has been disposed. + /// + private bool _disposed; + + /// + /// This constructor will connect to the server and share as specified in the connection parameters. + /// + /// It throws specific exceptions for connection and authentication failures. + /// + /// Connection Parameters + /// Timeouts to use for operations + private SMBShareConnection(SMB2Client client, ISMBFileStore fileStore, SMBConnectionParameters connectionParameters, TimeoutOptionsHelper.Timeouts timeouts) + { + _connectionParameters = connectionParameters; + _timeouts = timeouts; + _smbFileStore = fileStore; + _smb2Client = client; + } + + /// + /// Creates a new SMBShareConnection object asynchronously. + /// + /// The connection parameters + /// The timeouts to use for operations + /// The cancellation token + /// The SMBShareConnection object + /// Exception to be displayed to user + public static async Task CreateAsync(SMBConnectionParameters connectionParameters, TimeoutOptionsHelper.Timeouts timeouts, CancellationToken cancellationToken) + { + var client = new SMB2Client(); + var connected = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ => + client.Connect(connectionParameters.ServerName, connectionParameters.TransportType) + ).ConfigureAwait(false); + + if (!connected) + throw new UserInformationException($"{LC.L("Failed to connect to server")} {connectionParameters.ServerName}", "ConnectionError"); + + var status = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ => + client.Login(connectionParameters.AuthDomain ?? "", connectionParameters.AuthUser ?? "", connectionParameters.AuthPassword ?? "") + ).ConfigureAwait(false); + + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to authenticate to server")} {connectionParameters.ServerName} with status {status}", "ConnectionError"); + + (var res, status) = await Utility.Utility.WithTimeout(timeouts.ShortTimeout, cancellationToken, _ => + { + var res = client.TreeConnect(connectionParameters.ShareName, out status); + return (res, status); + } + ).ConfigureAwait(false); + + if (status != NTStatus.STATUS_SUCCESS || res == null) + throw new UserInformationException($"{LC.L("Failed to connect to share")} {connectionParameters.ShareName} with status {status}", "ConnectionError"); + + return new SMBShareConnection(client, res, connectionParameters, timeouts); + } + + /// + /// Deletes the file specified in the path specified in the connection parameters. + /// + /// Filename to be deleted + /// The cancellation token + /// Exception to be displayed to user + public async Task DeleteAsync(string fileName, CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken); + try + { + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ => + { + var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus, NormalizeSlashes(Path.Combine(_connectionParameters.Path, fileName)), + AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE, + FileAttributes.Normal, + ShareAccess.None, + CreateDisposition.FILE_OPEN, + CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, + null); + + if (status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND) + throw new FileMissingException(); + + if (status == NTStatus.STATUS_SUCCESS) + { + var fileDispositionInformation = new FileDispositionInformation + { + DeletePending = true + }; + status = _smbFileStore.SetFileInformation(fileHandle, fileDispositionInformation); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to delete file on DeleteAsync")} with status {status}", "DeleteFileError"); + status = _smbFileStore.CloseFile(fileHandle); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to close file on DeleteAsync")} with status {status}", "CloseFileError"); + } + }).ConfigureAwait(false); + } + finally + { + _semaphore.Release(); + } + } + + /// + /// Create the folder structure specified in the path. + /// + /// Path to create + /// Cancellation Token + /// Exception to be displayed to user + public async Task CreateFolderAsync(string path, CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken); + try + { + // Normalize path separators to forward slashes and trim any trailing separators + var linuxNormalizedPath = path.Replace('/', '\\').TrimEnd('\\'); + var currentPath = ""; + + foreach (var part in linuxNormalizedPath.Split('\\', StringSplitOptions.RemoveEmptyEntries)) + { + if (string.IsNullOrWhiteSpace(part) || part == ".") + continue; + + currentPath = currentPath.Length == 0 ? part : $"{currentPath}\\{part}"; + object? fileHandle = null; + try + { + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ => + { + var status = _smbFileStore.CreateFile( + out fileHandle, + out var fileStatus, + currentPath, + AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, + FileAttributes.Normal, + ShareAccess.None, + CreateDisposition.FILE_CREATE, + CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, + null + ); + if (status != NTStatus.STATUS_SUCCESS && + status != NTStatus.STATUS_OBJECT_NAME_COLLISION) // Ignore if directory already exists + throw new UserInformationException($"{LC.L("Failed to create directory")} {currentPath} with status{status}", "CreateDirectoryError"); + }).ConfigureAwait(false); + } + finally + { + if (fileHandle != null) + { + var status = _smbFileStore.CloseFile(fileHandle); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to close file handle on CreateFolderAsync")} with status {status.ToString()}", "HandleCloseError"); + } + } + } + } + finally + { + _semaphore.Release(); + } + } + + /// + /// Lists the folder contents of the share and path specified in the connection parameters. + /// + /// Path to list + /// Cancellation Token + /// Exception to be displayed to user + public async Task> ListAsync(string path, CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken); + + object? directoryHandle = null; + FileStatus fileStatus; + try + { + try + { + var fileList = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancellationToken, _ => + { + var status = _smbFileStore.CreateFile( + out directoryHandle, + out fileStatus, + NormalizeSlashes(path), + AccessMask.GENERIC_READ, + FileAttributes.Directory, + ShareAccess.Read | ShareAccess.Write, + CreateDisposition.FILE_OPEN, + CreateOptions.FILE_DIRECTORY_FILE, + null); + + if (status != NTStatus.STATUS_SUCCESS && fileStatus != FileStatus.FILE_OPENED) + if (status == NTStatus.STATUS_OBJECT_PATH_NOT_FOUND || status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND) + throw new FolderMissingException(); + else + throw new UserInformationException($"{LC.L("Failed to open directory")} {NormalizeSlashes(path)} with status {status}", "DirectoryOpenError"); + + List fileList; + status = _smbFileStore.QueryDirectory( + out fileList, + directoryHandle, + "*", + FileInformationClass.FileDirectoryInformation); + + if (status != NTStatus.STATUS_NO_MORE_FILES) + throw new UserInformationException($"{LC.L("Failed to query directory contents")} with status {status}", "DirectoryQueryError"); + + return fileList; + }).ConfigureAwait(false); + + return + [ + ..fileList + .OfType() + .Select(info => new FileEntry( + info.FileName, + info.EndOfFile, + info.LastAccessTime, + info.LastWriteTime) + { + IsFolder = info.FileAttributes == FileAttributes.Directory, + Created = info.CreationTime + }) + .ToList() + ]; + } + finally + { + if (directoryHandle != null) + { + var status = _smbFileStore.CloseFile(directoryHandle); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to close directory handle on ListAsync")} with status {status.ToString()}", "HandleCloseError"); + } + } + } + finally + { + _semaphore.Release(); + } + + } + + /// + /// Read a file from source and write it to the destination stream. + /// + /// Filename to be read + /// Destination stream to write contents read from source + /// Cancellation Token + public async Task GetAsync(string filename, Stream destinationStream, CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken); + + try + { + (var status, var fileStatus, var fileHandle) = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ => + { + var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus, + NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)), + AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read, + CreateDisposition.FILE_OPEN, + CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); + return (status, fileStatus, fileHandle); + } + ).ConfigureAwait(false); + + if (status == NTStatus.STATUS_SUCCESS || fileStatus != FileStatus.FILE_DOES_NOT_EXIST) + { + byte[] data; + long bytesRead = 0; + using var timeoutStream = destinationStream.ObserveWriteTimeout(_timeouts.ReadWriteTimeout, false); + while (!cancellationToken.IsCancellationRequested) + { + // Use the provided read buffer size if set, otherwise use the protocol negotiated maximum. Never exceed the negotiated maximum. + int readBufferSize = Math.Min(_connectionParameters.ReadBufferSize ?? (int)_smb2Client.MaxReadSize, (int)_smb2Client.MaxReadSize); + status = _smbFileStore.ReadFile(out data, fileHandle, bytesRead, readBufferSize); + if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) + throw new UserInformationException($"{LC.L("Failed to read file on GetAsync")} {filename} with status {status.ToString()}", "FileReadError"); + + if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0) + break; + + bytesRead += data.Length; + await timeoutStream.WriteAsync(data, 0, data.Length, cancellationToken); + } + + await timeoutStream.FlushAsync(cancellationToken); + + if (fileHandle != null) + { + status = _smbFileStore.CloseFile(fileHandle); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to close file handle on GetAsync")} {filename} with status {status.ToString()}", "HandleCloseError"); + } + } + else if (status == NTStatus.STATUS_OBJECT_NAME_NOT_FOUND) + throw new FileMissingException($"{LC.L("The requested file does not exist")} {filename}"); + else if (status == NTStatus.STATUS_OBJECT_PATH_NOT_FOUND) + throw new FolderMissingException(); + else + throw new UserInformationException( + $"{LC.L("Failed to open file with error")} {filename} with status {status}", + "FileOpenError"); + } + finally + { + _semaphore.Release(); + } + } + + /// + /// Writes file in the remote share and path specified in the connection parameters reading + /// from the sourceStream provided. + /// + /// + /// + /// + /// + public async Task PutAsync(string filename, Stream sourceStream, CancellationToken cancellationToken) + { + await _semaphore.WaitAsync(cancellationToken); + try + { + (var status, var fileStatus, var fileHandle) = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancellationToken, _ => + { + var status = _smbFileStore.CreateFile(out var fileHandle, out var fileStatus, + NormalizeSlashes(Path.Combine(_connectionParameters.Path, filename)), + AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, + FileAttributes.Normal, ShareAccess.None, + CreateDisposition.FILE_SUPERSEDE, + CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, + null); + return (status, fileStatus, fileHandle); + }).ConfigureAwait(false); + + if (status == NTStatus.STATUS_SUCCESS) + { + // Use the provided write buffer size if set, otherwise use the protocol negotiated maximum. Never exceed the negotiated maximum. + var buffer = new byte[Math.Min(_connectionParameters.WriteBufferSize ?? (int)_smb2Client.MaxWriteSize, _smb2Client.MaxWriteSize)]; + int bytesRead; + int numberOfBytesWritten; + int offset = 0; + using var timeoutStream = sourceStream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + while (!cancellationToken.IsCancellationRequested && timeoutStream.Position < timeoutStream.Length) + { + bytesRead = await timeoutStream.ReadAsync(buffer, cancellationToken); + if (bytesRead == 0) + break; + status = _smbFileStore.WriteFile(out numberOfBytesWritten, fileHandle, offset, buffer.Take(bytesRead).ToArray()); + offset += numberOfBytesWritten; + if (numberOfBytesWritten != bytesRead) + throw new UserInformationException(LC.L("Failed to write to file, difference between bytes read and bytes written"), "HandleWriteError"); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to write file on Putasync")} {filename} with status {status.ToString()}", "HandleWriteError"); + } + if (fileHandle != null) + { + status = _smbFileStore.CloseFile(fileHandle); + if (status != NTStatus.STATUS_SUCCESS) + throw new UserInformationException($"{LC.L("Failed to close file handle on PutAsync")} {filename} with status {status.ToString()}", "HandleCloseError"); + } + } + else + { + throw new UserInformationException($"{LC.L("Failed to create file for writing")} {filename} with status {status.ToString()}", "FileCreateError"); + } + } + finally + { + _semaphore.Release(); + } + } + + /// + /// Normalizes paths to use backslashes (for Windows shares compatibility) and removes trailing slashes. + /// + /// Samba deals with \ and / in paths, but Windows shares require backslashes. + /// + /// Path to be normalized + /// + private string NormalizeSlashes(string path) + { + return path.Replace('/', '\\').TrimEnd('\\'); + } + + /// + /// Synchronously dispose the resources. + /// + public void Dispose() + { + DisposeResourcesAsync().AsTask().GetAwaiter().GetResult(); + } + + /// + /// Async dispose implementation + /// + public async ValueTask DisposeAsync() + { + await DisposeResourcesAsync(); + GC.SuppressFinalize(this); + } + + /// + /// Inner dispose implementation + /// + private async ValueTask DisposeResourcesAsync() + { + if (_disposed) return; + + await _semaphore.WaitAsync(); // Ensure no operations are in progress + try + { + if (_smbFileStore != null) + { + try + { + await Task.Run(() => _smbFileStore.Disconnect()); + } + catch { /* Can be safely ignored */ } + } + + if (_smb2Client != null) + { + try + { + await Task.Run(() => _smb2Client.Logoff()); + await Task.Run(() => _smb2Client.Disconnect()); + } + catch { /* Can be safely ignored */ } + } + + _semaphore.Dispose(); + } + finally + { + _disposed = true; + } + } +} diff --git a/Duplicati/Library/Backend/SMB/Strings.cs b/Duplicati/Library/Backend/SMB/Strings.cs new file mode 100644 index 000000000..e0567cd11 --- /dev/null +++ b/Duplicati/Library/Backend/SMB/Strings.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Localization.Short; + +namespace Duplicati.Library.Backend.Strings +{ + internal static class SMBBackend + { + + public static string DescriptionAuthDomainLong => LC.L(@"The domain used to connect to the server. This may also be supplied as the environment variable ""AUTH_DOMAIN""."); + public static string DescriptionAuthDomainShort => LC.L(@"Supply the domain used to connect to the server"); + public static string Description => LC.L(@"This backend can read and write data to CIFS/SMB destinations. Allowed format is ""cifs://server/share""."); + public static string DisplayName => LC.L(@"CIFS/SMB"); + } + + internal static class Options + { + public static string TransportShort => + LC.L( + @"Defines the transport to be used in CIFS connection"); + public static string TransportLong => + LC.L( + @"Defines the transport to be used in CIFS connection. Can be DirectTCP or NetBios"); + public static string DescriptionReadBufferSizeShort => LC.L(@"Read buffer size for SMB operations."); + public static string DescriptionReadBufferSizeLong => LC.L(@"Read buffer size for SMB operations (Will be capped automatically by SMB negotiated values, values bellow 10000 bytes will be ignored)"); + public static string DescriptionWriteBufferSizeShort => LC.L(@"Write buffer size for SMB operations."); + public static string DescriptionWriteBufferSizeLong => LC.L(@"Write buffer size for SMB operations (Will be capped automatically by SMB negotiated values, values bellow 10000 bytes will be ignored)"); + } +} diff --git a/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj b/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj index 5c7100b7a..a4427a708 100644 --- a/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj +++ b/Duplicati/Library/Backend/SSHv2/Duplicati.Library.Backend.SSHv2.csproj @@ -4,15 +4,17 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable - - + + diff --git a/Duplicati/Library/Backend/SSHv2/KeyGenerator.cs b/Duplicati/Library/Backend/SSHv2/KeyGenerator.cs index c5a470181..05ac23ca4 100644 --- a/Duplicati/Library/Backend/SSHv2/KeyGenerator.cs +++ b/Duplicati/Library/Backend/SSHv2/KeyGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; using System.Security.Cryptography; using Duplicati.Library.Interface; -using Duplicati.Library.Utility; using System.Text; -using System.Linq; namespace Duplicati.Library.Backend { @@ -34,19 +30,19 @@ namespace Duplicati.Library.Backend private const string KEY_TYPE_NAME = "key-type"; private const string KEY_USERNAME = "key-username"; private const string KEY_KEYLEN = "key-bits"; - + private const string KEY_TEMPLATE_DSA = "-----BEGIN DSA PRIVATE KEY-----\n{0}\n-----END DSA PRIVATE KEY----- \n"; private const string KEY_TEMPLATE_RSA = "-----BEGIN RSA PRIVATE KEY-----\n{0}\n-----END RSA PRIVATE KEY----- \n"; private const string PUB_KEY_FORMAT_DSA = "ssh-dss"; private const string PUB_KEY_FORMAT_RSA = "ssh-rsa"; - + private const string KEYTYPE_DSA = "dsa"; private const string KEYTYPE_RSA = "rsa"; - + private readonly string DEFAULT_USERNAME = "backup-user@" + System.Environment.MachineName; - private const string DEFAULT_KEYTYPE = KEYTYPE_DSA; + private const string DEFAULT_KEYTYPE = KEYTYPE_DSA; private const int DEFAULT_KEYLEN = 1024; - + private static void EncodePEMLength(System.IO.Stream s, uint length) { s.WriteByte((byte)((length >> 24) & 0xff)); @@ -54,7 +50,7 @@ namespace Duplicati.Library.Backend s.WriteByte((byte)((length >> 8) & 0xff)); s.WriteByte((byte)(length & 0xff)); } - + private static void EncodeDERLength(System.IO.Stream s, uint length) { if (length < 0x7f) @@ -76,27 +72,27 @@ namespace Duplicati.Library.Backend s.WriteByte((byte)(length & 0xff)); } } - + private static byte[] EncodeDER(byte[][] data) { byte[] payload; - using(var ms = new System.IO.MemoryStream()) + using (var ms = new MemoryStream()) { - foreach(var b in data) + foreach (var b in data) { ms.WriteByte(0x02); var isNegative = (b[0] & 0x80) != 0; - + EncodeDERLength(ms, (uint)(b.Length + (isNegative ? 1 : 0))); if (isNegative) ms.WriteByte(0); - ms.Write(b, 0, b.Length); + ms.Write(b, 0, b.Length); } - + payload = ms.ToArray(); } - - using(var ms = new System.IO.MemoryStream()) + + using (var ms = new MemoryStream()) { ms.WriteByte(0x30); EncodeDERLength(ms, (uint)payload.Length); @@ -104,12 +100,12 @@ namespace Duplicati.Library.Backend return ms.ToArray(); } } - + private static byte[] EncodePEM(byte[][] data) { - using(var ms = new System.IO.MemoryStream()) + using (var ms = new System.IO.MemoryStream()) { - foreach(var n in data) + foreach (var n in data) { var isNegative = (n[0] & 0x80) != 0; EncodePEMLength(ms, (uint)(n.Length + (isNegative ? 1 : 0))); @@ -120,20 +116,20 @@ namespace Duplicati.Library.Backend return ms.ToArray(); } } - + private static IDictionary OutputKey(byte[] private_key, byte[] public_key, string pem_template, string key_name, string username) { var res = new Dictionary(); - + var b64_raw = Convert.ToBase64String(private_key); var sb = new StringBuilder(); var lw = 64; - for(var i = 0; i < b64_raw.Length; i += lw) + for (var i = 0; i < b64_raw.Length; i += lw) { sb.Append(b64_raw.Substring(i, Math.Min(lw, b64_raw.Length - i))); sb.Append("\n"); } - + var b64 = sb.ToString().Trim(); var pem = string.Format(pem_template, b64); var uri = SSHv2.KEYFILE_URI + Duplicati.Library.Utility.Uri.UrlEncode(pem); @@ -145,24 +141,20 @@ namespace Duplicati.Library.Backend res["pubkey"] = pub; return res; } - + #region IWebModule implementation public IDictionary Execute(IDictionary options) - { - string keytype; - if (!options.TryGetValue(KEY_TYPE_NAME, out keytype)) + { + if (!options.TryGetValue(KEY_TYPE_NAME, out var keytype)) keytype = DEFAULT_KEYTYPE; - string username; - if (!options.TryGetValue(KEY_USERNAME, out username)) + if (!options.TryGetValue(KEY_USERNAME, out var username)) username = DEFAULT_USERNAME; - string keylen_s; - if (!options.TryGetValue(KEY_KEYLEN, out keylen_s)) + if (!options.TryGetValue(KEY_KEYLEN, out var keylen_s)) keylen_s = "0"; - int keylen; - if (!int.TryParse(keylen_s, out keylen)) + if (!int.TryParse(keylen_s, out var keylen)) keylen = DEFAULT_KEYLEN; if (KEYTYPE_RSA.Equals(keytype, StringComparison.OrdinalIgnoreCase)) @@ -172,38 +164,37 @@ namespace Duplicati.Library.Backend rsa.KeySize = keylen; else rsa.KeySize = DEFAULT_KEYLEN; - + var key = rsa.ExportParameters(true); - - var privateEntries = new byte[][] { new byte[]{ 0x0 }, key.Modulus, key.Exponent, key.D, key.P, key.Q, key.DP, key.DQ, key.InverseQ }; - var publicEntries = new byte[][] { - System.Text.Encoding.ASCII.GetBytes(PUB_KEY_FORMAT_RSA), - key.Exponent, - key.Modulus + + var privateEntries = new byte[][] { new byte[] { 0x0 }, key.Modulus ?? [], key.Exponent ?? [], key.D ?? [], key.P ?? [], key.Q ?? [], key.DP ?? [], key.DQ ?? [], key.InverseQ ?? [] }; + var publicEntries = new byte[][] { + Encoding.ASCII.GetBytes(PUB_KEY_FORMAT_RSA), + key.Exponent ?? [], + key.Modulus ?? [] }; - + return OutputKey(EncodeDER(privateEntries), EncodePEM(publicEntries), KEY_TEMPLATE_RSA, PUB_KEY_FORMAT_RSA, username); } else if (KEYTYPE_DSA.Equals(keytype, StringComparison.OrdinalIgnoreCase)) { - var dsa = DSACryptoServiceProvider.Create(); if (keylen > 0) dsa.KeySize = keylen; else dsa.KeySize = DEFAULT_KEYLEN; - + var key = dsa.ExportParameters(true); - - var privateEntries = new byte[][] { new byte[] { 0x0 }, key.P, key.Q, key.G, key.Y, key.X }; + + var privateEntries = new byte[][] { new byte[] { 0x0 }, key.P ?? [], key.Q ?? [], key.G ?? [], key.Y ?? [], key.X ?? [] }; var publicEntries = new byte[][] { - System.Text.Encoding.ASCII.GetBytes(PUB_KEY_FORMAT_DSA), - key.P, - key.Q, - key.G, - key.Y + Encoding.ASCII.GetBytes(PUB_KEY_FORMAT_DSA), + key.P ?? [], + key.Q ?? [], + key.G ?? [], + key.Y ?? [] }; - + return OutputKey(EncodeDER(privateEntries), EncodePEM(publicEntries), KEY_TEMPLATE_DSA, PUB_KEY_FORMAT_DSA, username); } else @@ -211,21 +202,15 @@ namespace Duplicati.Library.Backend throw new UserInformationException(string.Format("Unsupported key type: {0}", keytype), "SSHUnsupportedKey"); } } - public string Key { get { return "ssh-keygen"; } } - - public string DisplayName { get { return Strings.KeyGenerator.DisplayName; } } - public string Description { get { return Strings.KeyGenerator.Description; } } - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(KEY_KEYLEN, CommandLineArgument.ArgumentType.Integer, Strings.KeyGenerator.KeyLenShort, Strings.KeyGenerator.KeyLenLong, DEFAULT_KEYLEN.ToString(), null, new string[] {"1024", "2048"}), - new CommandLineArgument(KEY_TYPE_NAME, CommandLineArgument.ArgumentType.Enumeration, Strings.KeyGenerator.KeyTypeShort, Strings.KeyGenerator.KeyTypeLong, DEFAULT_KEYTYPE, null, new string[] {KEYTYPE_DSA, KEYTYPE_RSA}), - new CommandLineArgument(KEY_USERNAME, CommandLineArgument.ArgumentType.Integer, Strings.KeyGenerator.KeyUsernameShort, Strings.KeyGenerator.KeyUsernameLong, DEFAULT_USERNAME), - }); - } - } + public string Key => "ssh-keygen"; + + public string DisplayName => Strings.KeyGenerator.DisplayName; + public string Description => Strings.KeyGenerator.Description; + public IList SupportedCommands => [ + new CommandLineArgument(KEY_KEYLEN, CommandLineArgument.ArgumentType.Integer, Strings.KeyGenerator.KeyLenShort, Strings.KeyGenerator.KeyLenLong, DEFAULT_KEYLEN.ToString(), null, new string[] {"1024", "2048"}), + new CommandLineArgument(KEY_TYPE_NAME, CommandLineArgument.ArgumentType.Enumeration, Strings.KeyGenerator.KeyTypeShort, Strings.KeyGenerator.KeyTypeLong, DEFAULT_KEYTYPE, null, new string[] {KEYTYPE_DSA, KEYTYPE_RSA}), + new CommandLineArgument(KEY_USERNAME, CommandLineArgument.ArgumentType.Integer, Strings.KeyGenerator.KeyUsernameShort, Strings.KeyGenerator.KeyUsernameLong, DEFAULT_USERNAME), + ]; #endregion } } diff --git a/Duplicati/Library/Backend/SSHv2/KeyUploader.cs b/Duplicati/Library/Backend/SSHv2/KeyUploader.cs index de2c05723..24a00af5b 100644 --- a/Duplicati/Library/Backend/SSHv2/KeyUploader.cs +++ b/Duplicati/Library/Backend/SSHv2/KeyUploader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,11 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System.Linq; namespace Duplicati.Library.Backend { @@ -31,46 +28,44 @@ namespace Duplicati.Library.Backend { private const string OPTION_URL = "ssh-url"; private const string OPTION_KEY = "ssh-pubkey"; - + private const string AUTHORIZED_KEYS_FILE = "authorized_keys"; private const string SSH_FOLDER = ".ssh"; private const string AUTHORIZED_KEYS_PATH = SSH_FOLDER + "/" + AUTHORIZED_KEYS_FILE; - + private const short SSH_FOLDER_PERMISSIONS = 755;// 0x1ED /*755*/; private const short AUTHORIZED_KEYS_PERMISSIONS = 644; //0x1A4 /*644*/; private const short AUTHORIZED_KEYS_BACKUP_PERMISSIONS = 600; //0x180 /*600*/; - + #region IWebModule implementation public IDictionary Execute(IDictionary options) - { + { var res = new Dictionary(); - - string url; - string pubkey_s; - - options.TryGetValue(OPTION_URL, out url); - options.TryGetValue(OPTION_KEY, out pubkey_s); - + + options.TryGetValue(OPTION_URL, out var url); + options.TryGetValue(OPTION_KEY, out var pubkey_s); + if (string.IsNullOrWhiteSpace(url)) throw new ArgumentException(OPTION_URL); if (string.IsNullOrWhiteSpace(pubkey_s)) throw new ArgumentException(OPTION_KEY); - - var uri = new Utility.Uri(url); - foreach(var key in uri.QueryParameters.AllKeys) - options[key] = uri.QueryParameters[key]; - - + + var uri = new Utility.Uri(url); + foreach (var key in uri.QueryParameters.AllKeys) + if (!string.IsNullOrWhiteSpace(key)) + options[key] = uri.QueryParameters[key] ?? ""; + + pubkey_s = pubkey_s.Trim(); var pubkey = pubkey_s.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).Where(x => x.Length > 0).ToArray(); - + if (pubkey.Length != 3) throw new ArgumentException(OPTION_KEY); - - using(var connection = new SSHv2(url, (Dictionary)options)) + + using (var connection = new SSHv2(url, (Dictionary)options)) { - var client = connection.Client; + var client = connection.CreateConnection(CancellationToken.None).Await(); try { client.ChangeDirectory(SSH_FOLDER); @@ -81,30 +76,30 @@ namespace Duplicati.Library.Backend client.ChangePermissions(SSH_FOLDER, SSH_FOLDER_PERMISSIONS); client.ChangeDirectory(SSH_FOLDER); } - - var sshfolder = client.ListDirectory(".").First(x => x.Name == "."); + + var sshfolder = client.ListDirectory(".").First(x => x.Name == "."); client.ChangeDirectory(".."); - + if (!sshfolder.OwnerCanRead || !sshfolder.OwnerCanWrite) client.ChangePermissions(SSH_FOLDER, SSH_FOLDER_PERMISSIONS); - - string authorized_keys = ""; - byte[] authorized_keys_bytes = null; - + + var authorized_keys = ""; + byte[]? authorized_keys_bytes = null; + var existing_authorized_keys = client.ListDirectory(SSH_FOLDER).Any(x => x.Name == AUTHORIZED_KEYS_FILE); if (existing_authorized_keys) { - using(var ms = new System.IO.MemoryStream()) + using (var ms = new MemoryStream()) { client.DownloadFile(AUTHORIZED_KEYS_PATH, ms); authorized_keys_bytes = ms.ToArray(); authorized_keys = System.Text.Encoding.ASCII.GetString(authorized_keys_bytes); } } - - var keys = authorized_keys == null ? new string[0] : authorized_keys.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + + var keys = authorized_keys.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); var cleaned_keys = keys.Select(x => x.Trim()).Where(x => x.Length > 0 && !x.StartsWith("#", StringComparison.Ordinal)); - + // Does the key already exist? if (cleaned_keys.Any(x => { @@ -120,43 +115,37 @@ namespace Duplicati.Library.Backend if (new_file.Trim().Length > 0) new_file = authorized_keys.Trim() + "\n"; new_file += string.Join(" ", pubkey) + "\n"; - - if (existing_authorized_keys) + + if (existing_authorized_keys && authorized_keys_bytes != null) { - var filename = AUTHORIZED_KEYS_PATH + ".backup-" + Library.Utility.Utility.SerializeDateTime(DateTime.UtcNow); - using(var ms = new System.IO.MemoryStream(authorized_keys_bytes)) + var filename = AUTHORIZED_KEYS_PATH + ".backup-" + Utility.Utility.SerializeDateTime(DateTime.UtcNow); + using (var ms = new MemoryStream(authorized_keys_bytes)) client.UploadFile(ms, filename); client.ChangePermissions(filename, AUTHORIZED_KEYS_BACKUP_PERMISSIONS); } - - using(var ms = new System.IO.MemoryStream(System.Text.Encoding.ASCII.GetBytes(new_file))) + + using (var ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(new_file))) client.UploadFile(ms, AUTHORIZED_KEYS_PATH); - + if (!existing_authorized_keys) client.ChangePermissions(AUTHORIZED_KEYS_PATH, AUTHORIZED_KEYS_PERMISSIONS); - + res["status"] = "Key updated"; } - + } - + return res; } - public string Key { get { return "ssh-keyupload"; } } - public string DisplayName { get { return Strings.KeyUploader.DisplayName; } } - public string Description { get { return Strings.KeyUploader.Description; } } + public string Key => "ssh-keyupload"; + public string DisplayName => Strings.KeyUploader.DisplayName; + public string Description => Strings.KeyUploader.Description; - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.KeyUploader.UrlShort, Strings.KeyUploader.UrlLong), - new CommandLineArgument(OPTION_KEY, CommandLineArgument.ArgumentType.String, Strings.KeyUploader.PubkeyShort, Strings.KeyUploader.PubkeyLong), - }.Union(new SSHv2().SupportedCommands).ToList()); - } - } + public IList SupportedCommands => new ICommandLineArgument[] { + new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.KeyUploader.UrlShort, Strings.KeyUploader.UrlLong), + new CommandLineArgument(OPTION_KEY, CommandLineArgument.ArgumentType.String, Strings.KeyUploader.PubkeyShort, Strings.KeyUploader.PubkeyLong), + }.Union(new SSHv2().SupportedCommands).ToList(); #endregion } diff --git a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs index 674e121f9..e715a3eff 100644 --- a/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs +++ b/Duplicati/Library/Backend/SSHv2/SSHv2Backend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,125 +22,124 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; +using Duplicati.Library.SourceProvider; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Renci.SshNet; using Renci.SshNet.Common; -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend { - public class SSHv2 : IStreamingBackend, IRenameEnabledBackend + public class SSHv2 : IStreamingBackend, IRenameEnabledBackend, IFolderEnabledBackend { private const string SSH_KEYFILE_OPTION = "ssh-keyfile"; private const string SSH_KEYFILE_INLINE = "ssh-key"; private const string SSH_FINGERPRINT_OPTION = "ssh-fingerprint"; + private const string SSH_RELATIVE_PATH_OPTION = "ssh-relative-path"; private const string SSH_FINGERPRINT_ACCEPT_ANY_OPTION = "ssh-accept-any-fingerprints"; public const string KEYFILE_URI = "sshkey://"; private const string SSH_TIMEOUT_OPTION = "ssh-operation-timeout"; private const string SSH_KEEPALIVE_OPTION = "ssh-keepalive"; - readonly Dictionary m_options; + readonly Dictionary m_options; private readonly string m_server; private readonly string m_path; private readonly string m_username; - private readonly string m_password; - private readonly string m_fingerprint; + private readonly string? m_password; + private readonly string? m_fingerprint; private readonly bool m_fingerprintallowall; private readonly TimeSpan m_operationtimeout; private readonly TimeSpan m_keepaliveinterval; private readonly int m_port = 22; + private readonly bool m_useRelativePath; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + private string? m_initialDirectory; - private SftpClient m_con; + private SftpClient? m_con; public SSHv2() { + m_server = null!; + m_path = null!; + m_username = null!; + m_options = null!; + m_timeouts = null!; + m_con = null!; } - public SSHv2(string url, Dictionary options) - : this() + public SSHv2(string url, Dictionary options) { m_options = options; var uri = new Utility.Uri(url); uri.RequireHost(); - if (options.ContainsKey("auth-username")) - m_username = options["auth-username"]; - if (options.ContainsKey("auth-password")) - m_password = options["auth-password"]; - if (options.ContainsKey(SSH_FINGERPRINT_OPTION)) - m_fingerprint = options[SSH_FINGERPRINT_OPTION]; - if (!string.IsNullOrEmpty(uri.Username)) - m_username = uri.Username; - if (!string.IsNullOrEmpty(uri.Password)) - m_password = uri.Password; + var auth = AuthOptionsHelper.Parse(options, uri); + if (!auth.HasUsername) + throw new UserInformationException(Strings.SSHv2Backend.UsernameRequired, "UsernameNotSpecified"); + m_username = auth.Username!; + m_password = auth.Password; + m_fingerprint = options.GetValueOrDefault(SSH_FINGERPRINT_OPTION); m_fingerprintallowall = Utility.Utility.ParseBoolOption(options, SSH_FINGERPRINT_ACCEPT_ANY_OPTION); + m_useRelativePath = Utility.Utility.ParseBoolOption(options, SSH_RELATIVE_PATH_OPTION); + m_timeouts = TimeoutOptionsHelper.Parse(options); m_path = uri.Path; if (!string.IsNullOrWhiteSpace(m_path)) - { m_path = Util.AppendDirSeparator(m_path, "/"); - } - if (!m_path.StartsWith("/", StringComparison.Ordinal)) - m_path = "/" + m_path; + if (m_useRelativePath) + { + m_path = m_path.TrimStart('/'); + } + else + { + if (!m_path.StartsWith("/", StringComparison.Ordinal)) + m_path = "/" + m_path; + } m_server = uri.Host; if (uri.Port > 0) m_port = uri.Port; - string timeoutstr; - options.TryGetValue(SSH_TIMEOUT_OPTION, out timeoutstr); - - if (!string.IsNullOrWhiteSpace(timeoutstr)) - m_operationtimeout = Library.Utility.Timeparser.ParseTimeSpan(timeoutstr); - - options.TryGetValue(SSH_KEEPALIVE_OPTION, out timeoutstr); - - if (!string.IsNullOrWhiteSpace(timeoutstr)) - m_keepaliveinterval = Library.Utility.Timeparser.ParseTimeSpan(timeoutstr); + m_keepaliveinterval = Utility.Utility.ParseTimespanOption(options, SSH_KEEPALIVE_OPTION, "0s"); + m_operationtimeout = Utility.Utility.ParseTimespanOption(options, SSH_TIMEOUT_OPTION, "0s"); } #region IBackend Members public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { - CreateConnection(); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); // Since the SftpClient.CreateDirectory method does not create all the parent directories // as needed, this has to be done manually. - string partialPath = String.Empty; + var partialPath = string.Empty; foreach (string part in m_path.Split('/').Where(x => !String.IsNullOrEmpty(x))) { partialPath += $"/{part}"; - if (this.m_con.Exists(partialPath)) + if (await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.ExistsAsync(partialPath, ct)).ConfigureAwait(false)) { - if (!this.m_con.GetAttributes(partialPath).IsDirectory) + if (!await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.GetAttributes(partialPath).IsDirectory).ConfigureAwait(false)) { throw new ArgumentException($"The path {partialPath} already exists and is not a directory."); } } else { - this.m_con.CreateDirectory(partialPath); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.CreateDirectoryAsync(partialPath, ct)).ConfigureAwait(false); } } - return Task.CompletedTask; } public string DisplayName => Strings.SSHv2Backend.DisplayName; @@ -149,15 +148,15 @@ namespace Duplicati.Library.Backend public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open, - System.IO.FileAccess.Read, System.IO.FileShare.Read)) + using (var fs = File.Open(filename, FileMode.Open, + FileAccess.Read, FileShare.Read)) await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = System.IO.File.Open(filename, System.IO.FileMode.Create, - System.IO.FileAccess.Write, System.IO.FileShare.None)) + using (var fs = File.Open(filename, FileMode.Create, + FileAccess.Write, FileShare.None)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } @@ -165,9 +164,9 @@ namespace Duplicati.Library.Backend { try { - CreateConnection(); - ChangeDirectory(m_path); - await m_con.DeleteFileAsync(remotename, cancelToken).ConfigureAwait(false); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.DeleteFileAsync(remotename, ct)).ConfigureAwait(false); } catch (SftpPathNotFoundException ex) { @@ -175,39 +174,31 @@ namespace Duplicati.Library.Backend } } - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] - { - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, - Strings.SSHv2Backend.DescriptionAuthPasswordShort, - Strings.SSHv2Backend.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, - Strings.SSHv2Backend.DescriptionAuthUsernameShort, - Strings.SSHv2Backend.DescriptionAuthUsernameLong), - new CommandLineArgument(SSH_FINGERPRINT_OPTION, CommandLineArgument.ArgumentType.String, - Strings.SSHv2Backend.DescriptionFingerprintShort, - Strings.SSHv2Backend.DescriptionFingerprintLong), - new CommandLineArgument(SSH_FINGERPRINT_ACCEPT_ANY_OPTION, CommandLineArgument.ArgumentType.Boolean, - Strings.SSHv2Backend.DescriptionAnyFingerprintShort, - Strings.SSHv2Backend.DescriptionAnyFingerprintLong), - new CommandLineArgument(SSH_KEYFILE_OPTION, CommandLineArgument.ArgumentType.Path, - Strings.SSHv2Backend.DescriptionSshkeyfileShort, - Strings.SSHv2Backend.DescriptionSshkeyfileLong), - new CommandLineArgument(SSH_KEYFILE_INLINE, CommandLineArgument.ArgumentType.Password, - Strings.SSHv2Backend.DescriptionSshkeyShort, - Strings.SSHv2Backend.DescriptionSshkeyLong(KEYFILE_URI)), - new CommandLineArgument(SSH_TIMEOUT_OPTION, CommandLineArgument.ArgumentType.Timespan, - Strings.SSHv2Backend.DescriptionSshtimeoutShort, Strings.SSHv2Backend.DescriptionSshtimeoutLong, - "0"), - new CommandLineArgument(SSH_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Timespan, - Strings.SSHv2Backend.DescriptionSshkeepaliveShort, - Strings.SSHv2Backend.DescriptionSshkeepaliveLong, "0"), - }); - } - } + public IList SupportedCommands => [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument(SSH_FINGERPRINT_OPTION, CommandLineArgument.ArgumentType.String, + Strings.SSHv2Backend.DescriptionFingerprintShort, + Strings.SSHv2Backend.DescriptionFingerprintLong), + new CommandLineArgument(SSH_FINGERPRINT_ACCEPT_ANY_OPTION, CommandLineArgument.ArgumentType.Boolean, + Strings.SSHv2Backend.DescriptionAnyFingerprintShort, + Strings.SSHv2Backend.DescriptionAnyFingerprintLong), + new CommandLineArgument(SSH_KEYFILE_OPTION, CommandLineArgument.ArgumentType.Path, + Strings.SSHv2Backend.DescriptionSshkeyfileShort, + Strings.SSHv2Backend.DescriptionSshkeyfileLong), + new CommandLineArgument(SSH_KEYFILE_INLINE, CommandLineArgument.ArgumentType.Password, + Strings.SSHv2Backend.DescriptionSshkeyShort, + Strings.SSHv2Backend.DescriptionSshkeyLong(KEYFILE_URI)), + new CommandLineArgument(SSH_TIMEOUT_OPTION, CommandLineArgument.ArgumentType.Timespan, + Strings.SSHv2Backend.DescriptionSshtimeoutShort, Strings.SSHv2Backend.DescriptionSshtimeoutLong, + "0", null,null, Strings.SSHv2Backend.TimeoutDeprecated(SSH_TIMEOUT_OPTION, TimeoutOptionsHelper.ShortTimeoutOption, TimeoutOptionsHelper.ListTimeoutOption, TimeoutOptionsHelper.ReadWriteTimeoutOption)), + new CommandLineArgument(SSH_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Timespan, + Strings.SSHv2Backend.DescriptionSshkeepaliveShort, + Strings.SSHv2Backend.DescriptionSshkeepaliveLong, "0"), + new CommandLineArgument(SSH_RELATIVE_PATH_OPTION, CommandLineArgument.ArgumentType.Boolean, + Strings.SSHv2Backend.DescriptionRelativePathShort, + Strings.SSHv2Backend.DescriptionRelativePathLong), + .. TimeoutOptionsHelper.GetOptions(), + ]; public string Description => Strings.SSHv2Backend.Description; @@ -239,26 +230,35 @@ namespace Duplicati.Library.Backend #region IStreamingBackend Implementation - public Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { - CreateConnection(); - ChangeDirectory(m_path); - return Task.Factory.FromAsync( - (cb, state) => m_con.BeginUploadFile(stream, remotename, cb, state, _ => cancelToken.ThrowIfCancellationRequested()), - m_con.EndUploadFile, - null); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancelToken).ConfigureAwait(false); + try + { + using var ts = stream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); + await Task.Factory.FromAsync( + (cb, state) => con.BeginUploadFile(ts, remotename, cb, state, _ => cancelToken.ThrowIfCancellationRequested()), + con.EndUploadFile, + null); + } + catch (SftpPathNotFoundException ex) + { + throw new FolderMissingException(ex); + } } - public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { - CreateConnection(); - ChangeDirectory(m_path); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancelToken).ConfigureAwait(false); try { + using var ts = stream.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout, false); await Task.Factory.FromAsync( - (cb, state) => m_con.BeginDownloadFile(remotename, stream, cb, state, _ => cancelToken.ThrowIfCancellationRequested()), - m_con.EndDownloadFile, + (cb, state) => con.BeginDownloadFile(remotename, ts, cb, state, _ => cancelToken.ThrowIfCancellationRequested()), + con.EndDownloadFile, null).ConfigureAwait(false); } catch (SftpPathNotFoundException ex) @@ -271,44 +271,38 @@ namespace Duplicati.Library.Backend #region IRenameEnabledBackend Implementation - public Task RenameAsync(string source, string target, CancellationToken cancelToken) + public async Task RenameAsync(string source, string target, CancellationToken cancelToken) { - CreateConnection(); - ChangeDirectory(m_path); - return m_con.RenameFileAsync(source, target, cancelToken); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.RenameFileAsync(source, target, ct).ConfigureAwait(false)); } #endregion #region Implementation - private void CreateConnection() + internal async Task CreateConnection(CancellationToken cancelToken) { if (m_con != null && m_con.IsConnected) - return; + return m_con; if (m_con != null && !m_con.IsConnected) { - this.TryConnect(m_con); - return; + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => m_con.ConnectAsync(ct)).ConfigureAwait(false); + return m_con; } SftpClient con; m_options.TryGetValue(SSH_KEYFILE_OPTION, out var keyFile); if (string.IsNullOrWhiteSpace(keyFile)) - { m_options.TryGetValue(SSH_KEYFILE_INLINE, out keyFile); - } if (!string.IsNullOrWhiteSpace(keyFile)) - { con = new SftpClient(m_server, m_port, m_username, ValidateKeyFile(keyFile, m_password)); - } else - { con = new SftpClient(m_server, m_port, m_username, m_password ?? string.Empty); - } con.HostKeyReceived += (sender, e) => { @@ -321,17 +315,17 @@ namespace Duplicati.Library.Backend } var hostFingerprint = - $"{e.HostKeyName} {e.KeyLength.ToString()} {BitConverter.ToString(e.FingerPrint).Replace('-', ':')}"; + $"{e.HostKeyName} {e.KeyLength} {BitConverter.ToString(e.FingerPrint).Replace('-', ':')}"; if (string.IsNullOrEmpty(m_fingerprint)) - throw new Library.Utility.HostKeyException( + throw new HostKeyException( Strings.SSHv2Backend.FingerprintNotSpecifiedManagedError( hostFingerprint.ToLower(CultureInfo.InvariantCulture), SSH_FINGERPRINT_OPTION, SSH_FINGERPRINT_ACCEPT_ANY_OPTION), hostFingerprint, m_fingerprint); if (!string.Equals(hostFingerprint, m_fingerprint, StringComparison.OrdinalIgnoreCase)) - throw new Library.Utility.HostKeyException( + throw new HostKeyException( Strings.SSHv2Backend.FingerprintNotMatchManagedError( hostFingerprint.ToLower(CultureInfo.InvariantCulture)), hostFingerprint, m_fingerprint); @@ -339,49 +333,52 @@ namespace Duplicati.Library.Backend e.CanTrust = true; }; - if (m_operationtimeout.Ticks != 0) + if (m_operationtimeout.TotalSeconds > 0) con.OperationTimeout = m_operationtimeout; - if (m_keepaliveinterval.Ticks != 0) + if (m_keepaliveinterval.TotalSeconds > 0) con.KeepAliveInterval = m_keepaliveinterval; - this.TryConnect(con); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => con.ConnectAsync(ct)).ConfigureAwait(false); + if (m_useRelativePath && (string.IsNullOrWhiteSpace(con.WorkingDirectory) || !con.WorkingDirectory.StartsWith("/"))) + throw new UserInformationException("Server does not report absolute initial directory, please switch to absolute paths", "RelativePathNotSupported"); + m_initialDirectory = con.WorkingDirectory; - m_con = con; + return m_con = con; } - private void TryConnect(SftpClient client) + private async Task SetWorkingDirectory(SftpClient connection, CancellationToken cancelToken) { - client.Connect(); - } - - private void ChangeDirectory(string path) - { - if (string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(m_path)) return; - var workingDir = Util.AppendDirSeparator(m_con.WorkingDirectory, "/"); - if (workingDir == path) + var targetPath = m_useRelativePath + ? Util.AppendDirSeparator(m_initialDirectory ?? "", "/") + m_path + : m_path; + + var workingDir = Util.AppendDirSeparator(connection.WorkingDirectory, "/"); + if (workingDir == targetPath) return; try { - m_con.ChangeDirectory(path); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, ct => connection.ChangeDirectoryAsync(targetPath, ct)).ConfigureAwait(false); } catch (Exception ex) { - throw new Interface.FolderMissingException( - Strings.SSHv2Backend.FolderNotFoundManagedError(path, ex.Message), ex); + throw new FolderMissingException( + Strings.SSHv2Backend.FolderNotFoundManagedError(m_path, ex.Message), ex); } } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - string path = "."; + var path = "."; - CreateConnection(); - ChangeDirectory(m_path); + var con = await CreateConnection(cancelToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancelToken).ConfigureAwait(false); - foreach (var ls in m_con.ListDirectory(path)) + await foreach (var ls in await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => con.ListDirectoryAsync(path, ct)).ConfigureAwait(false)) { if (ls.Name.ToString() == "." || ls.Name.ToString() == "..") continue; yield return new FileEntry(ls.Name, ls.Length, @@ -390,26 +387,26 @@ namespace Duplicati.Library.Backend } } - private static Renci.SshNet.PrivateKeyFile ValidateKeyFile(string filename, string password) + private static PrivateKeyFile ValidateKeyFile(string filename, string? password) { try { if (!filename.StartsWith(KEYFILE_URI, StringComparison.OrdinalIgnoreCase)) { - return String.IsNullOrEmpty(password) + return string.IsNullOrEmpty(password) ? new PrivateKeyFile(filename) : new PrivateKeyFile(filename, password); } - using (var ms = new System.IO.MemoryStream()) - using (var sr = new System.IO.StreamWriter(ms)) + using (var ms = new MemoryStream()) + using (var sr = new StreamWriter(ms)) { sr.Write(Utility.Uri.UrlDecode(filename.Substring(KEYFILE_URI.Length))); sr.Flush(); ms.Position = 0; - return String.IsNullOrEmpty(password) ? new PrivateKeyFile(ms) : new PrivateKeyFile(ms, password); + return string.IsNullOrEmpty(password) ? new PrivateKeyFile(ms) : new PrivateKeyFile(ms, password); } } catch (Exception ex) @@ -423,11 +420,38 @@ namespace Duplicati.Library.Backend #endregion - internal SftpClient Client + public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { m_server }); + + /// + public async IAsyncEnumerable ListAsync(string? path, [EnumeratorCancellation] CancellationToken cancellationToken) { - get { return m_con; } + var con = await CreateConnection(cancellationToken).ConfigureAwait(false); + await SetWorkingDirectory(con, cancellationToken).ConfigureAwait(false); + + var prefixPath = m_useRelativePath ? "" : m_path; + if (!string.IsNullOrWhiteSpace(prefixPath)) + prefixPath = Util.AppendDirSeparator(prefixPath, "/"); + + var filterPath = prefixPath + BackendSourceFileEntry.NormalizePathTo(path, '/'); + if (!string.IsNullOrWhiteSpace(filterPath)) + filterPath = Util.AppendDirSeparator(filterPath, "/"); + + if (string.IsNullOrWhiteSpace(filterPath)) + filterPath = "."; + + await foreach (var x in await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancellationToken, ct => con.ListDirectoryAsync(filterPath, ct)).ConfigureAwait(false)) + yield return new FileEntry( + x.Name, + x.Attributes.Size, + x.Attributes.LastAccessTimeUtc, + x.Attributes.LastWriteTimeUtc) + { + IsFolder = x.Attributes.IsDirectory + }; } - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { m_server }); + /// + public Task GetEntryAsync(string path, CancellationToken cancellationToken) + => Task.FromResult(null); } } \ No newline at end of file diff --git a/Duplicati/Library/Backend/SSHv2/Strings.cs b/Duplicati/Library/Backend/SSHv2/Strings.cs index 97367eae3..0f539c76f 100644 --- a/Duplicati/Library/Backend/SSHv2/Strings.cs +++ b/Duplicati/Library/Backend/SSHv2/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,46 +23,47 @@ namespace Duplicati.Library.Backend.Strings { internal static class KeyGenerator { - public static string Description { get { return LC.L(@"Module for generating SSH private/public keys"); } } - public static string DisplayName { get { return LC.L(@"SSH Key Generator"); } } - public static string KeyUsernameLong { get { return LC.L(@"A username to append to the public key."); } } - public static string KeyUsernameShort { get { return LC.L(@"Public key username"); } } - public static string KeyTypeLong { get { return LC.L(@"Determines the type of key to generate."); } } - public static string KeyTypeShort { get { return LC.L(@"The key type"); } } - public static string KeyLenLong { get { return LC.L(@"The length of the key in bits."); } } - public static string KeyLenShort { get { return LC.L(@"The key length"); } } + public static string Description => LC.L(@"Module for generating SSH private/public keys"); + public static string DisplayName => LC.L(@"SSH Key Generator"); + public static string KeyUsernameLong => LC.L(@"A username to append to the public key."); + public static string KeyUsernameShort => LC.L(@"Public key username"); + public static string KeyTypeLong => LC.L(@"Determines the type of key to generate."); + public static string KeyTypeShort => LC.L(@"The key type"); + public static string KeyLenLong => LC.L(@"The length of the key in bits."); + public static string KeyLenShort => LC.L(@"The key length"); } internal static class KeyUploader { - public static string Description { get { return LC.L(@"Module for uploading SSH public keys"); } } - public static string DisplayName { get { return LC.L(@"SSH Key Uploader"); } } - public static string UrlLong { get { return LC.L(@"The SSH connection URL used to establish the connection."); } } - public static string UrlShort { get { return LC.L(@"The SSH connection URL"); } } - public static string PubkeyLong { get { return LC.L(@"The SSH public key must be a valid SSH string, which is appended to the .ssh/authorized_keys file."); } } - public static string PubkeyShort { get { return LC.L(@"The SSH public key to append"); } } + public static string Description => LC.L(@"Module for uploading SSH public keys"); + public static string DisplayName => LC.L(@"SSH Key Uploader"); + public static string UrlLong => LC.L(@"The SSH connection URL used to establish the connection."); + public static string UrlShort => LC.L(@"The SSH connection URL"); + public static string PubkeyLong => LC.L(@"The SSH public key must be a valid SSH string, which is appended to the .ssh/authorized_keys file."); + public static string PubkeyShort => LC.L(@"The SSH public key to append"); } internal static class SSHv2Backend { - public static string Description { get { return LC.L(@"This backend can read and write data to an SSH based backend, using SFTP. Allowed formats are ""ssh://hostname/folder"" and ""ssh://username:password@hostname/folder""."); } } - public static string DisplayName { get { return LC.L(@"SFTP (SSH)"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } - public static string DescriptionFingerprintLong { get { return LC.L(@"The server fingerprint used for validation of server identity. Format is e.g. ""ssh-rsa 4096 11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66""."); } } - public static string DescriptionFingerprintShort { get { return LC.L(@"Supply server fingerprint used for validation of server identity"); } } - public static string DescriptionAnyFingerprintLong { get { return LC.L(@"To guard against man-in-the-middle attacks, the server fingerprint is verified on connection. Use this option to disable host-key fingerprint verification. You should only use this option for testing."); } } - public static string DescriptionAnyFingerprintShort { get { return LC.L(@"Disable fingerprint validation"); } } - public static string DescriptionSshkeyfileLong { get { return LC.L(@"Point to a valid OpenSSH keyfile. If the file is encrypted, the password supplied is used to decrypt it. If the keyfile is specified, the password is not used to authenticate."); } } - public static string DescriptionSshkeyfileShort { get { return LC.L(@"Use a SSH private key to authenticate"); } } + public static string Description => LC.L(@"This backend can read and write data to an SSH based backend, using SFTP. Allowed formats are ""ssh://hostname/folder"" and ""ssh://username:password@hostname/folder""."); + public static string DisplayName => LC.L(@"SFTP (SSH)"); + public static string UsernameRequired => LC.L(@"A username is required"); + public static string PasswordRequired => LC.L(@"A password is required if not using a keyfile"); + public static string DescriptionFingerprintLong => LC.L(@"The server fingerprint used for validation of server identity. Format is e.g. ""ssh-rsa 4096 11:22:33:44:55:66:77:88:99:00:11:22:33:44:55:66""."); + public static string DescriptionFingerprintShort => LC.L(@"Supply server fingerprint used for validation of server identity"); + public static string DescriptionAnyFingerprintLong => LC.L(@"To guard against man-in-the-middle attacks, the server fingerprint is verified on connection. Use this option to disable host-key fingerprint verification. You should only use this option for testing."); + public static string DescriptionAnyFingerprintShort => LC.L(@"Disable fingerprint validation"); + public static string DescriptionSshkeyfileLong => LC.L(@"Point to a valid OpenSSH keyfile. If the file is encrypted, the password supplied is used to decrypt it. If the keyfile is specified, the password is not used to authenticate."); + public static string DescriptionSshkeyfileShort => LC.L(@"Use a SSH private key to authenticate"); public static string DescriptionSshkeyLong(string urlprefix) { return LC.L(@"An url-encoded SSH private key. The private key must be prefixed with {0}. If the key is encrypted, the password supplied is used to decrypt it. If the private key is specified, the password is not used to authenticate.", urlprefix); } - public static string DescriptionSshkeyShort { get { return LC.L(@"Use a SSH private key to authenticate"); } } - public static string DescriptionSshtimeoutLong { get { return LC.L(@"Use this option to manage the internal timeout for SSH operations. If the value is set to zero, the operations will not time out."); } } - public static string DescriptionSshtimeoutShort { get { return LC.L(@"Set the operation timeout value"); } } - public static string DescriptionSshkeepaliveLong { get { return LC.L(@"Use this option to enable the keep-alive interval for the SSH connection. If the connection is idle, aggressive firewalls might close the connection. Using keep-alive will keep the connection open in this scenario. If this value is set to zero, the keep-alive is disabled."); } } - public static string DescriptionSshkeepaliveShort { get { return LC.L(@"Set a keepalive value"); } } + public static string DescriptionSshkeyShort => LC.L(@"Use a SSH private key to authenticate"); + public static string DescriptionSshtimeoutLong => LC.L(@"Use this option to manage the internal timeout for SSH operations. If the value is set to zero, the operations will not time out."); + public static string DescriptionSshtimeoutShort => LC.L(@"Set the operation timeout value"); + public static string DescriptionSshkeepaliveLong => LC.L(@"Use this option to enable the keep-alive interval for the SSH connection. If the connection is idle, aggressive firewalls might close the connection. Using keep-alive will keep the connection open in this scenario. If this value is set to zero, the keep-alive is disabled."); + public static string DescriptionSshkeepaliveShort => LC.L(@"Set a keepalive value"); + public static string DescriptionRelativePathShort => LC.L(@"Treat source path as relative to the initial path"); + public static string DescriptionRelativePathLong => LC.L(@"Use this option to treat the source path as relative to the initial path. This is useful when the full path of the system is not known."); public static string FolderNotFoundManagedError(string foldername, string message) { return LC.L(@"Unable to set folder to {0}, error message: {1}", foldername, message); } public static string FingerprintNotMatchManagedError(string fingerprint) { return LC.L(@"Validation of server fingerprint failed. Server returned fingerprint ""{0}"". Cause of this message is either not correct configuration or Man-in-the-middle attack!", fingerprint); } public static string FingerprintNotSpecifiedManagedError(string fingerprint, string hostkeyoption, string allkeysoptions) { return LC.L(@"Please add --{1}=""{0}"" to trust this host. Optionally you can use --{2} (NOT SECURE) for testing!", fingerprint, hostkeyoption, allkeysoptions); } + public static string TimeoutDeprecated(string self, string opt1, string opt2, string opt3) { return LC.L(@"The option {0} is deprecated. Please use --{1}, --{2} or --{3} instead.", self, opt1, opt2, opt3); } } } diff --git a/Duplicati/Library/Backend/SharePoint/Duplicati.Library.Backend.SharePoint.csproj b/Duplicati/Library/Backend/SharePoint/Duplicati.Library.Backend.SharePoint.csproj index 125e6eadc..0e0a72e66 100644 --- a/Duplicati/Library/Backend/SharePoint/Duplicati.Library.Backend.SharePoint.csproj +++ b/Duplicati/Library/Backend/SharePoint/Duplicati.Library.Backend.SharePoint.csproj @@ -4,7 +4,9 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/SharePoint/OneDriveForBusinessBackend.cs b/Duplicati/Library/Backend/SharePoint/OneDriveForBusinessBackend.cs index 059357208..408ac7afe 100644 --- a/Duplicati/Library/Backend/SharePoint/OneDriveForBusinessBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/OneDriveForBusinessBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,14 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -using Duplicati.Library.Interface; - namespace Duplicati.Library.Backend { /// @@ -39,29 +31,18 @@ namespace Duplicati.Library.Backend // This constructor is needed by the BackendLoader. public class OneDriveForBusinessBackend : SharePointBackend { - public override string ProtocolKey - { - get { return "od4b"; } - } + public override string ProtocolKey => "od4b"; - public override string DisplayName - { - get { return Strings.OneDriveForBusiness.DisplayName; } - } + public override string DisplayName => Strings.OneDriveForBusiness.DisplayName; - public override string Description - { - get { return Strings.OneDriveForBusiness.Description; } - } + public override string Description => Strings.OneDriveForBusiness.Description; public OneDriveForBusinessBackend() - :base () + : base() { } - public OneDriveForBusinessBackend(string url, Dictionary options) + public OneDriveForBusinessBackend(string url, Dictionary options) : base(url, options) { } - - } } diff --git a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs index ef7d2a832..8b977bbb1 100644 --- a/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs +++ b/Duplicati/Library/Backend/SharePoint/SharePointBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,14 +23,10 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Microsoft.SharePoint.Client; // Plain 'using' for extension methods -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Runtime.CompilerServices; using System.Text; -using System.Threading; -using System.Threading.Tasks; using SP = Microsoft.SharePoint.Client; namespace Duplicati.Library.Backend @@ -68,9 +64,9 @@ namespace Duplicati.Library.Backend private readonly bool m_useBinaryDirectMode = false; /// URL to SharePoint web. Will be determined from m_orgUri on first use. - private string m_spWebUrl; + private string? m_spWebUrl; /// Current context to SharePoint web. - private SP.ClientContext m_spContext; + private ClientContext? m_spContext; /// The chunk size for uploading files. private readonly int m_fileChunkSize = 4 << 20; // Default: 4MB @@ -78,79 +74,69 @@ namespace Duplicati.Library.Backend /// The chunk size for uploading files. private readonly int m_useContextTimeoutMs = -1; // default: do not touch original setting + /// The timeout values to use. + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + #endregion #region [Public Properties] - public virtual string ProtocolKey - { - get { return "mssp"; } - } + public virtual string ProtocolKey => "mssp"; - public virtual string DisplayName - { - get { return Strings.SharePoint.DisplayName; } - } + public virtual string DisplayName => Strings.SharePoint.DisplayName; - public virtual string Description - { - get { return Strings.SharePoint.Description; } - } + public virtual string Description => Strings.SharePoint.Description; - public virtual IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.SharePoint.DescriptionAuthPasswordShort, Strings.SharePoint.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.SharePoint.DescriptionAuthUsernameShort, Strings.SharePoint.DescriptionAuthUsernameLong), - new CommandLineArgument("integrated-authentication", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionIntegratedAuthenticationShort, Strings.SharePoint.DescriptionIntegratedAuthenticationLong), - new CommandLineArgument("delete-to-recycler", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionUseRecyclerShort, Strings.SharePoint.DescriptionUseRecyclerLong), - new CommandLineArgument("binary-direct-mode", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionBinaryDirectModeShort, Strings.SharePoint.DescriptionBinaryDirectModeLong, "false"), - new CommandLineArgument("web-timeout", CommandLineArgument.ArgumentType.Timespan, Strings.SharePoint.DescriptionWebTimeoutShort, Strings.SharePoint.DescriptionWebTimeoutLong), - new CommandLineArgument("chunk-size", CommandLineArgument.ArgumentType.Size, Strings.SharePoint.DescriptionChunkSizeShort, Strings.SharePoint.DescriptionChunkSizeLong, "4mb"), - }); - } - } + public virtual IList SupportedCommands => [ + .. AuthOptionsHelper.GetOptions(), + new CommandLineArgument("integrated-authentication", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionIntegratedAuthenticationShort, Strings.SharePoint.DescriptionIntegratedAuthenticationLong), + new CommandLineArgument("delete-to-recycler", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionUseRecyclerShort, Strings.SharePoint.DescriptionUseRecyclerLong), + new CommandLineArgument("binary-direct-mode", CommandLineArgument.ArgumentType.Boolean, Strings.SharePoint.DescriptionBinaryDirectModeShort, Strings.SharePoint.DescriptionBinaryDirectModeLong, "false"), + new CommandLineArgument("web-timeout", CommandLineArgument.ArgumentType.Timespan, Strings.SharePoint.DescriptionWebTimeoutShort, Strings.SharePoint.DescriptionWebTimeoutLong), + new CommandLineArgument("chunk-size", CommandLineArgument.ArgumentType.Size, Strings.SharePoint.DescriptionChunkSizeShort, Strings.SharePoint.DescriptionChunkSizeLong, "4mb"), + .. TimeoutOptionsHelper.GetOptions() + ]; - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new string[] { + public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new string?[] { m_orgUrl.Host, string.IsNullOrWhiteSpace(m_spWebUrl) ? null : new Utility.Uri(m_spWebUrl).Host - }.Where(s => !string.IsNullOrWhiteSpace(s)) - .ToArray()); + }.WhereNotNullOrWhiteSpace().ToArray()); #endregion #region [Constructors] public SharePointBackend() - { } + { + m_serverRelPath = null!; + m_userInfo = null!; + m_timeouts = null!; + } - public SharePointBackend(string url, Dictionary options) + public SharePointBackend(string url, Dictionary options) { m_deleteToRecycler = Utility.Utility.ParseBoolOption(options, "delete-to-recycler"); m_useBinaryDirectMode = Utility.Utility.ParseBoolOption(options, "binary-direct-mode"); + m_timeouts = TimeoutOptionsHelper.Parse(options); try { - string strSpan; - if (options.TryGetValue("web-timeout", out strSpan)) + if (options.TryGetValue("web-timeout", out var strSpan)) { - TimeSpan ts = Timeparser.ParseTimeSpan(strSpan); + var ts = Timeparser.ParseTimeSpan(strSpan); if (ts.TotalMilliseconds > 30000 && ts.TotalMilliseconds < int.MaxValue) - this.m_useContextTimeoutMs = (int)ts.TotalMilliseconds; + m_useContextTimeoutMs = (int)ts.TotalMilliseconds; } } catch { } try { - string strChunkSize; - if (options.TryGetValue("chunk-size", out strChunkSize)) + if (options.TryGetValue("chunk-size", out var strChunkSize)) { - long pSize = Utility.Sizeparser.ParseSize(strChunkSize, "MB"); + var pSize = Sizeparser.ParseSize(strChunkSize, "MB"); if (pSize >= (1 << 14) && pSize <= (1 << 30)) // [16kb .. 1GB] - this.m_fileChunkSize = (int)pSize; + m_fileChunkSize = (int)pSize; } } catch { } @@ -177,40 +163,29 @@ namespace Duplicati.Library.Backend // Otherwise: Use settings from URL(precedence) or from command line options. bool useIntegratedAuthentication = Utility.Utility.ParseBoolOption(options, "integrated-authentication"); - string useUsername = null; - string usePassword = null; + string? useUsername = null; + string? usePassword = null; if (!useIntegratedAuthentication) { - if (!string.IsNullOrEmpty(u.Username)) - { - useUsername = u.Username; - if (!string.IsNullOrEmpty(u.Password)) - usePassword = u.Password; - else if (options.ContainsKey("auth-password")) - usePassword = options["auth-password"]; - } - else - { - if (options.ContainsKey("auth-username")) - { - useUsername = options["auth-username"]; - if (options.ContainsKey("auth-password")) - usePassword = options["auth-password"]; - } - } + var auth = AuthOptionsHelper.Parse(options, u); + // No validation here, maybe at least username should be set? + useUsername = auth.Username; + usePassword = auth.Password; } - if (useIntegratedAuthentication || (useUsername == null || usePassword == null)) + if (useIntegratedAuthentication || useUsername == null || usePassword == null) { // This might or might not work for on-premises SP. Maybe support if someone complains... m_userInfo = System.Net.CredentialCache.DefaultNetworkCredentials; } else { - System.Security.SecureString securePwd = new System.Security.SecureString(); +#pragma warning disable DE0001 + var securePwd = new System.Security.SecureString(); +#pragma warning restore DE0001 usePassword.ToList().ForEach(c => securePwd.AppendChar(c)); - m_userInfo = new Microsoft.SharePoint.Client.SharePointOnlineCredentials(useUsername, securePwd); + m_userInfo = new SharePointOnlineCredentials(useUsername, securePwd); // Other options (also ADAL, see class remarks) might be supported on request. // Maybe go in deep then and also look at: // - Microsoft.SharePoint.Client.AppPrincipalCredential.CreateFromKeyGroup() @@ -229,18 +204,21 @@ namespace Duplicati.Library.Backend /// Tries a simple query to test the passed context. /// Returns 0 on success, negative if completely invalid, positive if SharePoint error (wrong creds are negative). /// - private static async Task testContextForWebAsync(SP.ClientContext ctx, bool rethrow, CancellationToken cancelToken) + private static async Task TestContextForWebAsync(ClientContext ctx, bool rethrow, TimeSpan timeout, CancellationToken cancelToken) { try { - ctx.Load(ctx.Web, w => w.Title); - await ctx.ExecuteQueryAsync().ConfigureAwait(false); // should fail and throw if anything wrong. - string webTitle = ctx.Web.Title; - if (webTitle == null) - throw new UnauthorizedAccessException(Strings.SharePoint.WebTitleReadFailedError); - return 0; + return await Utility.Utility.WithTimeout(timeout, cancelToken, async _ => + { + ctx.Load(ctx.Web, w => w.Title); + await ctx.ExecuteQueryAsync().ConfigureAwait(false); // should fail and throw if anything wrong. + string webTitle = ctx.Web.Title; + if (webTitle == null) + throw new UnauthorizedAccessException(Strings.SharePoint.WebTitleReadFailedError); + return 0; + }).ConfigureAwait(false); } - catch (Microsoft.SharePoint.Client.ServerException) + catch (ServerException) { if (rethrow) throw; else return 1; @@ -256,15 +234,15 @@ namespace Duplicati.Library.Backend /// Builds a client context and tries a simple query to test if there's a web. /// Returns 0 on success, negative if completely invalid, positive if SharePoint error (likely wrong creds). /// - private static async Task<(int status, SP.ClientContext retCtx)> testUrlForWebAsync(string url, System.Net.ICredentials userInfo, bool rethrow, CancellationToken cancelToken) + private static async Task<(int status, ClientContext? retCtx)> TestUrlForWebAsync(string url, System.Net.ICredentials userInfo, bool rethrow, TimeSpan timeout, CancellationToken cancelToken) { int result = -1; - SP.ClientContext retCtx = null; + ClientContext? retCtx = null; var ctx = CreateNewContext(url); try { ctx.Credentials = userInfo; - result = await testContextForWebAsync(ctx, rethrow, cancelToken).ConfigureAwait(false); + result = await TestContextForWebAsync(ctx, rethrow, timeout, cancelToken).ConfigureAwait(false); if (result >= 0) { retCtx = ctx; @@ -288,9 +266,9 @@ namespace Duplicati.Library.Backend /// If that won't help, we will try all possible paths from longest /// to shortest... /// - private static async Task<(string testUrl, SP.ClientContext retCtx)> findCorrectWebPathAsync(Utility.Uri orgUrl, System.Net.ICredentials userInfo, CancellationToken cancelToken) + private static async Task<(string? testUrl, ClientContext? retCtx)> FindCorrectWebPathAsync(Utility.Uri orgUrl, System.Net.ICredentials userInfo, TimeSpan timeout, CancellationToken cancelToken) { - SP.ClientContext retCtx = null; + ClientContext? retCtx = null; int status; var path = orgUrl.Path; @@ -300,34 +278,34 @@ namespace Duplicati.Library.Backend if (webIndicatorPos >= 0) { var testUrl = new Utility.Uri(orgUrl.Scheme, orgUrl.Host, path.Substring(0, webIndicatorPos), null, null, null, orgUrl.Port).ToString(); - (status, retCtx) = await testUrlForWebAsync(testUrl, userInfo, false, cancelToken).ConfigureAwait(false); + (status, retCtx) = await TestUrlForWebAsync(testUrl, userInfo, false, timeout, cancelToken).ConfigureAwait(false); if (status >= 0) return (testUrl, retCtx); } // Now go through path and see where we land a success. - string[] pathParts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + var pathParts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); // first we look for the doc library - int docLibrary = Array.FindIndex(pathParts, p => StringComparer.OrdinalIgnoreCase.Equals(p, "documents")); + var docLibrary = Array.FindIndex(pathParts, p => StringComparer.OrdinalIgnoreCase.Equals(p, "documents")); if (docLibrary >= 0) { - string testUrl = new Utility.Uri(orgUrl.Scheme, orgUrl.Host, + var testUrl = new Utility.Uri(orgUrl.Scheme, orgUrl.Host, string.Join("/", pathParts, 0, docLibrary), null, null, null, orgUrl.Port).ToString(); - (status, retCtx) = await testUrlForWebAsync(testUrl, userInfo, false, cancelToken).ConfigureAwait(false); + (status, retCtx) = await TestUrlForWebAsync(testUrl, userInfo, false, timeout, cancelToken).ConfigureAwait(false); if (status >= 0) return (testUrl, retCtx); } // last but not least: try one after the other. - for (int pi = pathParts.Length - 1; pi >= 0; pi--) + for (var pi = pathParts.Length - 1; pi >= 0; pi--) { if (pi == docLibrary) continue; // already tested - string testUrl = new Utility.Uri(orgUrl.Scheme, orgUrl.Host, + var testUrl = new Utility.Uri(orgUrl.Scheme, orgUrl.Host, string.Join("/", pathParts, 0, pi), null, null, null, orgUrl.Port).ToString(); - (status, retCtx) = await testUrlForWebAsync(testUrl, userInfo, false, cancelToken).ConfigureAwait(false); + (status, retCtx) = await TestUrlForWebAsync(testUrl, userInfo, false, timeout, cancelToken).ConfigureAwait(false); if (status >= 0) return (testUrl, retCtx); } @@ -337,7 +315,7 @@ namespace Duplicati.Library.Backend } /// Return the preconfigured SP.ClientContext to use. - private async Task getSpClientContextAsync(bool forceNewContext, CancellationToken cancelToken) + private async Task GetSpClientContextAsync(bool forceNewContext, CancellationToken cancelToken) { if (forceNewContext) { @@ -349,8 +327,8 @@ namespace Duplicati.Library.Backend { if (m_spWebUrl == null) { - (m_spWebUrl, m_spContext) = await findCorrectWebPathAsync(m_orgUrl, m_userInfo, cancelToken).ConfigureAwait(false); - if (m_spWebUrl == null) + (m_spWebUrl, m_spContext) = await FindCorrectWebPathAsync(m_orgUrl, m_userInfo, m_timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); + if (m_spWebUrl == null || m_spContext == null) throw new System.Net.WebException(Strings.SharePoint.NoSharePointWebFoundError(m_orgUrl.ToString())); } else @@ -359,52 +337,29 @@ namespace Duplicati.Library.Backend m_spContext = CreateNewContext(m_spWebUrl); m_spContext.Credentials = m_userInfo; } - if (m_spContext != null && m_useContextTimeoutMs > 0) + if (m_useContextTimeoutMs > 0) m_spContext.RequestTimeout = m_useContextTimeoutMs; } return m_spContext; } - /// /// Dedicated code to wrap ExecuteQuery on file ops and check for errors. /// We have to check for the exceptions thrown to know about file /folder existence. /// Why the funny guys at MS provided an .Exists field stays a mystery... /// - private void wrappedExecuteQueryOnContext(SP.ClientContext ctx, string serverRelPathInfo, bool isFolder) + private async Task WrappedExecuteQueryOnContextAsync(ClientContext ctx, string serverRelPathInfo, bool isFolder, TimeSpan timeout, CancellationToken cancelToken) { - try { ctx.ExecuteQuery(); } + try { await Utility.Utility.WithTimeout(timeout, cancelToken, _ => ctx.ExecuteQueryAsync()).ConfigureAwait(false); } catch (ServerException ex) { // funny: If a folder is not found, we still get a FileNotFoundException from Server...?!? // Thus, we help ourselves by just passing the info if we wanted to query a folder. if (ex.ServerErrorTypeName == "System.IO.DirectoryNotFoundException" || (ex.ServerErrorTypeName == "System.IO.FileNotFoundException" && isFolder)) - throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); + throw new FolderMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - throw new Interface.FileMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); - else - throw; - } - } - - /// - /// Dedicated code to wrap ExecuteQuery on file ops and check for errors. - /// We have to check for the exceptions thrown to know about file /folder existence. - /// Why the funny guys at MS provided an .Exists field stays a mystery... - /// - private async Task wrappedExecuteQueryOnContextAsync(SP.ClientContext ctx, string serverRelPathInfo, bool isFolder, CancellationToken cancelToken) - { - try { await ctx.ExecuteQueryAsync().ConfigureAwait(false); } - catch (ServerException ex) - { - // funny: If a folder is not found, we still get a FileNotFoundException from Server...?!? - // Thus, we help ourselves by just passing the info if we wanted to query a folder. - if (ex.ServerErrorTypeName == "System.IO.DirectoryNotFoundException" - || (ex.ServerErrorTypeName == "System.IO.FileNotFoundException" && isFolder)) - throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); - if (ex.ServerErrorTypeName == "System.IO.FileNotFoundException") - throw new Interface.FileMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); + throw new FileMissingException(Strings.SharePoint.MissingElementError(serverRelPathInfo, m_spWebUrl)); else throw; } @@ -415,9 +370,9 @@ namespace Duplicati.Library.Backend /// /// The new context. /// The url to create the context for. - private static SP.ClientContext CreateNewContext(string url) + private static ClientContext CreateNewContext(string url) { - var ctx = new SP.ClientContext(url); + var ctx = new ClientContext(url); ctx.WebRequestExecutorFactory = new CustomWebRequestExecutorFactory(ctx.WebRequestExecutorFactory); return ctx; } @@ -467,15 +422,18 @@ namespace Duplicati.Library.Backend public async Task TestAsync(CancellationToken cancelToken) { - var ctx = await getSpClientContextAsync(true, cancelToken).ConfigureAwait(false); - await testContextForWebAsync(ctx, true, cancelToken).ConfigureAwait(false); + var ctx = await GetSpClientContextAsync(true, cancelToken).ConfigureAwait(false); + await TestContextForWebAsync(ctx, true, m_timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); } - public IEnumerable List() { return doListAsync(false, CancellationToken.None).Await(); } - private async Task> doListAsync(bool useNewContext, CancellationToken cancelToken) + /// + public IAsyncEnumerable ListAsync(CancellationToken cancelToken) + => DoListAsync(false, cancelToken); + + private async IAsyncEnumerable DoListAsync(bool useNewContext, [EnumeratorCancellation] CancellationToken cancelToken) { - var ctx = await getSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); - SP.Folder remoteFolder = null; + var ctx = await GetSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); + Folder? remoteFolder = null; bool retry = false; try { @@ -483,13 +441,13 @@ namespace Duplicati.Library.Backend ctx.Load(remoteFolder, f => f.Exists); ctx.Load(remoteFolder, f => f.Files, f => f.Folders); - await wrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, cancelToken).ConfigureAwait(false); + await WrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, m_timeouts.ListTimeout, cancelToken).ConfigureAwait(false); if (!remoteFolder.Exists) - throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); + throw new FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); } catch (ServerException) { throw; /* rethrow if Server answered */ } - catch (Interface.FileMissingException) { throw; } - catch (Interface.FolderMissingException) { throw; } + catch (FileMissingException) { throw; } + catch (FolderMissingException) { throw; } catch { if (useNewContext) @@ -500,53 +458,60 @@ namespace Duplicati.Library.Backend if (retry) { // An exception was caught, and List() should be retried. - return await doListAsync(true, cancelToken).ConfigureAwait(false); + await foreach (var f in DoListAsync(true, cancelToken).ConfigureAwait(false)) + yield return f; } else { - return remoteFolder.Folders.Where(ff => ff.Exists) + if (remoteFolder == null) + throw new FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); + + var list = remoteFolder.Folders.Where(ff => ff.Exists) .Select(f => new FileEntry(f.Name, -1, f.TimeLastModified, f.TimeLastModified) { IsFolder = true }) .Concat(remoteFolder.Files.Where(ff => ff.Exists) - .Select(f => new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified) { IsFolder = false })) - .ToArray(); + .Select(f => new FileEntry(f.Name, f.Length, f.TimeLastModified, f.TimeLastModified) { IsFolder = false })); + + foreach (var f in list) + yield return f; } } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (System.IO.FileStream fs = System.IO.File.Create(filename)) + using (var fs = System.IO.File.Create(filename)) await GetAsync(remotename, fs, cancelToken); } - public Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) - => doGetAsync(remotename, stream, false, cancelToken); + public Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) + => DoGetAsync(remotename, stream, false, cancelToken); - private async Task doGetAsync(string remotename, System.IO.Stream stream, bool useNewContext, CancellationToken cancelToken) + private async Task DoGetAsync(string remotename, Stream stream, bool useNewContext, CancellationToken cancelToken) { string fileurl = m_serverRelPath + System.Web.HttpUtility.UrlPathEncode(remotename); - var ctx = await getSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); + var ctx = await GetSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); try { - SP.File remoteFile = ctx.Web.GetFileByServerRelativeUrl(fileurl); + var remoteFile = ctx.Web.GetFileByServerRelativeUrl(fileurl); ctx.Load(remoteFile, f => f.Exists); - await wrappedExecuteQueryOnContextAsync(ctx, fileurl, false, cancelToken).ConfigureAwait(false); + await WrappedExecuteQueryOnContextAsync(ctx, fileurl, false, m_timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); if (!remoteFile.Exists) - throw new Interface.FileMissingException(Strings.SharePoint.MissingElementError(fileurl, m_spWebUrl)); + throw new FileMissingException(Strings.SharePoint.MissingElementError(fileurl, m_spWebUrl)); } catch (ServerException) { throw; /* rethrow if Server answered */ } - catch (Interface.FileMissingException) { throw; } - catch (Interface.FolderMissingException) { throw; } + catch (FileMissingException) { throw; } + catch (FolderMissingException) { throw; } catch { if (useNewContext) throw; - await doGetAsync(remotename, stream, true, cancelToken); + await DoGetAsync(remotename, stream, true, cancelToken); } using (var fileInfo = SP.File.OpenBinaryDirect(ctx, fileurl)) using (var s = fileInfo.Stream) - await Utility.Utility.CopyStreamAsync(s, stream, true, cancelToken).ConfigureAwait(false); + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(t, stream, true, cancelToken).ConfigureAwait(false); } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) @@ -555,85 +520,86 @@ namespace Duplicati.Library.Backend await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { return doPutAsync(remotename, stream, false, cancelToken); } - private async Task doPutAsync(string remotename, Stream stream, bool useNewContext, CancellationToken cancelToken) + public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { return DoPutAsync(remotename, stream, false, cancelToken); } + private async Task DoPutAsync(string remotename, Stream stream, bool useNewContext, CancellationToken cancelToken) { - string fileurl = m_serverRelPath + System.Web.HttpUtility.UrlPathEncode(remotename); - var ctx = await getSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); + var fileurl = m_serverRelPath + System.Web.HttpUtility.UrlPathEncode(remotename); + var ctx = await GetSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); try { - SP.Folder remoteFolder = ctx.Web.GetFolderByServerRelativeUrl(m_serverRelPath); + var remoteFolder = ctx.Web.GetFolderByServerRelativeUrl(m_serverRelPath); ctx.Load(remoteFolder, f => f.Exists, f => f.ServerRelativeUrl); - await wrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, cancelToken).ConfigureAwait(false); + await WrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, m_timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); if (!remoteFolder.Exists) - throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); + throw new FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); useNewContext = true; // disable retry if (!m_useBinaryDirectMode) - await uploadFileSlicePerSlice(ctx, remoteFolder, stream, fileurl, cancelToken).ConfigureAwait(false); + await UploadFileSlicePerSlice(ctx, remoteFolder, stream, fileurl, cancelToken).ConfigureAwait(false); } catch (ServerException) { throw; /* rethrow if Server answered */ } - catch (Interface.FileMissingException) { throw; } - catch (Interface.FolderMissingException) { throw; } + catch (FileMissingException) { throw; } + catch (FolderMissingException) { throw; } catch { if (useNewContext) throw; - await doPutAsync(remotename, stream, true, cancelToken).ConfigureAwait(false); + await DoPutAsync(remotename, stream, true, cancelToken).ConfigureAwait(false); } if (m_useBinaryDirectMode) - SP.File.SaveBinaryDirect(ctx, fileurl, stream, true); + using (var ts = stream.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout, false)) + SP.File.SaveBinaryDirect(ctx, fileurl, ts, true); } /// /// Upload in chunks to bypass filesize limit. /// https://msdn.microsoft.com/en-us/library/office/dn904536.aspx /// - private async Task uploadFileSlicePerSlice(ClientContext ctx, Folder folder, Stream sourceFileStream, string fileName, CancellationToken cancelToken) + private async Task UploadFileSlicePerSlice(ClientContext ctx, Folder folder, Stream sourceFileStream, string fileName, CancellationToken cancelToken) { // Each sliced upload requires a unique ID. - Guid uploadId = Guid.NewGuid(); + var uploadId = Guid.NewGuid(); // Get the name of the file. - string uniqueFileName = Path.GetFileName(fileName); + var uniqueFileName = Path.GetFileName(fileName); // File object. - SP.File uploadFile = null; + SP.File? uploadFile = null; // Calculate block size in bytes. - int blockSize = m_fileChunkSize; - byte[] buf = new byte[blockSize]; + var blockSize = m_fileChunkSize; + var buf = new byte[blockSize]; - bool first = true; - bool needsFinalize = true; - long fileoffset = 0; + var needsFinalize = true; + var fileoffset = 0L; - int lastreadsize = -1; + var lastreadsize = -1; while (lastreadsize != 0) { - int bufCnt = 0; + var bufCnt = 0; // read chunk to array (necessary because chunk uploads fail if size unknown) while (bufCnt < blockSize && (lastreadsize = await sourceFileStream.ReadAsync(buf, bufCnt, blockSize - bufCnt, cancelToken).ConfigureAwait(false)) > 0) bufCnt += lastreadsize; using (var contentChunk = new MemoryStream(buf, 0, bufCnt, false)) + using (var timeoutStream = contentChunk.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) { - ClientResult bytesUploaded = null; - if (first) + ClientResult? bytesUploaded = null; + if (uploadFile == null) { // Add an empty / single chunk file. - FileCreationInformation fileInfo = new FileCreationInformation(); + var fileInfo = new FileCreationInformation(); fileInfo.Url = uniqueFileName; fileInfo.Overwrite = true; - fileInfo.ContentStream = (bufCnt < blockSize) ? contentChunk : new MemoryStream(0); + fileInfo.ContentStream = (bufCnt < blockSize) ? timeoutStream : new MemoryStream(0); uploadFile = folder.Files.Add(fileInfo); - if (bufCnt < blockSize) needsFinalize = false; - else bytesUploaded = uploadFile.StartUpload(uploadId, contentChunk); // new OverrideableStream(chunkStream)); - - first = false; + if (bufCnt < blockSize) + needsFinalize = false; + else + bytesUploaded = uploadFile.StartUpload(uploadId, timeoutStream); // new OverrideableStream(chunkStream)); } else { @@ -641,19 +607,19 @@ namespace Duplicati.Library.Backend //uploadFile = ctx.Web.GetFileByServerRelativeUrl(folder.ServerRelativeUrl + System.IO.Path.AltDirectorySeparatorChar + uniqueFileName); if (bufCnt < blockSize) // Last block: end sliced upload by calling FinishUpload. { - uploadFile = uploadFile.FinishUpload(uploadId, fileoffset, contentChunk); + uploadFile = uploadFile.FinishUpload(uploadId, fileoffset, timeoutStream); needsFinalize = false; // signal no final call necessary. } else // Continue sliced upload. { - bytesUploaded = uploadFile.ContinueUpload(uploadId, fileoffset, contentChunk); + bytesUploaded = uploadFile.ContinueUpload(uploadId, fileoffset, timeoutStream); } } if (bytesUploaded == null) ctx.Load(uploadFile, f => f.Length); - ctx.ExecuteQuery(); + await ctx.ExecuteQueryAsync().ConfigureAwait(false); // Check consistency and update fileoffset for the next slice. if (bytesUploaded != null) { @@ -664,31 +630,35 @@ namespace Duplicati.Library.Backend else fileoffset += bufCnt; } } - if (needsFinalize) // finalize file (should only occur if filesize is exactly a multiple of chunksize) + + if (needsFinalize && uploadFile != null) // finalize file (should only occur if filesize is exactly a multiple of chunksize) { // End sliced upload by calling FinishUpload. uploadFile = uploadFile.FinishUpload(uploadId, fileoffset, new MemoryStream(0)); ctx.Load(uploadFile, f => f.Length); - ctx.ExecuteQuery(); + await Utility.Utility.WithTimeout(m_timeouts.ReadWriteTimeout, cancelToken, _ => ctx.ExecuteQueryAsync()).ConfigureAwait(false); } + if (uploadFile == null) + throw new FileMissingException(Strings.SharePoint.MissingElementError(fileName, m_spWebUrl)); + if (uploadFile.Length != fileoffset) throw new InvalidDataException(string.Format("Reported final file size ({0:N0}) does not match internal recording ({1:N0}) for '{2}'.", uploadFile.Length, fileoffset, uniqueFileName)); return uploadFile; } - public Task CreateFolderAsync(CancellationToken cancelToken) { return doCreateFolderAsync(false, cancelToken); } - private async Task doCreateFolderAsync(bool useNewContext, CancellationToken cancelToken) + public Task CreateFolderAsync(CancellationToken cancelToken) { return DoCreateFolderAsync(false, cancelToken); } + private async Task DoCreateFolderAsync(bool useNewContext, CancellationToken cancelToken) { - var ctx = await getSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); + var ctx = await GetSpClientContextAsync(useNewContext, cancelToken).ConfigureAwait(false); try { - int pathLengthToWeb = new Utility.Uri(m_spWebUrl).Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length; + var pathLengthToWeb = new Utility.Uri(m_spWebUrl).Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries).Length; - string[] folderNames = m_serverRelPath.Substring(0, m_serverRelPath.Length - 1).Split('/'); + var folderNames = m_serverRelPath.Substring(0, m_serverRelPath.Length - 1).Split('/'); folderNames = Array.ConvertAll(folderNames, fold => System.Net.WebUtility.UrlDecode(fold)); - var spfolders = new SP.Folder[folderNames.Length]; + var spfolders = new Folder[folderNames.Length]; var relativePathBuilder = new StringBuilder(); int fi = 0; for (; fi < folderNames.Length; fi++) @@ -700,7 +670,7 @@ namespace Duplicati.Library.Backend var folder = ctx.Web.GetFolderByServerRelativeUrl(folderRelPath); spfolders[fi] = folder; ctx.Load(folder, f => f.Exists); - try { await wrappedExecuteQueryOnContextAsync(ctx, folderRelPath, true, cancelToken); } + try { await WrappedExecuteQueryOnContextAsync(ctx, folderRelPath, true, m_timeouts.ShortTimeout, cancelToken); } catch (FolderMissingException) { break; } if (!folder.Exists) break; @@ -710,51 +680,56 @@ namespace Duplicati.Library.Backend spfolders[fi] = spfolders[fi - 1].Folders.Add(folderNames[fi]); ctx.Load(spfolders[folderNames.Length - 1], f => f.Exists); - await wrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, cancelToken); + await WrappedExecuteQueryOnContextAsync(ctx, m_serverRelPath, true, m_timeouts.ShortTimeout, cancelToken).ConfigureAwait(false); if (!spfolders[folderNames.Length - 1].Exists) - throw new Interface.FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); + throw new FolderMissingException(Strings.SharePoint.MissingElementError(m_serverRelPath, m_spWebUrl)); } catch (ServerException) { throw; /* rethrow if Server answered */ } - catch (Interface.FileMissingException) { throw; } - catch (Interface.FolderMissingException) { throw; } + catch (FileMissingException) { throw; } + catch (FolderMissingException) { throw; } catch { if (useNewContext) throw; - await doCreateFolderAsync(true, cancelToken).ConfigureAwait(false); + await DoCreateFolderAsync(true, cancelToken).ConfigureAwait(false); } } public Task DeleteAsync(string remotename, CancellationToken cancellationToken) - => doDeleteAsync(remotename, false, cancellationToken); - private async Task doDeleteAsync(string remotename, bool useNewContext, CancellationToken cancellationToken) + => DoDeleteAsync(remotename, false, cancellationToken); + private async Task DoDeleteAsync(string remotename, bool useNewContext, CancellationToken cancellationToken) { - var ctx = await getSpClientContextAsync(useNewContext, cancellationToken).ConfigureAwait(false); + var ctx = await GetSpClientContextAsync(useNewContext, cancellationToken).ConfigureAwait(false); try { string fileurl = m_serverRelPath + System.Web.HttpUtility.UrlPathEncode(remotename); SP.File remoteFile = ctx.Web.GetFileByServerRelativeUrl(fileurl); ctx.Load(remoteFile); - await wrappedExecuteQueryOnContextAsync(ctx, fileurl, false, cancellationToken).ConfigureAwait(false); + await WrappedExecuteQueryOnContextAsync(ctx, fileurl, false, m_timeouts.ShortTimeout, cancellationToken).ConfigureAwait(false); if (!remoteFile.Exists) - throw new Interface.FileMissingException(Strings.SharePoint.MissingElementError(fileurl, m_spWebUrl)); + throw new FileMissingException(Strings.SharePoint.MissingElementError(fileurl, m_spWebUrl)); - if (m_deleteToRecycler) remoteFile.Recycle(); - else remoteFile.DeleteObject(); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => + { + if (m_deleteToRecycler) + remoteFile.Recycle(); + else + remoteFile.DeleteObject(); - await ctx.ExecuteQueryAsync().ConfigureAwait(false); + return ctx.ExecuteQueryAsync(); + }).ConfigureAwait(false); } catch (ServerException) { throw; /* rethrow if Server answered */ } - catch (Interface.FileMissingException) { throw; } - catch (Interface.FolderMissingException) { throw; } + catch (FileMissingException) { throw; } + catch (FolderMissingException) { throw; } catch { if (useNewContext) throw; - await doDeleteAsync(remotename, true, cancellationToken).ConfigureAwait(false); + await DoDeleteAsync(remotename, true, cancellationToken).ConfigureAwait(false); } } @@ -771,7 +746,11 @@ namespace Duplicati.Library.Backend m_spContext.Dispose(); } catch { } - m_userInfo = null; + finally + { + m_spContext = null; + } + m_userInfo = null!; } #endregion diff --git a/Duplicati/Library/Backend/SharePoint/Strings.cs b/Duplicati/Library/Backend/SharePoint/Strings.cs index 68b522a59..cd674308a 100644 --- a/Duplicati/Library/Backend/SharePoint/Strings.cs +++ b/Duplicati/Library/Backend/SharePoint/Strings.cs @@ -1,56 +1,52 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { internal static class SharePoint { - public static string Description { get { return LC.L(@"This backend can read and write data to a SharePoint server (including OneDrive for Business). Allowed formats are ""mssp://tennant.sharepoint.com/PathToWeb//BaseDocLibrary/subfolder"" and ""mssp://username:password@tennant.sharepoint.com/PathToWeb//BaseDocLibrary/subfolder"". Use a double slash '//' in the path to denote the web from the documents library."); } } - public static string DisplayName { get { return LC.L(@"Microsoft SharePoint"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } - public static string DescriptionIntegratedAuthenticationLong { get { return LC.L(@"If the server and client both supports integrated authentication, this option enables that authentication method. This is likely only available with windows servers and clients."); } } - public static string DescriptionIntegratedAuthenticationShort { get { return LC.L(@"Use windows integrated authentication to connect to the server"); } } - public static string DescriptionUseRecyclerLong { get { return LC.L(@"Use this option to have files moved to the recycle bin folder instead of removing them permanently when compacting or deleting backups."); } } - public static string DescriptionUseRecyclerShort { get { return LC.L(@"Move deleted files to the recycle bin"); } } + public static string Description => LC.L(@"This backend can read and write data to a SharePoint server (including OneDrive for Business). Allowed formats are ""mssp://tennant.sharepoint.com/PathToWeb//BaseDocLibrary/subfolder"" and ""mssp://username:password@tennant.sharepoint.com/PathToWeb//BaseDocLibrary/subfolder"". Use a double slash '//' in the path to denote the web from the documents library."); + public static string DisplayName => LC.L(@"Microsoft SharePoint"); + public static string DescriptionIntegratedAuthenticationLong => LC.L(@"If the server and client both supports integrated authentication, this option enables that authentication method. This is likely only available with windows servers and clients."); + public static string DescriptionIntegratedAuthenticationShort => LC.L(@"Use windows integrated authentication to connect to the server"); + public static string DescriptionUseRecyclerLong => LC.L(@"Use this option to have files moved to the recycle bin folder instead of removing them permanently when compacting or deleting backups."); + public static string DescriptionUseRecyclerShort => LC.L(@"Move deleted files to the recycle bin"); - public static string DescriptionBinaryDirectModeLong { get { return LC.L(@"Use this option to upload files to SharePoint as a whole with BinaryDirect mode. This is the most efficient way of uploading, but can cause non-recoverable timeouts under certain conditions. Use this option only with very fast and stable internet connections."); } } - public static string DescriptionBinaryDirectModeShort { get { return LC.L(@"Upload files using binary direct mode"); } } + public static string DescriptionBinaryDirectModeLong => LC.L(@"Use this option to upload files to SharePoint as a whole with BinaryDirect mode. This is the most efficient way of uploading, but can cause non-recoverable timeouts under certain conditions. Use this option only with very fast and stable internet connections."); + public static string DescriptionBinaryDirectModeShort => LC.L(@"Upload files using binary direct mode"); - public static string DescriptionWebTimeoutLong { get { return LC.L(@"Use this option to specify a custom value for timeouts of web operation when communicating with SharePoint Server. Recommended value is 180s."); } } - public static string DescriptionWebTimeoutShort { get { return LC.L(@"Set timeout for SharePoint web operations"); } } + public static string DescriptionWebTimeoutLong => LC.L(@"Use this option to specify a custom value for timeouts of web operation when communicating with SharePoint Server. Recommended value is 180s."); + public static string DescriptionWebTimeoutShort => LC.L(@"Set timeout for SharePoint web operations"); - public static string DescriptionChunkSizeLong { get { return LC.L(@"Use this option to specify the size of each chunk when uploading to SharePoint Server. Recommended value is 4MB."); } } - public static string DescriptionChunkSizeShort { get { return LC.L(@"Set block size for chunked uploads to SharePoint"); } } + public static string DescriptionChunkSizeLong => LC.L(@"Use this option to specify the size of each chunk when uploading to SharePoint Server. Recommended value is 4MB."); + public static string DescriptionChunkSizeShort => LC.L(@"Set block size for chunked uploads to SharePoint"); - public static string MissingElementError(string serverrelpath, string hosturl) { return LC.L(@"Element with path '{0}' not found on host '{1}'.", serverrelpath, hosturl); } + public static string MissingElementError(string serverrelpath, string? hosturl) { return LC.L(@"Element with path '{0}' not found on host '{1}'.", serverrelpath, hosturl); } public static string NoSharePointWebFoundError(string url) { return LC.L(@"No SharePoint web could be logged in to at path '{0}'. Maybe wrong credentials. Or try using '//' in path to separate web from folder path.", url); } - public static string WebTitleReadFailedError { get { return LC.L(@"Everything seemed alright, but then web title could not be read to test connection. Something's wrong."); } } + public static string WebTitleReadFailedError => LC.L(@"Everything seemed alright, but then web title could not be read to test connection. Something's wrong."); } internal static class OneDriveForBusiness { - public static string Description { get { return LC.L(@"This backend can read and write data to Microsoft OneDrive for Business. Allowed formats are ""od4b://tennant.sharepoint.com/personal/username_domain/Documents/subfolder"" and ""od4b://username:password@tennant.sharepoint.com/personal/username_domain/Documents/folder"". You can use a double slash '//' in the path to denote the base path from the documents folder."); } } - public static string DisplayName { get { return LC.L(@"Microsoft OneDrive for Business"); } } + public static string Description => LC.L(@"This backend can read and write data to Microsoft OneDrive for Business. Allowed formats are ""od4b://tennant.sharepoint.com/personal/username_domain/Documents/subfolder"" and ""od4b://username:password@tennant.sharepoint.com/personal/username_domain/Documents/folder"". You can use a double slash '//' in the path to denote the base path from the documents folder."); + public static string DisplayName => LC.L(@"Microsoft OneDrive for Business"); } } diff --git a/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj b/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj index 193c2695f..7de58c33f 100644 --- a/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj +++ b/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj @@ -3,7 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs index 29ac75574..2d41d9e0d 100644 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ b/Duplicati/Library/Backend/Sia/Sia.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,12 +21,11 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; +using System.Globalization; using System.Net; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend.Sia { @@ -40,35 +39,41 @@ namespace Duplicati.Library.Backend.Sia private readonly int m_apiport; private readonly string m_targetpath; private readonly float m_redundancy; - private readonly string m_authorization; + private readonly string? m_authorization; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. public Sia() { + m_apihost = null!; + m_targetpath = null!; + m_timeouts = null!; } // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. - public Sia(string url, Dictionary options) + public Sia(string url, Dictionary options) { var uri = new Utility.Uri(url); m_apihost = uri.Host; m_apiport = uri.Port; m_targetpath = uri.Path; + m_timeouts = TimeoutOptionsHelper.Parse(options); m_redundancy = 1.5F; - if (options.ContainsKey(SIA_REDUNDANCY)) - m_redundancy = float.Parse(options[SIA_REDUNDANCY]); + var redundancyString = options.GetValueOrDefault(SIA_REDUNDANCY); + if (!string.IsNullOrWhiteSpace(redundancyString)) + m_redundancy = (float)decimal.Parse(redundancyString, CultureInfo.InvariantCulture); if (m_apiport <= 0) m_apiport = 9980; - if (options.ContainsKey(SIA_TARGETPATH)) - { - m_targetpath = options[SIA_TARGETPATH]; - } + var targetPathString = options.GetValueOrDefault(SIA_TARGETPATH); + if (!string.IsNullOrWhiteSpace(targetPathString)) + m_targetpath = targetPathString; + while (m_targetpath.Contains("//")) m_targetpath = m_targetpath.Replace("//", "/"); while (m_targetpath.StartsWith("/", StringComparison.Ordinal)) @@ -80,14 +85,14 @@ namespace Duplicati.Library.Backend.Sia m_targetpath = "backup"; m_authorization = options.ContainsKey(SIA_PASSWORD) && !string.IsNullOrEmpty(options[SIA_PASSWORD]) - ? "Basic " + System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(":" + options[SIA_PASSWORD])) + ? "Basic " + Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(":" + options[SIA_PASSWORD])) : null; } - private System.Net.HttpWebRequest CreateRequest(string endpoint) + private HttpWebRequest CreateRequest(string endpoint) { string baseurl = "http://" + m_apihost + ":" + m_apiport; - System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(baseurl + endpoint); + var req = (HttpWebRequest)WebRequest.Create(baseurl + endpoint); if (m_authorization != null) { @@ -101,27 +106,25 @@ namespace Duplicati.Library.Backend.Sia return req; } - private string getResponseBodyOnError(string context, System.Net.WebException wex) + private async Task GetResponseBodyOnError(string context, WebException wex, CancellationToken cancellationToken) { - HttpWebResponse response = wex.Response as HttpWebResponse; + var response = wex.Response as HttpWebResponse; if (response is null) - { return $"{context} failed with error: {wex.Message}"; - } - string body = ""; - using (System.IO.Stream data = response.GetResponseStream()) - using (var reader = new System.IO.StreamReader(data)) + var body = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => { - body = reader.ReadToEnd(); - } + using (var data = response.GetResponseStream()) + using (var reader = new StreamReader(data)) + return reader.ReadToEnd(); + }).ConfigureAwait(false); return string.Format("{0} failed, response: {1}", context, body); } public class SiaFile { [JsonProperty("siapath")] - public string Siapath { get; set; } + public string? Siapath { get; set; } [JsonProperty("available")] public bool Available { get; set; } [JsonProperty("filesize")] @@ -135,69 +138,71 @@ namespace Duplicati.Library.Backend.Sia public class SiaFileList { [JsonProperty("files")] - public SiaFile[] Files { get; set; } + public SiaFile[]? Files { get; set; } } public class SiaDownloadFile { [JsonProperty("siapath")] - public string Siapath { get; set; } + public string? Siapath { get; set; } [JsonProperty("destination")] - public string Destination { get; set; } + public string? Destination { get; set; } [JsonProperty("filesize")] public long Filesize { get; set; } [JsonProperty("received")] public long Received { get; set; } [JsonProperty("starttime")] - public string Starttime { get; set; } + public string? Starttime { get; set; } [JsonProperty("error")] - public string Error { get; set; } + public string? Error { get; set; } } public class SiaDownloadList { [JsonProperty("downloads")] - public SiaDownloadFile[] Files { get; set; } + public SiaDownloadFile[]? Files { get; set; } } - private SiaFileList GetFiles() + private async Task GetFiles(CancellationToken cancelToken) { + // Remove warning until this is rewritten to use HttpClient + await Task.CompletedTask; + var fl = new SiaFileList(); - string endpoint = "/renter/files"; + var endpoint = "/renter/files"; try { - System.Net.HttpWebRequest req = CreateRequest(endpoint); - req.Method = System.Net.WebRequestMethods.Http.Get; + var req = CreateRequest(endpoint); + req.Method = WebRequestMethods.Http.Get; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { int code = (int)resp.StatusCode; if (code < 200 || code >= 300) - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); var serializer = new JsonSerializer(); - using (var rs = areq.GetResponseStream()) - using (var sr = new System.IO.StreamReader(rs)) - using (var jr = new Newtonsoft.Json.JsonTextReader(sr)) - { - fl = (SiaFileList)serializer.Deserialize(jr, typeof(SiaFileList)); - } + using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) + using (var sr = new StreamReader(rs)) + using (var jr = new JsonTextReader(sr)) + fl = serializer.Deserialize(jr) + ?? throw new Exception("Failed to deserialize response"); } } - catch (System.Net.WebException wex) + catch (WebException wex) { - throw new Exception(getResponseBodyOnError(endpoint, wex)); + throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); } return fl; } - private bool IsUploadComplete(string siafilename) + private async Task IsUploadComplete(string siafilename, CancellationToken cancelToken) { - SiaFileList fl = GetFiles(); + var fl = await GetFiles(cancelToken).ConfigureAwait(false); if (fl.Files == null) return false; @@ -214,44 +219,43 @@ namespace Duplicati.Library.Backend.Sia return false; } - private SiaDownloadList GetDownloads() + private async Task GetDownloads(CancellationToken cancelToken) { var fl = new SiaDownloadList(); string endpoint = "/renter/downloads"; try { - System.Net.HttpWebRequest req = CreateRequest(endpoint); - req.Method = System.Net.WebRequestMethods.Http.Get; + var req = CreateRequest(endpoint); + req.Method = WebRequestMethods.Http.Get; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { int code = (int)resp.StatusCode; if (code < 200 || code >= 300) - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); var serializer = new JsonSerializer(); - using (var rs = areq.GetResponseStream()) - using (var sr = new System.IO.StreamReader(rs)) - using (var jr = new Newtonsoft.Json.JsonTextReader(sr)) - { - fl = (SiaDownloadList)serializer.Deserialize(jr, typeof(SiaDownloadList)); - } + using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => areq.GetResponseStream())) + using (var sr = new StreamReader(rs)) + using (var jr = new JsonTextReader(sr)) + fl = serializer.Deserialize(jr) + ?? throw new Exception("Failed to deserialize response"); } } - catch (System.Net.WebException wex) + catch (WebException wex) { - throw new Exception(getResponseBodyOnError(endpoint, wex)); + throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); } return fl; } - private bool IsDownloadComplete(string siafilename, string localname) + private async Task IsDownloadComplete(string siafilename, string localname, CancellationToken cancelToken) { - SiaDownloadList fl = GetDownloads(); + var fl = await GetDownloads(cancelToken).ConfigureAwait(false); if (fl.Files == null) return false; @@ -268,10 +272,10 @@ namespace Duplicati.Library.Backend.Sia try { // Sia seems to keep the file open/locked for a while, make sure we can open it - System.IO.FileStream fs = new System.IO.FileStream(localname, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); - fs.Close(); + using (var fs = new FileStream(localname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + fs.Close(); } - catch (System.IO.IOException) + catch (IOException) { return false; } @@ -285,10 +289,7 @@ namespace Duplicati.Library.Backend.Sia #region IBackend Members public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); public Task CreateFolderAsync(CancellationToken cancelToken) { @@ -296,24 +297,19 @@ namespace Duplicati.Library.Backend.Sia return Task.CompletedTask; } - public string DisplayName - { - get { return Strings.Sia.DisplayName; } - } + public string DisplayName => Strings.Sia.DisplayName; - public string ProtocolKey - { - get { return "sia"; } - } + public string ProtocolKey => "sia"; - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { SiaFileList fl; try { - fl = GetFiles(); + fl = await GetFiles(cancelToken).ConfigureAwait(false); } - catch (System.Net.WebException wex) + catch (WebException wex) { throw new Exception("failed to call /renter/files " + wex.Message); } @@ -324,9 +320,9 @@ namespace Duplicati.Library.Backend.Sia { // Sia returns a complete file list, but we're only interested in files that are // in our target path - if (f.Siapath.StartsWith(m_targetpath, StringComparison.Ordinal)) + if (f.Siapath != null && f.Siapath.StartsWith(m_targetpath, StringComparison.Ordinal)) { - FileEntry fe = new FileEntry(f.Siapath.Substring(m_targetpath.Length + 1)) + var fe = new FileEntry(f.Siapath.Substring(m_targetpath.Length + 1)) { Size = f.Filesize, IsFolder = false @@ -337,10 +333,10 @@ namespace Duplicati.Library.Backend.Sia } } - public Task PutAsync(string remotename, string filename, CancellationToken cancelToken) + public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - string endpoint = ""; - string siafile = m_targetpath + "/" + remotename; + var endpoint = ""; + var siafile = m_targetpath + "/" + remotename; try { @@ -350,29 +346,25 @@ namespace Duplicati.Library.Backend.Sia Utility.Uri.UrlEncode(filename).Replace("+", "%20") ); - HttpWebRequest req = CreateRequest(endpoint); + var req = CreateRequest(endpoint); req.Method = WebRequestMethods.Http.Post; - AsyncHttpRequest areq = new AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); - using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse()) + using (var resp = (HttpWebResponse)areq.GetResponse()) { int code = (int)resp.StatusCode; if (code < 200 || code >= 300) throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - while (!IsUploadComplete(siafile)) - { - Thread.Sleep(5000); - } + while (!await IsUploadComplete(siafile, cancelToken).ConfigureAwait(false)) + await Task.Delay(5000, cancelToken).ConfigureAwait(false); } } catch (WebException wex) { - throw new Exception(getResponseBodyOnError(endpoint, wex)); + throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); } - - return Task.FromResult(true); } public async Task GetAsync(string remotename, string localname, CancellationToken cancelToken) @@ -385,27 +377,27 @@ namespace Duplicati.Library.Backend.Sia { endpoint = string.Format("/renter/download/{0}/{1}?destination={2}", m_targetpath, - Library.Utility.Uri.UrlEncode(remotename).Replace("+", "%20"), - Library.Utility.Uri.UrlEncode(tmpfilename).Replace("+", "%20") + Utility.Uri.UrlEncode(remotename).Replace("+", "%20"), + Utility.Uri.UrlEncode(tmpfilename).Replace("+", "%20") ); - System.Net.HttpWebRequest req = CreateRequest(endpoint); - req.Method = System.Net.WebRequestMethods.Http.Get; + var req = CreateRequest(endpoint); + req.Method = WebRequestMethods.Http.Get; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); + var areq = new AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = (HttpWebResponse)areq.GetResponse()) { int code = (int)resp.StatusCode; if (code < 200 || code >= 300) - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - while (!IsDownloadComplete(siafile, localname)) + while (!await IsDownloadComplete(siafile, localname, cancelToken).ConfigureAwait(false)) await Task.Delay(5000, cancelToken).ConfigureAwait(false); - System.IO.File.Copy(tmpfilename, localname, true); + File.Copy(tmpfilename, localname, true); try { - System.IO.File.Delete(tmpfilename); + File.Delete(tmpfilename); } catch (Exception) { @@ -413,16 +405,16 @@ namespace Duplicati.Library.Backend.Sia } } } - catch (System.Net.WebException wex) + catch (WebException wex) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) + if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) throw new FileMissingException(wex); else - throw new Exception(getResponseBodyOnError(endpoint, wex)); + throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); } } - public Task DeleteAsync(string remotename, CancellationToken cancellationToken) + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) { string endpoint = ""; @@ -430,51 +422,41 @@ namespace Duplicati.Library.Backend.Sia { endpoint = string.Format("/renter/delete/{0}/{1}", m_targetpath, - Library.Utility.Uri.UrlEncode(remotename).Replace("+", "%20") + Utility.Uri.UrlEncode(remotename).Replace("+", "%20") ); - System.Net.HttpWebRequest req = CreateRequest(endpoint); - req.Method = System.Net.WebRequestMethods.Http.Post; + var req = CreateRequest(endpoint); + req.Method = WebRequestMethods.Http.Post; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); - - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); - } + var areq = new AsyncHttpRequest(req); + using (var resp = (HttpWebResponse)areq.GetResponse()) + { + int code = (int)resp.StatusCode; + if (code < 200 || code >= 300) + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); + } + }).ConfigureAwait(false); } - catch (System.Net.WebException wex) + catch (WebException wex) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) + if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) throw new FileMissingException(wex); else - throw new Exception(getResponseBodyOnError(endpoint, wex)); + throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancellationToken).ConfigureAwait(false)); } - - return Task.CompletedTask; } - public IList SupportedCommands - { - get - { - return new List([ + public IList SupportedCommands => new List([ new CommandLineArgument(SIA_TARGETPATH, CommandLineArgument.ArgumentType.String, Strings.Sia.SiaPathDescriptionShort, Strings.Sia.SiaPathDescriptionLong, "/backup"), new CommandLineArgument(SIA_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Sia.SiaPasswordShort, Strings.Sia.SiaPasswordLong, null), - new CommandLineArgument(SIA_REDUNDANCY, CommandLineArgument.ArgumentType.String, Strings.Sia.SiaRedundancyDescriptionShort, Strings.Sia.SiaRedundancyDescriptionLong, "1.5"), + new CommandLineArgument(SIA_REDUNDANCY, CommandLineArgument.ArgumentType.Decimal, Strings.Sia.SiaRedundancyDescriptionShort, Strings.Sia.SiaRedundancyDescriptionLong, "1.5"), + .. TimeoutOptionsHelper.GetOptions() + .Where(x => x.Name != TimeoutOptionsHelper.ReadWriteTimeoutOption) ]); - } - } - public string Description - { - get - { - return Strings.Sia.Description; - } - } + public string Description => Strings.Sia.Description; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new System.Uri(m_apihost).Host }); diff --git a/Duplicati/Library/Backend/Sia/Strings.cs b/Duplicati/Library/Backend/Sia/Strings.cs index 2d5bc13bb..268bde58c 100644 --- a/Duplicati/Library/Backend/Sia/Strings.cs +++ b/Duplicati/Library/Backend/Sia/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,13 +23,13 @@ namespace Duplicati.Library.Backend.Strings { internal static class Sia { - public static string Description { get { return LC.L(@"This backend can read and write data to Sia."); } } - public static string DisplayName { get { return LC.L(@"Sia Decentralized Cloud"); } } - public static string SiaPathDescriptionLong { get { return LC.L(@"Set the target path. Example: /backup"); } } - public static string SiaPathDescriptionShort { get { return LC.L(@"Backup path"); } } - public static string SiaPasswordLong { get { return LC.L(@"Supply a password for Sia server."); } } - public static string SiaPasswordShort { get { return LC.L(@"Sia password"); } } - public static string SiaRedundancyDescriptionLong { get { return LC.L(@"The minimum value for redundancy is 1.0."); } } - public static string SiaRedundancyDescriptionShort { get { return LC.L(@"Set the minimum redundancy"); } } + public static string Description => LC.L(@"This backend can read and write data to Sia."); + public static string DisplayName => LC.L(@"Sia Decentralized Cloud"); + public static string SiaPathDescriptionLong => LC.L(@"Set the target path. Example: /backup"); + public static string SiaPathDescriptionShort => LC.L(@"Backup path"); + public static string SiaPasswordLong => LC.L(@"Supply a password for Sia server."); + public static string SiaPasswordShort => LC.L(@"Sia password"); + public static string SiaRedundancyDescriptionLong => LC.L(@"The minimum value for redundancy is 1.0."); + public static string SiaRedundancyDescriptionShort => LC.L(@"Set the minimum redundancy"); } } diff --git a/Duplicati/Library/Backend/Storj/Duplicati.Library.Backend.Storj.csproj b/Duplicati/Library/Backend/Storj/Duplicati.Library.Backend.Storj.csproj index c12ffc516..64cc4d4b0 100644 --- a/Duplicati/Library/Backend/Storj/Duplicati.Library.Backend.Storj.csproj +++ b/Duplicati/Library/Backend/Storj/Duplicati.Library.Backend.Storj.csproj @@ -1,10 +1,12 @@ - + net8.0 Library - Storj backend for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Storj backend for Duplicati + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/Storj/StorjBackend.cs b/Duplicati/Library/Backend/Storj/StorjBackend.cs index 132ea9dc4..e08ae37f0 100644 --- a/Duplicati/Library/Backend/Storj/StorjBackend.cs +++ b/Duplicati/Library/Backend/Storj/StorjBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,13 +18,11 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using Duplicati.Library.Utility.Options; +using System.Runtime.CompilerServices; using uplink.NET.Interfaces; using uplink.NET.Models; using uplink.NET.Services; @@ -44,14 +42,22 @@ namespace Duplicati.Library.Backend.Storj private const string PROTOCOL_KEY = "storj"; private const string STORJ_PARTNER_ID = "duplicati"; - private readonly string _satellite; - private readonly string _api_key; - private readonly string _secret; + private const string STORJ_AUTH_METHOD_API_KEY = "API key"; + private const string STORJ_AUTH_METHOD_ACCESS_GRANT = "Access grant"; + + private const string STORJ_DEFAULT_BUCKET = "duplicati"; + private const string STORJ_DEFAULT_SATELLITE = "us1.storj.io:7777"; + private const string STORJ_DEFAULT_AUTH_METHOD = STORJ_AUTH_METHOD_API_KEY; + + private readonly string? _satellite; + private readonly string? _api_key; + private readonly string? _secret; private readonly string _bucket; - private readonly string _folder; - private Access _access; - private IBucketService _bucketService; - private IObjectService _objectService; + private readonly string? _folder; + private readonly Access _access; + private readonly IBucketService _bucketService; + private readonly IObjectService _objectService; + private readonly TimeoutOptionsHelper.Timeouts _timeouts; public static readonly Dictionary KNOWN_STORJ_SATELLITES = new Dictionary(StringComparer.OrdinalIgnoreCase){ { "US Central", "us1.storj.io:7777" }, @@ -70,7 +76,7 @@ namespace Duplicati.Library.Backend.Storj if (_libraryLoaded) return; - Access.SetTempDirectory(Library.Utility.TempFolder.SystemTempPath); + Access.SetTempDirectory(Utility.TempFolder.SystemTempPath); _libraryLoaded = true; } @@ -78,16 +84,21 @@ namespace Duplicati.Library.Backend.Storj // This constructor is needed by the BackendLoader. public Storj() { + _bucket = null!; + _access = null!; + _bucketService = null!; + _objectService = null!; + _timeouts = null!; } // ReSharper disable once UnusedMember.Global // This constructor is needed by the BackendLoader. - public Storj(string url, Dictionary options) + public Storj(string url, Dictionary options) { InitStorjLibrary(); - var auth_method = options[STORJ_AUTH_METHOD]; - if (auth_method == "Access grant") + var auth_method = options.GetValueOrDefault(STORJ_AUTH_METHOD, STORJ_DEFAULT_AUTH_METHOD); + if (string.Equals(auth_method, STORJ_AUTH_METHOD_ACCESS_GRANT, StringComparison.OrdinalIgnoreCase)) { //Create an access from the access grant var shared_access = options[STORJ_SHARED_ACCESS]; @@ -96,16 +107,12 @@ namespace Duplicati.Library.Backend.Storj else { //Create an access for a satellite, API key and encryption passphrase - _satellite = options[STORJ_SATELLITE]; + _satellite = options.GetValueOrDefault(STORJ_SATELLITE, STORJ_DEFAULT_SATELLITE); + if (string.IsNullOrWhiteSpace(_satellite)) + _satellite = STORJ_DEFAULT_SATELLITE; - if (options.ContainsKey(STORJ_API_KEY)) - { - _api_key = options[STORJ_API_KEY]; - } - if (options.ContainsKey(STORJ_SECRET)) - { - _secret = options[STORJ_SECRET]; - } + _api_key = options.GetValueOrDefault(STORJ_API_KEY); + _secret = options.GetValueOrDefault(STORJ_SECRET); _access = new Access(_satellite, _api_key, _secret, new Config() { UserAgent = STORJ_PARTNER_ID }); } @@ -114,53 +121,36 @@ namespace Duplicati.Library.Backend.Storj _objectService = new ObjectService(_access); //If no bucket was provided use the default "duplicati"-bucket - if (options.ContainsKey(STORJ_BUCKET)) - { - _bucket = options[STORJ_BUCKET]; - } - else - { - _bucket = "duplicati"; - } + var bucket = options.GetValueOrDefault(STORJ_BUCKET); + _bucket = string.IsNullOrWhiteSpace(bucket) + ? STORJ_DEFAULT_BUCKET + : bucket; - if (options.ContainsKey(STORJ_FOLDER)) - { - _folder = options[STORJ_FOLDER]; - } + _folder = options.GetValueOrDefault(STORJ_FOLDER); + _timeouts = TimeoutOptionsHelper.Parse(options); } - public string DisplayName - { - get { return Strings.Storj.DisplayName; } - } + public string DisplayName => Strings.Storj.DisplayName; public string ProtocolKey => PROTOCOL_KEY; - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(STORJ_AUTH_METHOD, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAuthMethodDescriptionShort, Strings.Storj.StorjAuthMethodDescriptionLong, "API key"), - new CommandLineArgument(STORJ_SATELLITE, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSatelliteDescriptionShort, Strings.Storj.StorjSatelliteDescriptionLong, "us1.storj.io:7777"), - new CommandLineArgument(STORJ_API_KEY, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAPIKeyDescriptionShort, Strings.Storj.StorjAPIKeyDescriptionLong), - new CommandLineArgument(STORJ_SECRET, CommandLineArgument.ArgumentType.Password, Strings.Storj.StorjSecretDescriptionShort, Strings.Storj.StorjSecretDescriptionLong), - new CommandLineArgument(STORJ_SHARED_ACCESS, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSharedAccessDescriptionShort, Strings.Storj.StorjSharedAccessDescriptionLong), - new CommandLineArgument(STORJ_BUCKET, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjBucketDescriptionShort, Strings.Storj.StorjBucketDescriptionLong), - new CommandLineArgument(STORJ_FOLDER, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjFolderDescriptionShort, Strings.Storj.StorjFolderDescriptionLong), - }); - } - } + public IList SupportedCommands => [ + new CommandLineArgument(STORJ_AUTH_METHOD, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAuthMethodDescriptionShort, Strings.Storj.StorjAuthMethodDescriptionLong, STORJ_DEFAULT_AUTH_METHOD, null, [STORJ_AUTH_METHOD_API_KEY, STORJ_AUTH_METHOD_ACCESS_GRANT]), + new CommandLineArgument(STORJ_SATELLITE, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSatelliteDescriptionShort, Strings.Storj.StorjSatelliteDescriptionLong, STORJ_DEFAULT_SATELLITE), + new CommandLineArgument(STORJ_API_KEY, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjAPIKeyDescriptionShort, Strings.Storj.StorjAPIKeyDescriptionLong), + new CommandLineArgument(STORJ_SECRET, CommandLineArgument.ArgumentType.Password, Strings.Storj.StorjSecretDescriptionShort, Strings.Storj.StorjSecretDescriptionLong), + new CommandLineArgument(STORJ_SHARED_ACCESS, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjSharedAccessDescriptionShort, Strings.Storj.StorjSharedAccessDescriptionLong), + new CommandLineArgument(STORJ_BUCKET, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjBucketDescriptionShort, Strings.Storj.StorjBucketDescriptionLong, STORJ_DEFAULT_BUCKET), + new CommandLineArgument(STORJ_FOLDER, CommandLineArgument.ArgumentType.String, Strings.Storj.StorjFolderDescriptionShort, Strings.Storj.StorjFolderDescriptionLong), + .. TimeoutOptionsHelper.GetOptions(), + ]; - public string Description - { - get - { - return Strings.Storj.Description; - } - } + public string Description => Strings.Storj.Description; - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(Array.Empty()); + public Task GetDNSNamesAsync(CancellationToken cancelToken) + => Task.FromResult(string.IsNullOrWhiteSpace(_satellite) + ? [] + : [_satellite.Split(':').First()]); public Task CreateFolderAsync(CancellationToken cancelToken) { @@ -168,12 +158,15 @@ namespace Duplicati.Library.Backend.Storj return Task.CompletedTask; } + private Task GetBucketAsync(CancellationToken cancelToken) + => Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, _ => _bucketService.EnsureBucketAsync(_bucket)); + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); - await _objectService.DeleteObjectAsync(bucket, GetBasePath() + remotename).ConfigureAwait(false); + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, _ => _objectService.DeleteObjectAsync(bucket, GetBasePath() + remotename)).ConfigureAwait(false); } catch (Exception root) { @@ -183,25 +176,13 @@ namespace Duplicati.Library.Backend.Storj public void Dispose() { - if (_objectService != null) - { - _objectService = null; - } - if (_bucketService != null) - { - _bucketService = null; - } - if (_access != null) - { - _access.Dispose(); - _access = null; - } + _access?.Dispose(); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); - var download = await _objectService.DownloadObjectAsync(bucket, GetBasePath() + remotename, new DownloadOptions(), false).ConfigureAwait(false); + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); + var download = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, _ => _objectService.DownloadObjectAsync(bucket, GetBasePath() + remotename, new DownloadOptions(), false)).ConfigureAwait(false); await download.StartDownloadAsync().ConfigureAwait(false); if (download.Completed) @@ -216,44 +197,40 @@ namespace Duplicati.Library.Backend.Storj public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { - int index = 0; - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); - var download = await _objectService.DownloadObjectAsync(bucket, GetBasePath() + remotename, new DownloadOptions(), false).ConfigureAwait(false); + var index = 0; + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); + var download = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, _ => _objectService.DownloadObjectAsync(bucket, GetBasePath() + remotename, new DownloadOptions(), false)).ConfigureAwait(false); + using var ts = stream.ObserveWriteTimeout(_timeouts.ReadWriteTimeout, false); download.DownloadOperationProgressChanged += (op) => { cancelToken.ThrowIfCancellationRequested(); - int newPartLength = (int)op.BytesReceived - index; - byte[] newPart = new byte[newPartLength]; + var newPartLength = (int)op.BytesReceived - index; + // TODO: Copy here should not be needed? + // ts.Write(op.DownloadedBytes, index, newPartLength); + var newPart = new byte[newPartLength]; Array.Copy(op.DownloadedBytes, index, newPart, 0, newPartLength); - stream.Write(newPart, 0, newPartLength); + ts.Write(newPart, 0, newPartLength); index = index + newPartLength; }; await download.StartDownloadAsync().ConfigureAwait(false); } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { - return ListAsync().Await(); - } - - private async Task> ListAsync() - { - List files = new List(); - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); var prefix = GetBasePath(); - var objects = await _objectService.ListObjectsAsync(bucket, new ListObjectsOptions { Recursive = true, System = true, Custom = true, Prefix = prefix }).ConfigureAwait(false); + var objects = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, _ => _objectService.ListObjectsAsync(bucket, new ListObjectsOptions { Recursive = true, System = true, Custom = true, Prefix = prefix })).ConfigureAwait(false); foreach (var obj in objects.Items) { - StorjFile file = new StorjFile(obj); + var file = new StorjFile(obj); if (prefix != "") { file.Name = file.Name.Replace(prefix, ""); } - files.Add(file); + yield return file; } - - return files; } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) @@ -264,29 +241,19 @@ namespace Duplicati.Library.Backend.Storj public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); - CustomMetadata custom = new CustomMetadata(); + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); + var custom = new CustomMetadata(); custom.Entries.Add(new CustomMetadataEntry { Key = StorjFile.STORJ_LAST_ACCESS, Value = DateTime.Now.ToUniversalTime().ToString("O") }); custom.Entries.Add(new CustomMetadataEntry { Key = StorjFile.STORJ_LAST_MODIFICATION, Value = DateTime.Now.ToUniversalTime().ToString("O") }); - var upload = await _objectService.UploadObjectAsync(bucket, GetBasePath() + remotename, new UploadOptions(), stream, custom, false).ConfigureAwait(false); + using var ts = stream.ObserveReadTimeout(_timeouts.ReadWriteTimeout, false); + var upload = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, _ => _objectService.UploadObjectAsync(bucket, GetBasePath() + remotename, new UploadOptions(), ts, custom, false)).ConfigureAwait(false); await upload.StartUploadAsync().ConfigureAwait(false); if (upload.Failed) - { throw new Exception(upload.ErrorMessage); - } } - public async Task TestAsync(CancellationToken cancelToken) - { - var ct = CancellationTokenSource.CreateLinkedTokenSource(cancelToken); - var testTask = TestImplAsync(ct.Token); - ct.CancelAfter(10000); - await Task.WhenAny(testTask, Task.Delay(Timeout.Infinite, ct.Token)).ConfigureAwait(false); - if (testTask.IsCompleted) - await testTask.ConfigureAwait(false); - else if (!testTask.IsCompletedSuccessfully) - throw new Exception(Strings.Storj.TestConnectionFailed); - } + public Task TestAsync(CancellationToken cancelToken) + => Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => TestImplAsync(ct)); /// /// Test the connection by: @@ -297,9 +264,9 @@ namespace Duplicati.Library.Backend.Storj /// true, if the test was successfull or and exception private async Task TestImplAsync(CancellationToken cancelToken) { - string testFileName = GetBasePath() + "duplicati_test.dat"; + var testFileName = GetBasePath() + "duplicati_test.dat"; - var bucket = await _bucketService.EnsureBucketAsync(_bucket).ConfigureAwait(false); + var bucket = await GetBucketAsync(cancelToken).ConfigureAwait(false); var upload = await _objectService.UploadObjectAsync(bucket, testFileName, new UploadOptions(), GetRandomBytes(256), false).ConfigureAwait(false); await upload.StartUploadAsync().ConfigureAwait(false); @@ -309,9 +276,7 @@ namespace Duplicati.Library.Backend.Storj await _objectService.DeleteObjectAsync(bucket, testFileName).ConfigureAwait(false); if (download.Failed || download.BytesReceived != 256) - { throw new Exception(download.ErrorMessage); - } return true; } @@ -321,12 +286,9 @@ namespace Duplicati.Library.Backend.Storj /// /// The base path within a bucket where the backup shall be placed private string GetBasePath() - { - if (!string.IsNullOrEmpty(_folder)) - return _folder + "/"; - else - return ""; - } + => string.IsNullOrWhiteSpace(_folder) + ? "" + : Util.AppendDirSeparator(_folder, "/"); /// /// Creates some random bytes with the given length - just for testing the connection @@ -335,10 +297,8 @@ namespace Duplicati.Library.Backend.Storj /// A byte-array with the given length private static byte[] GetRandomBytes(long length) { - byte[] bytes = new byte[length]; - Random rand = new Random(); - rand.NextBytes(bytes); - + var bytes = new byte[length]; + Random.Shared.NextBytes(bytes); return bytes; } } diff --git a/Duplicati/Library/Backend/Storj/StorjConfig.cs b/Duplicati/Library/Backend/Storj/StorjConfig.cs index 5237844bc..99a3da856 100644 --- a/Duplicati/Library/Backend/Storj/StorjConfig.cs +++ b/Duplicati/Library/Backend/Storj/StorjConfig.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,10 +18,9 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. + using Duplicati.Library.Interface; using Duplicati.Library.Localization.Short; -using System; -using System.Collections.Generic; namespace Duplicati.Library.Backend.Storj { @@ -29,7 +28,7 @@ namespace Duplicati.Library.Backend.Storj { private const ConfigType DEFAULT_CONFIG_TYPE = ConfigType.Satellites; private const string KEY_CONFIGTYPE = "storj-config"; - private static readonly string DEFAULT_CONFIG_TYPE_STR = Enum.GetName(typeof(ConfigType), DEFAULT_CONFIG_TYPE); + private static readonly string DEFAULT_CONFIG_TYPE_STR = DEFAULT_CONFIG_TYPE.ToString(); public enum ConfigType { @@ -39,47 +38,31 @@ namespace Duplicati.Library.Backend.Storj #region IWebModule implementation - public string Key { get { return "storj-getconfig"; } } + public string Key => "storj-getconfig"; - public string DisplayName { get { return LC.L("Storj DCS configuration module"); } } + public string DisplayName => Strings.StorjConfig.DisplayName; - public string Description { get { return LC.L("Expose Storj DCS configuration as a web module"); } } + public string Description => Strings.StorjConfig.Description; - public IList SupportedCommands - { - get - { - return new List([ - new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, LC.L("The config to get"), LC.L("Provide different config values"), DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) - - ]); - } - } + public IList SupportedCommands => [ + new CommandLineArgument(KEY_CONFIGTYPE, CommandLineArgument.ArgumentType.Enumeration, LC.L("The config to get"), LC.L("Provide different config values"), DEFAULT_CONFIG_TYPE_STR, Enum.GetNames(typeof(ConfigType))) + ]; public IDictionary Execute(IDictionary options) { - string k; - options.TryGetValue(KEY_CONFIGTYPE, out k); + options.TryGetValue(KEY_CONFIGTYPE, out var k); if (string.IsNullOrWhiteSpace(k)) - { k = DEFAULT_CONFIG_TYPE_STR; - } - ConfigType ct; - if (!Enum.TryParse(k, true, out ct)) - { + if (!Enum.TryParse(k, true, out var ct)) ct = DEFAULT_CONFIG_TYPE; - } - switch (ct) + return ct switch { - case ConfigType.Satellites: - return Storj.KNOWN_STORJ_SATELLITES; - case ConfigType.AuthenticationMethods: - return Storj.KNOWN_AUTHENTICATION_METHODS; - default: - return Storj.KNOWN_STORJ_SATELLITES; - } + ConfigType.Satellites => Storj.KNOWN_STORJ_SATELLITES, + ConfigType.AuthenticationMethods => Storj.KNOWN_AUTHENTICATION_METHODS, + _ => Storj.KNOWN_STORJ_SATELLITES, + }; } #endregion } diff --git a/Duplicati/Library/Backend/Storj/StorjFile.cs b/Duplicati/Library/Backend/Storj/StorjFile.cs index d81a8604f..2e3512476 100644 --- a/Duplicati/Library/Backend/Storj/StorjFile.cs +++ b/Duplicati/Library/Backend/Storj/StorjFile.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -33,6 +33,7 @@ namespace Duplicati.Library.Backend.Storj public DateTime LastAccess { get; set; } public DateTime LastModification { get; set; } + public DateTime Created { get; set; } public string Name { get; set; } @@ -40,7 +41,7 @@ namespace Duplicati.Library.Backend.Storj public StorjFile() { - + Name = string.Empty; } public StorjFile(uplink.NET.Models.Object tardigradeObject) diff --git a/Duplicati/Library/Backend/Storj/Strings.cs b/Duplicati/Library/Backend/Storj/Strings.cs index d053d1ac9..92bc070f7 100644 --- a/Duplicati/Library/Backend/Storj/Strings.cs +++ b/Duplicati/Library/Backend/Storj/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,24 +22,29 @@ using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Backend.Strings { - public static class Storj + internal static class Storj { - public static string Description { get { return LC.L(@"This backend can read and write data to the Storj DCS."); } } - public static string DisplayName { get { return LC.L(@"Storj DCS (Decentralized Cloud Storage)"); } } - public static string TestConnectionFailed { get { return LC.L(@"Connection-test failed."); } } - public static string StorjAuthMethodDescriptionLong { get { return LC.L(@"Specify the authentication method which describes which way to use to connect to the network - either via API key or via an access grant."); } } - public static string StorjAuthMethodDescriptionShort { get { return LC.L(@"Authentication method"); } } - public static string StorjSatelliteDescriptionLong { get { return LC.L(@"Specify the satellite that keeps track of all metadata. Use a Storj DCS server for high-performance SLA-backed connectivity or use a community server. Or even host your own."); } } - public static string StorjSatelliteDescriptionShort { get { return LC.L(@"Satellite"); } } - public static string StorjAPIKeyDescriptionLong { get { return LC.L(@"Supply the API key which grants access to a specific project on your chosen satellite. Head over to the dashboard of your satellite to create one if you do not already have an API key."); } } - public static string StorjAPIKeyDescriptionShort { get { return LC.L(@"API key"); } } - public static string StorjSecretDescriptionLong { get { return LC.L(@"Supply the encryption passphrase used to encrypt your data before sending it to the Storj network. This passphrase can be the only secret to provide - for Storj you do not necessary need any additional encryption (from Duplicati) in place."); } } - public static string StorjSecretDescriptionShort { get { return LC.L(@"Encryption passphrase"); } } - public static string StorjSharedAccessDescriptionLong { get { return LC.L(@"Supply the access grant which contains all information in one encrypted string. You may use it instead of a satellite, API key and secret."); } } - public static string StorjSharedAccessDescriptionShort { get { return LC.L(@"Access grant"); } } - public static string StorjBucketDescriptionLong { get { return LC.L(@"Specify the bucket for storing the backup."); } } - public static string StorjBucketDescriptionShort { get { return LC.L(@"Bucket"); } } - public static string StorjFolderDescriptionLong { get { return LC.L(@"Specify the folder in the bucket for storing the backup."); } } - public static string StorjFolderDescriptionShort { get { return LC.L(@"Folder"); } } + public static string Description => LC.L(@"This backend can read and write data to the Storj DCS."); + public static string DisplayName => LC.L(@"Storj DCS (Decentralized Cloud Storage)"); + public static string StorjAuthMethodDescriptionLong => LC.L(@"Specify the authentication method which describes which way to use to connect to the network - either via API key or via an access grant."); + public static string StorjAuthMethodDescriptionShort => LC.L(@"Authentication method"); + public static string StorjSatelliteDescriptionLong => LC.L(@"Specify the satellite that keeps track of all metadata. Use a Storj DCS server for high-performance SLA-backed connectivity or use a community server. Or even host your own."); + public static string StorjSatelliteDescriptionShort => LC.L(@"Satellite"); + public static string StorjAPIKeyDescriptionLong => LC.L(@"Supply the API key which grants access to a specific project on your chosen satellite. Head over to the dashboard of your satellite to create one if you do not already have an API key."); + public static string StorjAPIKeyDescriptionShort => LC.L(@"API key"); + public static string StorjSecretDescriptionLong => LC.L(@"Supply the encryption passphrase used to encrypt your data before sending it to the Storj network. This passphrase can be the only secret to provide - for Storj you do not necessary need any additional encryption (from Duplicati) in place."); + public static string StorjSecretDescriptionShort => LC.L(@"Encryption passphrase"); + public static string StorjSharedAccessDescriptionLong => LC.L(@"Supply the access grant which contains all information in one encrypted string. You may use it instead of a satellite, API key and secret."); + public static string StorjSharedAccessDescriptionShort => LC.L(@"Access grant"); + public static string StorjBucketDescriptionLong => LC.L(@"Specify the bucket for storing the backup."); + public static string StorjBucketDescriptionShort => LC.L(@"Bucket"); + public static string StorjFolderDescriptionLong => LC.L(@"Specify the folder in the bucket for storing the backup."); + public static string StorjFolderDescriptionShort => LC.L(@"Folder"); + } + + internal static class StorjConfig + { + public static string DisplayName => LC.L("Storj DCS configuration module"); + public static string Description => LC.L("Expose Storj DCS configuration as a web module"); } } diff --git a/Duplicati/Library/Backend/TahoeLAFS/Duplicati.Library.Backend.TahoeLAFS.csproj b/Duplicati/Library/Backend/TahoeLAFS/Duplicati.Library.Backend.TahoeLAFS.csproj index ac39d7a5c..87a3061b8 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/Duplicati.Library.Backend.TahoeLAFS.csproj +++ b/Duplicati/Library/Backend/TahoeLAFS/Duplicati.Library.Backend.TahoeLAFS.csproj @@ -4,7 +4,9 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/TahoeLAFS/Strings.cs b/Duplicati/Library/Backend/TahoeLAFS/Strings.cs index 101476656..2ab06071b 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/Strings.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class TahoeBackend { - public static string Description { get { return LC.L(@"This backend can read and write data to a Tahoe-LAFS based backend. Allowed format is ""tahoe://hostname:port/uri/$DIRCAP""."); } } - public static string Displayname { get { return LC.L(@"Tahoe-LAFS"); } } - public static string DescriptionUseSSLLong { get { return LC.L(@"Use this option to communicate using Secure Socket Layer (SSL) over http (https)."); } } - public static string DescriptionUseSSLShort { get { return LC.L(@"Instruct Duplicati to use an SSL (https) connection"); } } +namespace Duplicati.Library.Backend.Strings +{ + internal static class TahoeBackend + { + public static string Description => LC.L(@"This backend can read and write data to a Tahoe-LAFS based backend. Allowed format is ""tahoe://hostname:port/uri/$DIRCAP""."); + public static string Displayname => LC.L(@"Tahoe-LAFS"); + public static string DescriptionUseSSLLong => LC.L(@"Use this option to communicate using Secure Socket Layer (SSL) over http (https)."); + public static string DescriptionUseSSLShort => LC.L(@"Instruct Duplicati to use an SSL (https) connection"); public static string MissingFolderError(string foldername, string message) { return LC.L(@"The folder {0} was not found, message: {1}", foldername, message); } - public static string UnrecognizedUriError { get { return LC.L(@"Unsupported URL format, must start with ""uri/URI:DIR2:"""); } } + public static string UnrecognizedUriError => LC.L(@"Unsupported URL format, must start with ""uri/URI:DIR2:"""); } } diff --git a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs index 9e868e8ce..c3da4ad88 100644 --- a/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs +++ b/Duplicati/Library/Backend/TahoeLAFS/TahoeBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,13 +21,12 @@ using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; using System.Net; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend { @@ -35,27 +34,28 @@ namespace Duplicati.Library.Backend { private readonly string m_url; private readonly bool m_useSSL = false; + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; private class TahoeEl { - public string nodetype { get; set; } - public TahoeNode node { get; set; } + public string? nodetype { get; set; } + public TahoeNode? node { get; set; } } private class TahoeNode { - public string rw_uri { get; set; } - public string verify_uri { get; set; } - public string ro_uri { get; set; } - public Dictionary children { get; set; } + public string? rw_uri { get; set; } + public string? verify_uri { get; set; } + public string? ro_uri { get; set; } + public Dictionary? children { get; set; } public bool mutable { get; set; } public long size { get; set; } - public TahoeMetadata metadata { get; set; } + public TahoeMetadata? metadata { get; set; } } private class TahoeMetadata { - public TahoeStamps tahoe { get; set; } + public TahoeStamps? tahoe { get; set; } } private class TahoeStamps @@ -67,15 +67,13 @@ namespace Duplicati.Library.Backend private class TahoeElConverter : JsonConverter { public override bool CanConvert(Type objectType) - { - return objectType == typeof(TahoeEl); - } + => objectType == typeof(TahoeEl); - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var array = JArray.Load(reader); - string nodetype = null; - TahoeNode node = null; + string? nodetype = null; + TahoeNode? node = null; foreach (var token in array.Children()) if (token.Type == JTokenType.String) nodetype = token.ToString(); @@ -85,18 +83,18 @@ namespace Duplicati.Library.Backend return new TahoeEl() { nodetype = nodetype, node = node }; } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + => throw new NotImplementedException(); } public TahoeBackend() { + m_url = null!; + m_timeouts = null!; } - public TahoeBackend(string url, Dictionary options) + public TahoeBackend(string url, Dictionary options) { //Validate URL var u = new Utility.Uri(url); @@ -105,15 +103,18 @@ namespace Duplicati.Library.Backend if (!u.Path.StartsWith("uri/URI:DIR2:", StringComparison.Ordinal) && !u.Path.StartsWith("uri/URI%3ADIR2%3A", StringComparison.Ordinal)) throw new UserInformationException(Strings.TahoeBackend.UnrecognizedUriError, "TahoeInvalidUri"); - m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl"); + // TODO: When upgrading to HttpClient, also support the certificate options + var certOptions = SslOptionsHelper.Parse(options); + m_useSSL = certOptions.UseSSL; m_url = u.SetScheme(m_useSSL ? "https" : "http").SetQuery(null).SetCredentials(null, null).ToString(); m_url = Util.AppendDirSeparator(m_url, "/"); + m_timeouts = TimeoutOptionsHelper.Parse(options); } - private System.Net.HttpWebRequest CreateRequest(string remotename, string queryparams) + private HttpWebRequest CreateRequest(string remotename, string queryparams) { - var req = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(m_url + (Library.Utility.Uri.UrlEncode(remotename).Replace("+", "%20")) + (string.IsNullOrEmpty(queryparams) || queryparams.Trim().Length == 0 ? "" : "?" + queryparams)); + var req = (HttpWebRequest)HttpWebRequest.Create($"{m_url}{Utility.Uri.UrlEncode(remotename).Replace("+", "%20")}{(string.IsNullOrEmpty(queryparams) || queryparams.Trim().Length == 0 ? "" : "?" + queryparams)}"); req.KeepAlive = false; req.UserAgent = "Duplicati Tahoe-LAFS Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; @@ -124,87 +125,75 @@ namespace Duplicati.Library.Backend #region IBackend Members public Task TestAsync(CancellationToken cancelToken) - { - this.TestList(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); - public Task CreateFolderAsync(CancellationToken cancelToken) + public async Task CreateFolderAsync(CancellationToken cancelToken) { var req = CreateRequest("", "t=mkdir"); - req.Method = System.Net.WebRequestMethods.Http.Post; - var areq = new Utility.AsyncHttpRequest(req); - using (areq.GetResponse()) + req.Method = WebRequestMethods.Http.Post; + var areq = new AsyncHttpRequest(req); + using (await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponse()).ConfigureAwait(false)) { } - - return Task.CompletedTask; } - public string DisplayName - { - get { return Strings.TahoeBackend.Displayname; } - } + public string DisplayName => Strings.TahoeBackend.Displayname; - public string ProtocolKey - { - get { return "tahoe"; } - } + public string ProtocolKey => "tahoe"; - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { TahoeEl data; try { var req = CreateRequest("", "t=json"); - req.Method = System.Net.WebRequestMethods.Http.Get; + req.Method = WebRequestMethods.Http.Get; - var areq = new Utility.AsyncHttpRequest(req); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + var areq = new AsyncHttpRequest(req); + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { - int code = (int)resp.StatusCode; + var code = (int)resp.StatusCode; if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); using (var rs = areq.GetResponseStream()) - using (var sr = new System.IO.StreamReader(rs)) - using (var jr = new Newtonsoft.Json.JsonTextReader(sr)) + using (var sr = new StreamReader(rs)) + using (var jr = new JsonTextReader(sr)) { - var jsr = new Newtonsoft.Json.JsonSerializer(); + var jsr = new JsonSerializer(); jsr.Converters.Add(new TahoeElConverter()); - data = jsr.Deserialize(jr); + data = jsr.Deserialize(jr) + ?? throw new Exception("Invalid folder listing response"); } } } - catch (System.Net.WebException wex) + catch (WebException wex) + when (wex.Response is HttpWebResponse response + && (response.StatusCode == HttpStatusCode.Conflict || response.StatusCode == HttpStatusCode.NotFound)) { - //Convert to better exception - if (wex.Response as System.Net.HttpWebResponse != null) - if ((wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.Conflict || (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.NotFound) - throw new Interface.FolderMissingException(Strings.TahoeBackend.MissingFolderError(m_url, wex.Message), wex); - - throw; + throw new FolderMissingException(Strings.TahoeBackend.MissingFolderError(m_url, wex.Message), wex); } if (data == null || data.node == null || data.nodetype != "dirnode") throw new Exception("Invalid folder listing response"); - foreach (var e in data.node.children) + foreach (var e in data.node.children ?? []) { if (e.Value == null || e.Value.node == null) continue; - bool isDir = e.Value.nodetype == "dirnode"; - bool isFile = e.Value.nodetype == "filenode"; + var isDir = e.Value.nodetype == "dirnode"; + var isFile = e.Value.nodetype == "filenode"; if (!isDir && !isFile) continue; - FileEntry fe = new FileEntry(e.Key); + var fe = new FileEntry(e.Key); fe.IsFolder = isDir; if (e.Value.node.metadata != null && e.Value.node.metadata.tahoe != null) - fe.LastModification = Duplicati.Library.Utility.Utility.EPOCH + TimeSpan.FromSeconds(e.Value.node.metadata.tahoe.linkmotime); + fe.LastModification = Utility.Utility.EPOCH + TimeSpan.FromSeconds(e.Value.node.metadata.tahoe.linkmotime); if (isFile) fe.Size = e.Value.node.size; @@ -215,53 +204,40 @@ namespace Duplicati.Library.Backend public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = System.IO.File.OpenRead(filename)) + using (var fs = File.OpenRead(filename)) await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = System.IO.File.Create(filename)) + using (var fs = File.Create(filename)) await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { - System.Net.HttpWebRequest req = CreateRequest(remotename, ""); + var req = CreateRequest(remotename, ""); req.Method = "DELETE"; - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); - using (areq.GetResponse()) + var areq = new AsyncHttpRequest(req); + using (await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => areq.GetResponse()).ConfigureAwait(false)) { } } - catch (System.Net.WebException wex) + catch (WebException wex) + when (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) { - if (wex.Response is HttpWebResponse response && response.StatusCode == System.Net.HttpStatusCode.NotFound) - throw new FileMissingException(wex); - else - throw; - } - - return Task.CompletedTask; - } - - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("use-ssl", CommandLineArgument.ArgumentType.Boolean, Strings.TahoeBackend.DescriptionUseSSLShort, Strings.TahoeBackend.DescriptionUseSSLLong), - }); + throw new FileMissingException(wex); } } - public string Description - { - get { return Strings.TahoeBackend.Description; } - } + public IList SupportedCommands => [ + .. SslOptionsHelper.GetSslOnlyOption(), + ]; - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new Uri(m_url).Host }); + public string Description => Strings.TahoeBackend.Description; + + public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new System.Uri(m_url).Host }); #endregion @@ -275,53 +251,52 @@ namespace Duplicati.Library.Backend #region IStreamingBackend Members - public async Task PutAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { - System.Net.HttpWebRequest req = CreateRequest(remotename, ""); - req.Method = System.Net.WebRequestMethods.Http.Put; + var req = CreateRequest(remotename, ""); + req.Method = WebRequestMethods.Http.Put; req.ContentType = "application/binary"; try { req.ContentLength = stream.Length; } catch { } - Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); - using (System.IO.Stream s = areq.GetRequestStream()) - await Utility.Utility.CopyStreamAsync(stream, s, true, cancelToken).ConfigureAwait(false); + var areq = new AsyncHttpRequest(req); + using (var s = areq.GetRequestStream()) + using (var t = s.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(stream, t, true, cancelToken).ConfigureAwait(false); - using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + if (code < 200 || code >= 300) + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); } } - catch (System.Net.WebException wex) + catch (WebException wex) + when (wex.Response is HttpWebResponse response + && (response.StatusCode == HttpStatusCode.Conflict || response.StatusCode == HttpStatusCode.NotFound)) { - //Convert to better exception - if (wex.Response as System.Net.HttpWebResponse != null) - if ((wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.Conflict || (wex.Response as System.Net.HttpWebResponse).StatusCode == System.Net.HttpStatusCode.NotFound) - throw new Interface.FolderMissingException(Strings.TahoeBackend.MissingFolderError(m_url, wex.Message), wex); - - throw; + throw new FolderMissingException(Strings.TahoeBackend.MissingFolderError(m_url, wex.Message), wex); } } public async Task GetAsync(string remotename, System.IO.Stream stream, CancellationToken cancelToken) { var req = CreateRequest(remotename, ""); - req.Method = System.Net.WebRequestMethods.Http.Get; + req.Method = WebRequestMethods.Http.Get; - var areq = new Utility.AsyncHttpRequest(req); - using (var resp = (System.Net.HttpWebResponse)areq.GetResponse()) + var areq = new AsyncHttpRequest(req); + using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) { int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically - throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp); + if (code < 200 || code >= 300) + throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); using (var s = areq.GetResponseStream()) - await Utility.Utility.CopyStreamAsync(s, stream, true, cancelToken).ConfigureAwait(false); + using (var t = s.ObserveReadTimeout(m_timeouts.ReadWriteTimeout)) + await Utility.Utility.CopyStreamAsync(t, stream, true, cancelToken).ConfigureAwait(false); } } diff --git a/Duplicati/Library/Backend/TencentCOS/COSBackend.cs b/Duplicati/Library/Backend/TencentCOS/COSBackend.cs index 2fd80a192..332018b21 100644 --- a/Duplicati/Library/Backend/TencentCOS/COSBackend.cs +++ b/Duplicati/Library/Backend/TencentCOS/COSBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. + using COSXML; using COSXML.Auth; using COSXML.Model.Bucket; @@ -26,13 +27,9 @@ using COSXML.Model.Tag; using COSXML.Utils; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; -using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace Duplicati.Library.Backend.TencentCOS { @@ -41,7 +38,7 @@ namespace Duplicati.Library.Backend.TencentCOS /// en: https://intl.cloud.tencent.com/document/product/436 /// zh: https://cloud.tencent.com/document/product/436 /// - public class COS : IBackend, IStreamingBackend, IRenameEnabledBackend + public class COS : IBackend, IRenameEnabledBackend { private static readonly string LOGTAG = Logging.Log.LogTagFromType(); @@ -69,9 +66,14 @@ namespace Duplicati.Library.Backend.TencentCOS /// private const int KEY_DURATION_SECOND = 60 * 60; + /// + /// Tencent Cloud Object Storage Configuration + /// private readonly CosOptions _cosOptions; - - CosXml cosXml; + /// + /// The timeout values + /// + private readonly TimeoutOptionsHelper.Timeouts _timeouts; /// /// Tencent Cloud Object Storage Configuration @@ -81,131 +83,116 @@ namespace Duplicati.Library.Backend.TencentCOS /// /// Tencent Cloud Account APPID /// - public string Appid { get; set; } + public required string? Appid { get; set; } /// /// Cloud API Secret ID /// - public string SecretId { get; set; } + public required string SecretId { get; set; } /// /// Cloud API Secret Key /// - public string SecretKey { get; set; } + public required string SecretKey { get; set; } /// /// Bucket region ap-guangzhou ap-hongkong /// en: https://intl.cloud.tencent.com/document/product/436/6224 /// zh: https://cloud.tencent.com/document/product/436/6224 /// - public string Region { get; set; } + public required string? Region { get; set; } /// /// Bucket, format: BucketName-APPID /// - public string Bucket { get; set; } + public required string Bucket { get; set; } /// /// A path or subfolder in a bucket /// - public string Path { get; set; } + public required string? Path { get; set; } /// /// Storage class of the object /// en: https://intl.cloud.tencent.com/document/product/436/30925 /// zh: https://cloud.tencent.com/document/product/436/33417 /// - public string StorageClass { get; set; } + public required string? StorageClass { get; set; } } - public COS() { } - - public COS(string url, Dictionary options) + public COS() { - _cosOptions = new CosOptions(); + _cosOptions = null!; + _timeouts = null!; + } + public COS(string url, Dictionary options) + { var uri = new Utility.Uri(url?.Trim()); var prefix = uri.HostAndPath?.Trim()?.Trim('/')?.Trim('\\'); + var auth = AuthOptionsHelper.ParseWithAlias(options, uri, COS_SECRET_ID, COS_SECRET_KEY) + .RequireCredentials(); - if (!string.IsNullOrEmpty(prefix)) - { - _cosOptions.Path = prefix + "/"; - } + var bucket = options.GetValueOrDefault(COS_BUCKET); + if (string.IsNullOrWhiteSpace(bucket)) + throw new ArgumentException("COS bucket name is required"); - if (options.ContainsKey(COS_APP_ID)) + _cosOptions = new CosOptions() { - _cosOptions.Appid = options[COS_APP_ID]; - } + Appid = options.GetValueOrDefault(COS_APP_ID), + Region = options.GetValueOrDefault(COS_REGION), + SecretId = auth.Username!, + SecretKey = auth.Password!, + Bucket = bucket, + StorageClass = options.GetValueOrDefault(COS_STORAGE_CLASS), + Path = string.IsNullOrEmpty(prefix) ? null : Util.AppendDirSeparator(prefix, "/") + }; - if (options.ContainsKey(COS_REGION)) - { - _cosOptions.Region = options[COS_REGION]; - } - - if (options.ContainsKey(COS_SECRET_ID)) - { - _cosOptions.SecretId = options[COS_SECRET_ID]; - } - - if (options.ContainsKey(COS_SECRET_KEY)) - { - _cosOptions.SecretKey = options[COS_SECRET_KEY]; - } - - if (options.ContainsKey(COS_BUCKET)) - { - _cosOptions.Bucket = options[COS_BUCKET]; - } - - if (options.ContainsKey(COS_STORAGE_CLASS)) - { - _cosOptions.StorageClass = options[COS_STORAGE_CLASS]; - } + _timeouts = TimeoutOptionsHelper.Parse(options); } - CosXml GetCosXml() + private CosXml GetCosXml() { - string appid = _cosOptions.Appid; - string region = _cosOptions.Region; - string secretId = _cosOptions.SecretId; - string secretKey = _cosOptions.SecretKey; - - CosXmlConfig config = new CosXmlConfig.Builder() + var config = new CosXmlConfig.Builder() .SetConnectionTimeoutMs(CONNECTION_TIMEOUT_MS) .SetReadWriteTimeoutMs(READ_WRITE_TIMEOUT_MS) .IsHttps(COS_USE_SSL) - .SetAppid(appid) - .SetRegion(region) + .SetAppid(_cosOptions.Appid) + .SetRegion(_cosOptions.Region) .Build(); // Initialization QCloudCredentialProvider, COS SDK provides three ways: the permanent temporary keys custom - QCloudCredentialProvider cosCredentialProvider = new DefaultQCloudCredentialProvider(secretId, secretKey, KEY_DURATION_SECOND); - + var cosCredentialProvider = new DefaultQCloudCredentialProvider(_cosOptions.SecretId, _cosOptions.SecretKey, KEY_DURATION_SECOND); return new CosXmlServer(config, cosCredentialProvider); } - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { bool isTruncated = true; - string filename = null; + string? filename = null; while (isTruncated) { - cosXml = GetCosXml(); - string bucket = _cosOptions.Bucket; - string prefix = _cosOptions.Path; - - GetBucketRequest request = new GetBucketRequest(bucket); + var cosXml = GetCosXml(); + var bucket = _cosOptions.Bucket; + var prefix = _cosOptions.Path; + var request = new GetBucketRequest(bucket); request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600); if (!string.IsNullOrEmpty(filename)) - { request.SetMarker(filename); - } if (!string.IsNullOrEmpty(prefix)) - { request.SetPrefix(prefix); - } - GetBucketResult result = cosXml.GetBucket(request); + var tcs = new TaskCompletionSource(); + var result = await Utility.Utility.WithTimeout(_timeouts.ListTimeout, cancelToken, async ct => + { + ct.Register(() => tcs.TrySetCanceled()); + cosXml.GetBucket(request, + (result) => tcs.SetResult((GetBucketResult)result), + (clientEx, serverEx) => tcs.SetException((Exception)clientEx ?? serverEx) + ); + return await tcs.Task.ConfigureAwait(false); + }).ConfigureAwait(false); - ListBucket info = result.listBucket; + var info = result.listBucket; isTruncated = result.listBucket.isTruncated; filename = result.listBucket.nextMarker; @@ -213,16 +200,13 @@ namespace Duplicati.Library.Backend.TencentCOS foreach (var item in info.contentsList) { var last = DateTime.Parse(item.lastModified); - var fileName = item.key; if (!string.IsNullOrWhiteSpace(prefix)) { fileName = fileName.Substring(prefix.Length); if (fileName.StartsWith("/", StringComparison.Ordinal)) - { fileName = fileName.Trim('/'); - } } yield return new FileEntry(fileName, item.size, last, last); @@ -230,32 +214,21 @@ namespace Duplicati.Library.Backend.TencentCOS } } - public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) - { - using (FileStream fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) - await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); - } - - public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) - { - using (var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None)) - await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); - } - - public Task DeleteAsync(string remotename, CancellationToken cancelToken) + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { - cosXml = GetCosXml(); - string bucket = _cosOptions.Bucket; - string key = GetFullKey(remotename); + var cosXml = GetCosXml(); + var bucket = _cosOptions.Bucket; + var key = GetFullKey(remotename); + await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => + { + var request = new DeleteObjectRequest(bucket, key); + ct.Register(() => request.Cancel()); + request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600); - var request = new DeleteObjectRequest(bucket, key); - cancelToken.Register(() => request.Cancel()); - - request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600); - - var result = cosXml.DeleteObject(request); + cosXml.DeleteObject(request); + }).ConfigureAwait(false); } catch (COSXML.CosException.CosClientException clientEx) { @@ -267,21 +240,22 @@ namespace Duplicati.Library.Backend.TencentCOS Logging.Log.WriteErrorMessage(LOGTAG, "Delete", serverEx, "Delete failed: {0}, {1}", remotename, serverEx.GetInfo()); throw; } - - return Task.CompletedTask; } - public Task TestAsync(CancellationToken cancelToken) + public async Task TestAsync(CancellationToken cancelToken) { var json = JsonConvert.SerializeObject(_cosOptions); try { - cosXml = GetCosXml(); + var cosXml = GetCosXml(); string bucket = _cosOptions.Bucket; - var request = new HeadBucketRequest(bucket); - request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600); - cancelToken.Register(() => request.Cancel()); - var result = cosXml.HeadBucket(request); + var result = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => + { + var request = new HeadBucketRequest(bucket); + request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), 600); + cancelToken.Register(() => request.Cancel()); + return cosXml.HeadBucket(request); + }).ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "Test", "Request complete {0}: {1}, {2}", result.httpCode, json, result.GetResultInfo()); } @@ -295,8 +269,6 @@ namespace Duplicati.Library.Backend.TencentCOS Logging.Log.WriteErrorMessage(LOGTAG, "Test", serverEx, "Request failed: {0}, {1}", json, serverEx.GetInfo()); throw; } - - return Task.CompletedTask; } public Task CreateFolderAsync(CancellationToken cancelToken) @@ -307,27 +279,17 @@ namespace Duplicati.Library.Backend.TencentCOS public void Dispose() { - if (cosXml != null) - { - cosXml = null; - } } - public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) + public Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { try { - cosXml = GetCosXml(); - string bucket = _cosOptions.Bucket; - string key = GetFullKey(remotename); + var cosXml = GetCosXml(); + var bucket = _cosOptions.Bucket; + var key = GetFullKey(remotename); - byte[] buffer = new byte[stream.Length]; - if (Utility.Utility.ForceStreamRead(stream, buffer, buffer.Length) != stream.Length) - { - throw new Exception("Bad file read"); - } - - PutObjectRequest request = new PutObjectRequest(bucket, key, buffer); + var request = new PutObjectRequest(bucket, key, filename); request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND); request.SetRequestHeader("Content-Type", "application/octet-stream"); @@ -353,13 +315,13 @@ namespace Duplicati.Library.Backend.TencentCOS return Task.CompletedTask; } - public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) + public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { try { - cosXml = GetCosXml(); - string bucket = _cosOptions.Bucket; - string key = GetFullKey(remotename); + var cosXml = GetCosXml(); + var bucket = _cosOptions.Bucket; + var key = GetFullKey(remotename); var request = new GetObjectBytesRequest(bucket, key); @@ -369,9 +331,7 @@ namespace Duplicati.Library.Backend.TencentCOS var result = cosXml.GetObject(request); var bytes = result.content; - - using (var ms = new MemoryStream(bytes)) - await Utility.Utility.CopyStreamAsync(ms, stream, cancelToken).ConfigureAwait(false); + await File.WriteAllBytesAsync(filename, bytes, cancelToken).ConfigureAwait(false); } catch (COSXML.CosException.CosClientException clientEx) { @@ -389,7 +349,7 @@ namespace Duplicati.Library.Backend.TencentCOS { try { - cosXml = GetCosXml(); + var cosXml = GetCosXml(); var sourceAppid = _cosOptions.Appid; var sourceBucket = _cosOptions.Bucket; var sourceRegion = _cosOptions.Region; @@ -399,14 +359,17 @@ namespace Duplicati.Library.Backend.TencentCOS var bucket = _cosOptions.Bucket; var key = GetFullKey(newname); - var request = new CopyObjectRequest(bucket, key); - request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND); - request.SetCopySource(copySource); - request.SetCopyMetaDataDirective(COSXML.Common.CosMetaDataDirective.COPY); - cancelToken.Register(() => request.Cancel()); + var result = await Utility.Utility.WithTimeout(_timeouts.ShortTimeout, cancelToken, ct => + { + var request = new CopyObjectRequest(bucket, key); + request.SetSign(TimeUtils.GetCurrentTime(TimeUnit.SECONDS), KEY_DURATION_SECOND); + request.SetCopySource(copySource); + request.SetCopyMetaDataDirective(COSXML.Common.CosMetaDataDirective.COPY); + ct.Register(() => request.Cancel()); - var result = cosXml.CopyObject(request); + return cosXml.CopyObject(request); + }).ConfigureAwait(false); //Console.WriteLine(result.GetResultInfo()); @@ -430,30 +393,20 @@ namespace Duplicati.Library.Backend.TencentCOS public string ProtocolKey => "cos"; - public IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(COS_APP_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAccountDescriptionShort, Strings.COSBackend.COSAccountDescriptionLong), - new CommandLineArgument(COS_REGION, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSLocationDescriptionShort, Strings.COSBackend.COSLocationDescriptionLong), - new CommandLineArgument(COS_SECRET_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAPISecretIdDescriptionShort, Strings.COSBackend.COSAPISecretIdDescriptionLong), - new CommandLineArgument(COS_SECRET_KEY, CommandLineArgument.ArgumentType.Password, Strings.COSBackend.COSAPISecretKeyDescriptionShort, Strings.COSBackend.COSAPISecretKeyDescriptionLong), - new CommandLineArgument(COS_BUCKET, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSBucketDescriptionShort, Strings.COSBackend.COSBucketDescriptionLong), - new CommandLineArgument(COS_STORAGE_CLASS, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSStorageClassDescriptionShort, Strings.COSBackend.COSStorageClassDescriptionLong) - }); - } - } + public IList SupportedCommands => [ + new CommandLineArgument(COS_APP_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAccountDescriptionShort, Strings.COSBackend.COSAccountDescriptionLong), + new CommandLineArgument(COS_REGION, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSLocationDescriptionShort, Strings.COSBackend.COSLocationDescriptionLong), + new CommandLineArgument(COS_SECRET_ID, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSAPISecretIdDescriptionShort, Strings.COSBackend.COSAPISecretIdDescriptionLong, null, [AuthOptionsHelper.AuthUsernameOption]), + new CommandLineArgument(COS_SECRET_KEY, CommandLineArgument.ArgumentType.Password, Strings.COSBackend.COSAPISecretKeyDescriptionShort, Strings.COSBackend.COSAPISecretKeyDescriptionLong, null, [AuthOptionsHelper.AuthPasswordOption]), + new CommandLineArgument(COS_BUCKET, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSBucketDescriptionShort, Strings.COSBackend.COSBucketDescriptionLong), + new CommandLineArgument(COS_STORAGE_CLASS, CommandLineArgument.ArgumentType.String, Strings.COSBackend.COSStorageClassDescriptionShort, Strings.COSBackend.COSStorageClassDescriptionLong), + .. TimeoutOptionsHelper.GetOptions() + .Where(x => x.Name != TimeoutOptionsHelper.ReadWriteTimeoutOption) + ]; public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(Array.Empty()); private string GetFullKey(string name) - { - if (string.IsNullOrWhiteSpace(_cosOptions?.Path)) - { - return name; - } - return _cosOptions.Path + name; - } + => string.IsNullOrWhiteSpace(_cosOptions?.Path) ? name : _cosOptions.Path + name; } } diff --git a/Duplicati/Library/Backend/TencentCOS/Duplicati.Library.Backend.TencentCOS.csproj b/Duplicati/Library/Backend/TencentCOS/Duplicati.Library.Backend.TencentCOS.csproj index 49bd4cd5f..47dece887 100644 --- a/Duplicati/Library/Backend/TencentCOS/Duplicati.Library.Backend.TencentCOS.csproj +++ b/Duplicati/Library/Backend/TencentCOS/Duplicati.Library.Backend.TencentCOS.csproj @@ -2,7 +2,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + enable + enable diff --git a/Duplicati/Library/Backend/TencentCOS/Strings.cs b/Duplicati/Library/Backend/TencentCOS/Strings.cs index a70e5ba1b..0546f1816 100644 --- a/Duplicati/Library/Backend/TencentCOS/Strings.cs +++ b/Duplicati/Library/Backend/TencentCOS/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,20 +24,20 @@ namespace Duplicati.Library.Backend.Strings { internal static class COSBackend { - public static string Description { get { return LC.L(@"This backend can read and write data to the Tencent COS."); } } - public static string DisplayName { get { return LC.L(@"Tencent COS (Cloud Object Storage)"); } } - public static string COSAccountDescriptionLong { get { return LC.L(@"Account ID of Tencent Cloud Account."); } } - public static string COSAccountDescriptionShort { get { return LC.L(@"Account ID"); } } - public static string COSAPISecretIdDescriptionLong { get { return LC.L(@"Cloud API Secret ID."); } } - public static string COSAPISecretIdDescriptionShort { get { return LC.L(@"Secret ID"); } } - public static string COSAPISecretKeyDescriptionLong { get { return LC.L(@"Cloud API Secret Key."); } } - public static string COSAPISecretKeyDescriptionShort { get { return LC.L(@"Secret Key"); } } - public static string COSBucketDescriptionLong { get { return LC.L(@"Bucket name, format: BucketName-APPID"); } } - public static string COSBucketDescriptionShort { get { return LC.L(@"Bucket name"); } } - public static string COSLocationDescriptionLong { get { return LC.L(@"Region is the distribution area of ​​the Tencent cloud hosting machine room. The object storage COS data is stored in the storage buckets of these regions. https://intl.cloud.tencent.com/document/product/436/6224."); } } - public static string COSLocationDescriptionShort { get { return LC.L(@"Specify COS location constraints"); } } - public static string COSStorageClassDescriptionLong { get { return LC.L(@"Storage class of the object; check enumerated values at https://intl.cloud.tencent.com/document/product/436/30925."); } } - public static string COSStorageClassDescriptionShort { get { return LC.L(@"Storage class of the object"); } } + public static string Description => LC.L(@"This backend can read and write data to the Tencent COS."); + public static string DisplayName => LC.L(@"Tencent COS (Cloud Object Storage)"); + public static string COSAccountDescriptionLong => LC.L(@"Account ID of Tencent Cloud Account."); + public static string COSAccountDescriptionShort => LC.L(@"Account ID"); + public static string COSAPISecretIdDescriptionLong => LC.L(@"Cloud API Secret ID."); + public static string COSAPISecretIdDescriptionShort => LC.L(@"Secret ID"); + public static string COSAPISecretKeyDescriptionLong => LC.L(@"Cloud API Secret Key."); + public static string COSAPISecretKeyDescriptionShort => LC.L(@"Secret Key"); + public static string COSBucketDescriptionLong => LC.L(@"Bucket name, format: BucketName-APPID"); + public static string COSBucketDescriptionShort => LC.L(@"Bucket name"); + public static string COSLocationDescriptionLong => LC.L(@"Region is the distribution area of ​​the Tencent cloud hosting machine room. The object storage COS data is stored in the storage buckets of these regions. https://intl.cloud.tencent.com/document/product/436/6224."); + public static string COSLocationDescriptionShort => LC.L(@"Specify COS location constraints"); + public static string COSStorageClassDescriptionLong => LC.L(@"Storage class of the object; check enumerated values at https://intl.cloud.tencent.com/document/product/436/30925."); + public static string COSStorageClassDescriptionShort => LC.L(@"Storage class of the object"); } } diff --git a/Duplicati/Library/Backend/WEBDAV/Duplicati.Library.Backend.WEBDAV.csproj b/Duplicati/Library/Backend/WEBDAV/Duplicati.Library.Backend.WEBDAV.csproj index e49e297f9..f704c2b22 100644 --- a/Duplicati/Library/Backend/WEBDAV/Duplicati.Library.Backend.WEBDAV.csproj +++ b/Duplicati/Library/Backend/WEBDAV/Duplicati.Library.Backend.WEBDAV.csproj @@ -4,7 +4,7 @@ net8.0 Library Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Backend/WEBDAV/Strings.cs b/Duplicati/Library/Backend/WEBDAV/Strings.cs index 802a12797..d7e98ae5e 100644 --- a/Duplicati/Library/Backend/WEBDAV/Strings.cs +++ b/Duplicati/Library/Backend/WEBDAV/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,30 +19,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings { - internal static class WEBDAV { +namespace Duplicati.Library.Backend.Strings +{ + internal static class WEBDAV + { public static string Description { get { return LC.L(@"This backend can read and write data to a WEBDAV enabled web server, using the HTTP protocol. Allowed formats are ""webdav://hostname/folder"" and ""webdav://username:password@hostname/folder""."); } } public static string DisplayName { get { return LC.L(@"WebDAV"); } } public static string DescriptionForceDigestLong { get { return LC.L(@"Using the HTTP Digest authentication method allows the user to authenticate with the server, without sending the password in clear. However, a man-in-the-middle attack is easy, because the HTTP protocol specifies a fallback to Basic authentication, which will make the client send the password to the attacker. Using this option, the client does not accept this, and always uses Digest authentication or fails to connect."); } } public static string DescriptionForceDigestShort { get { return LC.L(@"Force the use of the HTTP Digest authentication method"); } } - public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } - public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } - public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } - public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } public static string DescriptionIntegratedAuthenticationLong { get { return LC.L(@"If the server and client both supports integrated authentication, this option enables that authentication method. This is likely only available with windows servers and clients."); } } public static string DescriptionIntegratedAuthenticationShort { get { return LC.L(@"Use windows integrated authentication to connect to the server"); } } public static string MethodNotAllowedError(System.Net.HttpStatusCode statuscode) { return LC.L(@"The server returned the error code {0} ({1}), indicating that the server does not support WebDAV connections", (int)statuscode, statuscode); } - public static string MissingFolderError(string foldername, string message) { return LC.L(@"The folder {0} was not found, message: {1}", foldername, message); } - public static string SeenThenNotFoundError(string foldername, string filename, string extension, string errormessage) { return LC.L(@"When listing the folder {0} the file {1} was listed, but the server now reports that the file is not found. + public static string MissingFolderError(string foldername, string message) { return LC.L(@"The folder {0} was not found, message: {1}", foldername, message); } + public static string SeenThenNotFoundError(string foldername, string filename, string extension, string errormessage) { return LC.L(@"When listing the folder {0} the file {1} was listed, but the server now reports that the file is not found. This can be because the file is deleted or unavailable, but it can also be because the file extension {2} is blocked by the web server. IIS blocks unknown extensions by default. Error message: {3}", foldername, filename, extension, errormessage); } - public static string DescriptionUseSSLLong { get { return LC.L(@"Use this option to communicate using Secure Socket Layer (SSL) over http (https)."); } } - public static string DescriptionUseSSLShort { get { return LC.L(@"Instruct Duplicati to use an SSL (https) connection"); } } public static string DescriptionDebugPropfindLong { get { return LC.L(@"To aid in debugging issues, it is possible to set a path to a file that will be overwritten with the PROPFIND response."); } } public static string DescriptionDebugPropfindShort { get { return LC.L(@"Dump the PROPFIND response"); } } - public static string DescriptionAcceptAnyCertificateLong { get { return LC.L(@"Use this option to accept any server certificate, regardless of what errors it may have. Please use --{0} instead, whenever possible.", "accept-specified-ssl-hash"); } } - public static string DescriptionAcceptAnyCertificateShort { get { return LC.L(@"Accept any server certificate"); } } - public static string DescriptionAcceptHashLong2 { get { return LC.L(@"If your server certificate is reported as invalid (e.g. with self-signed certificates), you can supply the certificate hash (SHA1) to approve it anyway. The hash value must be entered in hex format without spaces or colons. You can enter multiple hashes separated by commas."); } } - public static string DescriptionAcceptHashShort { get { return LC.L(@"Optionally accept a known SSL certificate"); } } + public static string UsernameRequired { get { return LC.L(@"Digest authentication requires a username to be set"); } } } } diff --git a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs index 68979c14c..627a01890 100644 --- a/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs +++ b/Duplicati/Library/Backend/WEBDAV/WEBDAV.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,63 +19,80 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Utility; +using Duplicati.Library.Utility.Options; using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Duplicati.Library.Backend { - public class WEBDAV : IBackend, IStreamingBackend + public class WEBDAV : IStreamingBackend { - private record RequestResources : IDisposable - { - public RequestResources(HttpClient httpClient, HttpRequestMessage requestMessage) - { - HttpClient = httpClient; - RequestMessage = requestMessage; - } - public HttpRequestMessage RequestMessage { get; init; } - public HttpClient HttpClient { get; init; } - - public void Dispose() - { - try - { - RequestMessage?.Dispose(); - } - catch { } - try - { - HttpClient?.Dispose(); - } - catch { } - } - } - private readonly NetworkCredential m_userInfo; + /// + /// The credentials to use for the connection + /// + private readonly NetworkCredential? m_userInfo; + /// + /// The URL to connect to + /// private readonly string m_url; + /// + /// The path to use for all operations + /// private readonly string m_path; + /// + /// The sanitized URL to use for listing + /// private readonly string m_sanitizedUrl; + /// + /// The reverse protocol URL to use for listing + /// private readonly string m_reverseProtocolUrl; + /// + /// The raw URL to use for listing + /// private readonly string m_rawurl; + /// + /// The raw URL with port to use for listing + /// private readonly string m_rawurlPort; + /// + /// The DNS name used for the connection + /// private readonly string m_dnsName; + /// + /// Flag to indicate if integrated authentication should be used + /// private readonly bool m_useIntegratedAuthentication = false; + /// + /// Flag to indicate if digest authentication should be used + /// private readonly bool m_forceDigestAuthentication = false; - private readonly bool m_useSSL = false; + /// + /// The timeouts to use for the operations + /// + private readonly TimeoutOptionsHelper.Timeouts m_timeouts; + /// + /// The options for the SSL certificate validation + /// + private readonly SslOptionsHelper.SslCertificateOptions m_certificateOptions; /// /// A list of files seen in the last List operation. /// It is used to detect a problem with IIS where a file is listed, /// but IIS responds 404 because the file mapping is incorrect. /// - private List m_filenamelist = null; + private HashSet? m_filenamelist = null; // According to the WEBDAV standard, the "allprop" request should return all properties, however this seems to fail on some servers (box.net). // I've found this description: http://www.webdav.org/specs/rfc2518.html#METHOD_PROPFIND @@ -83,66 +100,48 @@ namespace Duplicati.Library.Backend // //private static readonly byte[] PROPFIND_BODY = System.Text.Encoding.UTF8.GetBytes(""); private static readonly byte[] PROPFIND_BODY = new byte[0]; - - /// - /// Option to accept any SSL certificate - /// - private readonly bool m_acceptAnyCertificate; - - /// - /// Specific hashes to be accepted by the certificate validator - /// - private readonly string[] m_acceptSpecificCertificates; /// - /// The default timeout in seconds for PUT/GET file operations + /// The HTTP client to use for all operations /// - private const int LONG_OPERATION_TIMEOUT_SECONDS = 30000; - - /// - /// The default timeout in seconds for LIST/CreateFolder operations - /// - private const int SHORT_OPERATION_TIMEOUT_SECONDS = 30; + private HttpClient? m_httpClient = null; public WEBDAV() { + m_url = string.Empty; + m_path = string.Empty; + m_sanitizedUrl = string.Empty; + m_reverseProtocolUrl = string.Empty; + m_rawurl = string.Empty; + m_rawurlPort = string.Empty; + m_dnsName = string.Empty; + m_certificateOptions = null!; + m_timeouts = null!; } - public WEBDAV(string url, Dictionary options) + public WEBDAV(string url, Dictionary options) { var u = new Utility.Uri(url); u.RequireHost(); m_dnsName = u.Host; - - if (!string.IsNullOrEmpty(u.Username)) + var auth = AuthOptionsHelper.Parse(options, u); + if (auth.HasUsername) { - m_userInfo = new NetworkCredential(); - m_userInfo.UserName = u.Username; - if (!string.IsNullOrEmpty(u.Password)) - m_userInfo.Password = u.Password; - else if (options.ContainsKey("auth-password")) - m_userInfo.Password = options["auth-password"]; - } - else - { - if (options.ContainsKey("auth-username")) - { - m_userInfo = new NetworkCredential(); - m_userInfo.UserName = options["auth-username"]; - if (options.ContainsKey("auth-password")) - m_userInfo.Password = options["auth-password"]; - } + m_userInfo = new NetworkCredential() { Domain = "" }; + m_userInfo.UserName = auth.Username; + if (auth.HasPassword) + m_userInfo.Password = auth.Password; } - //Bugfix, see http://connect.microsoft.com/VisualStudio/feedback/details/695227/networkcredential-default-constructor-leaves-domain-null-leading-to-null-object-reference-exceptions-in-framework-code - if (m_userInfo != null) - m_userInfo.Domain = ""; - + m_certificateOptions = SslOptionsHelper.Parse(options); m_useIntegratedAuthentication = Utility.Utility.ParseBoolOption(options, "integrated-authentication"); m_forceDigestAuthentication = Utility.Utility.ParseBoolOption(options, "force-digest-authentication"); - m_useSSL = Utility.Utility.ParseBoolOption(options, "use-ssl"); - m_url = u.SetScheme(m_useSSL ? "https" : "http").SetCredentials(null, null).SetQuery(null).ToString(); + // Explicitly support setups with no username and password + if (m_forceDigestAuthentication && m_userInfo == null) + throw new UserInformationException(Strings.WEBDAV.UsernameRequired, "UsernameRequired"); + + m_url = u.SetScheme(m_certificateOptions.UseSSL ? "https" : "http").SetCredentials(null, null).SetQuery(null).ToString(); m_url = Util.AppendDirSeparator(m_url, "/"); m_path = u.Path; @@ -151,30 +150,86 @@ namespace Duplicati.Library.Backend m_path = Util.AppendDirSeparator(m_path, "/"); m_path = Utility.Uri.UrlDecode(m_path); - m_rawurl = new Utility.Uri(m_useSSL ? "https" : "http", u.Host, m_path).ToString(); + m_rawurl = new Utility.Uri(m_certificateOptions.UseSSL ? "https" : "http", u.Host, m_path).ToString(); int port = u.Port; if (port <= 0) - port = m_useSSL ? 443 : 80; + port = m_certificateOptions.UseSSL ? 443 : 80; - m_rawurlPort = new Utility.Uri(m_useSSL ? "https" : "http", u.Host, m_path, null, null, null, port).ToString(); - m_sanitizedUrl = new Utility.Uri(m_useSSL ? "https" : "http", u.Host, m_path).ToString(); - m_reverseProtocolUrl = new Utility.Uri(m_useSSL ? "http" : "https", u.Host, m_path).ToString(); - m_acceptAnyCertificate = options.ContainsKey("accept-any-ssl-certificate") && Utility.Utility.ParseBoolOption(options, "accept-any-ssl-certificate"); - m_acceptSpecificCertificates = options.ContainsKey("accept-specified-ssl-hash") ? options["accept-specified-ssl-hash"].Split([",", ";"], StringSplitOptions.RemoveEmptyEntries): null; + m_rawurlPort = new Utility.Uri(m_certificateOptions.UseSSL ? "https" : "http", u.Host, m_path, null, null, null, port).ToString(); + m_sanitizedUrl = new Utility.Uri(m_certificateOptions.UseSSL ? "https" : "http", u.Host, m_path).ToString(); + m_reverseProtocolUrl = new Utility.Uri(m_certificateOptions.UseSSL ? "http" : "https", u.Host, m_path).ToString(); + m_timeouts = TimeoutOptionsHelper.Parse(options); + } + + /// + /// Gets the HTTP client to use for all operations + /// + /// The HTTP client to use + private HttpClient GetHttpClient() + { + if (m_httpClient == null) + { + var httpHandler = m_certificateOptions.CreateHandler(); + if (m_useIntegratedAuthentication) + { + httpHandler.UseDefaultCredentials = true; + } + else if (m_forceDigestAuthentication) + { + httpHandler.Credentials = new CredentialCache + { + { new System.Uri(m_url), "Digest", m_userInfo! } + }; + } + + m_httpClient = HttpClientHelper.CreateClient(httpHandler); + m_httpClient.Timeout = Timeout.InfiniteTimeSpan; + + if (!m_useIntegratedAuthentication && !m_forceDigestAuthentication && m_userInfo != null) + { + m_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{m_userInfo.UserName}:{m_userInfo.Password}")) + ); + } + } + + return m_httpClient; } #region IBackend Members + /// public string DisplayName => Strings.WEBDAV.DisplayName; + /// public string ProtocolKey => "webdav"; - public IEnumerable List() + /// + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) { + using var request = CreateRequest(string.Empty, new HttpMethod("PROPFIND")); + request.Headers.Add("Depth", "1"); + request.Content = new StreamContent(new MemoryStream(PROPFIND_BODY)); + request.Content.Headers.ContentLength = PROPFIND_BODY.Length; + + var doc = new System.Xml.XmlDocument(); + try { - return ListWithouExceptionCatch(); + using var response = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, ct => + GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseContentRead, ct) + ).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + using var sourceStream = await Utility.Utility.WithTimeout(m_timeouts.ReadWriteTimeout, cancelToken, ct => + response.Content.ReadAsStreamAsync(ct) + ).ConfigureAwait(false); + + using var timeoutStream = sourceStream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); + doc.Load(timeoutStream); } catch (HttpRequestException wex) { @@ -186,33 +241,19 @@ namespace Duplicati.Library.Backend throw; } - } - private IEnumerable ListWithouExceptionCatch() - { - using var timeoutToken = new CancellationTokenSource(); - timeoutToken.CancelAfter(TimeSpan.FromSeconds(SHORT_OPERATION_TIMEOUT_SECONDS)); - - using var requestResources = CreateRequest(string.Empty, new HttpMethod("PROPFIND")); - requestResources.RequestMessage.Headers.Add("Depth", "1"); - requestResources.RequestMessage.Content = new StreamContent(new MemoryStream(PROPFIND_BODY)); - requestResources.RequestMessage.Content.Headers.ContentLength = PROPFIND_BODY.Length; - - using var response = requestResources.HttpClient.SendAsync(requestResources.RequestMessage, HttpCompletionOption.ResponseContentRead, timeoutToken.Token).ConfigureAwait(false).GetAwaiter().GetResult(); - - response.EnsureSuccessStatusCode(); // This replaces the if needed when Mono was used. - - var doc = new System.Xml.XmlDocument(); - - doc.Load(response.Content.ReadAsStream()); - - System.Xml.XmlNamespaceManager nm = new System.Xml.XmlNamespaceManager(doc.NameTable); + var nm = new System.Xml.XmlNamespaceManager(doc.NameTable); nm.AddNamespace("D", "DAV:"); - List files = new List(); - m_filenamelist = new List(); + var filenamelist = new HashSet(); + var lst = doc.SelectNodes("D:multistatus/D:response/D:href", nm); + if (lst == null) + { + m_filenamelist = filenamelist; + yield break; + } - foreach (System.Xml.XmlNode n in doc.SelectNodes("D:multistatus/D:response/D:href", nm)) + foreach (System.Xml.XmlNode n in lst) { //IIS uses %20 for spaces and %2B for + //Apache uses %20 for spaces and + for + @@ -249,10 +290,10 @@ namespace Duplicati.Library.Backend DateTime lastModified = new DateTime(); bool isCollection = false; - System.Xml.XmlNode stat = n.ParentNode.SelectSingleNode("D:propstat/D:prop", nm); + System.Xml.XmlNode? stat = n.ParentNode?.SelectSingleNode("D:propstat/D:prop", nm); if (stat != null) { - System.Xml.XmlNode s = stat.SelectSingleNode("D:getcontentlength", nm); + System.Xml.XmlNode? s = stat.SelectSingleNode("D:getcontentlength", nm); if (s != null) size = long.Parse(s.InnerText); s = stat.SelectSingleNode("D:getlastmodified", nm); @@ -271,90 +312,91 @@ namespace Duplicati.Library.Backend isCollection = stat.SelectSingleNode("D:resourcetype/D:collection", nm) != null; } - FileEntry fe = new FileEntry(name, size, lastAccess, lastModified); - fe.IsFolder = isCollection; - files.Add(fe); - m_filenamelist.Add(name); + var fe = new FileEntry(name, size, lastAccess, lastModified) + { + IsFolder = isCollection + }; + + filenamelist.Add(name); + yield return fe; } - return files; + m_filenamelist = filenamelist; } + /// public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { - using (FileStream fs = File.OpenRead(filename)) - await PutAsync(remotename, fs, cancelToken); + await using FileStream fs = File.OpenRead(filename); + await PutAsync(remotename, fs, cancelToken).ConfigureAwait(false); } + /// public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) { - using (var fs = File.Create(filename)) - await GetAsync(remotename, fs, cancelToken); + await using var fs = File.Create(filename); + await GetAsync(remotename, fs, cancelToken).ConfigureAwait(false); } + /// public async Task DeleteAsync(string remotename, CancellationToken cancelToken) { try { - using var timeoutToken = new CancellationTokenSource(); - timeoutToken.CancelAfter(TimeSpan.FromSeconds(SHORT_OPERATION_TIMEOUT_SECONDS)); + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + using var request = CreateRequest(remotename); + request.Method = HttpMethod.Delete; - using var requestResources = CreateRequest(remotename); - requestResources.RequestMessage.Method = HttpMethod.Delete; + var response = await GetHttpClient().SendAsync(request, ct).ConfigureAwait(false); - var response = await requestResources.HttpClient.SendAsync(requestResources.RequestMessage, timeoutToken.Token); - - response.EnsureSuccessStatusCode(); // This replaces the if needed when Mono was used. + response.EnsureSuccessStatusCode(); + }).ConfigureAwait(false); } catch (HttpRequestException wex) { if (wex.StatusCode == HttpStatusCode.NotFound) throw new FileMissingException(wex); - else - throw; + throw; } } + /// public IList SupportedCommands { get { - return new List(new ICommandLineArgument[] { - new CommandLineArgument("auth-password", CommandLineArgument.ArgumentType.Password, Strings.WEBDAV.DescriptionAuthPasswordShort, Strings.WEBDAV.DescriptionAuthPasswordLong), - new CommandLineArgument("auth-username", CommandLineArgument.ArgumentType.String, Strings.WEBDAV.DescriptionAuthUsernameShort, Strings.WEBDAV.DescriptionAuthUsernameLong), + return + [ + .. AuthOptionsHelper.GetOptions(), new CommandLineArgument("integrated-authentication", CommandLineArgument.ArgumentType.Boolean, Strings.WEBDAV.DescriptionIntegratedAuthenticationShort, Strings.WEBDAV.DescriptionIntegratedAuthenticationLong), new CommandLineArgument("force-digest-authentication", CommandLineArgument.ArgumentType.Boolean, Strings.WEBDAV.DescriptionForceDigestShort, Strings.WEBDAV.DescriptionForceDigestLong), - new CommandLineArgument("use-ssl", CommandLineArgument.ArgumentType.Boolean, Strings.WEBDAV.DescriptionUseSSLShort, Strings.WEBDAV.DescriptionUseSSLLong), - new CommandLineArgument("accept-any-ssl-certificate", CommandLineArgument.ArgumentType.Boolean, Strings.WEBDAV.DescriptionAcceptAnyCertificateShort, Strings.WEBDAV.DescriptionAcceptAnyCertificateLong), - new CommandLineArgument("accept-specified-ssl-hash", CommandLineArgument.ArgumentType.String, Strings.WEBDAV.DescriptionAcceptHashShort, Strings.WEBDAV.DescriptionAcceptHashLong2) - }); + .. SslOptionsHelper.GetOptions(), + .. TimeoutOptionsHelper.GetOptions() + ]; } } - public string Description - { - get { return Strings.WEBDAV.Description; } - } + /// + public string Description => Strings.WEBDAV.Description; + /// public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { m_dnsName }); + /// public Task TestAsync(CancellationToken cancelToken) - { - List(); - return Task.CompletedTask; - } + => this.TestListAsync(cancelToken); + /// public async Task CreateFolderAsync(CancellationToken cancelToken) { - using var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken); - timeoutToken.CancelAfter(TimeSpan.FromSeconds(SHORT_OPERATION_TIMEOUT_SECONDS)); - - using var requestResources = CreateRequest(string.Empty, new HttpMethod("MKCOL")); - - using var response = await requestResources.HttpClient.SendAsync(requestResources.RequestMessage, timeoutToken.Token).ConfigureAwait(false); - - response.EnsureSuccessStatusCode(); // This replaces the if needed when Mono was used. + await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, async ct => + { + using var request = CreateRequest(string.Empty, new HttpMethod("MKCOL")); + using var response = await GetHttpClient().SendAsync(request, ct).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + }).ConfigureAwait(false); } #endregion @@ -368,67 +410,37 @@ namespace Duplicati.Library.Backend #endregion - private RequestResources CreateRequest(string remotename, HttpMethod method = null) + private HttpRequestMessage CreateRequest(string remotename, HttpMethod? method = null) { - HttpClient httpClient; - HttpClientHandler httpHandler = new HttpClientHandler(); - HttpClientHelper.ConfigureHandlerCertificateValidator(httpHandler, m_acceptAnyCertificate, m_acceptSpecificCertificates); - - if (m_useIntegratedAuthentication) - { - httpHandler.UseDefaultCredentials = true; - httpClient = HttpClientHelper.CreateClient(httpHandler); - } - else if (m_forceDigestAuthentication) - { - httpHandler.Credentials = new CredentialCache - { - { new System.Uri(m_url), "Digest", m_userInfo } - }; - httpClient = HttpClientHelper.CreateClient(httpHandler); - } - else - { - httpClient = HttpClientHelper.CreateClient(httpHandler); - httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( - "Basic", - Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{m_userInfo.UserName}:{m_userInfo.Password}")) - ); - } - - httpClient.Timeout = Timeout.InfiniteTimeSpan; - var request = new HttpRequestMessage(HttpMethod.Get, $"{m_url}{Utility.Uri.UrlEncode(remotename).Replace("+", "%20")}"); request.Headers.Add(HttpRequestHeader.UserAgent.ToString(), "Duplicati WEBDAV Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); - request.Headers.ConnectionClose = true; // Equivalent to KeepAlive = false + + request.Headers.ConnectionClose = !m_useIntegratedAuthentication; // ConnectionClose is incompatible with integrated authentication if (method != null) request.Method = method; - return new RequestResources(httpClient, request); + return request; } #region IStreamingBackend Members + /// public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { - using var timeoutToken = new CancellationTokenSource(); - timeoutToken.CancelAfter(TimeSpan.FromSeconds(LONG_OPERATION_TIMEOUT_SECONDS)); - using var combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(timeoutToken.Token, cancelToken); + using var request = CreateRequest(remotename, HttpMethod.Put); + using var timeoutStream = stream.ObserveReadTimeout(m_timeouts.ReadWriteTimeout, false); + request.Content = new StreamContent(timeoutStream); + request.Content.Headers.ContentLength = stream.Length; + request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + request.Version = HttpVersion.Version11; - using var requestResources = CreateRequest(remotename, HttpMethod.Put); + using var response = await GetHttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancelToken).ConfigureAwait(false); - requestResources.RequestMessage.Content = new StreamContent(stream); - requestResources.RequestMessage.Content.Headers.ContentLength = stream.Length; - requestResources.RequestMessage.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); - requestResources.RequestMessage.Version = HttpVersion.Version11; - - using var response = await requestResources.HttpClient.SendAsync(requestResources.RequestMessage, HttpCompletionOption.ResponseHeadersRead, combinedTokens.Token); - - response.EnsureSuccessStatusCode(); // This replaces the if needed when Mono was used. + response.EnsureSuccessStatusCode(); } catch (HttpRequestException wex) { @@ -439,17 +451,14 @@ namespace Duplicati.Library.Backend } } + /// public async Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) { try { - using var timeoutToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken); - timeoutToken.CancelAfter(TimeSpan.FromSeconds(LONG_OPERATION_TIMEOUT_SECONDS)); - - using var requestResources = CreateRequest(remotename, HttpMethod.Get); - - await requestResources.HttpClient.DownloadFile(requestResources.RequestMessage, stream, null, timeoutToken.Token).ConfigureAwait(false); - + using var request = CreateRequest(remotename, HttpMethod.Get); + using var timeoutStream = stream.ObserveWriteTimeout(m_timeouts.ReadWriteTimeout, false); + await GetHttpClient().DownloadFile(request, timeoutStream, null, cancelToken).ConfigureAwait(false); } catch (HttpRequestException wex) { diff --git a/Duplicati/Library/Backend/pCloud/Duplicati.Library.Backend.pCloud.csproj b/Duplicati/Library/Backend/pCloud/Duplicati.Library.Backend.pCloud.csproj new file mode 100644 index 000000000..b78f03557 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Duplicati.Library.Backend.pCloud.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + Library + Copyright © 2025 Team Duplicati, MIT license + enable + enable + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Duplicati/Library/Backend/pCloud/Model/PCloudFolderMetadata.cs b/Duplicati/Library/Backend/pCloud/Model/PCloudFolderMetadata.cs new file mode 100644 index 000000000..e78fdcdf6 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/PCloudFolderMetadata.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Metadata information for a pCloud folder +/// +/// The all lower case is to match the JSON response from the pCloud API +/// +internal record pCloudFolderMetadata +{ + /// + /// Name of the folder + /// + public string name { get; init; } = string.Empty; + + /// + /// Creation timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string created { get; init; } = string.Empty; + + /// + /// Indicates if the folder belongs to the authenticated user + /// + public bool ismine { get; init; } + + /// + /// Indicates if the folder has a thumbnail + /// + public bool thumb { get; init; } + + /// + /// Last modification timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string modified { get; init; } = string.Empty; + + /// + /// Number of comments on the folder + /// + public int comments { get; init; } + + /// + /// Unique identifier for the folder, prefixed with 'd' + /// + public string id { get; init; } = string.Empty; + + /// + /// Indicates if the folder is shared with other users + /// + public bool isshared { get; init; } + + /// + /// Icon type for the folder + /// + public string icon { get; init; } = string.Empty; + + /// + /// Always true for folder metadata + /// + public bool isfolder { get; init; } + + /// + /// Numeric ID of the parent folder + /// + public ulong parentfolderid { get; init; } + + /// + /// Numeric ID of this folder + /// + public ulong folderid { get; init; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudChecksum.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudChecksum.cs new file mode 100644 index 000000000..2d872b0bd --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudChecksum.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Checksum information for uploaded file +/// +internal record pCloudChecksum +{ + /// + /// SHA1 hash of the file + /// + public string sha1 { get; init; } = string.Empty; + + /// + /// SHA256 hash of the file + /// + public string sha256 { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudCreateFolderResponse.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudCreateFolderResponse.cs new file mode 100644 index 000000000..fd9f2883d --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudCreateFolderResponse.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Response from pCloud's create folder API endpoint +/// +/// The all lower case is to match the JSON response from the pCloud API +/// +internal record pCloudCreateFolderResponse +{ + /// + /// Result code from the API call. 0 indicates success + /// + public int result { get; init; } + + /// + /// Indicates if a new folder was created (false if folder already existed) + /// + public bool created { get; init; } + + /// + /// Detailed metadata about the created or existing folder + /// + public pCloudFolderMetadata? metadata { get; init; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudDeleteResponse.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudDeleteResponse.cs new file mode 100644 index 000000000..cbfd6994b --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudDeleteResponse.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Response from pCloud's create folder API endpoint +/// +/// The all lower case is to match the JSON response from the pCloud API +/// +internal record pCloudDeleteResponse +{ + /// + /// Result code from the API call. 0 indicates success + /// + public int result { get; init; } + + /// + /// Presumed to the be the ID of the deleted file + /// + public string? id { get; init; } + + /// + /// Detailed metadata about the created or existing folder + /// + public pCloudFileMetadata? metadata { get; init; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudDownloadResponse.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudDownloadResponse.cs new file mode 100644 index 000000000..c0ad67927 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudDownloadResponse.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Response from pCloud's download file API endpoint, containing file download information +/// +/// The all lower case is to match the JSON response from the pCloud API +/// +internal record pCloudDownloadResponse +{ + /// + /// Result code from the API call. 0 indicates success + /// + public int result { get; init; } + + /// + /// List of available download hosts/servers + /// + public List hosts { get; init; } = new(); + + /// + /// Download path to be used with the host + /// + public string path { get; init; } = string.Empty; + +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudErrorList.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudErrorList.cs new file mode 100644 index 000000000..c775ad989 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudErrorList.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Error codes/strings for pcloud. They will not be subject to translation so messages +/// can be googled to help users diagnose issues. +/// +/// +internal static class pCloudErrorList +{ + internal static readonly Dictionary ErrorMessages = new() + { + { 1000, "Log in required." }, + { 1001, "No full path or name/folderid provided." }, + { 1004, "No fileid or path provided." }, + { 1005, "Unknown content-type requested." }, + { 2000, "Log in failed." }, + { 2002, "A component of parent directory does not exist." }, + { 2003, "Access denied. You do not have permissions to preform this operation." }, + { 2009, "File not found." }, + { 2010, "Invalid path." }, + { 2094, "Invalid 'access_token' provided. Please check you are using the correct server (eapi.pcloud.com for EU or api.pcloud.com for non EU)"}, + { 2011, "Requested speed limit too low, see minspeed for minimum." }, + { 4000, "Too many login tries from this IP address." }, + { 5002, "Internal error, no servers available. Try again later." } + }; +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudFileEntry.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudFileEntry.cs new file mode 100644 index 000000000..9f2ed6264 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudFileEntry.cs @@ -0,0 +1,60 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Represents a file entry or folder in pCloud +/// +internal class pCloudFileEntry : IFileEntry +{ + /// + /// Indicates it is a folder on the remote filesystem + /// + public bool IsFolder { get; set; } + + /// + /// Last access date of the file + /// + public DateTime LastAccess { get; set; } + + /// + /// Last modification of the file + /// + public DateTime LastModification { get; set; } + + /// + /// Last modification of the file + /// + public DateTime Created { get; set; } + + /// + /// Filename or folder name + /// + public string? Name { get; set; } + + /// + /// Size of the object in bytes + /// + public long Size { get; set; } +} diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudFileMetadata.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudFileMetadata.cs new file mode 100644 index 000000000..5a9ee0b18 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudFileMetadata.cs @@ -0,0 +1,123 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Metadata information for a pCloud file +/// +internal record pCloudFileMetadata +{ + /// + /// Always false for file metadata + /// + public bool isfolder { get; init; } + + /// + /// Icon type representing the file type + /// + public string icon { get; init; } = string.Empty; + + /// + /// Size of the file in bytes + /// + public long size { get; init; } + + /// + /// Name of the file including extension + /// + public string name { get; init; } = string.Empty; + + /// + /// Category of the file (e.g., 1 for images) + /// + public int category { get; init; } + + /// + /// MIME type of the file + /// + public string contenttype { get; init; } = string.Empty; + + /// + /// ID of the parent folder containing this file + /// + public ulong parentfolderid { get; init; } + + /// + /// Indicates if the file has been deleted + /// + public bool isdeleted { get; init; } + + /// + /// Hash value of the file content + /// + public ulong hash { get; init; } + + /// + /// Indicates if the file belongs to the authenticated user + /// + public bool ismine { get; init; } + + /// + /// Indicates if the file is shared with other users + /// + public bool isshared { get; init; } + + /// + /// Unique identifier for the file, prefixed with 'f' + /// + public string id { get; init; } = string.Empty; + + /// + /// Height of the image in pixels (only for image files) + /// + public int height { get; init; } + + /// + /// Width of the image in pixels (only for image files) + /// + public int width { get; init; } + + /// + /// Last modification timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string modified { get; init; } = string.Empty; + + /// + /// Indicates if the file has a thumbnail + /// + public bool thumb { get; init; } + + /// + /// Creation timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string created { get; init; } = string.Empty; + + /// + /// Numeric ID of the file + /// + public ulong fileid { get; init; } + + /// + /// Full path to the file + /// + public string path { get; init; } = string.Empty; +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudFolderContent.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudFolderContent.cs new file mode 100644 index 000000000..1f08b584e --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudFolderContent.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Content item (file or folder) within a pCloud folder +/// +internal record pCloudFolderContent +{ + /// + /// Full path to the item + /// + public string path { get; init; } = string.Empty; + + /// + /// Name of the file or folder + /// + public string name { get; init; } = string.Empty; + + /// + /// Creation timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string created { get; init; } = string.Empty; + + /// + /// Indicates if the item belongs to the authenticated user + /// + public bool ismine { get; init; } + + /// + /// Indicates if the item has a thumbnail + /// + public bool thumb { get; init; } + + /// + /// Last modification timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string modified { get; init; } = string.Empty; + + /// + /// Number of comments on the item + /// + public int comments { get; init; } + + /// + /// Unique identifier for the item, prefixed with 'd' for folders or 'f' for files + /// + public string id { get; init; } = string.Empty; + + /// + /// Indicates if the item is shared with other users + /// + public bool isshared { get; init; } + + /// + /// Icon type for the item + /// + public string icon { get; init; } = string.Empty; + + /// + /// True if the item is a folder, false if it's a file + /// + public bool isfolder { get; init; } + + /// + /// Folder ID if the item is a folder (null otherwise) + /// + public ulong folderid { get; init; } + + /// + /// File ID if the item is a file (null otherwise) + /// + public ulong? fileid { get; init; } + + /// + /// Size in bytes if the item is a file (null otherwise) + /// + public long? size { get; init; } + + /// + /// Content type if the item is a file (null otherwise) + /// + public string? contenttype { get; init; } + + /// + /// ID of the parent folder containing this item + /// + public long parentfolderid { get; init; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudListFolderResponse.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudListFolderResponse.cs new file mode 100644 index 000000000..91345ee8f --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudListFolderResponse.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Response from pCloud's list folder API endpoint +/// +internal record pCloudListFolderResponse +{ + /// + /// Result code from the API call. 0 indicates success + /// + public int result { get; init; } + + /// + /// Detailed metadata about the folder and its contents + /// + public pCloudListFoldersMetadata? metadata { get; init; } +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudListFoldersMetadata.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudListFoldersMetadata.cs new file mode 100644 index 000000000..4b74fbaea --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudListFoldersMetadata.cs @@ -0,0 +1,93 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Metadata information for a pCloud folder listing, including its contents +/// +internal record pCloudListFoldersMetadata +{ + /// + /// Full path to the folder + /// + public string path { get; init; } = string.Empty; + + /// + /// Name of the folder + /// + public string name { get; init; } = string.Empty; + + /// + /// Creation timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string created { get; init; } = string.Empty; + + /// + /// Indicates if the folder belongs to the authenticated user + /// + public bool ismine { get; init; } + + /// + /// Indicates if the folder has a thumbnail + /// + public bool thumb { get; init; } + + /// + /// Last modification timestamp in format "Day, DD MMM YYYY HH:MM:SS +0000" + /// + public string modified { get; init; } = string.Empty; + + /// + /// Number of comments on the folder + /// + public int comments { get; init; } + + /// + /// Unique identifier for the folder, prefixed with 'd' + /// + public string id { get; init; } = string.Empty; + + /// + /// Indicates if the folder is shared with other users + /// + public bool isshared { get; init; } + + /// + /// Icon type for the folder + /// + public string icon { get; init; } = string.Empty; + + /// + /// Always true for folder metadata + /// + public bool isfolder { get; init; } + + /// + /// Numeric ID of this folder + /// + public long folderid { get; init; } + + /// + /// List of files and folders contained within this folder + /// + public List contents { get; init; } = new(); +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Model/pCloudUploadResponse.cs b/Duplicati/Library/Backend/pCloud/Model/pCloudUploadResponse.cs new file mode 100644 index 000000000..ead719b5b --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Model/pCloudUploadResponse.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Backend.pCloud; + +/// +/// Response from pCloud's upload file API endpoint +/// +internal record pCloudUploadResponse +{ + /// + /// Result code from the API call. 0 indicates success + /// + public int result { get; init; } + + /// + /// Array of metadata for uploaded files + /// + public List metadata { get; init; } = new(); + + /// + /// Checksums for uploaded files + /// + public List checksums { get; init; } = new(); + + /// + /// List of file IDs for uploaded files + /// + public List fileids { get; init; } = new(); +} \ No newline at end of file diff --git a/Duplicati/Library/Backend/pCloud/Strings.cs b/Duplicati/Library/Backend/pCloud/Strings.cs new file mode 100644 index 000000000..aeffffe54 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/Strings.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Localization.Short; + +namespace Duplicati.Library.Backend.Strings +{ + internal static class pCloudBackend + { + public static string Description => LC.L(@"This backend can read and write data to pCloud with native API. Allowed format is ""pcloud://api.pcloud.com""."); + public static string DisplayName => LC.L(@"pCloud (Native API)"); + public static string AuthPasswordDescriptionLong(string tokenurl) => LC.L(@"The oAuth token used to connect to the server. This may also be supplied as the environment variable ""AUTHID"". Visit {0} to get a new one", tokenurl); + public static string AuthPasswordDescriptionShort => LC.L(@"Supply the oAuth token used to connect to the server"); + public static string NoServerSpecified => LC.L(@"No server specified, must be either api.pcloud.com or eapi.pcloud.com for European hosting"); + public static string InvalidServerSpecified => LC.L(@"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com for European hosting"); + public static string FailedWithUnexpectedErrorCode(string operation, int resultcode) => LC.L(@"Operation {0} failed with unexpected result code: {1}", operation, resultcode); + } +} diff --git a/Duplicati/Library/Backend/pCloud/pCloudBackend.cs b/Duplicati/Library/Backend/pCloud/pCloudBackend.cs new file mode 100644 index 000000000..7fa0edb48 --- /dev/null +++ b/Duplicati/Library/Backend/pCloud/pCloudBackend.cs @@ -0,0 +1,557 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json; +using Duplicati.Library.Backend.pCloud; +using Duplicati.Library.Utility; +using Uri = System.Uri; +using System.Runtime.CompilerServices; +using Duplicati.Library.Utility.Options; + +namespace Duplicati.Library.Backend; + +/// +/// Native pCloud Backend API implementation +/// +public class pCloudBackend : IStreamingBackend +{ + /// + /// The URL used to get a new token + /// + private static readonly string TOKEN_URL = OAuthHelper.OAUTH_LOGIN_URL("pcloud"); + /// + /// Implementation of interface property for the backend key + /// + public string ProtocolKey => "pcloud"; + + /// + /// Implementation of interface property for the backend display name + /// + public string DisplayName => Strings.pCloudBackend.DisplayName; + + /// + /// Implementation of interface property for the backend description + /// + public string Description => Strings.pCloudBackend.Description; + + /// + /// The server URL to be used (pcloud uses 2 different endpoints depending if its an european or non european hosting) + /// + private readonly string _ServerUrl; + + /// + /// Bearer token to be using in the API + /// + private string _Token; + + /// + /// Remote path/folder to use used in the backend + /// + private readonly string _Path; + + /// + /// Hostname only (no ports or paths) to be used on DNS resolutions. + /// + private readonly string _DnsName; + + /// + /// The timeouts to be used in the backend + /// + private readonly TimeoutOptionsHelper.Timeouts _Timeouts; + + /// + /// HttpClient to be used for requests + /// + private readonly HttpClient _HttpClient; + + /// + /// Variable being used to cache the folder ID, as it is required to upload files + /// and the only way to obtain is with an API call. The cache is to avoid multiple + /// requests + /// + private ulong? _CachedFolderID; + + /// + /// Path separators (both Windows \ and unix /) to be used in path manipulation + /// + private static readonly char[] PATH_SEPARATORS = ['/', '\\']; + + /// + /// List of pcloud Servers and their respective hostnames + /// + private static readonly Dictionary PCLOUD_SERVERS = new(StringComparer.OrdinalIgnoreCase) + { + { "pCloud Global", "api.pcloud.com" }, + { "pCloud (EU)", "eapi.pcloud.com" }, + }; + + /// + /// Empty constructor is required for the backend to be loaded by the backend factory + /// + public pCloudBackend() + { + _ServerUrl = null!; + _Path = null!; + _DnsName = null!; + _Token = null!; + _Timeouts = null!; + _HttpClient = null!; + } + + /// + /// Actual constructor for the backend that accepts the url and options + /// + /// URL in Duplicati Uri format + /// options to be used in the backend + public pCloudBackend(string url, Dictionary options) + { + var uri = new Utility.Uri(url); + uri.RequireHost(); + _DnsName = uri.Host; + + _Token = AuthIdOptionsHelper.Parse(options) + .RequireCredentials(TOKEN_URL) + .AuthId!; + + if (!PCLOUD_SERVERS.ContainsValue(uri.Host)) + throw new UserInformationException(Strings.pCloudBackend.InvalidServerSpecified, + "InvalidpCloudServerSpecified"); + + if (string.IsNullOrWhiteSpace(uri.Host)) + throw new UserInformationException(Strings.pCloudBackend.NoServerSpecified, "NopCloudServerSpecified"); + + // Ensure that the path is in the correct format, without starting or tailing slashes + _Path = uri.Path.TrimStart(PATH_SEPARATORS).TrimEnd(PATH_SEPARATORS).Trim(); + _ServerUrl = uri.Host; + _Timeouts = TimeoutOptionsHelper.Parse(options); + + var httpHandler = new HttpClientHandler(); + _HttpClient = HttpClientHelper.CreateClient(httpHandler); + _HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + "Bearer", + _Token + ); + + // Set the timeout to infinite, all methods are called with cancelationTokens. + _HttpClient.Timeout = Timeout.InfiniteTimeSpan; + + } + + /// + /// Implementation of interface property to return supported command parameters + /// + public IList SupportedCommands => [ + new CommandLineArgument(AuthIdOptionsHelper.AuthIdOption, + CommandLineArgument.ArgumentType.Password, + Strings.pCloudBackend.AuthPasswordDescriptionShort, + Strings.pCloudBackend.AuthPasswordDescriptionLong(TOKEN_URL)), + ]; + + /// + /// Implementation of interface method for listing remote folder contents. + /// The root parameter is used to list the root folder, as the pCloud API + /// when using oauth tokens creates an isolated folder Applications/ApplicationName + /// + /// + /// The folder ID to consider as root + /// Cancellation Token + /// + private async Task> List(ulong folderId, CancellationToken cancellationToken) + { + var result = await ListWithMetadata(folderId, cancellationToken).ConfigureAwait(false); + return result.Select(item => new pCloudFileEntry + { + IsFolder = item.isfolder, + Name = item.name, + Size = item.size ?? 0, + LastAccess = DateTime.Parse(item.created), + LastModification = DateTime.Parse(item.modified) + }) + .ToList(); + } + + /// + /// Lists folders with pCloud metadata, necessary to obtain the folder IDs + /// + /// FolderId to be used as root. + /// Cancellation Token + /// + /// + private async Task> ListWithMetadata(ulong folderId, CancellationToken cancellationToken) + { + using var request = CreateRequest($"/listfolder?folderid={folderId}", HttpMethod.Get); + + using var response = await Utility.Utility.WithTimeout(_Timeouts.ListTimeout, cancellationToken, + ct => _HttpClient + .SendAsync(request, HttpCompletionOption.ResponseContentRead, + ct)).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + var content = await Utility.Utility.WithTimeout(_Timeouts.ListTimeout, cancellationToken, + ct => response.Content.ReadAsStringAsync(ct) + ).ConfigureAwait(false); + + var listFolderResponse = JsonSerializer.Deserialize(content) + ?? throw new Exception("Failed to deserialize list folder response"); + + if (pCloudErrorList.ErrorMessages.TryGetValue(listFolderResponse.result, out var message)) + throw new Exception(message); + + if (listFolderResponse.result != 0) + throw new Exception(Strings.pCloudBackend.FailedWithUnexpectedErrorCode("list", listFolderResponse.result)); + + return listFolderResponse.metadata?.contents ?? []; + } + + /// + /// Implementation of interface method for listing remote folder contents + /// + /// List of IFileEntry with directory listing result + public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancellationToken) + { + _CachedFolderID ??= await GetFolderId(cancellationToken).ConfigureAwait(false); + foreach (var v in await List(_CachedFolderID.Value, cancellationToken).ConfigureAwait(false)) + yield return v; + } + + /// + /// Upload files to remote location + /// + /// Filename at remote location + /// Filename to read from + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + public async Task PutAsync(string remotename, string localname, CancellationToken cancellationToken) + { + await using var fs = File.Open(localname, + FileMode.Open, FileAccess.Read, FileShare.Read); + await PutAsync(remotename, fs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Upload files to remote location + /// + /// Filename at remote location + /// Stream to read from + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + public async Task PutAsync(string remotename, Stream input, CancellationToken cancellationToken) + { + _CachedFolderID ??= await GetFolderId(cancellationToken).ConfigureAwait(false); + + var encodedPath = Uri.EscapeDataString(remotename); + + using var request = + CreateRequest($"/uploadfile?folderid={_CachedFolderID}&filename={encodedPath}&nopartial=1", + HttpMethod.Post); + using var timeoutStream = input.ObserveReadTimeout(_Timeouts.ReadWriteTimeout, false); + request.Content = new StreamContent(timeoutStream); + request.Content.Headers.ContentLength = timeoutStream.Length; + request.Content.Headers.ContentType = + new MediaTypeHeaderValue("application/octet-stream"); + + using var response = await _HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(false); + + var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + var uploadResponse = JsonSerializer.Deserialize(content) + ?? throw new Exception("Failed to deserialize upload response"); + + if (pCloudErrorList.ErrorMessages.TryGetValue(uploadResponse.result, out var message)) + throw new Exception(message); + + if (uploadResponse.result != 0) + throw new Exception(Strings.pCloudBackend.FailedWithUnexpectedErrorCode("upload", uploadResponse.result)); + } + + /// + /// Download files from remote + /// + /// Filename at remote location + /// Destination stream to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, string localname, CancellationToken cancellationToken) + { + await using var fs = File.Open(localname, + FileMode.Create, FileAccess.Write, + FileShare.None); + await GetAsync(remotename, fs, cancellationToken).ConfigureAwait(false); + } + + /// + /// Obtains the file id from the filename to be used in the download API + /// + /// + /// Filename at remote, path is automatically concatenated if needed + /// Cancellation Token + /// + /// FileMissingException when file is not found + /// Exceptions arising from either code execution + private async Task GetFileLink(string filename, CancellationToken cancellationToken) + { + using var request = CreateRequest($"/getfilelink?fileid={await GetFileId(filename, cancellationToken).ConfigureAwait(false)}", HttpMethod.Get); + + using var response = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, + ct => _HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ct) + ).ConfigureAwait(false); + + if (!response.IsSuccessStatusCode) + throw new Exception($"Failed to get download link. Status: {response.StatusCode}"); + + var content = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, ct => response.Content.ReadAsStringAsync(ct)).ConfigureAwait(false); + var getFileIdResponse = JsonSerializer.Deserialize(content) + ?? throw new Exception("Failed to deserialize getfilelink response"); + + if (pCloudErrorList.ErrorMessages.TryGetValue(getFileIdResponse.result, out var message)) + throw new FileMissingException(message); + + if (getFileIdResponse.result != 0) + throw new Exception( + Strings.pCloudBackend.FailedWithUnexpectedErrorCode("getfilelink", getFileIdResponse.result)); + + return $"https://{getFileIdResponse.hosts[0]}{getFileIdResponse.path}"; + } + + /// + /// Download files from remote + /// + /// Filename at remote location + /// Destination stream to write to + /// CancellationToken that is combined with internal timeout token + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or FileMissingException + public async Task GetAsync(string remotename, Stream output, CancellationToken cancellationToken) + { + try + { + using var request = CreateRequest(string.Empty, HttpMethod.Get); + request.RequestUri = new Uri(await GetFileLink(remotename, cancellationToken).ConfigureAwait(false)); + + using var timeoutStream = output.ObserveWriteTimeout(_Timeouts.ReadWriteTimeout, false); + await _HttpClient.DownloadFile(request, timeoutStream, null, + cancellationToken).ConfigureAwait(false); + } + catch (HttpRequestException wex) + { + /* + * Known Behaviour on pCloud + * + * If the temporary link is no longer valid, it will return a 401 + * if the url is corrupted/wrong it will return a 500 + */ + + switch (wex) + { + case { StatusCode: HttpStatusCode.Unauthorized }: + throw new FileMissingException("Temporary link is no longer valid", wex); + case { StatusCode: HttpStatusCode.InternalServerError }: + throw new FileMissingException("Temporary link is corrupted", wex); + default: + throw; + } + } + } + + /// + /// Delete remote file if it exists, if now, throws FileMissingException + /// + /// filename to be deleted on the remote + /// CancellationToken that is combined with internal timeout token + /// + /// FileMissingException when file is not found + /// Exceptions arising from either code execution or business logic when return code from pcloud indicates an error. + public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) + { + using var request = CreateRequest($"/deletefile?fileid={await GetFileId(remotename, cancellationToken).ConfigureAwait(false)}", HttpMethod.Get); + + using var response = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, + ct => _HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ct) + ).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var content = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, + ct => response.Content.ReadAsStringAsync(ct) + ).ConfigureAwait(false); + var deleteFileResponse = JsonSerializer.Deserialize(content) + ?? throw new Exception("Failed to deserialize delete file response"); + + // If no error code is matched, result was == 0 so it successfully created the folder + if (deleteFileResponse.result == 2009) + throw new FileMissingException(); + + if (pCloudErrorList.ErrorMessages.TryGetValue(deleteFileResponse.result, out var message)) + throw new Exception(message); + + if (deleteFileResponse.result != 0) + throw new Exception( + Strings.pCloudBackend.FailedWithUnexpectedErrorCode("delete", deleteFileResponse.result)); + } + + /// + /// Implementation of interface function to return hosnames used by the backend + /// + /// CancellationToken, in this call not used. + /// + public Task GetDNSNamesAsync(CancellationToken cancellationToken) => Task.FromResult(new[] { _DnsName }); + + /// + /// Tests backend connectivity by verifying the configured path exists + /// + /// The cancellation token (not used) + /// Thrown when configured path does not exist + public async Task TestAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_Path) || _Path.Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries).Length == 0) + return; + + // This method will search for the folderId recursively and throw an exception if the folder is not found + await GetFolderId(cancellationToken).ConfigureAwait(false); + + } + + /// + /// Create remote folder + /// + /// CancellationToken that will be combined with internal timeout token + /// + /// + public async Task CreateFolderAsync(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_Path) || _Path.Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries).Length == 0) + { + _CachedFolderID = 0; + return; + } + + var currentId = 0UL; + foreach (var folder in _Path.Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries)) + currentId = await CreateFolder(cancellationToken, currentId, folder).ConfigureAwait(false); + + _CachedFolderID = currentId; + } + + /// + /// Create remote folder in relation to the parent folder + /// + /// Cancellation Token + /// Parent Folder ID + /// Folder name + /// The folderID of the newly created folder + private async Task CreateFolder(CancellationToken cancellationToken, ulong parentFolderId, string folderName) + { + using var request = CreateRequest($"/createfolderifnotexists?folderid={parentFolderId}&name={folderName}", HttpMethod.Get); + + using var response = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, + ct => _HttpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, ct) + ).ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + + var content = await Utility.Utility.WithTimeout(_Timeouts.ShortTimeout, cancellationToken, + ct => response.Content.ReadAsStringAsync(ct) + ).ConfigureAwait(false); + var createFolderResponse = JsonSerializer.Deserialize(content) + ?? throw new Exception("Failed to deserialize create folder response"); + + if (pCloudErrorList.ErrorMessages.TryGetValue(createFolderResponse.result, out var message)) + throw new Exception(message); + + return createFolderResponse is { result: 0, metadata.folderid: var id } + ? id + : throw new Exception(Strings.pCloudBackend.FailedWithUnexpectedErrorCode("createfolder", createFolderResponse.result)); + } + + /// + /// Returns the fileID by listing the folder and searching for the filename & metadata. + /// + /// For operations such as delete/getfilelink using the fileID is more reliable than using direct path/filename concatenation. + /// + /// The filename + /// Cancellation Token + /// The fileID + /// + private async Task GetFileId(string name, CancellationToken cancellationToken) + { + _CachedFolderID ??= await GetFolderId(cancellationToken).ConfigureAwait(false); + + var result = await ListWithMetadata((ulong)_CachedFolderID, cancellationToken).ConfigureAwait(false); + + return result.FirstOrDefault(x => !x.isfolder && x.name == name)?.fileid ?? throw new FileMissingException(name); + + } + + /// + /// Returns the folder ID for the configured path, regardless of the depth + /// + /// + private async Task GetFolderId(CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(_Path) || _Path.Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries).Length == 0) + return 0UL; + + var currentFolderId = 0UL; + foreach (var folder in _Path.Split(PATH_SEPARATORS, StringSplitOptions.RemoveEmptyEntries)) + { + var folderContent = await ListWithMetadata(currentFolderId, cancellationToken).ConfigureAwait(false); + var matchingFolder = folderContent.FirstOrDefault(x => x.isfolder && x.name == folder); + + if (matchingFolder?.folderid == null) + throw new FolderMissingException(); + + currentFolderId = matchingFolder.folderid; + } + + return currentFolderId; + } + + /// + /// Helper method to create request resources with the bearer token and default headers + /// + /// url to be appended after host + /// Http Method + /// + private HttpRequestMessage CreateRequest(string url, HttpMethod? method = null) + { + var request = new HttpRequestMessage(HttpMethod.Get, $"https://{_ServerUrl}/{url}"); + request.Headers.Add(HttpRequestHeader.UserAgent.ToString(), + "Duplicati pCloud Client v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); + + if (method != null) + request.Method = method; + + return request; + } + + /// + /// Implementation of Dispose pattern enforced by interface + /// in this case, we don't need to dispose anything + /// + public void Dispose() + { + } +} \ No newline at end of file diff --git a/Duplicati/Library/Backends/BackendModules.cs b/Duplicati/Library/Backends/BackendModules.cs index 255bc3b07..15903767d 100644 --- a/Duplicati/Library/Backends/BackendModules.cs +++ b/Duplicati/Library/Backends/BackendModules.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -53,7 +53,7 @@ public static class BackendModules new Backend.Jottacloud(), new Backend.Mega.MegaBackend(), new Backend.MicrosoftGroup(), - new Backend.OneDriveV2(), + new Backend.OneDrive(), new Backend.OpenStack.OpenStackStorage(), new Backend.Rclone(), new Backend.S3(), @@ -65,7 +65,11 @@ public static class BackendModules IsStorjSupported? new Backend.Storj.Storj() : null, new Backend.TahoeBackend(), new Backend.TencentCOS.COS(), - new Backend.WEBDAV() + new Backend.WEBDAV(), + new Backend.pCloudBackend(), + new Backend.SMBBackend(), + new Backend.CIFSBackend(), + new Backend.Filen.FilenBackend(), } .Where(x => x != null) .ToList(); diff --git a/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj b/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj index 956603dc6..725189b2d 100644 --- a/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj +++ b/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj @@ -3,13 +3,15 @@ net8.0 Central reference to all backends Duplicati.Library.Backend - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + + @@ -20,6 +22,7 @@ + diff --git a/Duplicati/Library/Backends/WebModules.cs b/Duplicati/Library/Backends/WebModules.cs index 7f20db18c..3b1c244b4 100644 --- a/Duplicati/Library/Backends/WebModules.cs +++ b/Duplicati/Library/Backends/WebModules.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Common/Duplicati.Library.Common.csproj b/Duplicati/Library/Common/Duplicati.Library.Common.csproj index 0a1692ec4..c5aade15c 100644 --- a/Duplicati/Library/Common/Duplicati.Library.Common.csproj +++ b/Duplicati/Library/Common/Duplicati.Library.Common.csproj @@ -4,7 +4,7 @@ net8.0 Library Duplicati.Library.IO - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Common/ILiveControls.cs b/Duplicati/Library/Common/ILiveControls.cs index cef107b11..23e571931 100644 --- a/Duplicati/Library/Common/ILiveControls.cs +++ b/Duplicati/Library/Common/ILiveControls.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.IO; public interface ILiveControls diff --git a/Duplicati/Library/Common/IO/DefineDosDevice.cs b/Duplicati/Library/Common/IO/DefineDosDevice.cs index a1f2f14cf..ff4918d8c 100644 --- a/Duplicati/Library/Common/IO/DefineDosDevice.cs +++ b/Duplicati/Library/Common/IO/DefineDosDevice.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Common/IO/FileEntry.cs b/Duplicati/Library/Common/IO/FileEntry.cs index 3e842dc00..b603edd0d 100644 --- a/Duplicati/Library/Common/IO/FileEntry.cs +++ b/Duplicati/Library/Common/IO/FileEntry.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using Duplicati.Library.Interface; @@ -32,6 +32,7 @@ namespace Duplicati.Library.Common.IO private string m_name; private DateTime m_lastAccess; private DateTime m_lastModification; + private DateTime m_created; private long m_size; private bool m_isFolder; @@ -62,6 +63,15 @@ namespace Duplicati.Library.Common.IO set { m_lastModification = value; } } + /// + /// Gets or sets the time the file or folder was created + /// + public DateTime Created + { + get { return m_created; } + set { m_created = value; } + } + /// /// Gets or sets the size of the file or folder /// diff --git a/Duplicati/Library/Common/IO/ISystemIO.cs b/Duplicati/Library/Common/IO/ISystemIO.cs index 3167ad4ce..c291d2200 100644 --- a/Duplicati/Library/Common/IO/ISystemIO.cs +++ b/Duplicati/Library/Common/IO/ISystemIO.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -52,6 +52,7 @@ namespace Duplicati.Library.Common.IO bool FileExists(string path); long FileLength(string path); FileStream FileOpenRead(string path); + FileStream FileOpenReadWrite(string path); FileStream FileOpenWrite(string path); FileStream FileCreate(string path); FileAttributes GetFileAttributes(string path); @@ -81,6 +82,16 @@ namespace Duplicati.Library.Common.IO void SetMetadata(string path, Dictionary metdata, bool restorePermissions); Dictionary GetMetadata(string path, bool isSymlink, bool followSymlink); + /// + /// Sets the permission to read-write only for the current user. + /// + /// The file to set permissions on. + void FileSetPermissionUserRWOnly(string path); + /// + /// Sets the permission to read-write only for the current user. + /// + /// The directory to set permissions on. + void DirectorySetPermissionUserRWOnly(string path); } } diff --git a/Duplicati/Library/Common/IO/Path.cs b/Duplicati/Library/Common/IO/Path.cs index fef8b5c7a..a442a0d49 100755 --- a/Duplicati/Library/Common/IO/Path.cs +++ b/Duplicati/Library/Common/IO/Path.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Common/IO/PathInternal.Windows.cs b/Duplicati/Library/Common/IO/PathInternal.Windows.cs index a868c6ebc..66be456b9 100755 --- a/Duplicati/Library/Common/IO/PathInternal.Windows.cs +++ b/Duplicati/Library/Common/IO/PathInternal.Windows.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Common/IO/PosixFile.cs b/Duplicati/Library/Common/IO/PosixFile.cs index 2ad8fb9bb..1f749ea24 100644 --- a/Duplicati/Library/Common/IO/PosixFile.cs +++ b/Duplicati/Library/Common/IO/PosixFile.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -41,7 +41,7 @@ namespace Duplicati.Library.Common.IO Mono.Unix.Native.Syscall.llistxattr("/", out v); works = true; } - catch (EntryPointNotFoundException e) + catch (EntryPointNotFoundException) { } catch diff --git a/Duplicati/Library/Common/IO/SystemIO.cs b/Duplicati/Library/Common/IO/SystemIO.cs index 647ccb1a3..268940aba 100644 --- a/Duplicati/Library/Common/IO/SystemIO.cs +++ b/Duplicati/Library/Common/IO/SystemIO.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,37 +20,25 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Runtime.Versioning; namespace Duplicati.Library.Common.IO { public static class SystemIO { - - /// - /// A cached lookup for windows methods for dealing with long filenames - /// - [SupportedOSPlatform("windows")] - public static readonly ISystemIO IO_WIN; - - [SupportedOSPlatform("linux")] - [SupportedOSPlatform("macOS")] - public static readonly ISystemIO IO_SYS; - public static readonly ISystemIO IO_OS; static SystemIO() { - // TODO: These interfaces cannot be properly guarded by the supported platform attribute in this form. - // They are used in static methods of USNJournal on all platforms. - IO_WIN = new SystemIOWindows(); - IO_SYS = new SystemIOLinux(); if (OperatingSystem.IsWindows()) { - IO_OS = IO_WIN; + IO_OS = new SystemIOWindows(); } else if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) { - IO_OS = IO_SYS; + IO_OS = new SystemIOLinux(); + } + else + { + throw new PlatformNotSupportedException("The current platform is not supported"); } } } diff --git a/Duplicati/Library/Common/IO/SystemIOLinux.cs b/Duplicati/Library/Common/IO/SystemIOLinux.cs index a7b302607..fea6e8845 100644 --- a/Duplicati/Library/Common/IO/SystemIOLinux.cs +++ b/Duplicati/Library/Common/IO/SystemIOLinux.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,6 +25,7 @@ using System.Linq; using System.Collections.Generic; using Duplicati.Library.Interface; using System.Runtime.Versioning; +using System.Runtime.InteropServices; namespace Duplicati.Library.Common.IO { @@ -32,6 +33,26 @@ namespace Duplicati.Library.Common.IO [SupportedOSPlatform("macOS")] public struct SystemIOLinux : ISystemIO { + /// + /// PInvoke methods + /// + private static class PInvoke + { + /// + /// Gets the user ID of the current user + /// + /// The user ID + [DllImport("libc")] + public static extern uint getuid(); + + /// + /// Gets the group ID of the current user + /// + /// + [DllImport("libc")] + public static extern uint getgid(); + } + #region ISystemIO implementation public void DirectoryCreate(string path) @@ -64,7 +85,8 @@ namespace Duplicati.Library.Common.IO File.SetLastWriteTimeUtc(path, time); var gtime = FileGetLastWriteTimeUtc(path); - if(gtime != time) { + if (gtime != time) + { Console.Error.WriteLine($"DISS: {path} {gtime.ToFileTimeUtc() - time.ToFileTimeUtc()}"); } } @@ -94,6 +116,13 @@ namespace Duplicati.Library.Common.IO return File.OpenRead(path); } + public FileStream FileOpenReadWrite(string path) + { + return File.Exists(path) + ? File.Open(path, FileMode.Open, FileAccess.ReadWrite) + : File.Create(path); + } + public FileStream FileOpenWrite(string path) { return File.OpenWrite(path); @@ -123,7 +152,7 @@ namespace Duplicati.Library.Common.IO { return PosixFile.GetSymlinkTarget(NormalizePath(path)); } - + public string PathGetDirectoryName(string path) { return Path.GetDirectoryName(NormalizePath(path)); @@ -169,7 +198,7 @@ namespace Duplicati.Library.Common.IO { return Path.GetExtension(path); } - + public string PathChangeExtension(string path, string extension) { return Path.ChangeExtension(path, extension); @@ -207,7 +236,7 @@ namespace Duplicati.Library.Common.IO var n = PosixFile.GetExtendedAttributes(f, isSymlink, followSymlink); if (n != null) - foreach(var x in n) + foreach (var x in n) dict["unix-ext:" + x.Key] = Convert.ToBase64String(x.Value); var fse = PosixFile.GetUserGroupAndPermissions(f); @@ -231,7 +260,7 @@ namespace Duplicati.Library.Common.IO var f = NormalizePath(file); - foreach(var x in data.Where(x => x.Key.StartsWith("unix-ext:", StringComparison.Ordinal)).Select(x => new KeyValuePair(x.Key.Substring("unix-ext:".Length), Convert.FromBase64String(x.Value)))) + foreach (var x in data.Where(x => x.Key.StartsWith("unix-ext:", StringComparison.Ordinal)).Select(x => new KeyValuePair(x.Key.Substring("unix-ext:".Length), Convert.FromBase64String(x.Value)))) PosixFile.SetExtendedAttribute(f, x.Key, x.Value); if (restorePermissions && data.ContainsKey("unix:uid-gid-perm")) @@ -339,6 +368,40 @@ namespace Duplicati.Library.Common.IO { return new FileEntry(fileInfo.Name, fileInfo.Length, fileInfo.LastAccessTime, fileInfo.LastWriteTime); } + + private static (long uid, long gid) GetOwnerAndGroup(string path) + { + var fi = PosixFile.GetUserGroupAndPermissions(path); + return (fi.UID, fi.GID); + } + + /// + /// Sets the unix permission user read-write Only. + /// + /// The file to set permissions on. + public void FileSetPermissionUserRWOnly(string path) + { + PosixFile.SetUserGroupAndPermissions( + path, + PInvoke.getuid(), + PInvoke.getgid(), + Convert.ToInt32("600", 8) /* FilePermissions.S_IRUSR | FilePermissions.S_IWUSR*/ + ); + } + + /// + /// Sets the permission to read-write only for the current user. + /// + /// The directory to set permissions on. + public void DirectorySetPermissionUserRWOnly(string path) + { + PosixFile.SetUserGroupAndPermissions( + path, + PInvoke.getuid(), + PInvoke.getgid(), + Convert.ToInt32("700", 8) /* FilePermissions.S_IRUSR | FilePermissions.S_IWUSR | FilePermissions.S_IXUSR */ + ); + } } } diff --git a/Duplicati/Library/Common/IO/SystemIOWindows.cs b/Duplicati/Library/Common/IO/SystemIOWindows.cs index 1bcab4987..9e4b5476c 100644 --- a/Duplicati/Library/Common/IO/SystemIOWindows.cs +++ b/Duplicati/Library/Common/IO/SystemIOWindows.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in +// +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -28,9 +28,11 @@ using System.Linq; using Duplicati.Library.Interface; using Newtonsoft.Json; using System.Runtime.Versioning; +using System.Security.Principal; namespace Duplicati.Library.Common.IO { + [SupportedOSPlatform("windows")] public struct SystemIOWindows : ISystemIO { // Based on the constant names used in @@ -42,6 +44,16 @@ namespace Duplicati.Library.Common.IO private static readonly string DIRSEP = Util.DirectorySeparatorString; + /// + /// The current user name + /// + private static readonly string CURRENT_USERNAME = WindowsIdentity.GetCurrent().Name; + + /// + /// The LocalSystem user name + /// + private static readonly string LOCAL_SYSTEM_NAME = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null).Translate(typeof(NTAccount)).Value; + /// /// Prefix path with one of the extended device path prefixes /// (@"\\?\" or @"\\?\UNC\") but only if it's a fully qualified @@ -321,6 +333,13 @@ namespace Duplicati.Library.Common.IO return System.IO.File.Open(AddExtendedDevicePathPrefix(path), System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); } + public System.IO.FileStream FileOpenReadWrite(string path) + { + return !FileExists(path) + ? FileCreate(path) + : System.IO.File.Open(AddExtendedDevicePathPrefix(path), System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite); + } + public System.IO.FileStream FileOpenWrite(string path) { return !FileExists(path) @@ -586,11 +605,11 @@ namespace Duplicati.Library.Common.IO [SupportedOSPlatform("windows")] public void SetMetadata(string path, Dictionary data, bool restorePermissions) { - var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal); - var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path; - if (restorePermissions) { + var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal); + var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path; + FileSystemSecurity rules = isDirTarget ? GetAccessControlDir(targetpath) : GetAccessControlFile(targetpath); if (data.ContainsKey("win-ext:accessrulesprotected")) @@ -664,6 +683,68 @@ namespace Duplicati.Library.Common.IO if ((attr & System.IO.FileAttributes.ReparsePoint) == 0) throw new System.IO.IOException(string.Format("Unable to create symlink, check account permissions: {0}", symlinkfile)); } + + /// + /// Sets the unix permission user read-write Only. + /// + /// The file to set permissions on. + public void FileSetPermissionUserRWOnly(string path) + { + // Create directory security settings + var security = new FileSecurity(); + + // Remove inherited permissions to ensure only the current user has access + security.SetAccessRuleProtection(true, false); + + // Grant the current user read access + security.AddAccessRule(new FileSystemAccessRule( + CURRENT_USERNAME, + FileSystemRights.FullControl, + AccessControlType.Allow + )); + + security.AddAccessRule(new FileSystemAccessRule( + LOCAL_SYSTEM_NAME, + FileSystemRights.FullControl, + AccessControlType.Allow + )); + + // Adjust with the new security settings + new FileInfo(path).SetAccessControl(security); + } + + /// + /// Sets the permission to read-write only for the current user. + /// + /// The directory to set permissions on. + public void DirectorySetPermissionUserRWOnly(string path) + { + // Create directory security settings + var security = new DirectorySecurity(); + + // Remove inherited permissions to ensure only the current user has access + security.SetAccessRuleProtection(true, false); + + // Grant the current user read access + security.AddAccessRule(new FileSystemAccessRule( + CURRENT_USERNAME, + FileSystemRights.FullControl, + InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, // Apply to subfolders & files + PropagationFlags.None, // Keeps inheritance settings intact + AccessControlType.Allow + )); + + security.AddAccessRule(new FileSystemAccessRule( + LOCAL_SYSTEM_NAME, + FileSystemRights.FullControl, + InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, // Apply to subfolders & files + PropagationFlags.None, // Keeps inheritance settings intact + AccessControlType.Allow + )); + + // Adjust with the new security settings + new DirectoryInfo(path).SetAccessControl(security); + } #endregion } } diff --git a/Duplicati/Library/Common/IO/Util.cs b/Duplicati/Library/Common/IO/Util.cs index 325b253dc..80b247cf6 100644 --- a/Duplicati/Library/Common/IO/Util.cs +++ b/Duplicati/Library/Common/IO/Util.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -31,8 +31,16 @@ namespace Duplicati.Library.Common.IO /// public static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); + /// + /// A cached instance of the alternate directory separator as a string + /// public static readonly string AltDirectorySeparatorString = Path.AltDirectorySeparatorChar.ToString(); + /// + /// Filename of a marker file that can be put inside the data folder to prevent Duplicati from fixing lax permissions + /// + public const string InsecurePermissionsMarkerFile = "insecure-permissions.txt"; + /// /// Appends the appropriate directory separator to paths, depending on OS. /// Does not append the separator if the path already ends with it. @@ -66,5 +74,18 @@ namespace Duplicati.Library.Common.IO { return string.IsNullOrWhiteSpace(path) || path.StartsWith("/", StringComparison.Ordinal) ? "/" : "\\"; } + + /// + /// Checks if the path is inside the Windows folder + /// + /// The path to check + /// true if the path is inside the Windows folder, false otherwise + public static bool IsPathUnderWindowsFolder(string path) + { + if (!OperatingSystem.IsWindows()) + return false; + + return path.StartsWith(AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase); + } } } diff --git a/Duplicati/Library/Compression/CompressionModules.cs b/Duplicati/Library/Compression/CompressionModules.cs index 721380cea..4aac2e417 100644 --- a/Duplicati/Library/Compression/CompressionModules.cs +++ b/Duplicati/Library/Compression/CompressionModules.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -30,7 +30,6 @@ public static class CompressionModules /// The list of all built-in compression modules /// public static IReadOnlyList BuiltInCompressionModules => [ - new ZipCompression.FileArchiveZip(), - new SevenZipCompression() + new ZipCompression.FileArchiveZip() ]; } diff --git a/Duplicati/Library/Compression/Duplicati.Library.Compression.csproj b/Duplicati/Library/Compression/Duplicati.Library.Compression.csproj index 9d5cc374b..89520aa97 100644 --- a/Duplicati/Library/Compression/Duplicati.Library.Compression.csproj +++ b/Duplicati/Library/Compression/Duplicati.Library.Compression.csproj @@ -4,17 +4,11 @@ net8.0 Library Duplicati.Library.Compression - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license - - - - - - ..\..\..\thirdparty\ManagedLZMA\managed-lzma.dll - + diff --git a/Duplicati/Library/Compression/FileArchiveDirectory.cs b/Duplicati/Library/Compression/FileArchiveDirectory.cs index 4a7204a6c..ce3ab2b62 100644 --- a/Duplicati/Library/Compression/FileArchiveDirectory.cs +++ b/Duplicati/Library/Compression/FileArchiveDirectory.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Compression/SevenZipCompression.cs b/Duplicati/Library/Compression/SevenZipCompression.cs deleted file mode 100644 index 827b71fd7..000000000 --- a/Duplicati/Library/Compression/SevenZipCompression.cs +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Duplicati.Library.Interface; -using Duplicati.Library.Utility; - -namespace Duplicati.Library.Compression -{ - public class SevenZipCompression : ICompressionHinting - { - // next file starts a new stream if the previous stream is larger than this - private const int kStreamThreshold = 1 << 20; // 1 MB - - private Stream m_stream; - private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter m_writer; - private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.PlainEncoder m_copyEncoder; - private ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.ThreadedEncoder m_lzma2Encoder; - private master._7zip.Legacy.ArchiveReader m_reader; - private master._7zip.Legacy.CArchiveDatabaseEx m_archive; - private int m_threadCount; - private bool m_lowOverheadMode; - - private ManagedLzma.LZMA.Master.LZMA.CLzma2EncProps m_encoderProps; - - /// - /// Gets or sets a value indicating whether this - /// is in low overhead mode. - /// - /// true if low overhead mode; otherwise, false. - public bool LowOverheadMode - { - get { return m_lowOverheadMode; } - set - { - if (m_lowOverheadMode != value) - { - if (m_lzma2Encoder != null) - throw new NotSupportedException("Cannot change LowOverheadMode after writing has started."); - - m_lowOverheadMode = value; - } - } - } - - /// - /// Default constructor, used to read file extension and supported commands. - /// - public SevenZipCompression() { } - - /// - /// Constructs a new ZIP instance. - /// Access mode is specified by mode parameter. - /// Note that stream would not be disposed by FileArchiveZip instance so - /// you may reuse it and have to dispose it yourself. - /// - /// The stream to read or write depending access mode - /// The archive access mode - /// The options passed on the commandline - public SevenZipCompression(Stream stream, ArchiveMode mode, IDictionary options) - { - InitializeCompression(options.AsReadOnly()); - // Preventing the stream being closed by ArchiveWriter so it could be reused. - // Required for using with MemoryStream. - m_stream = new ShaderStream(stream, true); - if (mode == ArchiveMode.Write) - InitializeArchiveWriter(); - else - { - stream.Position = 0; - InitializeArchiveReader(); - } - } - - private void InitializeCompression(IReadOnlyDictionary options) - { - m_threadCount = DEFAULT_THREAD_COUNT; - - string threadCountSetting; - int threadCountValue; - if (options != null - && options.TryGetValue(THREAD_COUNT_OPTION, out threadCountSetting) - && Int32.TryParse(threadCountSetting, out threadCountValue) - && threadCountValue > 0) - { - // arbitrary limit to avoid stupid mistakes - if (threadCountValue > MAX_THREAD_COUNT) - threadCountValue = MAX_THREAD_COUNT; - - m_threadCount = threadCountValue; - } - - string cplvl; - int tmplvl; - if (options.TryGetValue(COMPRESSION_LEVEL_OPTION, out cplvl) && int.TryParse(cplvl, out tmplvl)) - tmplvl = Math.Max(Math.Min(9, tmplvl), 0); - else - tmplvl = DEFAULT_COMPRESSION_LEVEL; - - m_encoderProps = new ManagedLzma.LZMA.Master.LZMA.CLzma2EncProps(); - m_encoderProps.Lzma2EncProps_Init(); - - m_encoderProps.mLzmaProps.mLevel = tmplvl; - m_encoderProps.mLzmaProps.mAlgo = Library.Utility.Utility.ParseBoolOption(options, COMPRESSION_FASTALGO_OPTION) ? 0 : 1; - } - - private void InitializeArchiveReader() - { - m_archive = new master._7zip.Legacy.CArchiveDatabaseEx(); - m_reader = new master._7zip.Legacy.ArchiveReader(); - m_reader.Open(m_stream); - m_reader.ReadDatabase(m_archive, null); - m_archive.Fill(); - } - - private void InitializeArchiveWriter() - { - m_writer = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter(m_stream); - } - - public void Dispose() - { - if (m_archive != null) - m_archive = null; - - if (m_reader != null) - try { m_reader.Close(); } - finally { m_reader = null; } - - if (m_writer != null) - { - try - { - if (m_copyEncoder != null) - try - { - m_writer.ConnectEncoder(m_copyEncoder); - m_copyEncoder.Dispose(); - } - finally { m_copyEncoder = null; } - - if (m_lzma2Encoder != null) - try - { - m_writer.ConnectEncoder(m_lzma2Encoder); - m_lzma2Encoder.Dispose(); - } - finally { m_lzma2Encoder = null; } - - m_writer.WriteFinalHeader(); - } - finally { m_writer = null; } - } - - if (m_stream != null) - try { m_stream.Dispose(); } - finally { m_stream = null; } - } - - #region ICompression - Reader Methods - - public bool FileExists(string filename) - { - return m_reader.GetFiles(m_archive).Any(file => file.Name == filename); - } - - public string[] ListFiles(string prefix) - { - return ListFilesWithSize(prefix).Select(x => x.Key).ToArray(); - } - - public IEnumerable> ListFilesWithSize(string prefix) - { - if (m_reader == null) - throw new InvalidOperationException(Strings.SevenZipCompression.NoReaderError); - - StringComparison sc = Library.Utility.Utility.ClientFilenameStringComparison; - - return - from n in m_reader.GetFiles(m_archive) - where - !n.IsDir && - (prefix == null || n.Name.StartsWith(prefix, sc)) - select new KeyValuePair(n.Name, n.Size); - } - - public DateTime GetLastWriteTime(string file) - { - var item = m_reader.GetFiles(m_archive).FirstOrDefault(x => x.Name == file); - if (item == null) - throw new FileNotFoundException(Strings.SevenZipCompression.FileNotFoundError, file); - - if (!item.MTime.HasValue) - return new DateTime(0); - - return item.MTime.Value; - } - - public Stream OpenRead(string file) - { - if (string.IsNullOrEmpty(file)) - throw new ArgumentNullException(nameof(file)); - - if (m_reader == null) - throw new InvalidOperationException(Strings.SevenZipCompression.NoReaderError); - - var item = m_reader.GetFiles(m_archive).FirstOrDefault(x => x.Name == file); - return item == null - ? null - : m_reader.OpenStream(m_archive, m_reader.GetFileIndex(m_archive, item), null); - } - - #endregion - - #region Write Utilities - - private void mLzma2Encoder_OnOutputThresholdReached(object sender, EventArgs e) - { - m_writer.ConnectEncoder(m_lzma2Encoder); - } - - private sealed class WriterEntry : ManagedLzma.LZMA.Master.SevenZip.IArchiveWriterEntry - { - private readonly string mName; - private readonly DateTime? mTimestamp; - - internal WriterEntry(string name, DateTime? timestamp) - { - mName = name; - mTimestamp = timestamp; - } - - public string Name - { - get { return mName; } - } - - public FileAttributes? Attributes - { - get { return null; } - } - - public DateTime? CreationTime - { - get { return null; } - } - - public DateTime? LastWriteTime - { - get { return mTimestamp; } - } - - public DateTime? LastAccessTime - { - get { return null; } - } - } - - #endregion - - #region ICompression - Writer Methods - - public Stream CreateFile(string file, CompressionHint hint, DateTime lastWrite) - { - if (string.IsNullOrEmpty(file)) - throw new ArgumentNullException(nameof(file)); - - if (m_writer == null) - throw new InvalidOperationException(Strings.SevenZipCompression.NoWriterError); - - var entry = new WriterEntry(file, lastWrite); - - if (hint != CompressionHint.Noncompressible) - { - if (m_lzma2Encoder == null) - { - if (m_lowOverheadMode) - m_lzma2Encoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.LzmaEncoder(); - else - m_lzma2Encoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.Lzma2Encoder(m_threadCount, m_encoderProps); - - m_lzma2Encoder.OnOutputThresholdReached += mLzma2Encoder_OnOutputThresholdReached; - m_lzma2Encoder.SetOutputThreshold(kStreamThreshold); - } - - return m_lzma2Encoder.BeginWriteFile(entry); - } - else - { - if (m_copyEncoder == null) - m_copyEncoder = new ManagedLzma.LZMA.Master.SevenZip.ArchiveWriter.PlainEncoder(); - - if (m_lzma2Encoder != null && m_lzma2Encoder == m_writer.CurrentEncoder) - m_lzma2Encoder.SetOutputThreshold(kStreamThreshold); // rearm threshold so we can switch back - - if (m_writer.CurrentEncoder != m_copyEncoder) - m_writer.ConnectEncoder(m_copyEncoder); - - return m_copyEncoder.BeginWriteFile(entry); - } - } - - public long Size - { - get - { - if (m_writer != null) - return m_writer.WrittenSize; - else if (m_reader != null) - return m_stream.Length; - else - throw new InvalidOperationException(); - } - } - - public long FlushBufferSize - { - get - { - if (m_writer != null) - { - long remaining = m_writer.CurrentSizeLimit - m_writer.WrittenSize; - if (m_copyEncoder != null && m_copyEncoder != m_writer.CurrentEncoder) - remaining += m_copyEncoder.UpperBound; - if (m_lzma2Encoder != null && m_lzma2Encoder != m_writer.CurrentEncoder) - remaining += m_lzma2Encoder.UpperBound; - return remaining; - } - else if (m_reader != null) - return m_stream.Length; - else - throw new InvalidOperationException(); - } - } - - #endregion - - #region ICompression - Methods which don't need an archive - - public string FilenameExtension - { - get { return "7z"; } - } - - public string DisplayName - { - get { return Strings.SevenZipCompression.DisplayName; } - } - - public string Description - { - get { return Strings.SevenZipCompression.Description; } - } - - /// - /// The commandline option for toggling the compression level - /// - private const string THREAD_COUNT_OPTION = "lzma-thread-count"; - - /// - /// The default compression level - /// - private static readonly int DEFAULT_THREAD_COUNT = Environment.ProcessorCount; - - /// - /// The commandline option for toggling the compression level - /// - private const string COMPRESSION_LEVEL_OPTION = "7z-compression-level"; - - /// - /// The default compression level - /// - private const int DEFAULT_COMPRESSION_LEVEL = 5; - - /// - /// The commandline option for toggling the compression algorithm - /// - private const string COMPRESSION_FASTALGO_OPTION = "7z-compression-fast-algorithm"; - - /// - /// The default setting for the fast-algorithm option - /// - private const bool DEFAULT_FASTALGO = false; - - /// - /// Arbitrary limit to avoid problems with unreasonable user input. - /// - private const int MAX_THREAD_COUNT = 64; - - public IList SupportedCommands - { - get - { - return new List { - new CommandLineArgument( - THREAD_COUNT_OPTION, - CommandLineArgument.ArgumentType.Integer, - Strings.SevenZipCompression.ThreadcountShort, - Strings.SevenZipCompression.ThreadcountLong, - DEFAULT_THREAD_COUNT.ToString()), - - new CommandLineArgument( - COMPRESSION_LEVEL_OPTION, - CommandLineArgument.ArgumentType.Enumeration, - Strings.SevenZipCompression.CompressionlevelShort, - Strings.SevenZipCompression.CompressionlevelLong, - DEFAULT_COMPRESSION_LEVEL.ToString(), - null, - new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}), - - new CommandLineArgument( - COMPRESSION_FASTALGO_OPTION, - CommandLineArgument.ArgumentType.Boolean, - Strings.SevenZipCompression.FastalgoShort, - Strings.SevenZipCompression.FastalgoLong, - DEFAULT_FASTALGO.ToString()), - }; - } - } - - #endregion - } -} diff --git a/Duplicati/Library/Compression/StreamWrapper.cs b/Duplicati/Library/Compression/StreamWrapper.cs index 27efbc973..f1cfa8813 100644 --- a/Duplicati/Library/Compression/StreamWrapper.cs +++ b/Duplicati/Library/Compression/StreamWrapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Compression/Strings.cs b/Duplicati/Library/Compression/Strings.cs index 809220417..cd24ab5dd 100644 --- a/Duplicati/Library/Compression/Strings.cs +++ b/Duplicati/Library/Compression/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -32,22 +32,9 @@ namespace Duplicati.Library.Compression.Strings public static string CompressionmethodShort { get { return LC.L(@"Set the ZIP compression method"); } } public static string Compressionzip64Long { get { return LC.L(@"The ZIP64 format is required for files larger than 4GiB. Use this option to toggle it."); } } public static string Compressionzip64Short { get { return LC.L(@"Toggle ZIP64 support"); } } + public static string Compressionzip64Deprecated { get { return LC.L(@"ZIP64 support is now always enabled. This option is deprecated and will be removed in a future version."); } } public static string CompressionlibraryLong { get { return LC.L(@"This option changes the compression library used to read and write files. The SharpCompress library has more features and is more resilient where the built-in library is faster. When Auto is chosen, the built-in library will be used unless an option is added that requires SharpCompress."); } } public static string CompressionlibraryShort { get { return LC.L(@"Toggles the zip library to use"); } } public static string FileNotFoundError(string filename) { return LC.L(@"File not found: {0}", filename); } } - internal static class SevenZipCompression - { - public static string Description { get { return LC.L(@"*Experimental*: 7z Archive with LZMA2 support."); } } - public static string DisplayName { get { return LC.L(@"Experimental - 7z Archive"); } } - public static string NoWriterError { get { return LC.L(@"Archive not opened for writing"); } } - public static string NoReaderError { get { return LC.L(@"Archive not opened for reading"); } } - public static string FileNotFoundError { get { return LC.L(@"The given file is not part of this archive"); } } - public static string ThreadcountLong { get { return LC.L(@"The number of threads used in LZMA 2 compression. Defaults to the number of processor cores."); } } - public static string ThreadcountShort { get { return LC.L(@"Number of threads used in compression"); } } - public static string CompressionlevelLong { get { return LC.L(@"This option controls the compression level used. A setting of zero gives no compression, and a setting of 9 gives maximum compression."); } } - public static string CompressionlevelShort { get { return LC.L(@"Set the 7z compression level"); } } - public static string FastalgoLong { get { return LC.L(@"This option controls the compression algorithm used. Enabling this option will cause 7z to use the fast algorithm, which produces slightly less compression."); } } - public static string FastalgoShort { get { return LC.L(@"Set the 7z fast algorithm usage"); } } - } } diff --git a/Duplicati/Library/Compression/ZipCompression/BuiltinZipArchive.cs b/Duplicati/Library/Compression/ZipCompression/BuiltinZipArchive.cs index b32f68fe0..0e6b6417f 100644 --- a/Duplicati/Library/Compression/ZipCompression/BuiltinZipArchive.cs +++ b/Duplicati/Library/Compression/ZipCompression/BuiltinZipArchive.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -35,6 +35,10 @@ namespace Duplicati.Library.Compression.ZipCompression; /// public class BuiltinZipArchive : IZipArchive { + /// + /// The log tag + /// + private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); /// /// The wrapped zip archive /// @@ -83,7 +87,7 @@ public class BuiltinZipArchive : IZipArchive m_archive = new ZipArchive( stream, mode == ArchiveMode.Read ? ZipArchiveMode.Read : ZipArchiveMode.Create, - true // Mimic SharpCompress behavior, don't dispose base stream + leaveOpen: true // Mimic SharpCompress behavior, don't dispose base stream ); // Built-in ZipArchive has fewer compression levels @@ -108,6 +112,15 @@ public class BuiltinZipArchive : IZipArchive { foreach (var entry in m_archive.Entries) { + if (m_entries.ContainsKey(entry.FullName)) + Logging.Log.WriteMessage( + // Warning in unittest mode to trip tests, verbose otherwise + options.UnittestMode ? Logging.LogMessageType.Warning : Logging.LogMessageType.Verbose, + LOGTAG, + "DuplicateArchiveEntry", + null, + $"Found duplicate entry in archive: {entry.FullName}"); + m_entries[entry.FullName] = entry; m_pathMappings[entry.FullName.Replace('\\', '/')] = entry.FullName; } diff --git a/Duplicati/Library/Compression/ZipCompression/Constants.cs b/Duplicati/Library/Compression/ZipCompression/Constants.cs index b9b73e789..d8334215b 100644 --- a/Duplicati/Library/Compression/ZipCompression/Constants.cs +++ b/Duplicati/Library/Compression/ZipCompression/Constants.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Compression/ZipCompression/FileArchiveZip.cs b/Duplicati/Library/Compression/ZipCompression/FileArchiveZip.cs index 059bde04c..e5e56f6f9 100644 --- a/Duplicati/Library/Compression/ZipCompression/FileArchiveZip.cs +++ b/Duplicati/Library/Compression/ZipCompression/FileArchiveZip.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -75,10 +75,6 @@ namespace Duplicati.Library.Compression.ZipCompression /// private const string COMPRESSION_METHOD_OPTION = "zip-compression-method"; /// - /// The commandline option for toggling the ZIP64 support - /// - private const string COMPRESSION_ZIP64_OPTION = "zip-compression-zip64"; - /// /// The commandline option for toggling the compression library /// private const string COMPRESSION_LIBRARY_OPTION = "zip-compression-library"; @@ -93,16 +89,21 @@ namespace Duplicati.Library.Compression.ZipCompression /// private const CompressionType DEFAULT_COMPRESSION_METHOD = CompressionType.Deflate; - /// - /// The default setting for the ZIP64 support - /// - private const bool DEFAULT_ZIP64 = false; - /// /// The archive to use /// private readonly IZipArchive m_archive; + /// + /// The fallback archive to use, if any + /// + private IZipArchive? m_fallbackArchive = null; + + /// + /// The parameters used to create the archive + /// + private readonly (Stream Stream, ParsedZipOptions Options, CompressionLibrary Library) m_creationParams; + /// /// Default constructor, used to read file extension and supported commands /// @@ -117,15 +118,11 @@ namespace Duplicati.Library.Compression.ZipCompression /// The stream to read or write depending access mode /// The archive access mode /// The options passed on the commandline - public FileArchiveZip(Stream stream, ArchiveMode mode, IDictionary options) + public FileArchiveZip(Stream stream, ArchiveMode mode, IDictionary options) { var compressionType = DEFAULT_COMPRESSION_METHOD; var compressionLevel = DEFAULT_COMPRESSION_LEVEL; - var usingZip64 = options.ContainsKey(COMPRESSION_ZIP64_OPTION) - ? Utility.Utility.ParseBoolOption(options.AsReadOnly(), COMPRESSION_ZIP64_OPTION) - : DEFAULT_ZIP64; - CompressionType tmptype; if (options.TryGetValue(COMPRESSION_METHOD_OPTION, out var cpmethod) && Enum.TryParse(cpmethod, true, out tmptype)) compressionType = tmptype; @@ -139,11 +136,13 @@ namespace Duplicati.Library.Compression.ZipCompression if (options.ContainsKey(COMPRESSION_LIBRARY_OPTION) && options.TryGetValue(COMPRESSION_LIBRARY_OPTION, out var cplib) && Enum.TryParse(cplib, true, out var tmpcplib)) compressionLibrary = tmpcplib; - var parsedOptions = new ParsedZipOptions(compressionLevel, compressionType, usingZip64); + var unittestMode = Utility.Utility.ParseBoolOption(options.AsReadOnly(), "unittest-mode"); + var parsedOptions = new ParsedZipOptions(compressionLevel, compressionType, unittestMode); + var userCompressionLibrary = compressionLibrary; if (compressionLibrary == CompressionLibrary.Auto) { - if (compressionType != CompressionType.Deflate || usingZip64) + if (compressionType != CompressionType.Deflate) compressionLibrary = CompressionLibrary.SharpCompress; else compressionLibrary = CompressionLibrary.BuiltIn; @@ -164,6 +163,7 @@ namespace Duplicati.Library.Compression.ZipCompression } m_archive = archive ?? new SharpCompressZipArchive(stream, mode, parsedOptions); + m_creationParams = (stream, parsedOptions, userCompressionLibrary); } #region IFileArchive Members @@ -204,7 +204,7 @@ namespace Duplicati.Library.Compression.ZipCompression new CommandLineArgument(COMPRESSION_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionlevelShort, Strings.FileArchiveZip.CompressionlevelLong, DEFAULT_COMPRESSION_LEVEL.ToString(), null, new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}), new CommandLineArgument(COMPRESSION_LEVEL_OPTION_ALIAS, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionlevelShort, Strings.FileArchiveZip.CompressionlevelLong, DEFAULT_COMPRESSION_LEVEL.ToString(), null, new string[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}, Strings.FileArchiveZip.CompressionlevelDeprecated(COMPRESSION_LEVEL_OPTION)), new CommandLineArgument(COMPRESSION_METHOD_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionmethodShort, Strings.FileArchiveZip.CompressionmethodLong(COMPRESSION_LEVEL_OPTION), DEFAULT_COMPRESSION_METHOD.ToString(), null, methods), - new CommandLineArgument(COMPRESSION_ZIP64_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.FileArchiveZip.Compressionzip64Short, Strings.FileArchiveZip.Compressionzip64Long, DEFAULT_ZIP64.ToString()), + new CommandLineArgument("zip-compression-zip64", CommandLineArgument.ArgumentType.Boolean, Strings.FileArchiveZip.Compressionzip64Short, Strings.FileArchiveZip.Compressionzip64Long, "true", null, null, Strings.FileArchiveZip.Compressionzip64Deprecated), new CommandLineArgument(COMPRESSION_LIBRARY_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.FileArchiveZip.CompressionlibraryShort, Strings.FileArchiveZip.CompressionlibraryLong, CompressionLibrary.Auto.ToString(), null, Enum.GetNames(typeof(CompressionLibrary))) ]); } @@ -218,7 +218,9 @@ namespace Duplicati.Library.Compression.ZipCompression public void Dispose() { - m_archive.Dispose(); + if (m_fallbackArchive == null) + m_archive.Dispose(); + m_fallbackArchive?.Dispose(); } public string[] ListFiles(string prefix) @@ -228,7 +230,38 @@ namespace Duplicati.Library.Compression.ZipCompression => m_archive.ListFilesWithSize(prefix); public Stream? OpenRead(string file) - => m_archive.OpenRead(file); + { + // If we have started the fallback archive, we should continue using it + if (m_fallbackArchive != null) + return m_fallbackArchive.OpenRead(file); + + try + { + return m_archive.OpenRead(file); + } + catch (Exception ex) + { + if (m_creationParams.Library == CompressionLibrary.Auto && m_archive is not SharpCompressZipArchive && m_creationParams.Stream.CanSeek) + { + Log.WriteWarningMessage(LOGTAG, "CompressionReadErrorFallback", ex, "Failed to open file with built-in ZIP archive, falling back to SharpCompress"); + + try + { + m_creationParams.Item1.Seek(0, SeekOrigin.Begin); + m_archive.Dispose(); + } + catch + { + + } + + m_fallbackArchive = new SharpCompressZipArchive(m_creationParams.Stream, ArchiveMode.Read, m_creationParams.Options); + return m_fallbackArchive.OpenRead(file); + } + + throw; + } + } public DateTime GetLastWriteTime(string file) => m_archive.GetLastWriteTime(file); diff --git a/Duplicati/Library/Compression/ZipCompression/IZipArchive.cs b/Duplicati/Library/Compression/ZipCompression/IZipArchive.cs index 6e7bfd86d..fbadb9992 100644 --- a/Duplicati/Library/Compression/ZipCompression/IZipArchive.cs +++ b/Duplicati/Library/Compression/ZipCompression/IZipArchive.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Compression/ZipCompression/ParsedZipOptions.cs b/Duplicati/Library/Compression/ZipCompression/ParsedZipOptions.cs index 2d1dad702..576b865d3 100644 --- a/Duplicati/Library/Compression/ZipCompression/ParsedZipOptions.cs +++ b/Duplicati/Library/Compression/ZipCompression/ParsedZipOptions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -29,9 +29,9 @@ namespace Duplicati.Library.Compression.ZipCompression; /// /// The compression level to use /// The compression type to use -/// Whether to use zip64 extensions +/// Flag indicating if unittest mode is enabled public sealed record ParsedZipOptions( CompressionLevel DeflateCompressionLevel, CompressionType CompressionType, - bool UseZip64 + bool UnittestMode ); diff --git a/Duplicati/Library/Compression/ZipCompression/SharpCompressZipArchive.cs b/Duplicati/Library/Compression/ZipCompression/SharpCompressZipArchive.cs index 8d6665f4c..d00301896 100644 --- a/Duplicati/Library/Compression/ZipCompression/SharpCompressZipArchive.cs +++ b/Duplicati/Library/Compression/ZipCompression/SharpCompressZipArchive.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -77,9 +77,22 @@ public class SharpCompressZipArchive : IZipArchive /// private long m_flushBufferSize = 0; - private readonly bool m_usingZip64; + /// + /// Flag indicating if we are using Zip64 extensions + /// + private const bool USE_ZIP64 = true; + /// + /// The default compression level to use + /// private readonly CompressionLevel m_defaultCompressionLevel; + /// + /// The compression algorithm + /// private readonly CompressionType m_compressionType; + /// + /// Flag indicating if we are in unittest mode + /// + private readonly bool m_unittestMode; /// /// Constructs a new Zip instance. @@ -95,17 +108,18 @@ public class SharpCompressZipArchive : IZipArchive m_stream = stream; m_mode = mode; - m_usingZip64 = options.UseZip64; m_defaultCompressionLevel = options.DeflateCompressionLevel; m_compressionType = options.CompressionType; m_mode = mode; + m_unittestMode = options.UnittestMode; if (mode == ArchiveMode.Write) { var compression = new ZipWriterOptions(CompressionType.Deflate) { CompressionType = m_compressionType, - DeflateCompressionLevel = m_defaultCompressionLevel + DeflateCompressionLevel = m_defaultCompressionLevel, + UseZip64 = USE_ZIP64 }; m_writer = WriterFactory.Open(m_stream, ArchiveType.Zip, compression); @@ -232,7 +246,18 @@ public class SharpCompressZipArchive : IZipArchive { var d = new Dictionary(Duplicati.Library.Utility.Utility.ClientFilenameStringComparer); foreach (var en in Archive.Entries) + { + if (d.ContainsKey(en.Key)) + Logging.Log.WriteMessage( + // Warning in unittest mode to trip tests, verbose otherwise + m_unittestMode ? Logging.LogMessageType.Warning : Logging.LogMessageType.Verbose, + LOGTAG, + "DuplicateArchiveEntry", + null, + $"Found duplicate entry in archive: {en.Key}"); + d[en.Key] = en; + } m_entryDict = d; } catch (Exception ex) @@ -309,7 +334,7 @@ public class SharpCompressZipArchive : IZipArchive throw new InvalidOperationException(Constants.CannotWriteWhileReading); m_flushBufferSize += Constants.CENTRAL_HEADER_ENTRY_SIZE + System.Text.Encoding.UTF8.GetByteCount(file); - if (m_usingZip64) + if (USE_ZIP64) m_flushBufferSize += Constants.CENTRAL_HEADER_ENTRY_SIZE_ZIP64_EXTRA; return ((ZipWriter)m_writer!).WriteToStream(file, new ZipWriterEntryOptions() diff --git a/Duplicati/Library/Crashlog/CrashlogHelper.cs b/Duplicati/Library/Crashlog/CrashlogHelper.cs new file mode 100644 index 000000000..4ae7a5585 --- /dev/null +++ b/Duplicati/Library/Crashlog/CrashlogHelper.cs @@ -0,0 +1,183 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Duplicati.Library.Crashlog; +#nullable enable + +/// +/// Utility class to wrap a method in a try-catch block and log any exceptions to a file +/// +public static class CrashlogHelper +{ + /// + /// The system temp path + /// + private static readonly string SystemTempPath +#if DEBUG + = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly()?.Location) ?? Path.GetTempPath(); +#else + = Path.GetTempPath(); +#endif + + /// + /// The default directory to write crashlogs to + /// + public static string DefaultLogDir { get; set; } = SystemTempPath; + + /// + /// Event to subscribe to for unobserved task exceptions + /// + public static event Action? OnUnobservedTaskException; + + /// + /// Handler for unobserved task exceptions + /// + /// Unused sender + /// The exception event args + private static void TaskSchedulerOnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) + { + try + { + OnUnobservedTaskException?.Invoke(e.Exception); + } + catch + { + } + } + + /// + /// Wraps a method in a try-catch block and logs any exceptions to a file + /// + /// The return type of the method + /// The method to wrap + /// The result of the method + public static T WrapWithCrashLog(Func method) + { + try + { + TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException; + return method(); + } + catch (Exception ex) + { + LogCrashException(ex); + throw; + } + finally + { + TaskScheduler.UnobservedTaskException -= TaskSchedulerOnUnobservedTaskException; + } + } + + /// + /// Wraps a method in a try-catch block and logs any exceptions to a file + /// + /// The return type of the method + /// The method to wrap + /// The result of the method + public static async Task WrapWithCrashLog(Func> method) + { + try + { + TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException; + return await method().ConfigureAwait(false); + } + catch (Exception ex) + { + LogCrashException(ex); + throw; + } + finally + { + TaskScheduler.UnobservedTaskException -= TaskSchedulerOnUnobservedTaskException; + } + } + + /// + /// The application name + /// + private static readonly string ApplicationName = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name ?? Guid.NewGuid().ToString()[..8]; + + /// + /// Gets the path to the crashlog file + /// + /// The path to the crashlog file + private static string GetLogFilePath() + { + var logdir = string.IsNullOrWhiteSpace(DefaultLogDir) + ? SystemTempPath + : DefaultLogDir; + + return Path.Combine(logdir, $"{ApplicationName}-crashlog.txt"); + } + + /// + /// Gets the last crashlog, if any + /// + /// The last crashlog, or null if none + public static string? GetLastCrashLog() + { + try + { + var path = GetLogFilePath(); + if (!File.Exists(path)) + return null; + return File.ReadAllText(path); + } + catch + { + return null; + } + } + + /// + /// Logs the exception to a file + /// + /// The exception to log + public static void LogCrashException(Exception ex) + { + try + { + Console.WriteLine("Crash! {0}{1}", Environment.NewLine, ex); + } + catch + { + } + + try + { + File.WriteAllText(GetLogFilePath(), ex.ToString()); + } + catch (Exception writeex) + { + try + { + Console.WriteLine("Failed to write crashlog: {0}", writeex); + } + catch + { + } + } + } +} diff --git a/Duplicati/Library/Crashlog/Duplicati.Library.Crashlog.csproj b/Duplicati/Library/Crashlog/Duplicati.Library.Crashlog.csproj new file mode 100644 index 000000000..6fa519e12 --- /dev/null +++ b/Duplicati/Library/Crashlog/Duplicati.Library.Crashlog.csproj @@ -0,0 +1,8 @@ + + + net8.0 + Crashlog implementation + Duplicati.Library.Crashlog + Copyright © 2025 Team Duplicati, MIT license + + diff --git a/Duplicati/Library/DynamicLoader/BackendLoader.cs b/Duplicati/Library/DynamicLoader/BackendLoader.cs index 1b9a0ac97..63093804d 100644 --- a/Duplicati/Library/DynamicLoader/BackendLoader.cs +++ b/Duplicati/Library/DynamicLoader/BackendLoader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/DynamicLoader/CompressionLoader.cs b/Duplicati/Library/DynamicLoader/CompressionLoader.cs index 7d14916bd..d03435b74 100644 --- a/Duplicati/Library/DynamicLoader/CompressionLoader.cs +++ b/Duplicati/Library/DynamicLoader/CompressionLoader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/DynamicLoader/Duplicati.Library.DynamicLoader.csproj b/Duplicati/Library/DynamicLoader/Duplicati.Library.DynamicLoader.csproj index 838101412..0ed1b1e42 100644 --- a/Duplicati/Library/DynamicLoader/Duplicati.Library.DynamicLoader.csproj +++ b/Duplicati/Library/DynamicLoader/Duplicati.Library.DynamicLoader.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -16,6 +16,8 @@ + + diff --git a/Duplicati/Library/DynamicLoader/DynamicLoader.cs b/Duplicati/Library/DynamicLoader/DynamicLoader.cs index 56b9af9b8..fc2547ebb 100644 --- a/Duplicati/Library/DynamicLoader/DynamicLoader.cs +++ b/Duplicati/Library/DynamicLoader/DynamicLoader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs index 4c5cf7af2..92b81eadf 100644 --- a/Duplicati/Library/DynamicLoader/EncryptionLoader.cs +++ b/Duplicati/Library/DynamicLoader/EncryptionLoader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/DynamicLoader/GenericLoader.cs b/Duplicati/Library/DynamicLoader/GenericLoader.cs index f4fbc95c9..ce85554e0 100644 --- a/Duplicati/Library/DynamicLoader/GenericLoader.cs +++ b/Duplicati/Library/DynamicLoader/GenericLoader.cs @@ -1,24 +1,25 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using Duplicati.Library.Interface; using Duplicati.Library.Modules.Builtin; @@ -31,7 +32,7 @@ namespace Duplicati.Library.DynamicLoader public static class GenericLoader { /// - /// Implementation overrides specific to encryption + /// Implementation overrides specific to generic module use /// private class GenericLoaderSub : DynamicLoader { @@ -41,35 +42,36 @@ namespace Duplicati.Library.DynamicLoader /// The item to load the key for /// The file extension used by the module protected override string GetInterfaceKey(IGenericModule item) - { - return item.Key; - } + => item.Key; /// - /// Returns the subfolders searched for encryption modules + /// Returns the subfolders searched for generic modules /// protected override string[] Subfolders - { - get { return new string[] { "modules" }; } - } + => ["modules"]; /// /// The built-in modules /// - protected override IEnumerable BuiltInModules => GenericModules.BuiltInGenericModules; + protected override IEnumerable BuiltInModules + => GenericModules.BuiltInGenericModules; } #region Public static API + /// + /// The loader instance used to query the modules + /// + private static readonly Lazy _loader = new(() => new GenericLoaderSub()); /// /// Gets a list of loaded settings controls, the instances can be used to extract interface information, not used to interact with the module. /// - public static IGenericModule[] Modules { get { return new GenericLoaderSub().Interfaces; } } + public static IGenericModule[] Modules => _loader.Value.Interfaces; /// /// Gets a list of keys supported /// - public static string[] Keys { get { return new GenericLoaderSub().Keys; } } + public static string[] Keys => _loader.Value.Keys; #endregion diff --git a/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs b/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs new file mode 100644 index 000000000..eeac6378c --- /dev/null +++ b/Duplicati/Library/DynamicLoader/SecretProviderLoader.cs @@ -0,0 +1,157 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using Duplicati.Library.Interface; +using Duplicati.Library.SecretProvider; + +namespace Duplicati.Library.DynamicLoader; + +public class SecretProviderLoader +{ + /// + /// Loader for secret providers + /// + private class SecretProviderLoaderSub : DynamicLoader + { + /// + /// Gets the key for the secret provider + /// + /// The item to get the key for + /// The key + protected override string GetInterfaceKey(ISecretProvider item) + => item.Key; + + /// + /// Returns the subfolders searched for secret providers + /// + protected override string[] Subfolders => ["secretproviders"]; + + /// + /// The built-in modules + /// + protected override IEnumerable BuiltInModules => SecretProviderModules.Modules; + + /// + /// Gets the supported commands for a certain key + /// + /// The key to find commands for + /// The supported commands or null if the key was not found + public IReadOnlyList GetSupportedCommands(string key) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key)); + + LoadInterfaces(); + + lock (m_lock) + { + if (m_interfaces.TryGetValue(key, out var b) && b != null) + return GetSupportedCommandsCached(b).ToList(); + else + return []; + } + } + } + + /// + /// The loader instance + /// + private static readonly Lazy _loader = new(() => new SecretProviderLoaderSub()); + /// + /// The secret provider modules + /// + public static ISecretProvider[] Modules => _loader.Value.Interfaces; + /// + /// The keys for the secret providers + /// + public static string[] Keys { get { return _loader.Value.Keys; } } + + /// + /// Gets the supported commands for a certain key + /// + /// The key to find commands for + /// The supported commands or null if the key was not found + public static IReadOnlyList GetSupportedCommands(string key) + => _loader.Value.GetSupportedCommands(key); + + /// + /// Returns the metadata for a provider + /// + /// The key to get metadata for + /// The key, description, and supported commands + public static (string Key, string DisplayName, string Description, IReadOnlyList SupportedCommands) GetProviderMetadata(string key) + { + var provider = _loader.Value.Interfaces.FirstOrDefault(p => p.Key == key); + if (provider == null) + throw new ArgumentException($"No secret provider found for key {key}"); + + return (provider.Key, provider.DisplayName, provider.Description, provider.SupportedCommands.AsReadOnly()); + } + + /// + /// Creates an instance of a secret provider + /// + /// The configuration string + /// The secret provider instance + public static ISecretProvider CreateInstance(string config) + { + if (string.IsNullOrEmpty(config)) + throw new ArgumentNullException(nameof(config)); + + // Translate from environment variables + string? envName = null; + if (config.StartsWith("$")) + { + envName = config[1..]; + if (envName.StartsWith("{") && envName.EndsWith("}")) + envName = envName[1..^1]; + } + else if (config.StartsWith("%") && config.EndsWith("%")) + { + envName = config[1..^1]; + } + + if (envName != null) + { + var result = Environment.GetEnvironmentVariable(envName.ToUpperInvariant()); + if (string.IsNullOrEmpty(result)) + throw new ArgumentException($"The environment variable {envName} was not found"); + + config = result; + } + + var uri = new Uri(config); + var key = uri.Scheme; + + var providerType = Modules.FirstOrDefault(p => p.Key == key) + ?? throw new ArgumentException($"No secret provider found for key {key}"); + + if (Activator.CreateInstance(providerType.GetType()) is not ISecretProvider provider) + throw new InvalidOperationException($"Failed to create an instance of {providerType}"); + + return provider; + } +} diff --git a/Duplicati/Library/DynamicLoader/SourceProviderLoader.cs b/Duplicati/Library/DynamicLoader/SourceProviderLoader.cs new file mode 100644 index 000000000..979721398 --- /dev/null +++ b/Duplicati/Library/DynamicLoader/SourceProviderLoader.cs @@ -0,0 +1,219 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using Duplicati.Library.Interface; +using System.Linq; +using System.Runtime.ExceptionServices; +using Duplicati.Library.SourceProviders; +using Duplicati.Library.SourceProvider; +using System.Threading.Tasks; +using System.Threading; + +namespace Duplicati.Library.DynamicLoader +{ + /// + /// Loads all SourceProviders dynamically and exposes a list of those + /// + public class SourceProviderLoader + { + /// + /// Implementation overrides specific to SourceProviders + /// + private class SourceProviderLoaderSub : DynamicLoader + { + /// + /// Returns the protocol key + /// + /// The item to load the key for + /// The protocol key + protected override string GetInterfaceKey(ISourceProviderModule item) + { + return item.Key; + } + + /// + /// Returns the subfolders searched for SourceProviders + /// + protected override string[] Subfolders => ["SourceProviders"]; + + /// + /// The built-in modules + /// + protected override IEnumerable BuiltInModules => SourceProviderModules.BuiltInSourceProviderModules; + + /// + /// Instanciates a specific SourceProvider, given the url and options + /// + /// The url to create the instance for + /// The options to pass to the instance constructor + /// The instanciated SourceProvider or null if the url is not supported + public ISourceProviderModule GetSourceProvider(string url, string mountPoint, Dictionary options) + { + var uri = new Utility.Uri(url); + + LoadInterfaces(); + + var newOpts = new Dictionary(options); + foreach (var key in uri.QueryParameters.AllKeys) + newOpts[key] = uri.QueryParameters[key]; + + lock (m_lock) + { + try + { + if (m_interfaces.ContainsKey(uri.Scheme)) + return (ISourceProviderModule)Activator.CreateInstance(m_interfaces[uri.Scheme].GetType(), url, mountPoint, newOpts); + } + catch (System.Reflection.TargetInvocationException tex) + { + if (tex.InnerException != null) + { + // Unwrap exceptions for nicer display. The ExceptionDispatchInfo class allows us to + // rethrow an exception without changing the stack trace. + ExceptionDispatchInfo.Capture(tex.InnerException).Throw(); + } + + throw; + } + + return null; + } + } + + /// + /// Gets the supported commands for a certain url + /// + /// The url to find commands for + /// The supported commands or null if the url scheme was not supported + public IReadOnlyList GetSupportedCommands(string url) + { + var uri = new Utility.Uri(url); + + LoadInterfaces(); + + // TODO: The loading logic is replicated in the "GetSourceProvider" method, should be refactored + lock (m_lock) + { + ISourceProviderModule b; + if (m_interfaces.TryGetValue(uri.Scheme, out b) && b != null) + return GetSupportedCommandsCached(b).ToList(); + else if (uri.Scheme.EndsWith("s", StringComparison.Ordinal)) + { + var tmpscheme = uri.Scheme.Substring(0, uri.Scheme.Length - 1); + if (m_interfaces.TryGetValue(tmpscheme, out b) && b != null) + return GetSupportedCommandsCached(b).ToList(); + } + + return null; + } + } + } + + /// + /// The static instance used to access SourceProvider information + /// + private static readonly SourceProviderLoaderSub _SourceProviderLoader = new SourceProviderLoaderSub(); + + #region Public static API + + /// + /// Gets a list of loaded SourceProviders, the instances can be used to extract interface information, not used to interact with the SourceProvider. + /// + public static ISourceProviderModule[] SourceProviders { get { return _SourceProviderLoader.Interfaces; } } + + /// + /// Gets a list of keys supported + /// + public static string[] Keys { get { return _SourceProviderLoader.Keys; } } + + /// + /// Gets the supported commands for a given SourceProvider + /// + /// The url to find the commands for + /// The supported commands or null if the url is not supported + public static IReadOnlyList GetSupportedCommands(string url) + { + if (string.IsNullOrEmpty(url)) + throw new ArgumentNullException(nameof(url)); + + var commands = _SourceProviderLoader.GetSupportedCommands(url); + if (commands != null) + return commands; + + var backend = BackendLoader.GetBackend(url, []); + if (backend is IFolderEnabledBackend folderBackend) + commands = folderBackend.SupportedCommands.AsReadOnly(); + backend?.Dispose(); + + return commands; + } + + /// + /// Instanciates a specific SourceProvider, given the url and options + /// + /// The url to create the instance for + /// The mount point to use + /// The options to pass to the instance constructor + /// The cancellation token + /// The instanciated SourceProvider or null if the url is not supported + public static async Task GetSourceProvider(string url, string mountPoint, Dictionary options, CancellationToken cancellationToken) + { + // Source providers are preferred over backends + var provider = _SourceProviderLoader.GetSourceProvider(url, mountPoint, options); + if (provider == null) + { + // See if there is a backend that can also be a source + var backend = BackendLoader.GetBackend(url, options); + if (backend is IFolderEnabledBackend folderBackend) + provider = new BackendSourceProvider(folderBackend, mountPoint); + else + backend?.Dispose(); + } + + if (provider == null) + return null; + + try + { + await provider.Initialize(cancellationToken).ConfigureAwait(false); + return provider; + } + catch + { + provider.Dispose(); + throw; + } + + } + #endregion + + /// + /// Adds a SourceProvider to the loader + /// + /// The SourceProvider to add + public static void AddSourceProvider(ISourceProviderModule SourceProvider) + { + _SourceProviderLoader.AddModule(SourceProvider); + } + } +} diff --git a/Duplicati/Library/DynamicLoader/Strings.cs b/Duplicati/Library/DynamicLoader/Strings.cs index 24dcdc496..d624c5fd3 100644 --- a/Duplicati/Library/DynamicLoader/Strings.cs +++ b/Duplicati/Library/DynamicLoader/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/DynamicLoader/WebLoader.cs b/Duplicati/Library/DynamicLoader/WebLoader.cs index 05dd639bd..8d1db7266 100644 --- a/Duplicati/Library/DynamicLoader/WebLoader.cs +++ b/Duplicati/Library/DynamicLoader/WebLoader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; diff --git a/Duplicati/Library/Encryption/AESEncryption.cs b/Duplicati/Library/Encryption/AESEncryption.cs index 354e71f4e..8d5c12bf2 100644 --- a/Duplicati/Library/Encryption/AESEncryption.cs +++ b/Duplicati/Library/Encryption/AESEncryption.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Encryption/AESStringEncryption.cs b/Duplicati/Library/Encryption/AESStringEncryption.cs index 0a8690282..8fc2c338e 100644 --- a/Duplicati/Library/Encryption/AESStringEncryption.cs +++ b/Duplicati/Library/Encryption/AESStringEncryption.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/CryptoStreamWrapper.cs b/Duplicati/Library/Encryption/CryptoStreamWrapper.cs index 26a104cfb..5b0d0ed13 100644 --- a/Duplicati/Library/Encryption/CryptoStreamWrapper.cs +++ b/Duplicati/Library/Encryption/CryptoStreamWrapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj b/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj index aa91f0762..edd06f304 100644 --- a/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj +++ b/Duplicati/Library/Encryption/Duplicati.Library.Encryption.csproj @@ -1,10 +1,10 @@ - + net8.0 Library Duplicati.Library.Encryption - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Encryption/EncryptedFieldHelper.cs b/Duplicati/Library/Encryption/EncryptedFieldHelper.cs index 712381545..ebd937a48 100644 --- a/Duplicati/Library/Encryption/EncryptedFieldHelper.cs +++ b/Duplicati/Library/Encryption/EncryptedFieldHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -76,32 +76,6 @@ public static class EncryptedFieldHelper public static bool IsKeyBlacklisted(string key) => DeviceIDHelper.EMPTY_DEVICE_ID_HASHES.Contains(key); - - /// - /// The key based on the device ID - /// - private static readonly KeyInstance? DeviceIdKey = KeyInstance.CreateKeyIfValid(DeviceIDHelper.HasTrustedDeviceID ? DeviceIDHelper.GetDeviceIDHash() : null); - - /// - /// The default key to use for encryption - /// - private static readonly KeyInstance? SuppliedKey = KeyInstance.CreateKeyIfValid(Environment.GetEnvironmentVariable(ENVIROMENT_VARIABLE_NAME)); - - /// - /// The default key to use for encryption - /// - private static readonly KeyInstance? DefaultKey = SuppliedKey ?? DeviceIdKey; - - /// - /// Returns a value indicating if the default key is blacklisted and cannot be used - /// - public static bool IsDefaultKeyBlacklisted => DefaultKey?.IsBlacklisted ?? false; - - /// - /// Returns a value indicating if the default key is valid - /// - public static bool HasValidDefaultKey => DefaultKey != null; - /// /// Prefix used to identify an encrypted field /// @@ -120,20 +94,6 @@ public static class EncryptedFieldHelper public static bool IsEncryptedString(string value) => !string.IsNullOrWhiteSpace(value) && value.StartsWith(HEADER_PREFIX); - /// - /// Decrypts a value from the database, if it is not encrypted, it will be returned as is. - /// - /// If the value is encrypted, it will be decrypted using the key obtained from ActiveKey. - /// - /// The check for encryption is done by checking the prefix of the string. - /// An additional check is done by hashing the content and comparing it to the hash - /// - /// data from the field - /// Unencrypted data of the field - [return: NotNullIfNotNull("value")] - public static string? Decrypt(string? value) - => Decrypt(value, DefaultKey); - /// /// Decrypts a value from the database, if it is not encrypted, it will be returned as is. /// @@ -188,14 +148,6 @@ public static class EncryptedFieldHelper } - /// - /// Encrypts a value to be stored in the database. - /// - /// - /// The encrypted string - public static string Encrypt(string value) - => Encrypt(value, DefaultKey); - /// /// Encrypts a value to be stored in the database. /// diff --git a/Duplicati/Library/Encryption/EncryptionBase.cs b/Duplicati/Library/Encryption/EncryptionBase.cs index 02a8332f0..c5538b0ee 100644 --- a/Duplicati/Library/Encryption/EncryptionBase.cs +++ b/Duplicati/Library/Encryption/EncryptionBase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/EncryptionModules.cs b/Duplicati/Library/Encryption/EncryptionModules.cs index a04327939..5e7678cc9 100644 --- a/Duplicati/Library/Encryption/EncryptionModules.cs +++ b/Duplicati/Library/Encryption/EncryptionModules.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/GPGEncryption.cs b/Duplicati/Library/Encryption/GPGEncryption.cs index c343168e5..4340fb799 100644 --- a/Duplicati/Library/Encryption/GPGEncryption.cs +++ b/Duplicati/Library/Encryption/GPGEncryption.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -264,7 +264,7 @@ namespace Duplicati.Library.Encryption private static string GetGpgProgramPath() { var gpgpath = GPGLocator.GetGpgExecutablePath(); - Log.WriteInformationMessage("GetGpgProgramPath", "gpg", gpgpath); + Log.WriteVerboseMessage("GetGpgProgramPath", "gpg", gpgpath); return gpgpath; } } diff --git a/Duplicati/Library/Encryption/GPGLocator.cs b/Duplicati/Library/Encryption/GPGLocator.cs index b44f5e67a..d004fd0ff 100644 --- a/Duplicati/Library/Encryption/GPGLocator.cs +++ b/Duplicati/Library/Encryption/GPGLocator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/GPGStreamWrapper.cs b/Duplicati/Library/Encryption/GPGStreamWrapper.cs index 471360ecb..27d6ed54a 100644 --- a/Duplicati/Library/Encryption/GPGStreamWrapper.cs +++ b/Duplicati/Library/Encryption/GPGStreamWrapper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Encryption/Strings.cs b/Duplicati/Library/Encryption/Strings.cs index 47e2e759a..e5677f4af 100644 --- a/Duplicati/Library/Encryption/Strings.cs +++ b/Duplicati/Library/Encryption/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/BackendExtensions.cs b/Duplicati/Library/Interface/BackendExtensions.cs index f1600c0c2..cf8c78f0b 100644 --- a/Duplicati/Library/Interface/BackendExtensions.cs +++ b/Duplicati/Library/Interface/BackendExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,10 +18,8 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; + +using System.Threading; using System.Threading.Tasks; namespace Duplicati.Library.Interface @@ -36,12 +34,15 @@ namespace Duplicati.Library.Interface /// As long as the iteration can either complete or find at least one file without throwing, the test is successful /// /// Backend to test - public static void TestList(this IBackend backend) + /// Cancellation token + public static async Task TestListAsync(this IBackend backend, CancellationToken cancelToken) { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken); // If we can iterate successfully, even if it's empty, then the backend test is successful - foreach (IFileEntry file in backend.List()) + await foreach (IFileEntry file in backend.ListAsync(cts.Token).ConfigureAwait(false)) { - break; + cts.Cancel(); + return; } } } diff --git a/Duplicati/Library/Interface/CommandLineArgument.cs b/Duplicati/Library/Interface/CommandLineArgument.cs index 542e7e030..233d36363 100644 --- a/Duplicati/Library/Interface/CommandLineArgument.cs +++ b/Duplicati/Library/Interface/CommandLineArgument.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -188,6 +188,8 @@ namespace Duplicati.Library.Interface : this(name) { m_type = type; + if (type == ArgumentType.Boolean) + m_defaultValue = "false"; } /// @@ -216,6 +218,8 @@ namespace Duplicati.Library.Interface : this(name, type, shortDescription, longDescription) { m_defaultValue = defaultValue; + if (type == ArgumentType.Boolean && string.IsNullOrEmpty(m_defaultValue)) + m_defaultValue = "false"; } /// diff --git a/Duplicati/Library/Interface/CustomExceptions.cs b/Duplicati/Library/Interface/CustomExceptions.cs index af1ac0025..27068d494 100644 --- a/Duplicati/Library/Interface/CustomExceptions.cs +++ b/Duplicati/Library/Interface/CustomExceptions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,6 +20,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Net.Http.Headers; using Duplicati.Library.Localization.Short; namespace Duplicati.Library.Interface @@ -225,4 +226,36 @@ namespace Duplicati.Library.Interface : base(Strings.Common.SettingsKeyMissingExceptionError, "SettingsKeyMissing") { } } + + /// + /// An exception for carrying the Retry-After header value on 429 Exceptions + /// + [Serializable] + public class TooManyRequestException(RetryConditionHeaderValue retryAfter) : Exception + { + public readonly RetryConditionHeaderValue RetryAfter = retryAfter; + } + + /// + /// An exception indicating that the database state is inconsistent + /// + [Serializable] + public class DatabaseInconsistencyException : UserInformationException + { + public DatabaseInconsistencyException(string message) + : base(message, "DatabaseInconsistency") + { } + + public DatabaseInconsistencyException(string message, string helpId) + : base(message, helpId) + { } + + public DatabaseInconsistencyException(string message, Exception innerException) + : base(message, "DatabaseInconsistency", innerException) + { } + + public DatabaseInconsistencyException(string message, string helpId, Exception innerException) + : base(message, helpId, innerException) + { } + } } diff --git a/Duplicati/Library/Interface/Duplicati.Library.Interface.csproj b/Duplicati/Library/Interface/Duplicati.Library.Interface.csproj index 3d433a418..9ea88b7cd 100644 --- a/Duplicati/Library/Interface/Duplicati.Library.Interface.csproj +++ b/Duplicati/Library/Interface/Duplicati.Library.Interface.csproj @@ -4,7 +4,7 @@ net8.0 Library Duplicati.Library.Interface - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Interface/IBackend.cs b/Duplicati/Library/Interface/IBackend.cs index 353c64bcb..eee508a3a 100644 --- a/Duplicati/Library/Interface/IBackend.cs +++ b/Duplicati/Library/Interface/IBackend.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -51,8 +51,9 @@ namespace Duplicati.Library.Interface /// /// Enumerates a list of files found on the remote location /// + /// Token to cancel the operation. /// The list of files - IEnumerable List(); + IAsyncEnumerable ListAsync(CancellationToken cancellationToken); /// /// Puts the content of the file to the url passed diff --git a/Duplicati/Library/Interface/ICommandLineArgument.cs b/Duplicati/Library/Interface/ICommandLineArgument.cs index 40e82bdbf..e9be72ae5 100644 --- a/Duplicati/Library/Interface/ICommandLineArgument.cs +++ b/Duplicati/Library/Interface/ICommandLineArgument.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/ICompression.cs b/Duplicati/Library/Interface/ICompression.cs index 52940f1de..2d84e256b 100644 --- a/Duplicati/Library/Interface/ICompression.cs +++ b/Duplicati/Library/Interface/ICompression.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -61,20 +61,6 @@ namespace Duplicati.Library.Interface Noncompressible } - /// - /// An interface for passing additional hints to the compressor - /// about the expected contents of the volume - /// - public interface ICompressionHinting : ICompression - { - /// - /// Gets or sets a value indicating whether this - /// instance is in low overhead mode. - /// - /// true if low overhead mode; otherwise, false. - bool LowOverheadMode { get; set; } - } - public interface IArchiveReader { /// diff --git a/Duplicati/Library/Interface/IConnectionModule.cs b/Duplicati/Library/Interface/IConnectionModule.cs index bbd69adf2..44306c999 100644 --- a/Duplicati/Library/Interface/IConnectionModule.cs +++ b/Duplicati/Library/Interface/IConnectionModule.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Interface/IDestinationProvider.cs b/Duplicati/Library/Interface/IDestinationProvider.cs new file mode 100644 index 000000000..20467a0b5 --- /dev/null +++ b/Duplicati/Library/Interface/IDestinationProvider.cs @@ -0,0 +1,43 @@ +#nullable enable + +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Interface +{ + /// + /// Implements a destination where data can be restored to + /// + public interface IDestinationProvider : IDynamicModule + { + /// + /// Gets the module key + /// + string Key { get; } + /// + /// Gets the display name of the module + /// + string DisplayName { get; } + + /// + /// Checks if a restore is needed + /// + /// The path to restore to + /// The hash of the data to restore + /// The hash of the metadata to restore + /// The cancellation token + /// True if a restore is needed, false otherwise + Task IsRestoreNeededAsync(string path, string dataHash, string? metadataHash, CancellationToken cancel); + + /// + /// Restores a file to the given path + /// + /// The path to restore to + /// The data stream to restore + /// The metadata stream to restore + /// The cancellation token + /// A task that completes when the restore is done + Task WriteAsync(string path, Stream data, Stream? metadata, CancellationToken cancel); + } +} \ No newline at end of file diff --git a/Duplicati/Library/Interface/IDynamicModule.cs b/Duplicati/Library/Interface/IDynamicModule.cs index c38cf4d4d..7468b91d8 100644 --- a/Duplicati/Library/Interface/IDynamicModule.cs +++ b/Duplicati/Library/Interface/IDynamicModule.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/IEncryption.cs b/Duplicati/Library/Interface/IEncryption.cs index 422900082..367bfd7a7 100644 --- a/Duplicati/Library/Interface/IEncryption.cs +++ b/Duplicati/Library/Interface/IEncryption.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Interface/IFileEntry.cs b/Duplicati/Library/Interface/IFileEntry.cs index 5bcbda6ba..2e5fb1f01 100644 --- a/Duplicati/Library/Interface/IFileEntry.cs +++ b/Duplicati/Library/Interface/IFileEntry.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; @@ -41,6 +41,10 @@ namespace Duplicati.Library.Interface /// DateTime LastModification { get; } /// + /// The time the file or folder was created + /// + DateTime Created { get; } + /// /// The name of the file or folder /// string Name { get; } diff --git a/Duplicati/Library/Interface/IFolderEnabledBackend.cs b/Duplicati/Library/Interface/IFolderEnabledBackend.cs new file mode 100644 index 000000000..6f9374aed --- /dev/null +++ b/Duplicati/Library/Interface/IFolderEnabledBackend.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Interface; + +/// +/// An interface a backend may implement if it supports streaming operations. +/// Backends that implement this interface can be throttled and correctly shows +/// the progressbar when transferring data. +/// +public interface IFolderEnabledBackend : IBackend +{ + /// + /// Enumerates a list of files and folders found on the remote location + /// + /// The path to list, empty or null for the root folder + /// The cancellation token + /// The list of files, where paths are scoped to the backend. + /// + /// The paths returned should be only the filenames or folder names, not the full path. + /// Folders should end with the operating system's directory separator. + /// + IAsyncEnumerable ListAsync(string? path, CancellationToken cancellationToken); + + /// + /// Gets a single file or folder entry + /// + /// The path to get + /// The cancellation token + /// The file or folder entry + /// + /// The paths returned should be only the filenames or folder names, not the full path. + /// Folders should end with the operating system's directory separator. + /// + Task GetEntryAsync(string path, CancellationToken cancellationToken); +} + diff --git a/Duplicati/Library/Interface/IGenericCallbackModule.cs b/Duplicati/Library/Interface/IGenericCallbackModule.cs index 972d227e8..9453ac8e4 100644 --- a/Duplicati/Library/Interface/IGenericCallbackModule.cs +++ b/Duplicati/Library/Interface/IGenericCallbackModule.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -33,12 +33,12 @@ namespace Duplicati.Library.Interface /// /// The full name of the operation void OnStart(string operationname, ref string remoteurl, ref string[] localpath); - + /// /// Called when the operation finishes /// /// The result object, if this derives from an exception, the operation failed /// The exception that stopped the backup, or null - void OnFinish(object result, Exception exception); + void OnFinish(IBasicResults result, Exception exception); } } diff --git a/Duplicati/Library/Interface/IGenericModule.cs b/Duplicati/Library/Interface/IGenericModule.cs index c29444d4d..f18e463cd 100644 --- a/Duplicati/Library/Interface/IGenericModule.cs +++ b/Duplicati/Library/Interface/IGenericModule.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; diff --git a/Duplicati/Library/Interface/IGenericServerModule.cs b/Duplicati/Library/Interface/IGenericServerModule.cs index 1b52129f7..2d062d95b 100644 --- a/Duplicati/Library/Interface/IGenericServerModule.cs +++ b/Duplicati/Library/Interface/IGenericServerModule.cs @@ -1,29 +1,30 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; namespace Duplicati.Library.Interface { + // TODO: This interface has no implementers and should be removed + /// /// A module that displays a set of options in the general setup area /// diff --git a/Duplicati/Library/Interface/IGenericSourceModule.cs b/Duplicati/Library/Interface/IGenericSourceModule.cs index aff852596..9c2533dc7 100644 --- a/Duplicati/Library/Interface/IGenericSourceModule.cs +++ b/Duplicati/Library/Interface/IGenericSourceModule.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/IQuotaEnabledBackend.cs b/Duplicati/Library/Interface/IQuotaEnabledBackend.cs index 6a6cc73ab..a2aa06d59 100644 --- a/Duplicati/Library/Interface/IQuotaEnabledBackend.cs +++ b/Duplicati/Library/Interface/IQuotaEnabledBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,6 +22,8 @@ using System.Threading; using System.Threading.Tasks; +#nullable enable + namespace Duplicati.Library.Interface { /// diff --git a/Duplicati/Library/Interface/IQuotaInfo.cs b/Duplicati/Library/Interface/IQuotaInfo.cs index b111a8280..b4271ffb6 100644 --- a/Duplicati/Library/Interface/IQuotaInfo.cs +++ b/Duplicati/Library/Interface/IQuotaInfo.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/IRenameEnabledBackend.cs b/Duplicati/Library/Interface/IRenameEnabledBackend.cs index b30b953dc..4562b31f0 100644 --- a/Duplicati/Library/Interface/IRenameEnabledBackend.cs +++ b/Duplicati/Library/Interface/IRenameEnabledBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/ISecretProvider.cs b/Duplicati/Library/Interface/ISecretProvider.cs new file mode 100644 index 000000000..527cb271f --- /dev/null +++ b/Duplicati/Library/Interface/ISecretProvider.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Interface; + +/// +/// Interface for secret providers +/// +public interface ISecretProvider : IDynamicModule +{ + // abstract static Task CreateAsync(string config, CancellationToken cancellationToken) + // where T : ISecretProvider; + + /// + /// Initializes the secret provider + /// + /// The configuration string + /// The cancellation token + /// An awaitable task + Task InitializeAsync(Uri config, CancellationToken cancellationToken); + /// + /// Resolves the secrets for the given keys + /// + /// The keys to resolve + /// The cancellation token + /// A dictionary of resolved secrets. The dictionary has all requested keys or the call fails. + Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken); + /// + /// The key for the secret provider + /// + string Key { get; } + /// + /// The display name of the secret provider + /// + string DisplayName { get; } + /// + /// The description of the secret provider + /// + string Description { get; } +} diff --git a/Duplicati/Library/Interface/ISourceProvider.cs b/Duplicati/Library/Interface/ISourceProvider.cs new file mode 100644 index 000000000..6ac0b9ea0 --- /dev/null +++ b/Duplicati/Library/Interface/ISourceProvider.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Interface; + +/// +/// Implements a data source that can be backed up +/// +public interface ISourceProvider : IDisposable +{ + /// + /// Returns the path where this provider is logically mounted + /// + string MountedPath { get; } + + /// + /// Initializes the provider, if needed + /// + /// The cancellation token + /// An awaitable task + Task Initialize(CancellationToken cancellationToken); + + /// + /// Gets the root entries + /// + /// The cancellation token + /// An enumerable of file entries + IAsyncEnumerable Enumerate(CancellationToken cancellationToken); + + /// + /// Gets a specific entry + /// + /// The path to use + /// True if the path is a folder + /// The cancellation token + /// The file entry + Task GetEntry(string path, bool isFolder, CancellationToken cancellationToken); +} diff --git a/Duplicati/Library/Interface/ISourceProviderEntry.cs b/Duplicati/Library/Interface/ISourceProviderEntry.cs new file mode 100644 index 000000000..c93d945e2 --- /dev/null +++ b/Duplicati/Library/Interface/ISourceProviderEntry.cs @@ -0,0 +1,126 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace Duplicati.Library.Interface; + +/// +/// Interface for an instance of a file or folder +/// +public interface ISourceProviderEntry +{ + /// + /// True if the entry represents a folder, false otherwise + /// + bool IsFolder { get; } + /// + /// True if the entry is a meta entry, false otherwise + /// + bool IsMetaEntry { get; } + /// + /// True if the entry is the root entry, false otherwise + /// + bool IsRootEntry { get; } + /// + /// The creation time of the file or folder + /// + DateTime CreatedUtc { get; } + /// + /// The time the file or folder was last modified + /// + DateTime LastModificationUtc { get; } + /// + /// The name of the file or folder + /// + string Path { get; } + /// + /// The size of the file or folder + /// + long Size { get; } + /// + /// True if the file is a symlink, false otherwise + /// + bool IsSymlink { get; } + /// + /// The target of a symlink, if the file is a symlink + /// + string? SymlinkTarget { get; } + /// + /// The entry attributes + /// + FileAttributes Attributes { get; } + /// + /// The minor metadata of the file, should be less than 1KB + /// + Dictionary MinorMetadata { get; } + /// + /// True if the file is a block device, false otherwise + /// + bool IsBlockDevice { get; } + /// + /// True if the file is a character device, false otherwise + /// + bool IsCharacterDevice { get; } + /// + /// True if the files is an alternate stream, false otherwise + /// + bool IsAlternateStream { get; } + /// + /// The hardlink target id, if the file is a hardlink + /// + string? HardlinkTargetId { get; } + + /// + /// Opens the file for reading + /// + /// A token that can be used to cancel the operation + /// A stream that can be read + Task OpenRead(CancellationToken cancellationToken); + + /// + /// Opens the metadata for reading + /// + /// A token that can be used to cancel the operation + /// A stream that can be read + Task OpenMetadataRead(CancellationToken cancellationToken); + + /// + /// Checks if the specified file exists + /// + /// The file to check for existence + /// A token that can be used to cancel the operation + /// true if the file exists, false otherwise + Task FileExists(string filename, CancellationToken cancellationToken); + + /// + /// Returns the contents of the object, if it is a folder + /// + /// A token that can be used to cancel the operation + /// The contents of the folder + IAsyncEnumerable Enumerate(CancellationToken cancellationToken); +} diff --git a/Duplicati/Library/Interface/ISourceProviderModule.cs b/Duplicati/Library/Interface/ISourceProviderModule.cs new file mode 100644 index 000000000..469db1fb0 --- /dev/null +++ b/Duplicati/Library/Interface/ISourceProviderModule.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +namespace Duplicati.Library.Interface; + +/// +/// Implements a data source that can be backed up +/// +public interface ISourceProviderModule : ISourceProvider, IDynamicModule +{ + /// + /// Gets the module key + /// + string Key { get; } + /// + /// Gets the display name of the module + /// + string DisplayName { get; } +} diff --git a/Duplicati/Library/Interface/IStreamingBackend.cs b/Duplicati/Library/Interface/IStreamingBackend.cs index 47ceb5912..0e518b770 100644 --- a/Duplicati/Library/Interface/IStreamingBackend.cs +++ b/Duplicati/Library/Interface/IStreamingBackend.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Threading; diff --git a/Duplicati/Library/Interface/IWebModule.cs b/Duplicati/Library/Interface/IWebModule.cs index 2af3f4c25..a0a316fc2 100644 --- a/Duplicati/Library/Interface/IWebModule.cs +++ b/Duplicati/Library/Interface/IWebModule.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; namespace Duplicati.Library.Interface diff --git a/Duplicati/Library/Interface/QuotaInfo.cs b/Duplicati/Library/Interface/QuotaInfo.cs index ab26f92dd..6ff8bd091 100644 --- a/Duplicati/Library/Interface/QuotaInfo.cs +++ b/Duplicati/Library/Interface/QuotaInfo.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,11 +18,6 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Duplicati.Library.Interface { diff --git a/Duplicati/Library/Interface/ResultInterfaces.cs b/Duplicati/Library/Interface/ResultInterfaces.cs index 86fc3fecf..6f57abf67 100644 --- a/Duplicati/Library/Interface/ResultInterfaces.cs +++ b/Duplicati/Library/Interface/ResultInterfaces.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -38,7 +38,7 @@ namespace Duplicati.Library.Interface DateTime BeginTime { get; } DateTime EndTime { get; } TimeSpan Duration { get; } - + IEnumerable Errors { get; } IEnumerable Warnings { get; } IEnumerable Messages { get; } @@ -46,6 +46,11 @@ namespace Duplicati.Library.Interface bool Interrupted { get; } } + public interface IResultsWithVacuum + { + IVacuumResults VacuumResults { get; set; } + } + public interface IBackendStatstics { long RemoteCalls { get; } @@ -64,6 +69,7 @@ namespace Duplicati.Library.Interface long UnknownFileCount { get; set; } long KnownFileCount { get; set; } long KnownFileSize { get; set; } + long KnownFilesets { get; set; } DateTime LastBackupDate { get; set; } long BackupListCount { get; set; } long TotalQuotaSpace { get; set; } @@ -116,15 +122,15 @@ namespace Duplicati.Library.Interface IEnumerable LogMessages { get; } IEnumerable RemoteVolumes { get; } } - + public interface IDeleteResults : IBasicResults { IEnumerable> DeletedSets { get; } ICompactResults CompactResults { get; } bool Dryrun { get; } } - - public interface IBackupResults : IBasicResults, IBackendStatsticsReporter + + public interface IBackupResults : IBasicResults, IBackendStatsticsReporter, IResultsWithVacuum { long DeletedFiles { get; } long DeletedFolders { get; } @@ -143,16 +149,15 @@ namespace Duplicati.Library.Interface long ModifiedFolders { get; } long ModifiedSymlinks { get; } long AddedSymlinks { get; } - long DeletedSymlinks { get; } + long DeletedSymlinks { get; } bool PartialBackup { get; } bool Dryrun { get; } - + ICompactResults CompactResults { get; } - IVacuumResults VacuumResults { get; } IDeleteResults DeleteResults { get; } IRepairResults RepairResults { get; } } - + public interface IRestoreResults : IBasicResults { long RestoredFiles { get; } @@ -163,10 +168,10 @@ namespace Duplicati.Library.Interface long DeletedFiles { get; } long DeletedFolders { get; } long DeletedSymlinks { get; } - + IRecreateDatabaseResults RecreateDatabaseResults { get; } } - + public interface IRecreateDatabaseResults : IBasicResults { } @@ -176,7 +181,7 @@ namespace Duplicati.Library.Interface IEnumerable Files { get; } } - public interface ICompactResults : IBasicResults + public interface ICompactResults : IBasicResults, IResultsWithVacuum { long DeletedFileCount { get; } long DownloadedFileCount { get; } @@ -185,26 +190,24 @@ namespace Duplicati.Library.Interface long DownloadedFileSize { get; } long UploadedFileSize { get; } bool Dryrun { get; } - - IVacuumResults VacuumResults { get; } } - + public interface ICreateLogDatabaseResults : IBasicResults { string TargetPath { get; } } - + public interface IRestoreControlFilesResults : IBasicResults { IEnumerable Files { get; } } - + public interface IRepairResults : IBasicResults { IRecreateDatabaseResults RecreateDatabaseResults { get; } } - - + + /// /// The possible change types for an entry /// @@ -222,8 +225,8 @@ namespace Duplicati.Library.Interface /// The element was modified /// Modified - } - + } + /// /// The possible entry types /// @@ -242,16 +245,16 @@ namespace Duplicati.Library.Interface /// File } - + public interface IListChangesResults : IBasicResults { DateTime BaseVersionTimestamp { get; } DateTime CompareVersionTimestamp { get; } long BaseVersionIndex { get; } long CompareVersionIndex { get; } - - IEnumerable> ChangeDetails { get; } - + + IEnumerable> ChangeDetails { get; } + long AddedFolders { get; } long AddedSymlinks { get; } long AddedFiles { get; } @@ -263,14 +266,14 @@ namespace Duplicati.Library.Interface long ModifiedFolders { get; } long ModifiedSymlinks { get; } long ModifiedFiles { get; } - + long PreviousSize { get; } long CurrentSize { get; } long AddedSize { get; } long DeletedSize { get; } } - + /// /// The status of a change in a test entry /// @@ -298,7 +301,7 @@ namespace Duplicati.Library.Interface { IEnumerable>>> Verifications { get; } } - + public interface ITestFilterResults : IBasicResults { long FileSize { get; set; } diff --git a/Duplicati/Library/Interface/Strings.cs b/Duplicati/Library/Interface/Strings.cs index bf703110f..ce5fff9ca 100644 --- a/Duplicati/Library/Interface/Strings.cs +++ b/Duplicati/Library/Interface/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Interface/USNJournalDataEntry.cs b/Duplicati/Library/Interface/USNJournalDataEntry.cs index 3c7e25eb9..010442b4c 100644 --- a/Duplicati/Library/Interface/USNJournalDataEntry.cs +++ b/Duplicati/Library/Interface/USNJournalDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj b/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj index abd43ec32..233d91178 100644 --- a/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj +++ b/Duplicati/Library/Localization/Duplicati.Library.Localization.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Localization/ILocalizationService.cs b/Duplicati/Library/Localization/ILocalizationService.cs index 290d5bcbf..f9eeb825f 100644 --- a/Duplicati/Library/Localization/ILocalizationService.cs +++ b/Duplicati/Library/Localization/ILocalizationService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; +using System.Globalization; namespace Duplicati.Library.Localization { public interface ILocalizationService { + /// + /// The culture of the localization service + /// + public CultureInfo Culture { get; } + /// /// Localizes the string similar to how string.Format works /// diff --git a/Duplicati/Library/Localization/LocalizationContext.cs b/Duplicati/Library/Localization/LocalizationContext.cs index ec04afa32..9fed23cbb 100644 --- a/Duplicati/Library/Localization/LocalizationContext.cs +++ b/Duplicati/Library/Localization/LocalizationContext.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,8 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Logging; using System; +using System.Threading; namespace Duplicati.Library.Localization { @@ -29,10 +29,14 @@ namespace Duplicati.Library.Localization /// internal class LocalizationContext : IDisposable { + /// + /// The current culture, using an async local to keep the call context + /// + private static readonly AsyncLocal _currentCulture = new(); /// /// The previous context /// - private readonly object m_prev; + private readonly string m_prev; /// /// Flag to prevent double dispose @@ -45,12 +49,17 @@ namespace Duplicati.Library.Localization /// The localization to use. public LocalizationContext(System.Globalization.CultureInfo ci) { - m_prev = CallContext.GetData(LocalizationService.LOGICAL_CONTEXT_KEY) as string; - CallContext.SetData(LocalizationService.LOGICAL_CONTEXT_KEY, ci.Name); + m_prev = _currentCulture.Value; + _currentCulture.Value = ci.Name; m_isDisposed = false; } + /// + /// Returns the current culture value for the call context + /// + public static string Current => _currentCulture.Value; + /// /// Releases all resource used by the object. /// @@ -64,7 +73,7 @@ namespace Duplicati.Library.Localization { if (!m_isDisposed) { - CallContext.SetData(LocalizationService.LOGICAL_CONTEXT_KEY, m_prev); + _currentCulture.Value = m_prev; m_isDisposed = true; } } diff --git a/Duplicati/Library/Localization/LocalizationService.cs b/Duplicati/Library/Localization/LocalizationService.cs index db6e43e20..636a75217 100644 --- a/Duplicati/Library/Localization/LocalizationService.cs +++ b/Duplicati/Library/Localization/LocalizationService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,7 +24,6 @@ using System.Linq; using System.Globalization; using System.Collections.Generic; using System.Text.RegularExpressions; -using Duplicati.Library.Localization.Short; using Duplicati.Library.Logging; namespace Duplicati.Library.Localization @@ -39,11 +38,6 @@ namespace Duplicati.Library.Localization /// private static readonly Dictionary Services = new Dictionary(); - /// - /// The key for accessing logical context - /// - internal const string LOGICAL_CONTEXT_KEY = "DUPLICATI_LOCALIZATION_CULTURE_CONTEXT"; - /// /// Regular expression to match a locale /// @@ -97,13 +91,27 @@ namespace Duplicati.Library.Localization { get { - var lc = CallContext.GetData(LOGICAL_CONTEXT_KEY) as string; + var lc = LocalizationContext.Current; if (!string.IsNullOrWhiteSpace(lc)) return Get(new CultureInfo(lc)); return Get(CultureInfo.CurrentCulture); } } + /// + /// Gets a localization provider with the current UI language + /// + public static ILocalizationService CurrentUI + { + get + { + var lc = LocalizationContext.Current; + if (!string.IsNullOrWhiteSpace(lc)) + return Get(new CultureInfo(lc)); + return Get(CultureInfo.CurrentUICulture); + } + } + /// /// Gets a localization provider with the OS install language /// @@ -147,7 +155,7 @@ namespace Duplicati.Library.Localization /// /// Returns true if the culture has localization support /// - public static Boolean isCultureSupported(CultureInfo culture) + public static bool IsCultureSupported(CultureInfo culture) { return MoLocalizationService.isCultureSupported(culture); } diff --git a/Duplicati/Library/Localization/MoLocalizationService.cs b/Duplicati/Library/Localization/MoLocalizationService.cs index bf571ea6b..a5983cf77 100644 --- a/Duplicati/Library/Localization/MoLocalizationService.cs +++ b/Duplicati/Library/Localization/MoLocalizationService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -75,6 +75,7 @@ namespace Duplicati.Library.Localization /// The culture to find. public MoLocalizationService(CultureInfo ci) { + Culture = ci; var filenames = new string[] { // Load the specialized version first string.Format("localization-{0}.mo", ci.Name.Replace('-', '_')), @@ -117,6 +118,11 @@ namespace Duplicati.Library.Localization } } + /// + /// Gets the culture of the localization + /// + public CultureInfo Culture { get; } + /// /// Pre-processes the message to use Linux line endings /// diff --git a/Duplicati/Library/Localization/MockLocalizationService.cs b/Duplicati/Library/Localization/MockLocalizationService.cs index 25c92f126..1e2ee86e9 100644 --- a/Duplicati/Library/Localization/MockLocalizationService.cs +++ b/Duplicati/Library/Localization/MockLocalizationService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; +using System.Globalization; namespace Duplicati.Library.Localization { public class MockLocalizationService : ILocalizationService { + /// + /// The culture of the localization service + /// + public CultureInfo Culture => CultureInfo.InvariantCulture; + /// /// Localizes the string similar to how string.Format works /// diff --git a/Duplicati/Library/Localization/Short.cs b/Duplicati/Library/Localization/Short.cs index ef142802a..ff3d88b5b 100644 --- a/Duplicati/Library/Localization/Short.cs +++ b/Duplicati/Library/Localization/Short.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/CallContext.cs b/Duplicati/Library/Logging/CallContext.cs deleted file mode 100644 index 210cbf411..000000000 --- a/Duplicati/Library/Logging/CallContext.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Concurrent; -using System.Threading; - -namespace Duplicati.Library.Logging -{ - /// - /// Provides a way to set contextual data that flows with the call and - /// async context of a test or invocation. - /// - public static class CallContext - { - static ConcurrentDictionary> state = new ConcurrentDictionary>(); - - /// - /// Stores a given object and associates it with the specified name. - /// - /// The name with which to associate the new item in the call context. - /// The object to store in the call context. - public static void SetData(string name, object data) => - state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; - - /// - /// Retrieves an object with the specified name from the . - /// - /// The name of the item in the call context. - /// The object in the call context associated with the specified name, or if not found. - public static object GetData(string name) => - state.TryGetValue(name, out AsyncLocal data) ? data.Value : null; - } -} \ No newline at end of file diff --git a/Duplicati/Library/Logging/Duplicati.Library.Logging.csproj b/Duplicati/Library/Logging/Duplicati.Library.Logging.csproj index 3034eae50..7d2d68194 100644 --- a/Duplicati/Library/Logging/Duplicati.Library.Logging.csproj +++ b/Duplicati/Library/Logging/Duplicati.Library.Logging.csproj @@ -2,7 +2,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/Logging/ILogDestination.cs b/Duplicati/Library/Logging/ILogDestination.cs index 823776877..5eca15ab3 100644 --- a/Duplicati/Library/Logging/ILogDestination.cs +++ b/Duplicati/Library/Logging/ILogDestination.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Logging/ILogFilter.cs b/Duplicati/Library/Logging/ILogFilter.cs index 07c00a0ce..050386c26 100644 --- a/Duplicati/Library/Logging/ILogFilter.cs +++ b/Duplicati/Library/Logging/ILogFilter.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Logging/LambdaSupport.cs b/Duplicati/Library/Logging/LambdaSupport.cs index 4f15c764d..caa6cc3d2 100644 --- a/Duplicati/Library/Logging/LambdaSupport.cs +++ b/Duplicati/Library/Logging/LambdaSupport.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/Log.cs b/Duplicati/Library/Logging/Log.cs index eb1621381..d53a9de3f 100644 --- a/Duplicati/Library/Logging/Log.cs +++ b/Duplicati/Library/Logging/Log.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,7 +21,7 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Threading; namespace Duplicati.Library.Logging { @@ -72,18 +72,13 @@ namespace Duplicati.Library.Logging /// /// The key used to assign the current scope into the current call-context /// - private const string LOGICAL_CONTEXT_KEY = "Duplicati:LoggingEntry"; + private static readonly AsyncLocal _currentScope = new AsyncLocal(); /// /// The root scope /// private static readonly LogScope m_root = new LogScope(null, new LogTagFilter(LogMessageType.Error, null, null), null, true); - /// - /// The stored log instances - /// - private static readonly Dictionary m_log_instances = new Dictionary(); - /// /// Static lock object to provide thread safe logging /// @@ -216,15 +211,15 @@ namespace Duplicati.Library.Logging WriteMessage(LogMessageType.Verbose, tag, id, null, message, arguments); } - /// + /// /// Writes a verbose message to the current log destination /// /// The message to write /// The tag-type for this message /// The message id - /// The exception to log + /// The exception to log /// The message format arguments - public static void WriteVerboseMessage(string tag, string id, Exception ex, string message, params object[] arguments) + public static void WriteVerboseMessage(string tag, string id, Exception ex, string message, params object[] arguments) { WriteMessage(LogMessageType.Verbose, tag, id, ex, message, arguments); } @@ -428,11 +423,10 @@ namespace Duplicati.Library.Logging /// The scope to finish. internal static void CloseScope(LogScope scope) { - lock(m_lock) + lock (m_lock) { if (CurrentScope == scope && scope != m_root) CurrentScope = scope.Parent; - m_log_instances.Remove(scope.InstanceID); } } @@ -444,32 +438,12 @@ namespace Duplicati.Library.Logging get { lock (m_lock) - { - var cur = CallContext.GetData(LOGICAL_CONTEXT_KEY) as string; - if (cur == null || cur == m_root.InstanceID) - return m_root; - - LogScope sc; - if (!m_log_instances.TryGetValue(cur, out sc)) - throw new Exception("Unable to find log in lookup table, this may be caused by attempting to transport call contexts between AppDomains (e.g. with remoting calls)"); - - return sc; - } + return _currentScope.Value ?? m_root; } private set { lock (m_lock) - { - if (value != null) - { - m_log_instances[value.InstanceID] = value; - CallContext.SetData(LOGICAL_CONTEXT_KEY, value.InstanceID); - } - else - { - CallContext.SetData(LOGICAL_CONTEXT_KEY, null); - } - } + _currentScope.Value = value; } } } diff --git a/Duplicati/Library/Logging/LogEntry.cs b/Duplicati/Library/Logging/LogEntry.cs index 8b837c363..e12441052 100644 --- a/Duplicati/Library/Logging/LogEntry.cs +++ b/Duplicati/Library/Logging/LogEntry.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/LogScope.cs b/Duplicati/Library/Logging/LogScope.cs index 9fa97583c..2e24fa26c 100644 --- a/Duplicati/Library/Logging/LogScope.cs +++ b/Duplicati/Library/Logging/LogScope.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/LogTagFilter.cs b/Duplicati/Library/Logging/LogTagFilter.cs index 6a2b88f65..9d9c910ce 100644 --- a/Duplicati/Library/Logging/LogTagFilter.cs +++ b/Duplicati/Library/Logging/LogTagFilter.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/RepeatingLogScope.cs b/Duplicati/Library/Logging/RepeatingLogScope.cs index 9a6a04139..428d8c038 100644 --- a/Duplicati/Library/Logging/RepeatingLogScope.cs +++ b/Duplicati/Library/Logging/RepeatingLogScope.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/StreamLogDestination.cs b/Duplicati/Library/Logging/StreamLogDestination.cs index 7d87babb3..e6cf0d835 100644 --- a/Duplicati/Library/Logging/StreamLogDestination.cs +++ b/Duplicati/Library/Logging/StreamLogDestination.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Logging/Timer.cs b/Duplicati/Library/Logging/Timer.cs index 79a94d587..3801aaa85 100644 --- a/Duplicati/Library/Logging/Timer.cs +++ b/Duplicati/Library/Logging/Timer.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Main/AsyncDownloader.cs b/Duplicati/Library/Main/AsyncDownloader.cs deleted file mode 100644 index 47d7a383a..000000000 --- a/Duplicati/Library/Main/AsyncDownloader.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Duplicati.Library.Main.Database; - -namespace Duplicati.Library.Main -{ - internal interface IAsyncDownloadedFile : IRemoteVolume - { - Library.Utility.TempFile TempFile { get; } - } - - internal class AsyncDownloader : IEnumerable - { - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - private class AsyncDownloaderEnumerator : IEnumerator - { - private class AsyncDownloadedFile : IAsyncDownloadedFile - { - private readonly Exception m_exception; - private Library.Utility.TempFile m_file; - - public string Name { get; private set; } - public string Hash { get; private set; } - public long Size { get; private set; } - - public void DisposeTempFile() - { - if (m_file != null) - try { m_file.Dispose(); } - finally { m_file = null; } - } - - - public Library.Utility.TempFile TempFile - { - get - { - if (m_exception != null) - throw m_exception; - - return m_file; - } - } - - public AsyncDownloadedFile(string name, string hash, long size, Library.Utility.TempFile tempfile, Exception exception) - { - this.Name = name; - this.Hash = hash; - this.Size = size; - m_exception = exception; - m_file = tempfile; - } - } - - private readonly IList m_volumes; - private BackendManager.IDownloadWaitHandle m_handle; - private readonly BackendManager m_backend; - private int m_index; - private AsyncDownloadedFile m_current; - - public AsyncDownloaderEnumerator(IList volumes, BackendManager backend) - { - m_volumes = volumes; - m_backend = backend; - m_index = 0; - } - - public IAsyncDownloadedFile Current - { - get { return m_current; } - } - - public void Dispose() - { - if (m_current != null) - { - m_current.DisposeTempFile(); - m_current = null; - } - } - - object System.Collections.IEnumerator.Current - { - get { return this.Current; } - } - - public bool MoveNext() - { - if (m_current != null) - { - m_current.DisposeTempFile(); - m_current = null; - } - - if (m_index >= m_volumes.Count) - return false; - - if (m_handle == null) - m_handle = m_backend.GetAsync(m_volumes[m_index].Name, m_volumes[m_index].Size, m_volumes[m_index].Hash); - - string hash = null; - long size = -1; - Library.Utility.TempFile file = null; - Exception exception = null; - try - { - file = m_handle.Wait(out hash, out size); - - } - catch (Exception ex) - { - Logging.Log.WriteErrorMessage(LOGTAG, "FailedToRetrieveFile", ex, "Failed to retrieve file {0}", m_volumes[m_index].Name); - exception = ex; - } - - m_current = new AsyncDownloadedFile(m_volumes[m_index].Name, hash, size, file, exception); - m_handle = null; - - m_index++; - if (m_index < m_volumes.Count) - m_handle = m_backend.GetAsync(m_volumes[m_index].Name, m_volumes[m_index].Size, m_volumes[m_index].Hash); - - return true; - } - - public void Reset() - { - throw new NotSupportedException("Cannot reset " + this.GetType().FullName); - } - } - - private readonly IList m_volumes; - private readonly BackendManager m_backend; - - public AsyncDownloader(IList volumes, BackendManager backend) - { - m_volumes = volumes; - m_backend = backend; - } - - public IEnumerator GetEnumerator() - { - return new AsyncDownloaderEnumerator(m_volumes, m_backend); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - } -} diff --git a/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs b/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs new file mode 100644 index 000000000..9358885a1 --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Duplicati.Library.Logging; +using Duplicati.Library.Main.Database; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// A class to collect operations performed on the remote destination. + /// This class is used to log operations so they can later be written to the database. + /// The goal is to keep the database state as close as possible to the remote operation state. + /// Since the backend communication is done in parallel with the actual operations, + /// we store the performed operations in a queue and flush them to the database when the database is available. + /// The main operations are responsible for flushing the messages when comitting a transaction. + /// If the operation fails, the logged messages here should still be flushed to the database, + /// as they have already been performed on the remote destination. + /// + private class DatabaseCollector + { + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Log.LogTagFromType(); + /// + /// The lock object for the database queue + /// + private readonly object m_dbqueuelock = new object(); + /// + /// The queue of database operations + /// + private List m_dbqueue = []; + + /// + /// Interface for database entries + /// + private interface IRemoteOperationEntry { } + + /// + /// Logs an operation performed on the remote destination + /// + /// The performed action + /// The file the operation is performed on + /// The result of the operation + private sealed record RemoteOperationLogEntry(string Action, string File, string? Result) : IRemoteOperationEntry; + + + /// + /// Logs a database update after a remote file operation + /// + /// The remote name of the volume + /// The new state of the volume + /// The new size of the volume + /// The new hash of the volume + private sealed record RemoteVolumeUpdate(string Remotename, RemoteVolumeState State, long Size, string? Hash) : IRemoteOperationEntry; + + /// + /// Logs a rename of a remote file + /// + /// The old name of the file + /// The new name of the file + private sealed record RenameRemoteVolume(string Oldname, string Newname) : IRemoteOperationEntry; + + /// + /// Logs an operation performed on the remote destination + /// + /// The action of the operation + /// The file of the operation + /// The result of the operation + public void LogRemoteOperation(string action, string file, string? result) + { + lock (m_dbqueuelock) + m_dbqueue.Add(new RemoteOperationLogEntry(action, file, result)); + } + + /// + /// Logs a database update after a remote file operation + /// + /// The remote name of the volume + /// The new state of the volume + /// The new size of the volume + /// The new hash of the volume + public void LogRemoteVolumeUpdated(string remotename, RemoteVolumeState state, long size, string? hash) + { + lock (m_dbqueuelock) + m_dbqueue.Add(new RemoteVolumeUpdate(remotename, state, size, hash)); + } + + /// + /// Logs a remote file rename + /// + /// The old name of the file + /// The new name of the file + public void LogRemoteVolumeRenamed(string oldname, string newname) + { + lock (m_dbqueuelock) + m_dbqueue.Add(new RenameRemoteVolume(oldname, newname)); + } + + /// + /// Drops all pending messages from the queue + /// + public void ClearPendingMessages() + { + lock (m_dbqueuelock) + m_dbqueue = []; + } + + /// + /// Flushes all messages to the database + /// + /// The database to write pending messages to + /// The transaction to use, if any + /// Whether any messages were flushed + public bool FlushPendingMessages(LocalDatabase db, System.Data.IDbTransaction? transaction) + { + List entries; + lock (m_dbqueuelock) + if (m_dbqueue.Count == 0) + return false; + else + { + entries = m_dbqueue; + m_dbqueue = []; + } + + // Collect removed volumes for final db cleanup. + var volsRemoved = new HashSet(); + + // As we replace the list, we can now freely access the elements without locking + foreach (var e in entries) + if (e is RemoteOperationLogEntry operation) + db.LogRemoteOperation(operation.Action, operation.File, operation.Result, transaction); + else if (e is RemoteVolumeUpdate update && update.State == RemoteVolumeState.Deleted) + { + db.UpdateRemoteVolume(update.Remotename, RemoteVolumeState.Deleted, update.Size, update.Hash, true, TimeSpan.FromHours(2), transaction); + volsRemoved.Add(update.Remotename); + } + else if (e is RemoteVolumeUpdate dbUpdate) + db.UpdateRemoteVolume(dbUpdate.Remotename, dbUpdate.State, dbUpdate.Size, dbUpdate.Hash, transaction); + else if (e is RenameRemoteVolume rename) + db.RenameRemoteFile(rename.Oldname, rename.Newname, transaction); + else if (e != null) + Log.WriteErrorMessage(LOGTAG, "InvalidQueueElement", null, "Queue had element of type: {0}, {1}", e.GetType(), e); + + // Finally remove volumes from DB. + if (volsRemoved.Count > 0) + db.RemoveRemoteVolumes(volsRemoved); + + return true; + } + + /// + /// Flushes all messages to the log after stopping the processing + /// + public void FlushMessagesToLog() + { + if (m_dbqueue.Count == 0) + return; + + string message; + lock (m_dbqueuelock) + message = string.Join("\n", m_dbqueue.Select(e => e switch + { + RemoteOperationLogEntry operation => $"Operation: {operation.Action} File: {operation.File} Result: {operation.Result}", + RemoteVolumeUpdate update => $"Update: {update.Remotename} State: {update.State} Size: {update.Size} Hash: {update.Hash}", + RenameRemoteVolume rename => $"Rename: {rename.Oldname} -> {rename.Newname}", + _ => $"InvalidQueueElement: {e.GetType()} {e}" + })); + + Log.WriteWarningMessage(LOGTAG, "FlushingMessagesToLog", null, message); + + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.DeleteOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.DeleteOperation.cs new file mode 100644 index 000000000..8b313aafa --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.DeleteOperation.cs @@ -0,0 +1,107 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Localization.Short; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending DELETE operation + /// + private class DeleteOperation : PendingOperation + { + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The remote filename that is to be deleted + /// + public override string RemoteFilename { get; } + /// + /// The size of the remote file, or -1 if unknown + /// + public override long Size { get; } + + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.Delete; + + /// + /// Creates a new DeleteOperation + /// + /// The remote filename to delete + /// The size of the remote file, or -1 if unknown + /// The execution context + /// Whether to wait for the operation to complete + public DeleteOperation(string remoteName, long size, ExecuteContext context, bool waitForComplete, CancellationToken cancelToken) + : base(context, waitForComplete, cancelToken) + { + RemoteFilename = remoteName; + Size = size; + } + + /// + public override async Task ExecuteAsync(IBackend backend, CancellationToken cancelToken) + { + Context.Statwriter.SendEvent(BackendActionType.Delete, BackendEventType.Started, RemoteFilename, Size); + + string? result = null; + try + { + await backend.DeleteAsync(RemoteFilename, cancelToken).ConfigureAwait(false); + } + catch (Exception ex) + { + // Check if the file was not found, and if so, check if it was actually deleted + var isFileMissingException = ex is Library.Interface.FileMissingException || ex is System.IO.FileNotFoundException; + var wr = (ex as System.Net.WebException)?.Response as System.Net.HttpWebResponse; + bool recovered = false; + + if (isFileMissingException || (wr != null && wr.StatusCode == System.Net.HttpStatusCode.NotFound)) + { + Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileFailed", LC.L($"Delete operation failed for {RemoteFilename} with FileNotFound, listing contents")); + + try + { + recovered = !await backend.ListAsync(cancelToken).Select(x => x.Name).ContainsAsync(RemoteFilename).ConfigureAwait(false); + } + catch + { + } + + if (recovered) + Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileSuccess", LC.L($"Listing indicates file {RemoteFilename} was deleted correctly")); + else + Logging.Log.WriteWarningMessage(LOGTAG, "DeleteRemoteFileFailed", ex, LC.L($"Listing confirms file {RemoteFilename} was not deleted")); + } + + if (!recovered) + { + result = ex.ToString(); + throw; + } + } + finally + { + // Here, we do not know if the file was actually deleted or not + // We log that the operation was performed, and the result + Context.Database.LogRemoteOperation("delete", RemoteFilename, result); + + // We also log the new state of the file, so it will be attempted to be re-deleted on later listings + Context.Database.LogRemoteVolumeUpdated(RemoteFilename, RemoteVolumeState.Deleted, -1, null); + } + + Context.Statwriter.SendEvent(BackendActionType.Delete, BackendEventType.Completed, RemoteFilename, Size); + return true; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs new file mode 100644 index 000000000..d159ef046 --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs @@ -0,0 +1,266 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using SharpAESCrypt; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending GET operation + /// + private class GetOperation : PendingOperation<(TempFile File, string Hash, long Size)> + { + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The remote filename that is to be downloaded + /// + public override string RemoteFilename { get; } + /// + /// The size of the remote file, or -1 if unknown + /// + public override long Size { get; } + /// + /// Flag indicating whether the file should be decrypted + /// + public bool Decrypt { get; set; } + /// + /// The hash of the remote file, or null if unknown + /// + public required string? Hash { get; set; } + + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.Get; + + /// + /// Creates a new GetOperation + /// + /// The remote filename to download + /// The size of the remote file, or -1 if unknown + /// The execution context + /// The cancellation token + public GetOperation(string remotefilename, long size, ExecuteContext context, CancellationToken cancelToken) + : base(context, true, cancelToken) + { + RemoteFilename = remotefilename; + Size = size; + } + + /// + /// Executes the operation + /// + /// The backend to download from + /// The cancellation token + /// The file, hash and size of the downloaded file + public override async Task<(TempFile File, string Hash, long Size)> ExecuteAsync(IBackend backend, CancellationToken cancelToken) + { + TempFile? tmpfile = null; + Context.Statwriter.SendEvent(BackendActionType.Get, BackendEventType.Started, RemoteFilename, Size); + + try + { + // Start and time the donwload + var begin = DateTime.Now; + + (tmpfile, var dataSizeDownloaded, var fileHash) = await DoGetFileAsync(backend, cancelToken).ConfigureAwait(false); + + var duration = DateTime.Now - begin; + Logging.Log.WriteProfilingMessage(LOGTAG, "DownloadSpeed", "Downloaded {0} to {1} ({2}) in {3}, {4}/s", + RemoteFilename, tmpfile.Name, Library.Utility.Utility.FormatSizeString(dataSizeDownloaded), + duration, Library.Utility.Utility.FormatSizeString((long)(dataSizeDownloaded / duration.TotalSeconds))); + + Context.Database.LogRemoteOperation("get", RemoteFilename, System.Text.Json.JsonSerializer.Serialize(new { Size = dataSizeDownloaded, Hash = fileHash })); + Context.Statwriter.SendEvent(BackendActionType.Get, BackendEventType.Completed, RemoteFilename, dataSizeDownloaded); + + if (!Context.Options.SkipFileHashChecks) + { + if (Size >= 0 && dataSizeDownloaded != Size) + throw new Exception(Strings.Controller.DownloadedFileSizeError(RemoteFilename, dataSizeDownloaded, Size)); + + if (!string.IsNullOrEmpty(Hash) && fileHash != Hash) + throw new HashMismatchException(Strings.Controller.HashMismatchError(tmpfile, Hash, fileHash)); + } + + // Perform decryption after hash validation, if needed + if (Decrypt) + tmpfile = DecryptFile(tmpfile, RemoteFilename, Context.Options); + + return (tmpfile, fileHash, dataSizeDownloaded); + } + catch + { + tmpfile?.Dispose(); + throw; + } + } + + /// + /// Downloads a file from the backend + /// + /// The backend to download from + /// The cancellation token + /// The downloaded file, the size of the file, and the hash of the file + private async Task<(TempFile tempFile, long downloadSize, string remotehash)> DoGetFileAsync(IBackend backend, CancellationToken cancelToken) + { + TempFile? dlTarget = null; + try + { + long retDownloadSize; + string retHashcode; + dlTarget = new TempFile(); + if (backend is IStreamingBackend streamingBackend && !Context.Options.DisableStreamingTransfers) + { + // extended to use stacked streams + using (var fs = System.IO.File.OpenWrite(dlTarget)) + using (var hasher = HashFactory.CreateHasher(Context.Options.FileHashAlgorithm)) + using (var hs = new HashCalculatingStream(fs, hasher)) + using (var ss = new ShaderStream(hs, true)) + { + using (var ts = new ThrottledStream(ss, 0, Context.Options.MaxDownloadPrSecond)) + using (var pgs = new ProgressReportingStream(ts, pg => Context.HandleProgress(ts, pg, RemoteFilename))) + { + await streamingBackend.GetAsync(RemoteFilename, pgs, cancelToken).ConfigureAwait(false); + } + ss.Flush(); + retDownloadSize = ss.TotalBytesWritten; + retHashcode = Convert.ToBase64String(hs.GetFinalHash()); + } + } + else + { + await backend.GetAsync(RemoteFilename, dlTarget, cancelToken).ConfigureAwait(false); + retDownloadSize = new System.IO.FileInfo(dlTarget).Length; + retHashcode = CalculateFileHash(dlTarget, Context.Options); + } + + var retTarget = dlTarget; + dlTarget = null; + return (retTarget, retDownloadSize, retHashcode); + } + finally + { + // Remove temp files on failure + dlTarget?.Dispose(); + } + } + + /// + /// Detects the encryption module to use, based on the filename. + /// This makes it possible to restore from a folder with mixed encryption modules. + /// + /// The default encryption module + /// The encryption module to use + private static IEncryption? DetectEncryptionModule(string filename, Options options, IEncryption? encryption) + { + try + { + // Auto-guess the encryption module + var ext = (System.IO.Path.GetExtension(filename) ?? "").TrimStart('.'); + if (!ext.Equals(encryption?.FilenameExtension, StringComparison.OrdinalIgnoreCase)) + { + // Check if the file is not encrypted + if (DynamicLoader.CompressionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) + { + if (encryption != null) + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, options.EncryptionModule); + return null; + } + // Check if the file is encrypted with something else + else if (DynamicLoader.EncryptionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use matching encryption module", ext, options.EncryptionModule); + + try + { + return DynamicLoader.EncryptionLoader.GetModule(ext, options.Passphrase, options.RawOptions) + ?? encryption; + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "AutomaticDecryptionDetection", ex, "Failed to load encryption module \"{0}\", using specified encryption module \"{1}\"", ext, options.EncryptionModule); + } + } + // Fallback, lets see what happens... + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, options.EncryptionModule); + } + } + + return encryption; + } + // If we fail here, make sure that we throw a crypto exception + catch (System.Security.Cryptography.CryptographicException) { throw; } + catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } + } + + /// + /// Performs decryption of a file. This could be more efficient if we decrypt while downloading, + /// but that would prevent us from verifying the hash of the encrypted file, before decrypting it. + /// Decrypting afterwards also ensures we can control the thrown exceptions. + /// + /// The encrypted file + /// Then encryption module to use, or null for no encryption + /// The decrypted file + private static TempFile DecryptFile(TempFile tempFile, IEncryption? decrypter) + { + // Support no encryption + if (decrypter == null) + return tempFile; + + TempFile? decryptTarget = null; + + // Always dispose the source file + using (tempFile) + using (new Logging.Timer(LOGTAG, "DecryptFile", "Decrypting " + tempFile)) + { + try + { + decryptTarget = new TempFile(); + try { decrypter.Decrypt(tempFile, decryptTarget); } + // If we fail here, make sure that we throw a crypto exception + catch (System.Security.Cryptography.CryptographicException) { throw; } + catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } + + var result = decryptTarget; + decryptTarget = null; + return result; + } + finally + { + // Remove temp files on failure + decryptTarget?.Dispose(); + } + } + } + + /// + /// Decrypts a file using the specified options + /// + /// The file to decrypt + /// The name of the file. Used for detecting encryption algorithm if not specified in options or if it differs from the options + /// The Duplicati options + /// The decrypted file + public static TempFile DecryptFile(TempFile tmpfile, string filename, Options options) + { + using var encryption = options.NoEncryption + ? null + : (DynamicLoader.EncryptionLoader.GetModule(options.EncryptionModule, options.Passphrase, options.RawOptions) + ?? throw new Exception(Strings.BackendMananger.EncryptionModuleNotFound(options.EncryptionModule)) + ); + return DecryptFile(tmpfile, DetectEncryptionModule(filename, options, encryption)); + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs new file mode 100644 index 000000000..cf1198926 --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs @@ -0,0 +1,551 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Main.Backend; + +partial class BackendManager +{ + /// + /// Wrapper class for making a backend disposable and reclaimable + /// + private sealed class ReclaimableBackend : IDisposable + { + /// + /// The tag used for logging + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The backend being wrapped + /// + public IBackend Backend { get; } + /// + /// The pool where the backend should be returned to + /// + private readonly ConcurrentQueue pool; + /// + /// Whether the backend should be reused + /// + private bool reuse; + /// + /// Whether the backend wrapper has been disposed + /// + private bool disposed; + + /// + /// Creates a new instance of the class + /// + /// The backend to wrap + /// The pool where the backend should be returned to + /// Whether the backend should be reused or disposed + public ReclaimableBackend(IBackend backend, ConcurrentQueue pool, bool reuse) + { + Backend = backend; + this.pool = pool; + this.reuse = reuse; + } + + /// + /// Prevents the backend from being reclaimed + /// + public void PreventReuse() + { + reuse = false; + } + + /// + /// Disposes the backend wrapper + /// + public void Dispose() + { + if (disposed) + return; + disposed = true; + + if (reuse) + pool.Enqueue(Backend); + else + try { Backend.Dispose(); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendDisposeError", ex, "Failed to dispose backend instance: {0}", ex.Message); } + } + } + + /// + /// The handler for processing backend operations + /// + private class Handler + { + /// + /// The tag used for logging + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The list of active downloads + /// + private readonly List activeDownloads = []; + /// + /// The list of active uploads + /// + private readonly List activeUploads = []; + /// + /// The pool of backends currently created + /// + private readonly ConcurrentQueue backendPool = new(); + /// + /// The URL of the backend + /// + private readonly string backendUrl; + /// + /// The context for the handler + /// + private readonly ExecuteContext context; + /// + /// The maximum number of parallel downloads + /// + private readonly int maxParallelDownloads; + /// + /// The maximum number of parallel uploads + /// + private readonly int maxParallelUploads; + /// + /// The maximum number of retries + /// + private readonly int maxRetries; + /// + /// The delay between retries + /// + private readonly TimeSpan retryDelay; + /// + /// Whether to retry with exponential backoff + /// + private readonly bool retryWithExponentialBackoff; + /// + /// Whether to allow backend reuse + /// + private readonly bool allowBackendReuse; + /// + /// Whether any files have been uploaded + /// + private bool anyUploaded; + /// + /// Whether any files have been downloaded + /// + private bool anyDownloaded; + + /// + /// Creates and runs with an instance of the class + /// + /// The channel for pending operations + /// The URL of the backend + /// The execution context + /// An awaitable task + public static Task RunHandlerAsync(IReadChannel requestChannel, string backendUrl, ExecuteContext context) + => AutomationExtensions.RunTask(new { requestChannel }, + self => new Handler(backendUrl, context).Run(self.requestChannel) + ); + + /// + /// Creates a new instance of the class + /// + /// The URL of the backend + /// The execution context + private Handler(string backendUrl, ExecuteContext context) + { + this.backendUrl = backendUrl; + this.context = context; + + // TODO Currently, only the restore process uses parallel downloads. If others need it as well, maybe use another option. + maxParallelDownloads = Math.Max(1, context.Options.RestoreVolumeDownloaders); + maxParallelUploads = Math.Max(1, context.Options.AsynchronousConcurrentUploadLimit); + maxRetries = context.Options.NumberOfRetries; + retryDelay = context.Options.RetryDelay; + retryWithExponentialBackoff = context.Options.RetryWithExponentialBackoff; + allowBackendReuse = !context.Options.NoConnectionReuse; + + } + + /// + /// Creates a new backend instance or reuses an existing one + /// + /// The backend instance + private ReclaimableBackend CreateBackend() + { + backendPool.TryDequeue(out var backend); + if (backend == null) + backend = DynamicLoader.BackendLoader.GetBackend(backendUrl, context.Options.RawOptions); + + return new ReclaimableBackend( + backend, + backendPool, + allowBackendReuse + ); + } + + /// + /// Reclaims completed tasks + /// + /// The list of tasks to reclaim + /// An awaitable task + private static async Task ReclaimCompletedTasks(List tasks) + { + for (int i = tasks.Count - 1; i >= 0; i--) + { + if (tasks[i].IsCompleted) + { + var t = tasks[i]; + tasks.RemoveAt(i); + // Make sure the task is awaited so we capture any exceptions + await t.ConfigureAwait(false); + } + } + } + + /// + /// Reclaims completed tasks from uploads and downloads + /// + /// An awaitable task + private async Task ReclaimCompletedTasks() + { + await ReclaimCompletedTasks(activeUploads); + await ReclaimCompletedTasks(activeDownloads); + } + + /// + /// Ensures that there are at most N - 1 active tasks + /// + /// The maximum number of active tasks + /// The list of active tasks + /// An awaitable task + private static async Task EnsureAtMostNActiveTasks(int n, List tasks) + { + while (tasks.Count >= n) + { + await Task.WhenAny(tasks).ConfigureAwait(false); + await ReclaimCompletedTasks(tasks).ConfigureAwait(false); + } + } + + /// + /// Ensures that there are at most N - 1 active tasks + /// + /// The number of active uploads + /// The number of active downloads + /// An awaitable task + private async Task EnsureAtMostNActiveTasks(int uploads, int downloads) + { + await EnsureAtMostNActiveTasks(uploads, activeUploads).ConfigureAwait(false); + await EnsureAtMostNActiveTasks(downloads, activeDownloads).ConfigureAwait(false); + } + + /// + /// Runs the handler + /// + /// The channel for pending operations + /// An awaitable task + private async Task Run(IReadChannel requestChannel) + { + using var tcs = new CancellationTokenSource(); + try + { + while (true) + { + // Get next operation + var op = await requestChannel.ReadAsync().ConfigureAwait(false); + + try + { + // Clean up completed uploads, if any + await ReclaimCompletedTasks().ConfigureAwait(false); + + // Allow PUT operations to be queued, if requested + if (op is PutOperation putOp && !putOp.WaitForComplete) + { + // Wait for any active downloads to complete before starting an upload + await EnsureAtMostNActiveTasks(maxParallelUploads, 1).ConfigureAwait(false); + + // Operation is accepted into queue, so we can signal completion + putOp.SetComplete(true); + activeUploads.Add(ExecuteWithRetry(putOp, tcs.Token)); + } + else if (op is GetOperation getOp) + { + // Wait for any active uploads to complete before starting a download + await EnsureAtMostNActiveTasks(1, maxParallelDownloads).ConfigureAwait(false); + + // Operation is accepted into queue, so we can signal completion + activeDownloads.Add(ExecuteWithRetry(getOp, tcs.Token)); + } + else + { + // Wait for all of the active uploads and downloads to complete + await EnsureAtMostNActiveTasks(1, 1).ConfigureAwait(false); + + // Execute the operation + await ExecuteWithRetry(op, tcs.Token).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerHandlerFailure", ex, "Error in handler: {0}", ex.Message); + // If we fail, the task may "hang", so we ensure it is completed here + op.SetFailed(ex); + throw; + } + } + } + finally + { + // Terminate any active uploads and downloads. Exceptions thrown by the downloads should be captured by the callers. + tcs.Cancel(); + + if (activeUploads.Count > 0) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeWhileActive", null, "Terminating {0} active uploads", activeUploads.Count); + + await WaitForPendingItems("upload", activeUploads).ConfigureAwait(false); + await WaitForPendingItems("download", activeDownloads).ConfigureAwait(false); + + // Dispose of any remaining backends + while (backendPool.TryDequeue(out var backend)) + try { backend.Dispose(); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", ex, "Failed to dispose backend instance: {0}", ex.Message); } + } + } + } + + /// + /// + /// + /// + /// + /// + private static async Task WaitForPendingItems(string description, List tasks) + { + if (tasks.Count > 0) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeWhileActive", null, "Terminating {0} active {1}s", tasks.Count, description); + + // Wait for all active tasks to complete + await Task.WhenAny(Task.Delay(1000), Task.WhenAll(tasks)).ConfigureAwait(false); + for (int i = tasks.Count - 1; i >= 0; i--) + { + var t = tasks[i]; + if (t.IsCompleted) + { + tasks.RemoveAt(i); + if (t.IsCanceled) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", t.Exception, "Error in active {0}: Cancelled", description); + else if (t.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", t.Exception, "Error in active {0}: {1}", description, t.Exception?.Message ?? "null"); + else + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "{0} was active during termination, but completed successfully", description); + } + else + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "{0} was active during termination, but had state: {1}", description, t.Status); + } + + if (tasks.Count > 0) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerDisposeError", null, "Terminating, but {0} active {1}(s) are still active", tasks.Count, description); + } + } + } + + /// + /// Tries to create a folder, handling errors + /// + /// true if the folder was created, false otherwise + private async Task TryCreateFolder() + { + using var backend = CreateBackend(); + try + { + // If we successfully create the folder, we can re-use the connection + await backend.Backend.CreateFolderAsync(context.TaskReader.TransferToken).ConfigureAwait(false); + return true; + } + catch (Exception ex) + { + // Failure should not reuse the backend + backend.PreventReuse(); + Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateError", ex, "Failed to create folder: {0}", ex.Message); + } + + return false; + } + + /// + /// Executes an operation with retries and error handling + /// + /// The operation to execute + /// The cancellation token + /// An awaitable task + private async Task ExecuteWithRetry(PendingOperationBase op, CancellationToken cancellationToken) + { + // Once in this method, we MUST set the op result, + // or the program will hang waiting for the operation to complete + + int retries = 0; + Exception? lastException = null; + + do + { + try + { + // Happy case is execute and return + await Execute(op, cancellationToken).ConfigureAwait(false); + return; + } + catch (Exception ex) + { + retries++; + lastException = ex; + Logging.Log.WriteRetryMessage(LOGTAG, $"Retry{op.Operation}", ex, "Operation {0} with file {1} attempt {2} of {3} failed with message: {4}", op.Operation, op.RemoteFilename, retries, maxRetries, ex.Message); + + // If we are cancelled, stop retrying + if (op.CancelToken.IsCancellationRequested || context.TaskReader.ProgressToken.IsCancellationRequested || context.TaskReader.TransferToken.IsCancellationRequested) + { + op.SetCancelled(); + return; + } + + // Refresh DNS name if we fail to connect in order to prevent issues with incorrect DNS entries + var dnsFailure = Library.Utility.Utility.FlattenException(ex).Any(x => x is System.Net.WebException wex && wex.Status == System.Net.WebExceptionStatus.NameResolutionFailure); + if (dnsFailure) + { + try + { + using (var backend = CreateBackend()) + foreach (var name in await backend.Backend.GetDNSNamesAsync(context.TaskReader.TransferToken).ConfigureAwait(false) ?? []) + if (!string.IsNullOrWhiteSpace(name)) + System.Net.Dns.GetHostEntry(name); + } + catch + { + } + } + + context.Statwriter.SendEvent(op.Operation, retries <= maxRetries ? BackendEventType.Retrying : BackendEventType.Failed, op.RemoteFilename, op.Size); + + // Check if we can recover from the error + var recovered = false; + + // Check if this was a folder missing exception and we are allowed to autocreate folders + if (!(anyDownloaded || anyUploaded) && context.Options.AutocreateFolders && Library.Utility.Utility.FlattenException(ex).Any(x => x is FolderMissingException)) + { + if (await TryCreateFolder().ConfigureAwait(false)) + recovered = true; + } + + // We did not recover, so wait or give up + if (!recovered && retries <= maxRetries && retryDelay.Ticks != 0) + { + var delay = Library.Utility.Utility.GetRetryDelay(retryDelay, retries, retryWithExponentialBackoff); + await Task.Delay(delay, context.TaskReader.ProgressToken).ConfigureAwait(false); + } + } + + } while (retries <= maxRetries); + + // If we have a last exception, we failed + if (lastException != null) + { + op.SetFailed(lastException); + (op as IDisposable)?.Dispose(); + + // Stop processing tasks if the operation failed and is not being waited for + // Delete operations can be retried later, so we don't stop processing + if (!op.WaitForComplete && op is not DeleteOperation) + throw lastException; + } + } + + /// + /// Fan-out for executing operations. + /// This method requires manual updates when new operation types are added, + /// but avoids a reflection-based dispatch. + /// + /// The operation to execute + /// The cancellation token + /// An awaitable task + private async Task Execute(PendingOperationBase op, CancellationToken cancellationToken) + { + await context.TaskReader.ProgressRendevouz().ConfigureAwait(false); + using (new Logging.Timer(LOGTAG, $"RemoteOperation{op.Operation}", $"RemoteOperation{op.Operation}")) + switch (op) + { + case PutOperation putOp: + await Execute(putOp, cancellationToken).ConfigureAwait(false); + anyUploaded = true; + return; + case GetOperation getOp: + await Execute(getOp, cancellationToken).ConfigureAwait(false); + anyDownloaded = true; + return; + case DeleteOperation deleteOp: + await Execute(deleteOp, cancellationToken).ConfigureAwait(false); + return; + case ListOperation listOp: + await Execute(listOp, cancellationToken).ConfigureAwait(false); + return; + case QuotaInfoOperation quotaOp: + await Execute(quotaOp, cancellationToken).ConfigureAwait(false); + return; + case WaitForEmptyOperation waitOp: + waitOp.SetComplete(true); + return; + default: + throw new NotImplementedException($"Operation type {op.GetType()} is not supported"); + } + } + + /// + /// Executes a specific operation + /// + /// The return value type of the operation + /// The operation to execute + /// The cancellation token + /// An awaitable task + private async Task Execute(PendingOperation op, CancellationToken cancellationToken) + { + using var backend = CreateBackend(); + using var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, op.CancelToken, context.TaskReader.TransferToken); + + try + { + // Start processing the operation + var task = op.ExecuteAsync(backend.Backend, token.Token); + + if (typeof(TResult) == typeof(bool) && !op.WaitForComplete) + { + // Operation is accepted into queue, so we can signal completion + op.SetComplete((TResult)(object)true); + await task.ConfigureAwait(false); + } + else + { + if (!op.WaitForComplete) + throw new NotImplementedException($"WaitForComplete is required for operations returning a value: {op.GetType().FullName}"); + + // Wait for the operation to complete + op.SetComplete(await task.ConfigureAwait(false)); + } + } + catch + { + // If the operation fails, we prevent reuse of the backend + backend.PreventReuse(); + throw; + } + } + } +} diff --git a/Duplicati/Library/Main/Backend/BackendManager.ListOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.ListOperation.cs new file mode 100644 index 000000000..de9647e86 --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.ListOperation.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending LIST operation + /// + /// + /// Creates a new ListOperation + /// + /// The execution context + /// The cancellation token + private class ListOperation(ExecuteContext context, CancellationToken cancelToken) + : PendingOperation>(context, true, cancelToken) + { + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.List; + + /// + /// Executes the operation + /// + /// The backend to list + /// The cancellation token + /// An awaitable task + public override async Task> ExecuteAsync(IBackend backend, CancellationToken cancelToken) + { + Context.Statwriter.SendEvent(BackendActionType.List, BackendEventType.Started, null, -1); + + // TODO: Consider returning IAsyncEnumerable instead of List + var r = await backend.ListAsync(cancelToken).ToListAsync(cancelToken).ConfigureAwait(false); + + // TODO: Investigate better way to solve this so we do not use memory for large lists + var sb = new StringBuilder(); + sb.AppendLine("["); + long count = 0; + foreach (var e in r) + { + if (count != 0) + sb.AppendLine(","); + count++; + sb.Append(System.Text.Json.JsonSerializer.Serialize(e)); + } + + sb.AppendLine(); + sb.Append("]"); + Context.Database.LogRemoteOperation("list", "", sb.ToString()); + + Context.Statwriter.SendEvent(BackendActionType.List, BackendEventType.Completed, null, r.Count); + + return r; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs new file mode 100644 index 000000000..42fd7e040 --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs @@ -0,0 +1,138 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Main.Operation.Common; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Execution context for the backend manager + /// + /// A progress handler + /// The stat writer + /// The database collector + /// The task reader + /// The options + private sealed record ExecuteContext( + Action HandleProgress, + IBackendWriter Statwriter, + DatabaseCollector Database, + ITaskReader TaskReader, + Options Options); + + /// + /// A base non-generic pending operation + /// + private abstract class PendingOperationBase + { + /// + /// The execution context + /// + public ExecuteContext Context { get; } + /// + /// Whether to wait for the operation to complete. + /// If false, the operation is queued and the task is returned after the operation is queued. + /// + public bool WaitForComplete { get; } + /// + /// The cancellation token + /// + public CancellationToken CancelToken { get; set; } + /// + /// The type of operation the pending operation represents + /// + public abstract BackendActionType Operation { get; } + /// + /// The remote filename, if any + /// + public virtual string RemoteFilename => string.Empty; + /// + /// The remote size, if any + /// + public virtual long Size => -1L; + + /// + /// Sets the operation as cancelled + /// + public abstract void SetCancelled(); + /// + /// Sets the operation as failed + /// + /// The exception + public abstract void SetFailed(Exception ex); + + /// + /// Creates a new pending operation + /// + /// The execution context + /// Whether to wait for the operation to complete + /// The cancellation token + public PendingOperationBase(ExecuteContext context, bool waitForComplete, CancellationToken cancelToken) + { + Context = context; + WaitForComplete = waitForComplete; + CancelToken = cancelToken; + } + } + + /// + /// The basic queued backend operation + /// + /// + /// Creates a new pending operation + /// + /// The execution context + /// Whether to wait for the operation to complete + /// The cancellation token + private abstract class PendingOperation(ExecuteContext context, bool waitForComplete, CancellationToken cancelToken) : PendingOperationBase(context, waitForComplete, cancelToken) + { + /// + /// A signal for the task completion + /// + private readonly TaskCompletionSource taskCompleteSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// + /// The task that is completed when the operation is done + /// + public Task GetResult() => taskCompleteSignal.Task; + + /// + /// Sets the operation as complete + /// + public void SetComplete(TResult result) + { + taskCompleteSignal.TrySetResult(result); + } + + /// + /// Sets the operation as failed + /// + /// The exception + public override void SetFailed(Exception ex) + { + taskCompleteSignal.TrySetException(ex); + } + + /// + /// Sets the operation as cancelled + /// + public override void SetCancelled() + { + taskCompleteSignal.TrySetCanceled(); + } + + /// + /// Executes the operation + /// + /// The backend to execute the operation on + /// The cancellation token + /// An awaitable task + public abstract Task ExecuteAsync(IBackend backend, CancellationToken cancelToken); + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs b/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs new file mode 100644 index 000000000..67b77b10b --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs @@ -0,0 +1,54 @@ +using Duplicati.Library.Main.Operation.Common; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending QuotaInfo operation + /// + private sealed record ProgressHandler(Options Options, IBackendWriter Stats, ITaskReader TaskReader) + { + /// + /// Stores the last throttle values + /// + /// The upload throttle value + /// The download throttle value + private sealed record ThrottleValues(string? Upload, string? Download); + + /// + /// The last throttle values + /// + private ThrottleValues lastThrottleValue = new(string.Empty, string.Empty); + + /// + /// Handles the progress of a throttled stream + /// + /// The throttled stream + /// The progress + /// The filename + public void HandleProgress(ThrottledStream ts, long pg, string filename) + { + // This pauses and throws on cancellation, but ignores stop + TaskReader.TransferRendevouz().Await(); + + var prev = lastThrottleValue; + + // Update the throttle speeds if they have changed + Options.RawOptions.TryGetValue("throttle-upload", out var tmpUpload); + Options.RawOptions.TryGetValue("throttle-download", out var tmpDownload); + + if (tmpUpload != prev.Upload || tmpDownload != prev.Download) + { + ts.WriteSpeed = Options.MaxUploadPrSecond; + ts.ReadSpeed = Options.MaxDownloadPrSecond; + lastThrottleValue = new(tmpUpload, tmpDownload); + } + + Stats.BackendProgressUpdater.UpdateProgress(pg); + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs new file mode 100644 index 000000000..a63d5f07a --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs @@ -0,0 +1,343 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; +using Newtonsoft.Json; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending PUT operation + /// + private class PutOperation : PendingOperation + { + /// + /// Different states of the operation + /// + private enum OperationState + { + /// + /// The operation is not started + /// + None, + /// + /// The upload has started + /// + Uploading, + /// + /// The upload has completed + /// + Uploaded, + /// + /// The operation has completed + /// + Completed + } + + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.Put; + /// + /// The remote filename backing field + /// + private string remoteFilename; + /// + /// The remote filename + /// + public override string RemoteFilename => remoteFilename; + /// + /// The local temporary file + /// + public required TempFile? LocalTempfile { get; set; } + /// + /// Flag indicating if the file is not to be encrypted + /// + public required bool Unencrypted { get; set; } + /// + /// The local filename as a string + /// + public string LocalFilename => LocalTempfile; + /// + /// Flag to indicate if the file is not tracked in the database + /// + public required bool TrackedInDb { get; init; } + /// + /// The encryption and hashing task, the return value indicates if the hash and size was updated + /// + private Task<(string Hash, long Size)>? encryptionAndHashingTask; + /// + /// The state of the operation + /// + private OperationState operationState = OperationState.None; + /// + /// The pending index operation, if any + /// + private (IndexVolumeWriter Writer, PutOperation Operation)? indexOperation; + /// + /// The original index file, if any + /// + public required IndexVolumeWriter? OriginalIndexFile { get; init; } + /// + /// A callback that is invoked when the index volume is finished + /// + public required Action? IndexVolumeFinishedCallback { get; init; } + + /// + /// Creates a new put operation + /// + /// The execution context + /// True if the operation should wait for completion + /// The cancellation token + public PutOperation(string remotefilename, ExecuteContext context, bool waitForComplete, CancellationToken cancelToken) + : base(context, waitForComplete, cancelToken) + { + remoteFilename = remotefilename; + } + + /// + /// Starts the encryption of the file + /// + /// The options to use + public void StartEncryptionAndHashing() + { + if (encryptionAndHashingTask != null) + throw new Exception("Encryption already started"); + + // Run detached to allow encrypting while waiting in upload queue + encryptionAndHashingTask = Task.Run(() => + { + if (!Context.Options.NoEncryption && !Unencrypted) + { + using var enc = DynamicLoader.EncryptionLoader.GetModule(Context.Options.EncryptionModule, Context.Options.Passphrase, Context.Options.RawOptions); + if (enc == null) + throw new Exception(Strings.BackendMananger.EncryptionModuleNotFound(Context.Options.EncryptionModule)); + + var tempfile = new TempFile(); + enc.Encrypt(LocalFilename, tempfile); + DeleteLocalFile(); + LocalTempfile = tempfile; + } + + if (!TrackedInDb) + return (string.Empty, -1L); + + return CalculateHashAndSize(); + + }); + } + + /// + /// Deletes the local temporary file + /// + private void DeleteLocalFile() + { + try { LocalTempfile?.Dispose(); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DeleteTemporaryFileError", ex, $"Failed to dispose temporary file: {LocalTempfile}"); } + finally { LocalTempfile = null; } + } + + /// + /// Calculates the hash and size of the file + /// + /// The hash and size of the file + private (string Hash, long Size) CalculateHashAndSize() + { + var hash = CalculateFileHash(LocalFilename, Context.Options); + var size = new System.IO.FileInfo(LocalFilename).Length; + + return (hash, size); + + } + + /// + /// Executes the operation + /// + /// The backend to use + /// The cancellation token + /// An awaitable task + public override async Task ExecuteAsync(IBackend backend, CancellationToken cancelToken) + { + // This operation is slightly more complex than the others, as it involves two files. + // To keep the rest of the code simpler, the upload treats the pair as a single operation + // for the caller, but internally it is two separate operations. + + // If there is a retry, this function needs to handle a retry in various states + // And, if the block file has been renamed, the index file needs to be rewritten + + if (operationState == OperationState.Completed) + throw new Exception("Operation already completed"); + + // If this is a retry after the block has uploaded, pass the operating to the index file upload + if (operationState == OperationState.Uploaded && indexOperation != null) + { + await indexOperation.Value.Operation.ExecuteAsync(backend, cancelToken).ConfigureAwait(false); + indexOperation.Value.Writer.Dispose(); + operationState = OperationState.Completed; + return true; + } + + // Check if the operation is in a valid state + if (operationState == OperationState.Uploaded) + throw new Exception("Operation already uploaded"); + + if (this.encryptionAndHashingTask == null) + throw new Exception("Encryption not started"); + + // On retry attempts, we get the same value, without recalculating + (var hash, var size) = await encryptionAndHashingTask.ConfigureAwait(false); + + // On first upload attempt, calculate the hash and size + if (operationState == OperationState.None && TrackedInDb) + Context.Database.LogRemoteVolumeUpdated(RemoteFilename, RemoteVolumeState.Uploading, size, hash); + + // First attempt here, finish the index file now that all information is known + if (OriginalIndexFile != null && indexOperation == null) + { + Context.Database.LogRemoteVolumeUpdated(OriginalIndexFile.RemoteFilename, RemoteVolumeState.Uploading, -1, null); + + // Prepare an upload operation for the index file + var req2 = new PutOperation(OriginalIndexFile.RemoteFilename, Context, false, cancelToken) + { + LocalTempfile = OriginalIndexFile.TempFile, + TrackedInDb = TrackedInDb, + Unencrypted = Unencrypted, + IndexVolumeFinishedCallback = null, + OriginalIndexFile = null + }; + + OriginalIndexFile.FinishVolume(hash, size); + IndexVolumeFinishedCallback?.Invoke(); + OriginalIndexFile.Close(); + + indexOperation = (OriginalIndexFile, req2); + } + + // If we have previously attempted to upload, we need to rename the file + if (operationState == OperationState.Uploading) + RenameFileAfterError(size); + + // Flag for next attempt, if any + operationState = OperationState.Uploading; + + await PerformUpload(backend, hash, size, cancelToken).ConfigureAwait(false); + + operationState = OperationState.Uploaded; + + // Upload completed, prepare the index file if any + if (indexOperation != null) + { + // TODO: It would be better if we encrypt the index file while uploading the block file + // since most operations work correctly. But if the upload of the block file fails + // we need to deal with decryption, or keep the unencrypted file around + indexOperation.Value.Operation.StartEncryptionAndHashing(); + await indexOperation.Value.Operation.ExecuteAsync(backend, cancelToken).ConfigureAwait(false); + indexOperation.Value.Writer.Dispose(); + } + + operationState = OperationState.Completed; + return true; + } + + /// + /// Performs the actual upload of a file + /// + /// The backend to upload to + /// The hash of the file + /// The size of the file + /// The cancellation token + /// An awaitable task + private async Task PerformUpload(IBackend backend, string Hash, long Size, CancellationToken cancelToken) + { + Context.Database.LogRemoteOperation("put", RemoteFilename, JsonConvert.SerializeObject(new { Size = Size, Hash = Hash })); + Context.Statwriter.SendEvent(BackendActionType.Put, BackendEventType.Started, RemoteFilename, Size); + + var begin = DateTime.Now; + + if (backend is IStreamingBackend streamingBackend && !Context.Options.DisableStreamingTransfers) + { + using var fs = System.IO.File.OpenRead(LocalFilename); + using var ts = new ThrottledStream(fs, Context.Options.MaxUploadPrSecond, 0); + using var pgs = new ProgressReportingStream(ts, pg => Context.HandleProgress(ts, pg, RemoteFilename)); + await streamingBackend.PutAsync(RemoteFilename, pgs, cancelToken).ConfigureAwait(false); + } + else + await backend.PutAsync(RemoteFilename, LocalFilename, cancelToken).ConfigureAwait(false); + + var duration = DateTime.Now - begin; + Logging.Log.WriteProfilingMessage(LOGTAG, "UploadSpeed", "Uploaded {0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(Size), duration, Library.Utility.Utility.FormatSizeString((long)(Size / duration.TotalSeconds))); + + if (TrackedInDb) + Context.Database.LogRemoteVolumeUpdated(RemoteFilename, RemoteVolumeState.Uploaded, Size, Hash); + + Context.Statwriter.SendEvent(BackendActionType.Put, BackendEventType.Completed, RemoteFilename, Size); + + if (Context.Options.ListVerifyUploads) + { + var f = await backend.ListAsync(cancelToken).FirstOrDefaultAsync(n => n.Name.Equals(RemoteFilename, StringComparison.OrdinalIgnoreCase)).ConfigureAwait(false); + if (f == null) + throw new Exception(string.Format($"List verify failed, file was not found after upload: {RemoteFilename}")); + else if (f.Size != Size && f.Size >= 0) + throw new Exception(string.Format($"List verify failed for file: {f.Name}, size was {f.Size} but expected to be {Size}")); + } + + DeleteLocalFile(); + } + + /// + /// Renames the remote target file after an error, and updates the index file, if any + /// + /// The size of the file + private void RenameFileAfterError(long Size) + { + var p = VolumeBase.ParseFilename(RemoteFilename); + var guid = VolumeWriterBase.GenerateGuid(); + var time = p.Time.Ticks == 0 ? p.Time : p.Time.AddSeconds(1); + var newname = VolumeBase.GenerateFilename(p.FileType, p.Prefix, guid, time, p.CompressionModule, p.EncryptionModule); + var oldname = RemoteFilename; + + Context.Statwriter.SendEvent(BackendActionType.Put, BackendEventType.Rename, oldname, Size); + Context.Statwriter.SendEvent(BackendActionType.Put, BackendEventType.Rename, newname, Size); + Logging.Log.WriteInformationMessage(LOGTAG, "RenameRemoteTargetFile", "Renaming \"{0}\" to \"{1}\"", oldname, newname); + Context.Database.LogRemoteVolumeRenamed(oldname, newname); + remoteFilename = newname; + + // If there is an index file attached to the block file, + // it references the block filename, so we create a new index file + // which is a copy of the current, but with the new name + if (indexOperation != null) + { + IndexVolumeWriter? wr = null; + try + { + var hashsize = HashFactory.HashSizeBytes(Context.Options.BlockHashAlgorithm); + wr = new IndexVolumeWriter(Context.Options); + using (var rd = new IndexVolumeReader(p.CompressionModule, indexOperation.Value.Operation.LocalFilename, Context.Options, hashsize)) + wr.CopyFrom(rd, x => x == oldname ? newname : x); + indexOperation.Value.Writer.Dispose(); + indexOperation = (wr, indexOperation.Value.Operation); + indexOperation.Value.Operation.LocalTempfile?.Dispose(); + indexOperation.Value.Operation.LocalTempfile = wr.TempFile; + wr.Close(); + } + catch + { + wr?.Dispose(); + throw; + } + } + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.QuotaInfoOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.QuotaInfoOperation.cs new file mode 100644 index 000000000..7ce537dfa --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.QuotaInfoOperation.cs @@ -0,0 +1,46 @@ +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending QuotaInfo operation + /// + /// + /// Creates a new QuotaInfoOperation + /// + /// The execution context + /// The cancellation token + private class QuotaInfoOperation(ExecuteContext context, CancellationToken cancelToken) + : PendingOperation(context, true, cancelToken) + { + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.QuotaInfo; + + /// + /// Executes the operation + /// + /// The backend to get the quota info from + /// The cancellation token + /// An awaitable task + public override async Task ExecuteAsync(IBackend backend, CancellationToken cancelToken) + { + IQuotaInfo? quotaInfo = null; + if (backend is IQuotaEnabledBackend quotaEnabledBackend && !Context.Options.QuotaDisable) + { + Context.Statwriter.SendEvent(BackendActionType.QuotaInfo, BackendEventType.Started, null, 0); + quotaInfo = await quotaEnabledBackend.GetQuotaInfoAsync(cancelToken).ConfigureAwait(false); + } + + return quotaInfo; + } + + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.WaitForEmptyOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.WaitForEmptyOperation.cs new file mode 100644 index 000000000..be792191d --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.WaitForEmptyOperation.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +partial class BackendManager +{ + /// + /// Represents a pending QuotaInfo operation + /// + /// + /// Creates a new WaitForEmptyOperation + /// + /// The execution context + /// The cancellation token + private class WaitForEmptyOperation(ExecuteContext context, CancellationToken cancelToken) + : PendingOperation(context, true, cancelToken) + { + /// + /// The operation type + /// + public override BackendActionType Operation => BackendActionType.WaitForEmpty; + + /// + /// Executes the operation + /// + /// The backend to get the quota info from + /// The cancellation token + /// An awaitable task + public override Task ExecuteAsync(IBackend backend, CancellationToken cancelToken) + => Task.FromResult(true); + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Backend/BackendManager.cs b/Duplicati/Library/Main/Backend/BackendManager.cs new file mode 100644 index 000000000..2c948edbe --- /dev/null +++ b/Duplicati/Library/Main/Backend/BackendManager.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Interface; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Main.Operation.Common; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Backend; + +#nullable enable + +/// +/// The backend manager +/// +internal partial class BackendManager : IBackendManager +{ + /// + /// The log tag for the class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// The channel for issuing and handling requests + /// + private readonly IChannel requestChannel = ChannelManager.CreateChannel(name: "BackendManager"); + + /// + /// The queue runner task + /// + private readonly Task queueRunner; + + /// + /// The last file read size + /// + public long LastReadSize { get; private set; } + /// + /// The last file write size + /// + public long LastWriteSize { get; private set; } + + /// + /// The execution context + /// + private readonly ExecuteContext context; + + /// + /// Flag keeping track of whether the object has been disposed + /// + private bool isDisposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// The backend URL + /// The options + /// The backend writer + /// The task reader + public BackendManager(string backendUrl, Options options, IBackendWriter backendWriter, ITaskReader taskReader) + { + if (string.IsNullOrWhiteSpace(backendUrl)) + throw new ArgumentNullException(nameof(backendUrl)); + + // To avoid excessive parameter passing, the context is captured here + context = new ExecuteContext( + new ProgressHandler(options, backendWriter, taskReader).HandleProgress, + backendWriter ?? throw new ArgumentNullException(nameof(backendWriter)), + new DatabaseCollector(), + taskReader ?? throw new ArgumentNullException(nameof(taskReader)), + options ?? throw new ArgumentNullException(nameof(options)) + ); + + // The BackendManager class is a wrapper that essentially sends + // requests into a queue and processes them in order. + // The Handler class is the one that actually processes the requests. + queueRunner = Handler.RunHandlerAsync( + requestChannel, + backendUrl, + context); + } + + /// + /// Enters a task into the queue for processing. + /// + /// The operation to queue + /// An awaitable task + private async Task QueueTask(PendingOperationBase op) + { + if (queueRunner.IsCompleted) + { + if (queueRunner.IsFaulted) + await queueRunner.ConfigureAwait(false); + if (queueRunner.IsCanceled) + throw new OperationCanceledException("Backend manager is stopped", queueRunner.Exception); + throw new InvalidOperationException("Backend manager is stopped"); + } + + try + { + await requestChannel.WriteAsync(op).ConfigureAwait(false); + } + catch (RetiredException ex) + { + // Try to get a better error message + if (queueRunner.IsFaulted) + await queueRunner.ConfigureAwait(false); + + throw new InvalidOperationException("Backend manager is stopped", ex); + } + } + + /// + /// Calculates the hash of a file + /// + /// The filename + /// The options + /// The hash + protected static string CalculateFileHash(string filename, Options options) + { + using (var fs = System.IO.File.OpenRead(filename)) + using (var hasher = HashFactory.CreateHasher(options.FileHashAlgorithm)) + return Convert.ToBase64String(hasher.ComputeHash(fs)); + } + + /// + /// Decrypts a file using the specified options + /// + /// The file to decrypt + /// The name of the file. Used for detecting encryption algorithm if not specified in options or if it differs from the options + /// The Duplicati options + /// The decrypted file + public TempFile DecryptFile(TempFile volume, string volume_name, Options options) + { + return GetOperation.DecryptFile(volume, volume_name, options); + } + + /// + /// Deletes a remote file + /// + /// The name of the remote file + /// The size of the remote file, for statistics + /// True if the operation should wait for the file to actually be deleted. If this argument is false, the task will complete once the operation is queued + /// The cancellation token + /// An awaitable task + public async Task DeleteAsync(string remotename, long size, bool waitForComplete, CancellationToken cancelToken) + { + var op = new DeleteOperation(remotename, size, context, waitForComplete, cancelToken); + await QueueTask(op).ConfigureAwait(false); + await op.GetResult().ConfigureAwait(false); + } + + /// + /// Gets a file from the remote location + /// + /// The name of the remote file + /// The hash of the remote file, for verification + /// The size of the remote file, for verification + /// The cancellation token + /// A temporary file with the contents of the remote file + public async Task GetAsync(string remotename, string hash, long size, CancellationToken cancelToken) + { + var op = new GetOperation(remotename, size, context, cancelToken) + { + Hash = hash, + Decrypt = true + }; + await QueueTask(op).ConfigureAwait(false); + (var file, var _, var downloadSize) = await op.GetResult().ConfigureAwait(false); + LastReadSize = downloadSize; + return file; + } + + /// + /// Gets a file from the remote location without decrypting it + /// + /// The name of the remote file + /// The hash of the remote file, for verification + /// The size of the remote file, for verification + /// The cancellation token + /// A temporary file with the contents of the remote file + public async Task GetDirectAsync(string remotename, string hash, long size, CancellationToken cancelToken) + { + var op = new GetOperation(remotename, size, context, cancelToken) + { + Hash = hash, + Decrypt = false + }; + await QueueTask(op).ConfigureAwait(false); + (var file, var _, var downloadSize) = await op.GetResult().ConfigureAwait(false); + LastReadSize = downloadSize; + return file; + } + + /// + /// Gets quota information from the backend + /// + /// The cancellation token + /// The quota information + public async Task GetQuotaInfoAsync(CancellationToken cancelToken) + { + var op = new QuotaInfoOperation(context, cancelToken); + await QueueTask(op).ConfigureAwait(false); + return await op.GetResult().ConfigureAwait(false); + } + + /// + /// Gets a file from the remote location, along with the hash and size of the file + /// + /// The name of the remote file + /// The hash of the remote file, or null if not known + /// The size of the remote file, or -1 if not known + /// The cancellation token + /// A tuple containing the temporary file, the hash of the file, and the size of the file + public async Task<(TempFile File, string Hash, long Size)> GetWithInfoAsync(string remotename, string hash, long size, CancellationToken cancelToken) + { + var op = new GetOperation(remotename, size, context, cancelToken) + { + Hash = hash, + Decrypt = true + }; + await QueueTask(op).ConfigureAwait(false); + (var file, var downloadHash, var downloadSize) = await op.GetResult().ConfigureAwait(false); + LastReadSize = downloadSize; + return (file, downloadHash, downloadSize); + } + + /// + /// Lists files on the remote destination + /// + /// The cancellation token + /// The list of files + public async Task> ListAsync(CancellationToken cancelToken) + { + var op = new ListOperation(context, cancelToken); + await QueueTask(op).ConfigureAwait(false); + return await op.GetResult().ConfigureAwait(false); + } + + /// + /// Uploads a volume to the remote location + /// + /// The volume to upload + /// The index volume to upload, if any + /// The callback to call when the index volume is finished + /// True if the operation should wait for the file to actually be uploaded. If this argument is false, the task will complete once the operation is queued + /// The cancellation token + /// An awaitable task + public async Task PutAsync(VolumeWriterBase volume, IndexVolumeWriter? indexVolume, Action? indexVolumeFinished, bool waitForComplete, CancellationToken cancelToken) + { + volume.Close(); + + var op = new PutOperation(volume.RemoteFilename, context, waitForComplete, cancelToken) + { + LocalTempfile = volume.TempFile, + OriginalIndexFile = indexVolume, + Unencrypted = false, + TrackedInDb = true, + IndexVolumeFinishedCallback = indexVolumeFinished + }; + + // Prepare encryption + op.StartEncryptionAndHashing(); + await QueueTask(op).ConfigureAwait(false); + await op.GetResult().ConfigureAwait(false); + } + + /// + /// Uploads a verification file to the remote location without encryption + /// + /// The name of the remote file + /// The temporary file to upload + /// The cancellation token + /// An awaitable task + public async Task PutVerificationFileAsync(string remotename, TempFile tempFile, CancellationToken cancelToken) + { + var op = new PutOperation(remotename, context, true, cancelToken) + { + LocalTempfile = tempFile, + Unencrypted = true, // Avoid encrypting + TrackedInDb = false, // Not tracked + OriginalIndexFile = null, + IndexVolumeFinishedCallback = null + }; + + // Sets the task as already completed + op.StartEncryptionAndHashing(); + await QueueTask(op).ConfigureAwait(false); + await op.GetResult().ConfigureAwait(false); + } + + /// + /// Waits for the backend queue to be empty and flushes the database messages + /// + /// The cancellation token + /// An awaitable task + public async Task WaitForEmptyAsync(LocalDatabase database, IDbTransaction? transaction, CancellationToken cancellationToken) + { + context.Database.FlushPendingMessages(database, transaction); + + var op = new WaitForEmptyOperation(context, cancellationToken); + await QueueTask(op).ConfigureAwait(false); + await op.GetResult().ConfigureAwait(false); + + context.Database.FlushPendingMessages(database, transaction); + } + + /// + /// Stops the backend manager and flushes any pending messages to the database + /// + /// The database to write pending messages to + /// The transaction to use, if any + public async Task StopRunnerAndFlushMessages(LocalDatabase database, IDbTransaction? transaction) + { + await requestChannel.RetireAsync().ConfigureAwait(false); + context.Database.FlushPendingMessages(database, transaction); + if (queueRunner.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerShutdown", queueRunner.Exception, "Backend manager queue runner crashed"); + } + + /// + /// Stops the backend manager and discards any pending messages + /// + public void StopRunnerAndDiscardMessages() + { + requestChannel.RetireAsync().Await(); + if (queueRunner.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerShutdown", queueRunner.Exception, "Backend manager queue runner crashed"); + context.Database.ClearPendingMessages(); + } + + /// + /// Performs a download of the files specified, with pre-fetch to overlap the download and processing + /// + /// The volumes to download + /// The cancellation token + /// The downloaded files and the volume they came from + public async IAsyncEnumerable<(TempFile File, string Hash, long Size, string Name)> GetFilesOverlappedAsync(IEnumerable volumes, [EnumeratorCancellation] CancellationToken cancelToken) + { + var prevVolume = volumes.FirstOrDefault(); + if (prevVolume == null) + yield break; + + // Get the first volume, so we do not have pending parallel transfers + var prevResult = await GetWithInfoAsync(prevVolume.Name, prevVolume.Hash, prevVolume.Size, cancelToken); + + foreach (var volume in volumes.Skip(1)) + { + // Prepare the next volume, while processing the previous one + var nextTask = GetWithInfoAsync(volume.Name, volume.Hash, volume.Size, cancelToken); + + // Assuming we do not throw while yielding, otherwise we would need to dispose nextTask + yield return (prevResult.File, prevResult.Hash, prevResult.Size, prevVolume.Name); + prevResult.File.Dispose(); + + // Set up for next iteration + prevVolume = volume; + prevResult = await nextTask; + } + + // Return the last result + yield return (prevResult.File, prevResult.Hash, prevResult.Size, prevVolume.Name); + prevResult.File.Dispose(); + } + + /// + /// Disposes the backend manager + /// + public void Dispose() + { + if (isDisposed) + return; + + isDisposed = true; + requestChannel.RetireAsync().Await(); + context.Database.FlushMessagesToLog(); + + if (!queueRunner.IsCompleted) + { + Task.WhenAny(queueRunner, Task.Delay(1000)).Await(); + + if (!queueRunner.IsCompleted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerShutdown", null, "Backend manager queue runner did not stop"); + if (queueRunner.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "BackendManagerShutdown", queueRunner.Exception, "Backend manager queue runner crashed"); + + } + } +} diff --git a/Duplicati/Library/Main/BackendManager.cs b/Duplicati/Library/Main/BackendManager.cs deleted file mode 100644 index 6d5eb2a55..000000000 --- a/Duplicati/Library/Main/BackendManager.cs +++ /dev/null @@ -1,1347 +0,0 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Duplicati.Library.Utility; -using Duplicati.Library.Main.Database; -using Duplicati.Library.Main.Volumes; -using Newtonsoft.Json; -using Duplicati.Library.Localization.Short; -using System.Threading; -using System.Net; -using Duplicati.Library.Interface; -using System.Threading.Tasks; - -namespace Duplicati.Library.Main -{ - internal class BackendManager : IDisposable - { - /// - /// The tag used for logging - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - /// - /// Class to represent hash failures - /// - [Serializable] - public class HashMismatchException : Exception - { - /// - /// Default constructor, sets a generic string as the message - /// - public HashMismatchException() : base() { } - - /// - /// Constructor with non-default message - /// - /// The exception message - public HashMismatchException(string message) : base(message) { } - - /// - /// Constructor with non-default message and inner exception details - /// - /// The exception message - /// The exception that caused this exception - public HashMismatchException(string message, Exception innerException) : base(message, innerException) { } - } - - private enum OperationType - { - Get, - Put, - List, - Delete, - CreateFolder, - Terminate, - Nothing - } - - public interface IDownloadWaitHandle - { - TempFile Wait(); - TempFile Wait(out string hash, out long size); - } - - private class FileEntryItem : IDownloadWaitHandle - { - /// - /// The current operation this entry represents - /// - public readonly OperationType Operation; - /// - /// The name of the remote file - /// - public string RemoteFilename; - /// - /// The name of the local file - /// - public string LocalFilename { get { return LocalTempfile; } } - /// - /// A reference to a temporary file that is disposed upon - /// failure or completion of the item - /// - public TempFile LocalTempfile; - /// - /// True if the item has been encrypted - /// - public bool Encrypted; - /// - /// The result object - /// - public object Result; - /// - /// The expected hash value of the file - /// - public string Hash; - /// - /// The expected size of the file - /// - public long Size; - /// - /// Reference to the index file entry that is updated if this entry changes - /// - public Tuple Indexfile; - /// - /// A flag indicating if the final hash and size of the block volume has been written to the index file - /// - public bool IndexfileUpdated; - /// - /// An exception that this item has caused - /// - public Exception Exception; - /// - /// True if an exception ultimately kills the handler, - /// false if the item is returned with an exception - /// - public readonly bool ExceptionKillsHandler; - /// - /// A flag indicating if the file is a extra metadata file - /// that has no entry in the database - /// - public bool NotTrackedInDb; - /// - /// A flag that indicates that the download is only checked for the hash and the file is not decrypted or returned - /// - public bool VerifyHashOnly; - - /// - /// The event that is signaled once the operation is complete or has failed - /// - private readonly System.Threading.ManualResetEvent DoneEvent; - - public FileEntryItem(OperationType operation, string remotefilename, Tuple indexfile = null) - { - Operation = operation; - RemoteFilename = remotefilename; - Indexfile = indexfile; - ExceptionKillsHandler = operation != OperationType.Get; - Size = -1; - - DoneEvent = new System.Threading.ManualResetEvent(false); - } - - public FileEntryItem(OperationType operation, string remotefilename, long size, string hash, Tuple indexfile = null) - : this(operation, remotefilename, indexfile) - { - Size = size; - Hash = hash; - } - - public void SetLocalfilename(string name) - { - this.LocalTempfile = Library.Utility.TempFile.WrapExistingFile(name); - this.LocalTempfile.Protected = true; - } - - public void SignalComplete() - { - DoneEvent.Set(); - } - - public void WaitForComplete() - { - DoneEvent.WaitOne(); - } - - TempFile IDownloadWaitHandle.Wait() - { - this.WaitForComplete(); - if (Exception != null) - throw Exception; - - return (TempFile)this.Result; - } - - TempFile IDownloadWaitHandle.Wait(out string hash, out long size) - { - this.WaitForComplete(); - - if (Exception != null) - throw Exception; - - hash = this.Hash; - size = this.Size; - - return (TempFile)this.Result; - } - - public void Encrypt(Library.Interface.IEncryption encryption, IBackendWriter stat) - { - if (encryption != null && !this.Encrypted) - { - var tempfile = new Library.Utility.TempFile(); - encryption.Encrypt(this.LocalFilename, tempfile); - this.DeleteLocalFile(stat); - this.LocalTempfile = tempfile; - this.Hash = null; - this.Size = 0; - this.Encrypted = true; - } - } - - public bool UpdateHashAndSize(Options options) - { - if (Hash == null || Size < 0) - { - Hash = CalculateFileHash(this.LocalFilename, options); - Size = new System.IO.FileInfo(this.LocalFilename).Length; - return true; - } - - return false; - } - - public void DeleteLocalFile(IBackendWriter stat) - { - if (this.LocalTempfile != null) - try { this.LocalTempfile.Dispose(); } - catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DeleteTemporaryFileError", ex, "Failed to dispose temporary file: {0}", this.LocalTempfile); } - finally { this.LocalTempfile = null; } - } - - public BackendActionType BackendActionType - { - get - { - switch (this.Operation) - { - case OperationType.Get: - return BackendActionType.Get; - case OperationType.Put: - return BackendActionType.Put; - case OperationType.Delete: - return BackendActionType.Delete; - case OperationType.List: - return BackendActionType.List; - case OperationType.CreateFolder: - return BackendActionType.CreateFolder; - default: - throw new Exception(string.Format("Unexpected operation type: {0}", this.Operation)); - } - } - } - - } - - private class DatabaseCollector - { - private readonly object m_dbqueuelock = new object(); - private readonly LocalDatabase m_database; - private readonly System.Threading.Thread m_callerThread; - private List m_dbqueue; - - private interface IDbEntry { } - - private class DbOperation : IDbEntry - { - public string Action; - public string File; - public string Result; - } - - private class DbUpdate : IDbEntry - { - public string Remotename; - public RemoteVolumeState State; - public long Size; - public string Hash; - } - - private class DbRename : IDbEntry - { - public string Oldname; - public string Newname; - } - - public DatabaseCollector(LocalDatabase database) - { - m_database = database; - m_dbqueue = new List(); - if (m_database != null) - m_callerThread = System.Threading.Thread.CurrentThread; - } - - - public void LogDbOperation(string action, string file, string result) - { - lock (m_dbqueuelock) - m_dbqueue.Add(new DbOperation() { Action = action, File = file, Result = result }); - } - - public void LogDbUpdate(string remotename, RemoteVolumeState state, long size, string hash) - { - lock (m_dbqueuelock) - m_dbqueue.Add(new DbUpdate() { Remotename = remotename, State = state, Size = size, Hash = hash }); - } - - public void LogDbRename(string oldname, string newname) - { - lock (m_dbqueuelock) - m_dbqueue.Add(new DbRename() { Oldname = oldname, Newname = newname }); - } - - public bool FlushDbMessages(bool checkThread = false) - { - if (m_database != null && (checkThread == false || m_callerThread == System.Threading.Thread.CurrentThread)) - return FlushDbMessages(m_database, null); - - return false; - } - - public bool FlushDbMessages(LocalDatabase db, System.Data.IDbTransaction transaction) - { - List entries; - lock (m_dbqueuelock) - if (m_dbqueue.Count == 0) - return false; - else - { - entries = m_dbqueue; - m_dbqueue = new List(); - } - - // collect removed volumes for final db cleanup. - HashSet volsRemoved = new HashSet(); - - //As we replace the list, we can now freely access the elements without locking - foreach (var e in entries) - if (e is DbOperation operation) - db.LogRemoteOperation(operation.Action, operation.File, operation.Result, transaction); - else if (e is DbUpdate update && update.State == RemoteVolumeState.Deleted) - { - db.UpdateRemoteVolume(update.Remotename, RemoteVolumeState.Deleted, update.Size, update.Hash, true, TimeSpan.FromHours(2), transaction); - volsRemoved.Add(update.Remotename); - } - else if (e is DbUpdate dbUpdate) - db.UpdateRemoteVolume(dbUpdate.Remotename, dbUpdate.State, dbUpdate.Size, dbUpdate.Hash, transaction); - else if (e is DbRename rename) - db.RenameRemoteFile(rename.Oldname, rename.Newname, transaction); - else if (e != null) - Logging.Log.WriteErrorMessage(LOGTAG, "InvalidQueueElement", null, "Queue had element of type: {0}, {1}", e.GetType(), e); - - // Finally remove volumes from DB. - if (volsRemoved.Count > 0) - db.RemoveRemoteVolumes(volsRemoved); - - return true; - } - } - - private readonly BlockingQueue m_queue; - private readonly Options m_options; - private volatile Exception m_lastException; - private readonly Library.Interface.IEncryption m_encryption; - private readonly object m_encryptionLock = new object(); - private Library.Interface.IBackend m_backend; - private readonly string m_backendurl; - private readonly IBackendWriter m_statwriter; - private System.Threading.Thread m_thread; - private readonly BasicResults m_taskControl; - private readonly DatabaseCollector m_db; - - // Cache these - private readonly int m_numberofretries; - private readonly TimeSpan m_retrydelay; - private readonly Boolean m_retrywithexponentialbackoff; - - public string BackendUrl { get { return m_backendurl; } } - - public BackendManager(string backendurl, Options options, IBackendWriter statwriter, LocalDatabase database) - { - m_options = options; - m_backendurl = backendurl; - m_statwriter = statwriter; - m_taskControl = statwriter as BasicResults; - m_numberofretries = options.NumberOfRetries; - m_retrydelay = options.RetryDelay; - m_retrywithexponentialbackoff = options.RetryWithExponentialBackoff; - - m_db = new DatabaseCollector(database); - - m_backend = DynamicLoader.BackendLoader.GetBackend(m_backendurl, m_options.RawOptions); - if (m_backend == null) - { - string shortname = m_backendurl; - - // Try not to leak hostnames or other information in the error messages - try { shortname = new Library.Utility.Uri(shortname).Scheme; } - catch { } - - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Backend not supported: {0}", shortname), "BackendNotSupported"); - } - - if (!m_options.NoEncryption) - { - m_encryption = DynamicLoader.EncryptionLoader.GetModule(m_options.EncryptionModule, m_options.Passphrase, m_options.RawOptions); - if (m_encryption == null) - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Encryption method not supported: {0}", m_options.EncryptionModule), "EncryptionMethodNotSupported"); - } - - if (m_taskControl != null) - m_taskControl.StateChangedEvent += (state) => - { - if (state == TaskControlState.Abort) - m_thread.Interrupt(); - }; - m_queue = new BlockingQueue(options.SynchronousUpload ? 1 : (options.AsynchronousUploadLimit == 0 ? int.MaxValue : options.AsynchronousUploadLimit)); - m_thread = new System.Threading.Thread(this.ThreadRun); - m_thread.Name = "Backend Async Worker"; - m_thread.IsBackground = true; - m_thread.Start(); - } - - public static string CalculateFileHash(string filename, Options options) - { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) - return CalculateFileHash(fs, options); - } - - /// Calculate file hash directly on stream object (for piping) - public static string CalculateFileHash(System.IO.Stream stream, Options options) - { - using (var hasher = HashFactory.CreateHasher(options.FileHashAlgorithm)) - return Convert.ToBase64String(hasher.ComputeHash(stream)); - } - - - private void ThreadRun() - { - var uploadSuccess = false; - while (!m_queue.Completed) - { - var item = m_queue.Dequeue(); - if (item != null) - { - int retries = 0; - Exception lastException = null; - - do - { - try - { - if (m_taskControl != null) - m_taskControl.TaskControlRendevouz(); - - if (m_options.NoConnectionReuse && m_backend != null) - { - m_backend.Dispose(); - m_backend = null; - } - - if (m_backend == null) - m_backend = DynamicLoader.BackendLoader.GetBackend(m_backendurl, m_options.RawOptions); - if (m_backend == null) - throw new Exception("Backend failed to re-load"); - - using (new Logging.Timer(LOGTAG, string.Format("RemoteOperation{0}", item.Operation), string.Format("RemoteOperation{0}", item.Operation))) - switch (item.Operation) - { - case OperationType.Put: - DoPutAsync(item, CancellationToken.None).Await(); - // We do not auto create folders, - // because we know the folder exists - uploadSuccess = true; - break; - case OperationType.Get: - DoGetAsync(item, CancellationToken.None).Await(); - break; - case OperationType.List: - DoList(item); - break; - case OperationType.Delete: - DoDeleteAsync(item, CancellationToken.None).Await(); - break; - case OperationType.CreateFolder: - DoCreateFolderAsync(item, CancellationToken.None).Await(); - break; - case OperationType.Terminate: - m_queue.SetCompleted(); - break; - case OperationType.Nothing: - item.SignalComplete(); - break; - } - - lastException = null; - retries = m_numberofretries; - } - catch (Exception ex) - { - retries++; - lastException = ex; - Logging.Log.WriteRetryMessage(LOGTAG, $"Retry{item.Operation}", ex, "Operation {0} with file {1} attempt {2} of {3} failed with message: {4}", item.Operation, item.RemoteFilename, retries, m_numberofretries, ex.Message); - - // If the thread is aborted, we exit here - if (ex is System.Threading.ThreadAbortException) - { - m_queue.SetCompleted(); - item.Exception = ex; - item.SignalComplete(); - throw; - } - - if (ex is WebException exception) - { - // Refresh DNS name if we fail to connect in order to prevent issues with incorrect DNS entries - if (exception.Status == System.Net.WebExceptionStatus.NameResolutionFailure) - { - try - { - var names = m_backend.GetDNSNamesAsync(CancellationToken.None).Await() ?? new string[0]; - foreach (var name in names) - if (!string.IsNullOrWhiteSpace(name)) - System.Net.Dns.GetHostEntry(name); - } - catch - { - } - } - } - - m_statwriter.SendEvent(item.BackendActionType, retries < m_numberofretries ? BackendEventType.Retrying : BackendEventType.Failed, item.RemoteFilename, item.Size); - - bool recovered = false; - if (!uploadSuccess && ex is Duplicati.Library.Interface.FolderMissingException && m_options.AutocreateFolders) - { - try - { - // If we successfully create the folder, we can re-use the connection - m_backend.CreateFolderAsync(CancellationToken.None).Await(); - recovered = true; - } - catch (Exception dex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateError", dex, "Failed to create folder: {0}", ex.Message); - } - } - - // To work around the Apache WEBDAV issue, we rename the file here - if (item.Operation == OperationType.Put && retries < m_numberofretries && !item.NotTrackedInDb) - RenameFileAfterError(item); - - if (!recovered) - { - try { m_backend.Dispose(); } - catch (Exception dex) { Logging.Log.WriteWarningMessage(LOGTAG, "BackendDisposeError", dex, "Failed to dispose backend instance: {0}", ex.Message); } - - m_backend = null; - - if (retries < m_numberofretries && m_retrydelay.Ticks != 0) - { - var delay = Library.Utility.Utility.GetRetryDelay(m_retrydelay, retries, m_retrywithexponentialbackoff); - var target = DateTime.Now.Add(delay); - - while (target > DateTime.Now) - { - if (m_taskControl != null && m_taskControl.IsAbortRequested()) - break; - - System.Threading.Thread.Sleep(500); - } - } - } - } - - - } while (retries < m_numberofretries); - - if (lastException != null && !(lastException is Duplicati.Library.Interface.FileMissingException) && item.Operation == OperationType.Delete) - { - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteFileFailed", LC.L("Failed to delete file {0}, testing if file exists", item.RemoteFilename)); - try - { - if (!m_backend.List().Select(x => x.Name).Contains(item.RemoteFilename)) - { - lastException = null; - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteFileFailureRecovered", LC.L("Recovered from problem with attempting to delete non-existing file {0}", item.RemoteFilename)); - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "DeleteFileFailure", ex, LC.L("Failed to recover from error deleting file {0}", item.RemoteFilename), ex); - } - } - - if (lastException != null) - { - item.Exception = lastException; - if (item.Operation == OperationType.Put) - item.DeleteLocalFile(m_statwriter); - - if (item.ExceptionKillsHandler) - { - m_lastException = lastException; - - //TODO: If there are temp files in the queue, we must delete them - m_queue.SetCompleted(); - } - - } - - item.SignalComplete(); - } - } - - //Make sure everything in the queue is signalled - FileEntryItem i; - while ((i = m_queue.Dequeue()) != null) - i.SignalComplete(); - } - - private void RenameFileAfterError(FileEntryItem item) - { - var p = VolumeBase.ParseFilename(item.RemoteFilename); - var guid = VolumeWriterBase.GenerateGuid(); - var time = p.Time.Ticks == 0 ? p.Time : p.Time.AddSeconds(1); - var newname = VolumeBase.GenerateFilename(p.FileType, p.Prefix, guid, time, p.CompressionModule, p.EncryptionModule); - var oldname = item.RemoteFilename; - - m_statwriter.SendEvent(item.BackendActionType, BackendEventType.Rename, oldname, item.Size); - m_statwriter.SendEvent(item.BackendActionType, BackendEventType.Rename, newname, item.Size); - Logging.Log.WriteInformationMessage(LOGTAG, "RenameRemoteTargetFile", "Renaming \"{0}\" to \"{1}\"", oldname, newname); - m_db.LogDbRename(oldname, newname); - item.RemoteFilename = newname; - - // If there is an index file attached to the block file, - // it references the block filename, so we create a new index file - // which is a copy of the current, but with the new name - if (item.Indexfile != null) - { - if (!item.IndexfileUpdated) - { - item.Indexfile.Item1.FinishVolume(item.Hash, item.Size); - item.Indexfile.Item1.Close(); - item.IndexfileUpdated = true; - } - - IndexVolumeWriter wr = null; - try - { - var hashsize = HashFactory.HashSizeBytes(m_options.BlockHashAlgorithm); - wr = new IndexVolumeWriter(m_options); - using (var rd = new IndexVolumeReader(p.CompressionModule, item.Indexfile.Item2.LocalFilename, m_options, hashsize)) - wr.CopyFrom(rd, x => x == oldname ? newname : x); - item.Indexfile.Item1.Dispose(); - item.Indexfile = new Tuple(wr, item.Indexfile.Item2); - item.Indexfile.Item2.LocalTempfile.Dispose(); - item.Indexfile.Item2.LocalTempfile = wr.TempFile; - wr.Close(); - } - catch - { - wr?.Dispose(); - throw; - } - } - } - - private string m_lastThrottleUploadValue = null; - private string m_lastThrottleDownloadValue = null; - - private void HandleProgress(ThrottledStream ts, long pg) - { - // TODO: Should we pause here as well? - // It might give annoying timeouts for transfers - if (m_taskControl != null) - m_taskControl.TaskControlRendevouz(); - - // Update the throttle speeds if they have changed - string tmp; - m_options.RawOptions.TryGetValue("throttle-upload", out tmp); - if (tmp != m_lastThrottleUploadValue) - { - ts.WriteSpeed = m_options.MaxUploadPrSecond; - m_lastThrottleUploadValue = tmp; - } - - m_options.RawOptions.TryGetValue("throttle-download", out tmp); - if (tmp != m_lastThrottleDownloadValue) - { - ts.ReadSpeed = m_options.MaxDownloadPrSecond; - m_lastThrottleDownloadValue = tmp; - } - - m_statwriter.BackendProgressUpdater.UpdateProgress(pg); - } - - private async Task DoPutAsync(FileEntryItem item, CancellationToken cancellationToken) - { - if (m_encryption != null) - lock (m_encryptionLock) - item.Encrypt(m_encryption, m_statwriter); - - if (item.UpdateHashAndSize(m_options) && !item.NotTrackedInDb) - m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploading, item.Size, item.Hash); - - if (item.Indexfile != null && !item.IndexfileUpdated) - { - item.Indexfile.Item1.FinishVolume(item.Hash, item.Size); - item.Indexfile.Item1.Close(); - item.IndexfileUpdated = true; - } - - m_db.LogDbOperation("put", item.RemoteFilename, JsonConvert.SerializeObject(new { Size = item.Size, Hash = item.Hash })); - m_statwriter.SendEvent(BackendActionType.Put, BackendEventType.Started, item.RemoteFilename, item.Size); - - var begin = DateTime.Now; - - if (m_backend is Library.Interface.IStreamingBackend streamingBackend && !m_options.DisableStreamingTransfers) - { - using (var fs = System.IO.File.OpenRead(item.LocalFilename)) - using (var ts = new ThrottledStream(fs, m_options.MaxUploadPrSecond, 0)) - using (var pgs = new Library.Utility.ProgressReportingStream(ts, pg => HandleProgress(ts, pg))) - await streamingBackend.PutAsync(item.RemoteFilename, pgs, cancellationToken); - } - else - await m_backend.PutAsync(item.RemoteFilename, item.LocalFilename, cancellationToken); - - var duration = DateTime.Now - begin; - Logging.Log.WriteProfilingMessage(LOGTAG, "UploadSpeed", "Uploaded {0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(item.Size), duration, Library.Utility.Utility.FormatSizeString((long)(item.Size / duration.TotalSeconds))); - - if (!item.NotTrackedInDb) - m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploaded, item.Size, item.Hash); - - m_statwriter.SendEvent(BackendActionType.Put, BackendEventType.Completed, item.RemoteFilename, item.Size); - - if (m_options.ListVerifyUploads) - { - var f = m_backend.List().FirstOrDefault(n => n.Name.Equals(item.RemoteFilename, StringComparison.OrdinalIgnoreCase)); - if (f == null) - throw new Exception(string.Format("List verify failed, file was not found after upload: {0}", item.RemoteFilename)); - else if (f.Size != item.Size && f.Size >= 0) - throw new Exception(string.Format("List verify failed for file: {0}, size was {1} but expected to be {2}", f.Name, f.Size, item.Size)); - } - - item.DeleteLocalFile(m_statwriter); - } - - private async Task<(TempFile tempFile, long downloadSize, string remotehash)> DoGetFile(FileEntryItem item, IEncryption useDecrypter, CancellationToken cancellationToken) - { - TempFile retTarget, dlTarget = null, decryptTarget = null; - long retDownloadSize; - string retHashcode; - try - { - dlTarget = new Library.Utility.TempFile(); - if (m_backend is Library.Interface.IStreamingBackend streamingBackend && !m_options.DisableStreamingTransfers) - { - // extended to use stacked streams - using (var fs = System.IO.File.OpenWrite(dlTarget)) - using (var hasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) - using (var hs = new HashCalculatingStream(fs, hasher)) - using (var ss = new ShaderStream(hs, true)) - { - // NOTE: It is possible to hash the file in parallel with download - // but this requires some careful handling of buffers and threads/tasks - // to avoid adding more overhead than what is gained. - - using (var ts = new ThrottledStream(ss, 0, m_options.MaxDownloadPrSecond)) - using (var pgs = new Library.Utility.ProgressReportingStream(ts, pg => HandleProgress(ts, pg))) - { await streamingBackend.GetAsync(item.RemoteFilename, pgs, cancellationToken); } - ss.Flush(); - retDownloadSize = ss.TotalBytesWritten; - retHashcode = Convert.ToBase64String(hs.GetFinalHash()); - } - } - else - { - await m_backend.GetAsync(item.RemoteFilename, dlTarget, cancellationToken); - retDownloadSize = new System.IO.FileInfo(dlTarget).Length; - retHashcode = CalculateFileHash(dlTarget, m_options); - } - - // Decryption is not placed in the stream stack because there seemed to be an effort - // to throw a CryptographicException on fail. If in main stack, we cannot differentiate - // in which part of the stack the source of an exception resides. - if (useDecrypter != null) - { - decryptTarget = new Library.Utility.TempFile(); - lock (m_encryptionLock) - { - try { useDecrypter.Decrypt(dlTarget, decryptTarget); } - // If we fail here, make sure that we throw a crypto exception - catch (System.Security.Cryptography.CryptographicException) { throw; } - catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } - } - retTarget = decryptTarget; - decryptTarget = null; - } - else - { - retTarget = dlTarget; - dlTarget = null; - } - } - finally - { - if (dlTarget != null) dlTarget.Dispose(); - if (decryptTarget != null) decryptTarget.Dispose(); - } - - return (retTarget, retDownloadSize, retHashcode); - } - - private async Task DoGetAsync(FileEntryItem item, CancellationToken cancellationToken) - { - Library.Utility.TempFile tmpfile = null; - m_statwriter.SendEvent(BackendActionType.Get, BackendEventType.Started, item.RemoteFilename, item.Size); - - try - { - var begin = DateTime.Now; - - // We already know the filename, so we put the decision about if and which decryptor to - // use prior to download. This allows to set up stacked streams or a pipe doing decryption - Interface.IEncryption useDecrypter = null; - if (!item.VerifyHashOnly && !m_options.NoEncryption) - { - useDecrypter = m_encryption; - { - lock (m_encryptionLock) - { - try - { - // Auto-guess the encryption module - var ext = (System.IO.Path.GetExtension(item.RemoteFilename) ?? "").TrimStart('.'); - if (!m_encryption.FilenameExtension.Equals(ext, StringComparison.OrdinalIgnoreCase)) - { - // Check if the file is encrypted with something else - if (DynamicLoader.EncryptionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", using matching encryption module", ext, m_options.EncryptionModule); - useDecrypter = DynamicLoader.EncryptionLoader.GetModule(ext, m_options.Passphrase, m_options.RawOptions); - useDecrypter = useDecrypter ?? m_encryption; - } - // Check if the file is not encrypted - else if (DynamicLoader.CompressionLoader.Keys.Contains(ext, StringComparer.OrdinalIgnoreCase)) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", guessing that it is not encrypted", ext, m_options.EncryptionModule); - useDecrypter = null; - } - // Fallback, lets see what happens... - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "AutomaticDecryptionDetection", "Filename extension \"{0}\" does not match encryption module \"{1}\", attempting to use specified encryption module as no others match", ext, m_options.EncryptionModule); - } - } - } - // If we fail here, make sure that we throw a crypto exception - catch (System.Security.Cryptography.CryptographicException) { throw; } - catch (Exception ex) { throw new System.Security.Cryptography.CryptographicException(ex.Message, ex); } - } - } - } - - (tmpfile, var dataSizeDownloaded, var fileHash) = await DoGetFile(item, useDecrypter, cancellationToken); - - var duration = DateTime.Now - begin; - Logging.Log.WriteProfilingMessage(LOGTAG, "DownloadSpeed", "Downloaded {3}{0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(dataSizeDownloaded), - duration, Library.Utility.Utility.FormatSizeString((long)(dataSizeDownloaded / duration.TotalSeconds)), - useDecrypter == null ? "" : "and decrypted "); - - m_db.LogDbOperation("get", item.RemoteFilename, JsonConvert.SerializeObject(new { Size = dataSizeDownloaded, Hash = fileHash })); - m_statwriter.SendEvent(BackendActionType.Get, BackendEventType.Completed, item.RemoteFilename, dataSizeDownloaded); - - if (!m_options.SkipFileHashChecks) - { - if (item.Size >= 0) - { - if (dataSizeDownloaded != item.Size) - throw new Exception(Strings.Controller.DownloadedFileSizeError(item.RemoteFilename, dataSizeDownloaded, item.Size)); - } - else - item.Size = dataSizeDownloaded; - - if (!string.IsNullOrEmpty(item.Hash)) - { - if (fileHash != item.Hash) - throw new HashMismatchException(Strings.Controller.HashMismatchError(tmpfile, item.Hash, fileHash)); - } - else - item.Hash = fileHash; - } - - if (item.VerifyHashOnly) - { - tmpfile.Dispose(); - } - else - { - item.Result = tmpfile; - tmpfile = null; - } - - } - catch - { - if (tmpfile != null) - tmpfile.Dispose(); - - throw; - } - } - - private void DoList(FileEntryItem item) - { - m_statwriter.SendEvent(BackendActionType.List, BackendEventType.Started, null, -1); - - var r = m_backend.List().ToList(); - - StringBuilder sb = new StringBuilder(); - sb.AppendLine("["); - long count = 0; - foreach (var e in r) - { - if (count != 0) - sb.AppendLine(","); - count++; - sb.Append(JsonConvert.SerializeObject(e)); - } - - sb.AppendLine(); - sb.Append("]"); - m_db.LogDbOperation("list", "", sb.ToString()); - item.Result = r; - - m_statwriter.SendEvent(BackendActionType.List, BackendEventType.Completed, null, r.Count); - } - - private async Task DoDeleteAsync(FileEntryItem item, CancellationToken cancellationToken) - { - m_statwriter.SendEvent(BackendActionType.Delete, BackendEventType.Started, item.RemoteFilename, item.Size); - - string result = null; - try - { - await m_backend.DeleteAsync(item.RemoteFilename, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - var isFileMissingException = ex is Library.Interface.FileMissingException || ex is System.IO.FileNotFoundException; - var wr = ex as System.Net.WebException == null ? null : (ex as System.Net.WebException).Response as System.Net.HttpWebResponse; - - if (isFileMissingException || (wr != null && wr.StatusCode == System.Net.HttpStatusCode.NotFound)) - { - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileFailed", LC.L("Delete operation failed for {0} with FileNotFound, listing contents", item.RemoteFilename)); - bool success = false; - - try - { - success = !m_backend.List().Select(x => x.Name).Contains(item.RemoteFilename); - } - catch - { - } - - if (success) - { - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileSuccess", LC.L("Listing indicates file {0} was deleted correctly", item.RemoteFilename)); - return; - } - else - { - Logging.Log.WriteWarningMessage(LOGTAG, "DeleteRemoteFileFailed", ex, LC.L("Listing confirms file {0} was not deleted", item.RemoteFilename)); - } - } - - result = ex.ToString(); - throw; - } - finally - { - m_db.LogDbOperation("delete", item.RemoteFilename, result); - } - - m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Deleted, -1, null); - m_statwriter.SendEvent(BackendActionType.Delete, BackendEventType.Completed, item.RemoteFilename, item.Size); - } - - private async Task DoCreateFolderAsync(FileEntryItem item, CancellationToken cancelToken) - { - m_statwriter.SendEvent(BackendActionType.CreateFolder, BackendEventType.Started, null, -1); - - string result = null; - try - { - await m_backend.CreateFolderAsync(cancelToken).ConfigureAwait(false); - } - catch (Exception ex) - { - result = ex.ToString(); - throw; - } - finally - { - m_db.LogDbOperation("createfolder", item.RemoteFilename, result); - } - - m_statwriter.SendEvent(BackendActionType.CreateFolder, BackendEventType.Completed, null, -1); - } - - public void PutUnencrypted(string remotename, string localpath) - { - if (m_lastException != null) - throw m_lastException; - - var req = new FileEntryItem(OperationType.Put, remotename, null); - req.SetLocalfilename(localpath); - req.Encrypted = true; //Prevent encryption - req.NotTrackedInDb = true; //Prevent Db updates - - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req) && m_options.SynchronousUpload) - { - req.WaitForComplete(); - if (req.Exception != null) - throw req.Exception; - } - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - } - - public void Put(VolumeWriterBase item, IndexVolumeWriter indexfile = null, Action indexVolumeFinishedCallback = null, bool synchronous = false) - { - if (m_lastException != null) - throw m_lastException; - - item.Close(); - var req = new FileEntryItem(OperationType.Put, item.RemoteFilename, null); - req.LocalTempfile = item.TempFile; - - if (m_lastException != null) - throw m_lastException; - - FileEntryItem req2 = null; - - // As the network link is the bottleneck, - // we encrypt the dblock volume before the - // upload is enqueued (i.e. on the worker thread) - if (m_encryption != null) - lock (m_encryptionLock) - req.Encrypt(m_encryption, m_statwriter); - - req.UpdateHashAndSize(m_options); - m_db.LogDbUpdate(item.RemoteFilename, RemoteVolumeState.Uploading, req.Size, req.Hash); - - // We do not encrypt the dindex volume, because it is small, - // and may need to be re-written if the dblock upload is retried - if (indexfile != null) - { - m_db.LogDbUpdate(indexfile.RemoteFilename, RemoteVolumeState.Uploading, -1, null); - req2 = new FileEntryItem(OperationType.Put, indexfile.RemoteFilename); - req2.LocalTempfile = indexfile.TempFile; - req.Indexfile = new Tuple(indexfile, req2); - - indexfile.FinishVolume(req.Hash, req.Size); - indexVolumeFinishedCallback?.Invoke(); - indexfile.Close(); - req.IndexfileUpdated = true; - } - - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - m_db.FlushDbMessages(true); - - if (m_queue.Enqueue(req) && (m_options.SynchronousUpload || synchronous)) - { - req.WaitForComplete(); - if (req.Exception != null) - throw req.Exception; - } - - if (req2 != null && m_queue.Enqueue(req2) && (m_options.SynchronousUpload || synchronous)) - { - req2.WaitForComplete(); - if (req2.Exception != null) - throw req2.Exception; - } - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - } - - public Library.Utility.TempFile GetWithInfo(string remotename, out long size, out string hash) - { - if (m_lastException != null) - throw m_lastException; - - hash = null; size = -1; - var req = new FileEntryItem(OperationType.Get, remotename, -1, null); - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req)) - ((IDownloadWaitHandle)req).Wait(out hash, out size); - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - - return (Library.Utility.TempFile)req.Result; - } - - public Library.Utility.TempFile Get(string remotename, long size, string hash) - { - if (m_lastException != null) - throw m_lastException; - - var req = new FileEntryItem(OperationType.Get, remotename, size, hash); - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req)) - ((IDownloadWaitHandle)req).Wait(); - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - - return (Library.Utility.TempFile)req.Result; - } - - public IDownloadWaitHandle GetAsync(string remotename, long size, string hash) - { - if (m_lastException != null) - throw m_lastException; - - var req = new FileEntryItem(OperationType.Get, remotename, size, hash); - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req)) - return req; - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - else - throw new InvalidOperationException("GetAsync called after backend is shut down"); - } - - public void GetForTesting(string remotename, long size, string hash) - { - if (m_lastException != null) - throw m_lastException; - - if (string.IsNullOrWhiteSpace(hash)) - throw new InvalidOperationException("Cannot test a file without the hash"); - - var req = new FileEntryItem(OperationType.Get, remotename, size, hash); - req.VerifyHashOnly = true; - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req)) - { - req.WaitForComplete(); - if (req.Exception != null) - throw req.Exception; - } - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - } - - public IList List() - { - if (m_lastException != null) - throw m_lastException; - - var req = new FileEntryItem(OperationType.List, null); - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req)) - { - req.WaitForComplete(); - if (req.Exception != null) - throw req.Exception; - } - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - - return (IList)req.Result; - } - - public void WaitForComplete(LocalDatabase db, System.Data.IDbTransaction transation) - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - m_db.FlushDbMessages(db, transation); - if (m_lastException != null) - throw m_lastException; - - var item = new FileEntryItem(OperationType.Terminate, null); - if (m_queue.Enqueue(item)) - item.WaitForComplete(); - - m_db.FlushDbMessages(db, transation); - - if (m_lastException != null) - throw m_lastException; - } - - public void WaitForEmpty(LocalDatabase db, System.Data.IDbTransaction transation) - { - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - m_db.FlushDbMessages(db, transation); - if (m_lastException != null) - throw m_lastException; - - var item = new FileEntryItem(OperationType.Nothing, null); - if (m_queue.Enqueue(item)) - item.WaitForComplete(); - - m_db.FlushDbMessages(db, transation); - - if (m_lastException != null) - throw m_lastException; - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - } - - public void Delete(string remotename, long size, bool synchronous = false) - { - if (m_lastException != null) - throw m_lastException; - - m_db.LogDbUpdate(remotename, RemoteVolumeState.Deleting, size, null); - var req = new FileEntryItem(OperationType.Delete, remotename, size, null); - try - { - m_statwriter.BackendProgressUpdater.SetBlocking(true); - if (m_queue.Enqueue(req) && synchronous) - { - req.WaitForComplete(); - if (req.Exception != null) - throw req.Exception; - } - } - finally - { - m_statwriter.BackendProgressUpdater.SetBlocking(false); - } - - if (m_lastException != null) - throw m_lastException; - } - - public Task GetQuotaInfoAsync(CancellationToken cancelToken) - => (m_backend as IQuotaEnabledBackend)?.GetQuotaInfoAsync(cancelToken) ?? Task.FromResult(null); - - public bool FlushDbMessages() - { - return m_db.FlushDbMessages(false); - } - - public void Dispose() - { - if (m_queue != null && !m_queue.Completed) - m_queue.SetCompleted(); - - if (m_thread != null) - { - if (!m_thread.Join(TimeSpan.FromSeconds(10))) - { - m_thread.Interrupt(); - m_thread.Join(TimeSpan.FromSeconds(10)); - } - - m_thread = null; - } - - //TODO: We cannot null this, because it will be recreated - //Should we wait for queue completion or abort immediately? - if (m_backend != null) - { - m_backend.Dispose(); - m_backend = null; - } - - try { m_db.FlushDbMessages(true); } - catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "ShutdownError", ex, "Backend Shutdown error: {0}", ex.Message); } - } - } -} diff --git a/Duplicati/Library/Main/Blockprocessor.cs b/Duplicati/Library/Main/Blockprocessor.cs index 2f5bb857b..38a77e839 100644 --- a/Duplicati/Library/Main/Blockprocessor.cs +++ b/Duplicati/Library/Main/Blockprocessor.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Main/DatabaseLocator.cs b/Duplicati/Library/Main/CLIDatabaseLocator.cs similarity index 55% rename from Duplicati/Library/Main/DatabaseLocator.cs rename to Duplicati/Library/Main/CLIDatabaseLocator.cs index 209478ba9..0cd7f93df 100644 --- a/Duplicati/Library/Main/DatabaseLocator.cs +++ b/Duplicati/Library/Main/CLIDatabaseLocator.cs @@ -1,268 +1,258 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Linq; -using System.Collections.Generic; - -namespace Duplicati.Library.Main -{ - public static class DatabaseLocator - { - public class BackendEntry - { - public string Type; - public string Server; - public string Path; - public string Prefix; - public string Username; - //public string Passwordhash; - public int Port; - public string Databasepath; - public string ParameterFile; - } - - /// - /// The filename of the file with database configurations - /// - private const string CONFIG_FILE = "dbconfig.json"; - - /// - /// Finds a default storage folder, using the operating system specific locations. - /// The targetfilename is used to detect locations that are used in previous versions. - /// If the targetfilename is found in an old location, but not the current, the old location is used. - /// If running with DEBUG defined, the storage folder is placed in the same folder as the executable - /// - /// The filename to look for - /// The name of the application - /// The default storage folder - public static string GetDefaultStorageFolderWithDebugSupport(string targetfilename, string appName = "Duplicati") - { -#if DEBUG - return System.IO.Path.GetDirectoryName(typeof(DatabaseLocator).Assembly.Location) ?? string.Empty; -#else - return GetDefaultStorageFolder(targetfilename, appName); -#endif - } - - /// - /// Finds a default storage folder, using the operating system specific locations. - /// The targetfilename is used to detect locations that are used in previous versions. - /// If the targetfilename is found in an old location, but not the current, the old location is used. - /// - /// The filename to look for - /// The name of the application - /// The default storage folder - public static string GetDefaultStorageFolder(string targetfilename, string appName = "Duplicati") - { - //Normal mode uses the systems "(Local) Application Data" folder - // %LOCALAPPDATA% on Windows, ~/.config on Linux - var folder = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName); - - if (OperatingSystem.IsWindows()) - { - // Special handling for Windows: - // - Older versions use %APPDATA% - // - but new versions use %LOCALAPPDATA% - var newlocation = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName); - - var prevfile = System.IO.Path.Combine(folder, targetfilename); - var curfile = System.IO.Path.Combine(newlocation, targetfilename); - - // If the new file exists, we use that - // If the new file does not exist, and the old file exists we use the old - // Otherwise we use the new location - if (System.IO.File.Exists(curfile) || !System.IO.File.Exists(prevfile)) - folder = newlocation; - } - - if (OperatingSystem.IsMacOS()) - { - // Special handling for MacOS: - // - Older versions use ~/.config/ - // - but new versions use ~/Library/Application\ Support/ - var configfolder = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config", appName); - - var prevfile = System.IO.Path.Combine(configfolder, targetfilename); - var curfile = System.IO.Path.Combine(folder, targetfilename); - - // If the old file exists, and not the new file, we use the old - // Otherwise we use the new location - if (!System.IO.File.Exists(curfile) && System.IO.File.Exists(prevfile)) - folder = configfolder; - } - - return folder; - } - - public static string GetDatabasePath(string backend, Options options, bool autoCreate = true, bool anyUsername = false) - { - if (options == null) - options = new Options(new Dictionary()); - - if (!string.IsNullOrEmpty(options.Dbpath)) - return options.Dbpath; - - var folder = GetDefaultStorageFolderWithDebugSupport(CONFIG_FILE); - - var file = System.IO.Path.Combine(folder, CONFIG_FILE); - List configs; - if (!System.IO.File.Exists(file)) - configs = new List(); - else - configs = Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)); - - var uri = new Library.Utility.Uri(backend); - string server = uri.Host; - string path = uri.Path; - string type = uri.Scheme; - int port = uri.Port; - string username = uri.Username; - string prefix = options.Prefix; - - if (username == null || uri.Password == null) - { - var sopts = DynamicLoader.BackendLoader.GetSupportedCommands(backend); - var ropts = new Dictionary(options.RawOptions); - foreach (var k in uri.QueryParameters.AllKeys) - ropts[k] = uri.QueryParameters[k]; - - if (sopts != null) - { - foreach (var o in sopts) - { - if (username == null && o.Aliases != null && o.Aliases.Contains("auth-username", StringComparer.OrdinalIgnoreCase) && ropts.ContainsKey(o.Name)) - username = ropts[o.Name]; - } - - foreach (var o in sopts) - { - if (username == null && o.Name.Equals("auth-username", StringComparison.OrdinalIgnoreCase) && ropts.ContainsKey("auth-username")) - username = ropts["auth-username"]; - } - } - } - - //Now find the one that matches :) - var matches = (from n in configs - where - n.Type == type && - //n.Passwordhash == password && - n.Username == username && - n.Port == port && - n.Server == server && - n.Path == path && - n.Prefix == prefix - select n).ToList(); - - if (matches.Count > 1) - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Multiple sources found for: {0}", backend), "MultipleLocalDatabaseSourcesFound"); - - // Re-select - if (matches.Count == 0 && anyUsername && string.IsNullOrEmpty(username)) - { - matches = (from n in configs - where - n.Type == type && - n.Port == port && - n.Server == server && - n.Path == path && - n.Prefix == prefix - select n).ToList(); - - if (matches.Count > 1) - throw new Duplicati.Library.Interface.UserInformationException(String.Format("Multiple sources found for \"{0}\", try supplying --{1}", backend, "auth-username"), "MultipleLocalDatabaseSourcesFound"); - } - - if (matches.Count == 0 && !autoCreate) - return null; - - if (matches.Count == 0) - { - var backupname = options.BackupName; - if (string.IsNullOrEmpty(backupname) || backupname == Options.DefaultBackupName) - backupname = GenerateRandomName(); - else - { - foreach (var c in System.IO.Path.GetInvalidFileNameChars()) - backupname = backupname.Replace(c.ToString(), ""); - } - - var newpath = System.IO.Path.Combine(folder, backupname + ".sqlite"); - int max_tries = 100; - while (System.IO.File.Exists(newpath) && max_tries-- > 0) - newpath = System.IO.Path.Combine(folder, GenerateRandomName()); - - if (System.IO.File.Exists(newpath)) - throw new Duplicati.Library.Interface.UserInformationException("Unable to find a unique name for the database, please use --dbpath", "CannotCreateRandomName"); - - //Create a new one, add it to the list, and save it - configs.Add(new BackendEntry() - { - Type = type, - Server = server, - Path = path, - Prefix = prefix, - Username = username, - //Passwordhash = password, - Port = port, - Databasepath = newpath, - ParameterFile = null - }); - - if (!System.IO.Directory.Exists(folder)) - System.IO.Directory.CreateDirectory(folder); - - var settings = new Newtonsoft.Json.JsonSerializerSettings(); - settings.Formatting = Newtonsoft.Json.Formatting.Indented; - System.IO.File.WriteAllText(file, Newtonsoft.Json.JsonConvert.SerializeObject(configs, settings), System.Text.Encoding.UTF8); - - return newpath; - } - else - { - return matches[0].Databasepath; - } - - } - - public static string GenerateRandomName() - { - var rnd = new Random(); - - System.Text.StringBuilder backupName = new System.Text.StringBuilder(); - for (var i = 0; i < 10; i++) - backupName.Append((char)rnd.Next('A', 'Z' + 1)); - - return backupName.ToString(); - } - - public static bool IsDatabasePathInUse(string path) - { - var file = System.IO.Path.Combine(GetDefaultStorageFolderWithDebugSupport(CONFIG_FILE), CONFIG_FILE); - if (!System.IO.File.Exists(file)) - return false; - - return Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)).Any(x => string.Equals(path, x.Databasepath, Library.Utility.Utility.ClientFilenameStringComparison)); - } - } -} - +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using System.Collections.Generic; +using Duplicati.Library.Common.IO; + +#nullable enable + +namespace Duplicati.Library.Main +{ + public static class CLIDatabaseLocator + { + /// + /// The log tag for this class + /// + public static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(CLIDatabaseLocator)); + + /// + /// The entry for a backend configuration, used for serialization + /// + public class BackendEntry + { + /// + /// The type of the backend + /// + public required string Type; + /// + /// The server/hostname of the backend + /// + public required string Server; + /// + /// The path of the backend + /// + public required string Path; + /// + /// The prefix of the backup + /// + public required string Prefix; + /// + /// The username of the backend + /// + public required string? Username; + /// + /// The port of the backend + /// + public required int Port; + /// + /// The path to the database + /// + public required string Databasepath; + /// + /// The path to the parameter file + /// + public required string? ParameterFile; + } + + /// + /// The filename of the file with database configurations + /// + private const string CONFIG_FILE = "dbconfig.json"; + + /// + /// Gets the database path for a given backend url, using the JSON configuration file lookup. + /// If is true, a new database is created if none is found. + /// Otherwise, null is returned if no database is found. + /// + /// The backend url + /// The options to use + /// If true, a new database is created if none is found + /// If true, any username is accepted + /// The database path or null + public static string? GetDatabasePathForCLI(string backend, Options? options, bool autoCreate = true, bool anyUsername = false) + { + options ??= new Options(new Dictionary()); + + if (!string.IsNullOrEmpty(options.Dbpath)) + return options.Dbpath; + + // Ideally, this should use DataFolderManager.DATAFOLDER, but we cannot due to backwards compatibility + var folder = AutoUpdater.DataFolderLocator.GetDefaultStorageFolder(CONFIG_FILE, true); + // Emit a warning if the database is stored in the Windows folder + if (Util.IsPathUnderWindowsFolder(folder)) + Logging.Log.WriteWarningMessage(LOGTAG, "DatabaseInWindowsFolder", null, "The database config is stored in the Windows folder, this is not recommended as it will be deleted on Windows upgrades."); + + + var file = System.IO.Path.Combine(folder, CONFIG_FILE); + List configs; + if (!System.IO.File.Exists(file)) + configs = new List(); + else + configs = Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)) + ?? new List(); + + var uri = new Library.Utility.Uri(backend); + var server = uri.Host; + var path = uri.Path; + var type = uri.Scheme; + var port = uri.Port; + var username = uri.Username; + var prefix = options.Prefix; + + if (username == null || uri.Password == null) + { + var sopts = DynamicLoader.BackendLoader.GetSupportedCommands(backend); + var ropts = new Dictionary(options.RawOptions); + foreach (var k in uri.QueryParameters.AllKeys) + if (k != null) + ropts[k] = uri.QueryParameters[k]; + + if (sopts != null) + { + foreach (var o in sopts) + { + if (username == null && o.Aliases != null && o.Aliases.Contains("auth-username", StringComparer.OrdinalIgnoreCase) && ropts.ContainsKey(o.Name)) + username = ropts[o.Name]; + } + + foreach (var o in sopts) + { + if (username == null && o.Name.Equals("auth-username", StringComparison.OrdinalIgnoreCase) && ropts.ContainsKey("auth-username")) + username = ropts["auth-username"]; + } + } + } + + //Now find the one that matches :) + var matches = (from n in configs + where + n.Type == type && + n.Username == username && + n.Port == port && + n.Server == server && + n.Path == path && + n.Prefix == prefix + select n).ToList(); + + if (matches.Count > 1) + throw new Duplicati.Library.Interface.UserInformationException(string.Format("Multiple sources found for: {0}", backend), "MultipleLocalDatabaseSourcesFound"); + + // Re-select + if (matches.Count == 0 && anyUsername && string.IsNullOrEmpty(username)) + { + matches = (from n in configs + where + n.Type == type && + n.Port == port && + n.Server == server && + n.Path == path && + n.Prefix == prefix + select n).ToList(); + + if (matches.Count > 1) + throw new Duplicati.Library.Interface.UserInformationException(String.Format("Multiple sources found for \"{0}\", try supplying --{1}", backend, "auth-username"), "MultipleLocalDatabaseSourcesFound"); + } + + if (matches.Count == 0 && !autoCreate) + return null; + + if (matches.Count == 0) + { + var backupname = options.BackupName; + if (string.IsNullOrEmpty(backupname) || backupname == Options.DefaultBackupName) + backupname = GenerateRandomName(); + else + { + foreach (var c in System.IO.Path.GetInvalidFileNameChars()) + backupname = backupname.Replace(c.ToString(), ""); + } + + var newpath = System.IO.Path.Combine(folder, backupname + ".sqlite"); + int max_tries = 100; + while (System.IO.File.Exists(newpath) && max_tries-- > 0) + newpath = System.IO.Path.Combine(folder, GenerateRandomName()); + + if (System.IO.File.Exists(newpath)) + throw new Duplicati.Library.Interface.UserInformationException("Unable to find a unique name for the database, please use --dbpath", "CannotCreateRandomName"); + + //Create a new one, add it to the list, and save it + configs.Add(new BackendEntry() + { + Type = type, + Server = server, + Path = path, + Prefix = prefix, + Username = username, + //Passwordhash = password, + Port = port, + Databasepath = newpath, + ParameterFile = null + }); + + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + settings.Formatting = Newtonsoft.Json.Formatting.Indented; + System.IO.File.WriteAllText(file, Newtonsoft.Json.JsonConvert.SerializeObject(configs, settings), System.Text.Encoding.UTF8); + + return newpath; + } + else + { + return matches[0].Databasepath; + } + } + + /// + /// Generates a random name for a database + /// + /// A random name + public static string GenerateRandomName() + { + var rnd = new Random(); + + var backupName = new System.Text.StringBuilder(); + for (var i = 0; i < 10; i++) + backupName.Append((char)rnd.Next('A', 'Z' + 1)); + + return backupName.ToString(); + } + + /// + /// Checks if a database path is in use by any backup. + /// If the file does not exist, it is assumed to be free. + /// If the file exists, it is checked if it is in use by any backup. + /// + /// The path to check + /// true if the path is in use, false otherwise + public static bool IsDatabasePathInUse(string path) + { + // Ideally, this should use DataFolderManager.DATAFOLDER, but we cannot due to backwards compatibility + var file = System.IO.Path.Combine(AutoUpdater.DataFolderLocator.GetDefaultStorageFolder(CONFIG_FILE, false), CONFIG_FILE); + if (!System.IO.File.Exists(file)) + return false; + + return (Newtonsoft.Json.JsonConvert.DeserializeObject>(System.IO.File.ReadAllText(file, System.Text.Encoding.UTF8)) ?? []) + .Any(x => string.Equals(path, x.Databasepath, Library.Utility.Utility.ClientFilenameStringComparison)); + } + } +} + diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs index b6746b9b6..481daf97a 100644 --- a/Duplicati/Library/Main/Controller.cs +++ b/Duplicati/Library/Main/Controller.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,9 +25,11 @@ using System.Linq; using System.Threading; using Duplicati.Library.Utility; using Duplicati.Library.Common.IO; -using Duplicati.Library.Common; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; +using System.Threading.Tasks; +using Duplicati.Library.Main.Operation.Common; +using System.IO; namespace Duplicati.Library.Main { @@ -40,12 +42,16 @@ namespace Duplicati.Library.Main /// /// The backend url /// - private string m_backend; + private string m_backendUrl; /// /// The parsed type-safe version of the commandline options /// private readonly Options m_options; /// + /// The optional secret provider, if none provided in options + /// + public ISecretProvider SecretProvider { get; private set; } + /// /// The destination for all output messages during execution /// private IMessageSink m_messageSink; @@ -53,17 +59,7 @@ namespace Duplicati.Library.Main /// /// The current executing task /// - private ITaskControl m_currentTask = null; - - /// - /// The thread running the current task - /// - private System.Threading.Thread m_currentTaskThread = null; - - /// - /// The thread priority to reset to - /// - private System.Threading.ThreadPriority? m_resetPriority; + private ITaskControl m_currentTaskControl = null; /// /// If not null, active locale change that needs to be reset @@ -76,18 +72,23 @@ namespace Duplicati.Library.Main private ControllerMultiLogTarget m_logTarget; /// - /// The cancellation token for the running task + /// Callback method invoked when an operation is started /// - private readonly CancellationTokenSource m_cancellationTokenSource = new CancellationTokenSource(); + public Action OnOperationStarted { get; set; } + + /// + /// Callback method invoked when an operation is completed + /// + public Action OnOperationCompleted { get; set; } /// /// Constructs a new interface for performing backup and restore operations /// - /// The url for the backend to use + /// The url for the backend to use /// All required options - public Controller(string backend, Dictionary options, IMessageSink messageSink) + public Controller(string backendUrl, Dictionary options, IMessageSink messageSink) { - m_backend = backend; + m_backendUrl = backendUrl; m_options = new Options(options); m_messageSink = messageSink; } @@ -104,111 +105,119 @@ namespace Duplicati.Library.Main m_messageSink = new MultiMessageSink(m_messageSink, sink); } - public Duplicati.Library.Interface.IBackupResults Backup(string[] inputsources, IFilter filter = null) + /// + /// Sets a secret provider to use for all operations + /// + /// The secret provider to use + public void SetSecretProvider(ISecretProvider secretProvider) { - Library.UsageReporter.Reporter.Report("USE_BACKEND", new Library.Utility.Uri(m_backend).Scheme); - Library.UsageReporter.Reporter.Report("USE_COMPRESSION", m_options.CompressionModule); - Library.UsageReporter.Reporter.Report("USE_ENCRYPTION", m_options.EncryptionModule); + SecretProvider = secretProvider; + } + + public IBackupResults Backup(string[] inputsources, IFilter filter = null) + { + UsageReporter.Reporter.Report("USE_BACKEND", new Library.Utility.Uri(m_backendUrl).Scheme); + UsageReporter.Reporter.Report("USE_COMPRESSION", m_options.CompressionModule); + UsageReporter.Reporter.Report("USE_ENCRYPTION", m_options.EncryptionModule); CheckAutoCompactInterval(); CheckAutoVacuumInterval(); - return RunAction(new BackupResults(), ref inputsources, ref filter, (result) => + return RunAction(new BackupResults(), ref inputsources, ref filter, async (result, backendManager) => { - using (var h = new Operation.BackupHandler(m_backend, m_options, result)) - { - h.Run(ExpandInputSources(inputsources, filter), filter, m_cancellationTokenSource.Token); - } + using (var h = new Operation.BackupHandler(m_options, result)) + await h.RunAsync(ExpandInputSources(inputsources, filter), backendManager, filter) + .ConfigureAwait(false); - Library.UsageReporter.Reporter.Report("BACKUP_FILECOUNT", result.ExaminedFiles); - Library.UsageReporter.Reporter.Report("BACKUP_FILESIZE", result.SizeOfExaminedFiles); - Library.UsageReporter.Reporter.Report("BACKUP_DURATION", (long)result.Duration.TotalSeconds); + UsageReporter.Reporter.Report("BACKUP_FILECOUNT", result.ExaminedFiles); + UsageReporter.Reporter.Report("BACKUP_FILESIZE", result.SizeOfExaminedFiles); + UsageReporter.Reporter.Report("BACKUP_DURATION", (long)result.Duration.TotalSeconds); }); } - public Library.Interface.IRestoreResults Restore(string[] paths, Library.Utility.IFilter filter = null) + public IRestoreResults Restore(string[] paths, IFilter filter = null) { - return RunAction(new RestoreResults(), ref paths, ref filter, (result) => + return RunAction(new RestoreResults(), ref paths, ref filter, async (result, backendManager) => { - new Operation.RestoreHandler(m_backend, m_options, result).Run(paths, filter); + await new Operation.RestoreHandler(m_options, result) + .RunAsync(paths, backendManager, filter) + .ConfigureAwait(false); - Library.UsageReporter.Reporter.Report("RESTORE_FILECOUNT", result.RestoredFiles); - Library.UsageReporter.Reporter.Report("RESTORE_FILESIZE", result.SizeOfRestoredFiles); - Library.UsageReporter.Reporter.Report("RESTORE_DURATION", (long)result.Duration.TotalSeconds); + UsageReporter.Reporter.Report("RESTORE_FILECOUNT", result.RestoredFiles); + UsageReporter.Reporter.Report("RESTORE_FILESIZE", result.SizeOfRestoredFiles); + UsageReporter.Reporter.Report("RESTORE_DURATION", (long)result.Duration.TotalSeconds); }); } - public Duplicati.Library.Interface.IRestoreControlFilesResults RestoreControlFiles(IEnumerable files = null, Library.Utility.IFilter filter = null) + public IRestoreControlFilesResults RestoreControlFiles(IEnumerable files = null, IFilter filter = null) { - return RunAction(new RestoreControlFilesResults(), ref filter, (result) => - { - new Operation.RestoreControlFilesHandler(m_backend, m_options, result).Run(files, filter); - }); + return RunAction(new RestoreControlFilesResults(), ref filter, (result, backendManager) => + new Operation.RestoreControlFilesHandler(m_options, result) + .RunAsync(files, backendManager, filter) + ); } - public Duplicati.Library.Interface.IDeleteResults Delete() + public IDeleteResults Delete() { - return RunAction(new DeleteResults(), (result) => - { - new Operation.DeleteHandler(m_backend, m_options, result).Run(); - }); + return RunAction(new DeleteResults(), (result, backendManager) => + new Operation.DeleteHandler(m_options, result) + .RunAsync(backendManager) + ); } - public Duplicati.Library.Interface.IRepairResults Repair(Library.Utility.IFilter filter = null) + public IRepairResults Repair(IFilter filter = null) { - return RunAction(new RepairResults(), ref filter, (result) => - { - new Operation.RepairHandler(m_backend, m_options, result).Run(filter); - }); + return RunAction(new RepairResults(), ref filter, (result, backendManager) => + new Operation.RepairHandler(m_options, result) + .RunAsync(backendManager, filter) + ); } - public Duplicati.Library.Interface.IListResults List() + public IListResults List() { return List(null, null); } - public Duplicati.Library.Interface.IListResults List(string filterstring) + public IListResults List(string filterstring) { return List(filterstring == null ? null : new string[] { filterstring }, null); } - public Duplicati.Library.Interface.IListResults List(IEnumerable filterstrings, Library.Utility.IFilter filter) + public IListResults List(IEnumerable filterstrings, IFilter filter) { - return RunAction(new ListResults(), ref filter, (result) => + return RunAction(new ListResults(), ref filter, (result, backendManager) => + new Operation.ListFilesHandler(m_options, result) + .RunAsync(backendManager, filterstrings, filter) + ); + } + + public IListResults ListControlFiles(IEnumerable filterstrings, IFilter filter) + { + return RunAction(new ListResults(), ref filter, (result, backendManager) => + new Operation.ListControlFilesHandler(m_options, result) + .RunAsync(backendManager, filterstrings, filter) + ); + } + + public IListRemoteResults ListRemote() + { + return RunAction(new ListRemoteResults(), async (result, backendManager) => { - new Operation.ListFilesHandler(m_backend, m_options, result).Run(filterstrings, filter); + using (var tf = File.Exists(m_options.Dbpath) ? null : new TempFile()) + using (var db = new LocalDatabase(((string)tf) ?? m_options.Dbpath, "list-remote", true)) + result.SetResult(await backendManager.ListAsync(CancellationToken.None).ConfigureAwait(false)); }); } - public Duplicati.Library.Interface.IListResults ListControlFiles(IEnumerable filterstrings, Library.Utility.IFilter filter) + public IListRemoteResults DeleteAllRemoteFiles() { - return RunAction(new ListResults(), ref filter, (result) => - { - new Operation.ListControlFilesHandler(m_backend, m_options, result).Run(filterstrings, filter); - }); - } - - public Duplicati.Library.Interface.IListRemoteResults ListRemote() - { - return RunAction(new ListRemoteResults(), (result) => - { - using (var tf = System.IO.File.Exists(m_options.Dbpath) ? null : new Library.Utility.TempFile()) - using (var db = new Database.LocalDatabase(((string)tf) ?? m_options.Dbpath, "list-remote", true)) - using (var bk = new BackendManager(m_backend, m_options, result.BackendWriter, null)) - result.SetResult(bk.List()); - }); - } - - public Duplicati.Library.Interface.IListRemoteResults DeleteAllRemoteFiles() - { - return RunAction(new ListRemoteResults(), (result) => + return RunAction(new ListRemoteResults(), async (result, backendManager) => { result.OperationProgressUpdater.UpdatePhase(OperationPhase.Delete_Listing); - using (var bk = new BackendManager(m_backend, m_options, result.BackendWriter, null)) { // Only delete files that match the expected pattern and prefix - var list = bk.List() + var list = (await backendManager.ListAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) .Select(x => Volumes.VolumeBase.ParseFilename(x)) .Where(x => x != null) .Where(x => x.Prefix == m_options.Prefix) @@ -218,12 +227,12 @@ namespace Duplicati.Library.Main // from the backend. Otherwise, we may accidentally delete non-Duplicati files, or // files from a different Duplicati configuration that points to the same backend location // and uses the same prefix (see issues #2678, #3845, and #4244). - if (System.IO.File.Exists(m_options.Dbpath)) + if (File.Exists(m_options.Dbpath)) { using (LocalDatabase db = new LocalDatabase(m_options.Dbpath, "list-remote", true)) { - IEnumerable dbRemoteVolumes = db.GetRemoteVolumes(); - HashSet dbRemoteFiles = new HashSet(dbRemoteVolumes.Select(x => x.Name)); + var dbRemoteVolumes = db.GetRemoteVolumes(); + var dbRemoteFiles = dbRemoteVolumes.Select(x => x.Name).ToHashSet(); list = list.Where(x => dbRemoteFiles.Contains(x.File.Name)).ToList(); } } @@ -234,7 +243,7 @@ namespace Duplicati.Library.Main { try { - bk.Delete(list[i].File.Name, list[i].File.Size, true); + await backendManager.DeleteAsync(list[i].File.Name, list[i].File.Size, true, result.TaskControl.ProgressToken).ConfigureAwait(false); } catch (Exception ex) { @@ -247,67 +256,67 @@ namespace Duplicati.Library.Main }); } - public Duplicati.Library.Interface.ICompactResults Compact() + public ICompactResults Compact() { CheckAutoVacuumInterval(); - return RunAction(new CompactResults(), (result) => - { - new Operation.CompactHandler(m_backend, m_options, result).Run(); - }); + return RunAction(new CompactResults(), (result, backendManager) => + new Operation.CompactHandler(m_options, result) + .RunAsync(backendManager) + ); } - public Duplicati.Library.Interface.IRecreateDatabaseResults UpdateDatabaseWithVersions(Library.Utility.IFilter filter = null) + public IRecreateDatabaseResults UpdateDatabaseWithVersions(IFilter filter = null) { var filelistfilter = Operation.RestoreHandler.FilterNumberedFilelist(m_options.Time, m_options.Version, singleTimeMatch: true); - return RunAction(new RecreateDatabaseResults(), ref filter, (result) => + return RunAction(new RecreateDatabaseResults(), ref filter, async (result, backendManager) => { - using (var h = new Operation.RecreateDatabaseHandler(m_backend, m_options, result)) - h.RunUpdate(filter, filelistfilter); + using (var h = new Operation.RecreateDatabaseHandler(m_options, result)) + await h.RunUpdateAsync(backendManager, filter, filelistfilter, null) + .ConfigureAwait(false); }); } - public Duplicati.Library.Interface.ICreateLogDatabaseResults CreateLogDatabase(string targetpath) + public ICreateLogDatabaseResults CreateLogDatabase(string targetpath) { var t = new string[] { targetpath }; - return RunAction(new CreateLogDatabaseResults(), ref t, (result) => - { - new Operation.CreateBugReportHandler(t[0], m_options, result).Run(); - }); + return RunAction(new CreateLogDatabaseResults(), ref t, (result, backendManager) => + new Operation.CreateBugReportHandler(t[0], m_options, result).RunAsync() + ); } - public Duplicati.Library.Interface.IListChangesResults ListChanges(string baseVersion, string targetVersion, IEnumerable filterstrings = null, Library.Utility.IFilter filter = null, Action>> callback = null) + public IListChangesResults ListChanges(string baseVersion, string targetVersion, IEnumerable filterstrings = null, IFilter filter = null, Action>> callback = null) { var t = new string[] { baseVersion, targetVersion }; - return RunAction(new ListChangesResults(), ref t, ref filter, (result) => - { - new Operation.ListChangesHandler(m_backend, m_options, result).Run(t[0], t[1], filterstrings, filter, callback); - }); + return RunAction(new ListChangesResults(), ref t, ref filter, (result, backendManager) => + new Operation.ListChangesHandler(m_options, result) + .RunAsync(t[0], t[1], backendManager, filterstrings, filter, callback) + ); } - public Duplicati.Library.Interface.IListAffectedResults ListAffected(List args, Action callback = null) + public IListAffectedResults ListAffected(List args, Action callback = null) { - return RunAction(new ListAffectedResults(), (result) => - { - new Operation.ListAffected(m_options, result).Run(args, callback); - }); + return RunAction(new ListAffectedResults(), (result, backendManager) => + new Operation.ListAffected(m_options, result) + .RunAsync(args, callback) + ); } - public Duplicati.Library.Interface.ITestResults Test(long samples = 1) + public ITestResults Test(long samples = 1) { if (!m_options.RawOptions.ContainsKey("full-remote-verification")) m_options.RawOptions["full-remote-verification"] = "true"; - return RunAction(new TestResults(), (result) => - { - new Operation.TestHandler(m_backend, m_options, result).Run(samples); - }); + return RunAction(new TestResults(), (result, backendManager) => + new Operation.TestHandler(m_options, result) + .RunAsync(samples, backendManager) + ); } - public Library.Interface.ITestFilterResults TestFilter(string[] paths, Library.Utility.IFilter filter = null) + public ITestFilterResults TestFilter(string[] paths, IFilter filter = null) { m_options.RawOptions["dry-run"] = "true"; m_options.RawOptions["dbpath"] = "INVALID!"; @@ -316,46 +325,45 @@ namespace Duplicati.Library.Main var filtertag = Logging.Log.LogTagFromType(typeof(Operation.Backup.FileEnumerationProcess)); using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag))) { - return RunAction(new TestFilterResults(), ref paths, ref filter, (result) => - { - new Operation.TestFilterHandler(m_options, result).Run(ExpandInputSources(paths, filter), filter, m_cancellationTokenSource.Token); - }); + return RunAction(new TestFilterResults(), ref paths, ref filter, (result, backendManager) => + new Operation.TestFilterHandler(m_options, result) + .RunAsync(ExpandInputSources(paths, filter), filter) + ); } } - public Library.Interface.ISystemInfoResults SystemInfo() + public ISystemInfoResults SystemInfo() { - return RunAction(new SystemInfoResults(), result => - { - Operation.SystemInfoHandler.Run(result); - }); + return RunAction(new SystemInfoResults(), (result, backendManager) => + Operation.SystemInfoHandler.RunAsync(result) + ); } - public Library.Interface.IPurgeFilesResults PurgeFiles(Library.Utility.IFilter filter) + public IPurgeFilesResults PurgeFiles(IFilter filter) { - return RunAction(new PurgeFilesResults(), result => - { - new Operation.PurgeFilesHandler(m_backend, m_options, result).Run(filter); - }); + return RunAction(new PurgeFilesResults(), (result, backendManager) => + new Operation.PurgeFilesHandler(m_options, result) + .RunAsync(backendManager, filter) + ); } - public Library.Interface.IListBrokenFilesResults ListBrokenFiles(Library.Utility.IFilter filter, Func callbackhandler = null) + public IListBrokenFilesResults ListBrokenFiles(IFilter filter, Func callbackhandler = null) { - return RunAction(new ListBrokenFilesResults(), result => - { - new Operation.ListBrokenFilesHandler(m_backend, m_options, result).Run(filter, callbackhandler); - }); + return RunAction(new ListBrokenFilesResults(), (result, backendManager) => + new Operation.ListBrokenFilesHandler(m_options, result) + .RunAsync(backendManager, filter, callbackhandler) + ); } - public Library.Interface.IPurgeBrokenFilesResults PurgeBrokenFiles(Library.Utility.IFilter filter) + public IPurgeBrokenFilesResults PurgeBrokenFiles(IFilter filter) { - return RunAction(new PurgeBrokenFilesResults(), result => - { - new Operation.PurgeBrokenFilesHandler(m_backend, m_options, result).Run(filter); - }); + return RunAction(new PurgeBrokenFilesResults(), (result, backendManager) => + new Operation.PurgeBrokenFilesHandler(m_options, result) + .RunAsync(backendManager, filter) + ); } - public Library.Interface.ISendMailResults SendMail() + public ISendMailResults SendMail() { m_options.RawOptions["send-mail-level"] = "all"; m_options.RawOptions["send-mail-any-operation"] = "true"; @@ -377,47 +385,51 @@ namespace Duplicati.Library.Main var filtertag = Logging.Log.LogTagFromType(); using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag))) { - return RunAction(new SendMailResults(), result => - { - result.Lines = new string[0]; - System.Threading.Thread.Sleep(5); - }); + return RunAction(new SendMailResults(), (result, backendManager) => + Task.Run(() => + { + result.Lines = new string[0]; + System.Threading.Thread.Sleep(5); + }) + ); } } - public Library.Interface.IVacuumResults Vacuum() + public IVacuumResults Vacuum() { - return RunAction(new VacuumResults(), result => - { - new Operation.VacuumHandler(m_options, result).Run(); - }); + return RunAction(new VacuumResults(), (result, backendManager) => + new Operation.VacuumHandler(m_options, result) + .RunAsync() + ); } - private T RunAction(T result, Action method) - where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination + private T RunAction(T result, Func method) + where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { var tmp = new string[0]; IFilter tempfilter = null; return RunAction(result, ref tmp, ref tempfilter, method); } - private T RunAction(T result, ref string[] paths, Action method) - where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination + private T RunAction(T result, ref string[] paths, Func method) + where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { IFilter tempfilter = null; return RunAction(result, ref paths, ref tempfilter, method); } - private T RunAction(T result, ref IFilter filter, Action method) - where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination + private T RunAction(T result, ref IFilter filter, Func method) + where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { var tmp = new string[0]; return RunAction(result, ref tmp, ref filter, method); } - private T RunAction(T result, ref string[] paths, ref IFilter filter, Action method) - where T : ISetCommonOptions, ITaskControl, Logging.ILogDestination + private T RunAction(T result, ref string[] paths, ref IFilter filter, Func method) + where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { + OnOperationStarted?.Invoke(result); + var resultSetter = result as ISetCommonOptions; m_logTarget = new ControllerMultiLogTarget(result, Logging.LogMessageType.Information, null); using (Logging.Log.StartScope(m_logTarget, null)) { @@ -426,9 +438,9 @@ namespace Duplicati.Library.Main try { - m_currentTask = result; - m_currentTaskThread = System.Threading.Thread.CurrentThread; - + m_currentTaskControl = result.TaskControl; + m_options.MainAction = result.MainOperation; + ApplySecretProvider(CancellationToken.None).Await(); SetupCommonOptions(result, ref paths, ref filter); Logging.Log.WriteInformationMessage(LOGTAG, "StartingOperation", Strings.Controller.StartingOperationMessage(m_options.MainAction)); @@ -436,131 +448,135 @@ namespace Duplicati.Library.Main using (new Logging.Timer(LOGTAG, string.Format("Run{0}", result.MainOperation), string.Format("Running {0}", result.MainOperation))) using (new CoCoL.IsolatedChannelScope()) using (m_options.ConcurrencyMaxThreads <= 0 ? null : new CoCoL.CappedThreadedThreadPool(m_options.ConcurrencyMaxThreads)) - method(result); - - if (result.EndTime.Ticks == 0) - result.EndTime = DateTime.UtcNow; - result.SetDatabase(null); - if (result is BasicResults r) + using (var backend = new Backend.BackendManager(m_backendUrl, m_options, result.BackendWriter, result.TaskControl)) { - r.Interrupted = false; + method(result, backend).Await(); + + // TODO: Should also have a single shared database connection for all operations + // The transactions should be managed inside the connection, and not passed around + + // This would allow us to pass the database instance to the backend manager + // And safeguard against remote operations not being logged in the database + + // This would also allow us to control the unclean shutdown flag, + // by toggling this on start and completion of transfers in the manager, + // instead of relying on the operations to correctly toggle the flag + if (LocalDatabase.Exists(m_options.Dbpath)) + { + using (var db = new LocalDatabase(m_options.Dbpath, result.MainOperation.ToString(), true)) + backend.StopRunnerAndFlushMessages(db, null).Await(); + } + else + { + backend.StopRunnerAndDiscardMessages(); + } } - OnOperationComplete(result, null); + if (resultSetter.EndTime.Ticks == 0) + resultSetter.EndTime = DateTime.UtcNow; + resultSetter.Interrupted = false; + + if (LocalDatabase.Exists(m_options.Dbpath) && !m_options.Dryrun) + { + using (var db = new LocalDatabase(m_options.Dbpath, null, true)) + { + db.WriteResults(result); + db.PurgeLogData(m_options.LogRetention); + db.PurgeDeletedVolumes(DateTime.UtcNow); + + // Vacuum is done AFTER the results are written to the database + // This means that the information about the vacuum is not stored in the database, + // but will be reported in the log output and messages sent with any of the reporting modules + if (m_options.AutoVacuum && result is IResultsWithVacuum vacuumResults && result is BasicResults basicResults) + { + try + { + vacuumResults.VacuumResults = new VacuumResults(basicResults); + new Operation.VacuumHandler(m_options, (VacuumResults)vacuumResults.VacuumResults) + .RunAsync().Await(); + } + catch (Exception ex) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "FailedToVacuum", ex, "Failed to vacuum database"); + } + } + } + } + + OperationComplete(result, null); Logging.Log.WriteInformationMessage(LOGTAG, "CompletedOperation", Strings.Controller.CompletedOperationMessage(m_options.MainAction)); return result; } + // Handle custom abort exception from run-script + catch (OperationAbortException oae) + { + resultSetter.EndTime = DateTime.UtcNow; + // Log this as a normal operation, as the script raising the exception, + // has already populated either warning or log messages as required + Logging.Log.WriteInformationMessage(LOGTAG, "AbortOperation", "Aborting operation by request, requested result: {0}", oae.AbortReason); + + resultSetter.Interrupted = true; + try + { + // No operation was started in database, so write logs to new operation + if (LocalDatabase.Exists(m_options.Dbpath) && !m_options.Dryrun) + using (var db = new LocalDatabase(m_options.Dbpath, result.MainOperation.ToString(), true)) + db.WriteResults(result); + } + catch (Exception we) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedWriteOperation", we, we.Message); + } + + // Do not propagate the cancel exception + OperationComplete(result, null); + + return result; + } catch (Exception ex) { - result.EndTime = DateTime.UtcNow; + Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message)); - if (ex is Library.Interface.OperationAbortException oae) + try { - // Log this as a normal operation, as the script raising the exception, - // has already populated either warning or log messages as required - Logging.Log.WriteInformationMessage(LOGTAG, "AbortOperation", "Aborting operation by request, requested result: {0}", oae.AbortReason); - if (result is BasicResults basicResults) - { - basicResults.Interrupted = true; - try - { - // No operation was started in database, so write logs to new operation - using (var db = new LocalDatabase(m_options.Dbpath, result.MainOperation.ToString(), true)) - { - basicResults.SetDatabase(db); - db.WriteResults(); - } + basicResults.OperationProgressUpdater.UpdatePhase(OperationPhase.Error); - // Do not propagate the cancel exception - OnOperationComplete(result, null); - } - catch { } - } - else - { - // Perform the module shutdown - OnOperationComplete(ex, ex); - } - - return result; + resultSetter.Fatal = true; + // Write logs to previous operation if database exists + if (LocalDatabase.Exists(m_options.Dbpath) && !m_options.Dryrun) + using (var db = new LocalDatabase(m_options.Dbpath, null, true)) + db.WriteResults(result); } - else + catch (Exception we) { - Logging.Log.WriteErrorMessage(LOGTAG, "FailedOperation", ex, Strings.Controller.FailedOperationMessage(m_options.MainAction, ex.Message)); - - if (result is BasicResults basicResults) - { - try - { - basicResults.OperationProgressUpdater.UpdatePhase(OperationPhase.Error); - basicResults.Fatal = true; - // Write logs to previous operation if database exists - if (LocalDatabase.Exists(m_options.Dbpath)) - { - using (var db = new LocalDatabase(m_options.Dbpath, null, true)) - { - basicResults.SetDatabase(db); - db.WriteResults(); - } - } - - // Report the result, and the failure - OnOperationComplete(result, ex); - - } - catch { } - } - else - { - // Perform the module shutdown - OnOperationComplete(ex, ex); - } - - throw; + Logging.Log.WriteWarningMessage(LOGTAG, "FailedWriteOperation", we, we.Message); } + // Report the result, and the failure + OperationComplete(result, ex); + + throw; } finally { - m_currentTask = null; - m_currentTaskThread = null; + m_currentTaskControl = null; } } } - /// - /// Attempts to get the locale, but delays linking to the calls as they are missing in some environments - /// - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private static void DoGetLocale(out System.Globalization.CultureInfo locale, out System.Globalization.CultureInfo uiLocale) - { - locale = System.Globalization.CultureInfo.DefaultThreadCurrentCulture; - uiLocale = System.Globalization.CultureInfo.DefaultThreadCurrentUICulture; - } - - /// - /// Attempts to set the locale, but delays linking to the calls as they are missing in some environments - /// - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - private static void DoSetLocale(System.Globalization.CultureInfo locale, System.Globalization.CultureInfo uiLocale) - { - System.Globalization.CultureInfo.DefaultThreadCurrentCulture = locale; - System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = uiLocale; - } - - private void OnOperationComplete(object result, Exception exception) + private void OperationComplete(IBasicResults result, Exception exception) { if (m_options != null && m_options.LoadedModules != null) { - foreach (KeyValuePair mx in m_options.LoadedModules) + foreach (KeyValuePair mx in m_options.LoadedModules) if (mx.Key && mx.Value is IGenericCallbackModule module) try { module.OnFinish(result, exception); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, $"OnFinishError{mx.Key}", ex, "OnFinish callback {0} failed: {1}", mx.Key, ex.Message); } - foreach (KeyValuePair mx in m_options.LoadedModules) + foreach (KeyValuePair mx in m_options.LoadedModules) if (mx.Key && mx.Value is IDisposable disposable) try { disposable.Dispose(); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, $"DisposeError{mx.Key}", ex, "Dispose for {0} failed: {1}", mx.Key, ex.Message); } @@ -568,12 +584,6 @@ namespace Duplicati.Library.Main m_options.LoadedModules.Clear(); } - if (m_resetPriority != null) - { - System.Threading.Thread.CurrentThread.Priority = m_resetPriority.Value; - m_resetPriority = null; - } - if (m_localeChange != null) { m_localeChange.Dispose(); @@ -585,6 +595,8 @@ namespace Duplicati.Library.Main m_logTarget.Dispose(); m_logTarget = null; } + + OnOperationCompleted?.Invoke(result, exception); } private void SetupCommonOptions(ISetCommonOptions result, ref string[] paths, ref IFilter filter) @@ -606,16 +618,16 @@ namespace Duplicati.Library.Main //Load all generic modules m_options.LoadedModules.Clear(); - foreach (Library.Interface.IGenericModule m in DynamicLoader.GenericLoader.Modules) - m_options.LoadedModules.Add(new KeyValuePair(!m_options.DisableModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase) && (m.LoadAsDefault || m_options.EnableModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase)), m)); + foreach (IGenericModule m in DynamicLoader.GenericLoader.Modules) + m_options.LoadedModules.Add(new KeyValuePair(!m_options.DisableModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase) && (m.LoadAsDefault || m_options.EnableModules.Contains(m.Key, StringComparer.OrdinalIgnoreCase)), m)); // Make the filter read-n-write able in the generic modules - var pristinefilter = string.Join(System.IO.Path.PathSeparator.ToString(), FilterExpression.Serialize(filter)); + var pristinefilter = string.Join(Path.PathSeparator.ToString(), FilterExpression.Serialize(filter)); m_options.RawOptions["filter"] = pristinefilter; // Store the URL connection options separately, as these should only be visible to modules implementing IConnectionModule var conopts = new Dictionary(m_options.RawOptions); - var qp = new Library.Utility.Uri(m_backend).QueryParameters; + var qp = new Library.Utility.Uri(m_backendUrl).QueryParameters; foreach (var k in qp.Keys) conopts[(string)k] = qp[(string)k]; @@ -634,7 +646,7 @@ namespace Duplicati.Library.Main foreach (var mx in LoadedModules) if (mx.Key) { - if (mx.Value is Library.Interface.IConnectionModule) + if (mx.Value is IConnectionModule) mx.Value.Configure(conopts); else mx.Value.Configure(m_options.RawOptions); @@ -651,21 +663,21 @@ namespace Duplicati.Library.Main } if (mx.Value is IGenericCallbackModule module) - module.OnStart(result.MainOperation.ToString(), ref m_backend, ref paths); + module.OnStart(result.MainOperation.ToString(), ref m_backendUrl, ref paths); } // If the filters were changed by a module, read them back in if (pristinefilter != m_options.RawOptions["filter"]) { - filter = FilterExpression.Deserialize(m_options.RawOptions["filter"].Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); + filter = FilterExpression.Deserialize(m_options.RawOptions["filter"].Split(new string[] { Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); } m_options.RawOptions.Remove("filter"); // "--filter" is not a supported command line option if (!string.IsNullOrEmpty(m_options.Logfile)) { - var path = System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(m_options.Logfile)); - if (!System.IO.Directory.Exists(path)) - System.IO.Directory.CreateDirectory(path); + var path = Path.GetDirectoryName(Path.GetFullPath(m_options.Logfile)); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); m_logTarget.AddTarget( new Library.Logging.StreamLogDestination(m_options.Logfile), @@ -693,18 +705,20 @@ namespace Duplicati.Library.Main } } - if (!string.IsNullOrEmpty(m_options.ThreadPriority)) - { - m_resetPriority = System.Threading.Thread.CurrentThread.Priority; - System.Threading.Thread.CurrentThread.Priority = Library.Utility.Utility.ParsePriority(m_options.ThreadPriority); - } - if (string.IsNullOrEmpty(m_options.Dbpath)) - m_options.Dbpath = DatabaseLocator.GetDatabasePath(m_backend, m_options); + m_options.Dbpath = CLIDatabaseLocator.GetDatabasePathForCLI(m_backendUrl, m_options); ValidateOptions(); } + private async Task ApplySecretProvider(CancellationToken cancellationToken) + { + var args = new[] { new Library.Utility.Uri(m_backendUrl) }; + await SecretProviderHelper.ApplySecretProviderAsync([], args, m_options.RawOptions, TempFolder.SystemTempPath, SecretProvider, cancellationToken); + // Write back the backend argument, if it was modified by the secret provider + m_backendUrl = args[0].ToString(); + } + /// /// This function will examine all options passed on the commandline, and test for unsupported or deprecated values. /// Any errors will be logged into the statistics module. @@ -739,6 +753,9 @@ namespace Duplicati.Library.Main if (!string.IsNullOrWhiteSpace(m_options.Prefix) && m_options.Prefix.Contains("-")) throw new Interface.UserInformationException("The prefix cannot contain hyphens (-)", "PrefixCannotContainHyphens"); + if (m_options.VolumeSize < m_options.Blocksize * 2) + throw new Interface.UserInformationException("The volume size must be at least twice the block size", "VolumeSizeTooSmall"); + //Check validity of retention-policy option value try { @@ -757,25 +774,25 @@ namespace Duplicati.Library.Main } //Keep a list of all supplied options - Dictionary ropts = new Dictionary(m_options.RawOptions); + var ropts = new Dictionary(m_options.RawOptions); //Keep a list of all supported options - Dictionary supportedOptions = new Dictionary(); + var supportedOptions = new Dictionary(); //There are a few internal options that are not accessible from outside, and thus not listed foreach (string s in Options.InternalOptions) supportedOptions[s] = null; //Figure out what module options are supported in the current setup - List moduleOptions = new List(); - Dictionary disabledModuleOptions = new Dictionary(); + var moduleOptions = new List(); + var disabledModuleOptions = new Dictionary(); - foreach (KeyValuePair m in m_options.LoadedModules) + foreach (var m in m_options.LoadedModules) if (m.Value.SupportedCommands != null) if (m.Key) moduleOptions.AddRange(m.Value.SupportedCommands); else - foreach (Library.Interface.ICommandLineArgument c in m.Value.SupportedCommands) + foreach (ICommandLineArgument c in m.Value.SupportedCommands) { disabledModuleOptions[c.Name] = m.Value.DisplayName + " (" + m.Value.Key + ")"; @@ -786,22 +803,22 @@ namespace Duplicati.Library.Main // Throw url-encoded options into the mix //TODO: This can hide values if both commandline and url-parameters supply the same key - var ext = new Library.Utility.Uri(m_backend).QueryParameters; + var ext = new Library.Utility.Uri(m_backendUrl).QueryParameters; foreach (var k in ext.AllKeys) ropts[k] = ext[k]; //Now run through all supported options, and look for deprecated options foreach (var l in new IEnumerable[] { m_options.SupportedCommands, - DynamicLoader.BackendLoader.GetSupportedCommands(m_backend), + DynamicLoader.BackendLoader.GetSupportedCommands(m_backendUrl), m_options.NoEncryption ? null : DynamicLoader.EncryptionLoader.GetSupportedCommands(m_options.EncryptionModule), moduleOptions, DynamicLoader.CompressionLoader.GetSupportedCommands(m_options.CompressionModule) }) { if (l != null) - foreach (Library.Interface.ICommandLineArgument a in l) + foreach (ICommandLineArgument a in l) { - if (supportedOptions.ContainsKey(a.Name) && !Options.KnownDuplicates.Contains(a.Name, StringComparer.OrdinalIgnoreCase)) + if (supportedOptions.ContainsKey(a.Name) && !Options.KnownDuplicates.Contains(a.Name)) Logging.Log.WriteWarningMessage(LOGTAG, "DuplicateOption", null, Strings.Controller.DuplicateOptionNameWarning(a.Name)); supportedOptions[a.Name] = a; @@ -809,7 +826,7 @@ namespace Duplicati.Library.Main if (a.Aliases != null) foreach (string s in a.Aliases) { - if (supportedOptions.ContainsKey(s) && !Options.KnownDuplicates.Contains(s, StringComparer.OrdinalIgnoreCase)) + if (supportedOptions.ContainsKey(s) && !Options.KnownDuplicates.Contains(s)) Logging.Log.WriteWarningMessage(LOGTAG, "DuplicateOption", null, Strings.Controller.DuplicateOptionNameWarning(s)); supportedOptions[s] = a; @@ -837,7 +854,7 @@ namespace Duplicati.Library.Main } //Now look for options that were supplied but not supported - foreach (string s in ropts.Keys) + foreach (var s in ropts.Keys) if (!supportedOptions.ContainsKey(s)) if (disabledModuleOptions.ContainsKey(s)) Logging.Log.WriteWarningMessage(LOGTAG, "UnsupportedDisabledModule", null, Strings.Controller.UnsupportedOptionDisabledModuleWarning(s, disabledModuleOptions[s]), null); @@ -845,25 +862,24 @@ namespace Duplicati.Library.Main Logging.Log.WriteWarningMessage(LOGTAG, "UnsupportedOption", null, Strings.Controller.UnsupportedOptionWarning(s), null); //Look at the value supplied for each argument and see if is valid according to its type - foreach (string s in ropts.Keys) + foreach (var s in ropts.Keys) { - Library.Interface.ICommandLineArgument arg; - if (supportedOptions.TryGetValue(s, out arg) && arg != null) + if (supportedOptions.TryGetValue(s, out var arg) && arg != null) { - string validationMessage = ValidateOptionValue(arg, s, ropts[s]); - if (validationMessage != null) + var validationMessage = CommandLineArgumentValidator.ValidateOptionValue(arg, s, ropts[s]); + if (!string.IsNullOrWhiteSpace(validationMessage)) Logging.Log.WriteWarningMessage(LOGTAG, "OptionValidationError", null, validationMessage); } } - // For now, warn not to use 7z - if (string.Equals(m_options.CompressionModule, "7z", StringComparison.OrdinalIgnoreCase)) - Logging.Log.WriteWarningMessage(LOGTAG, "7zModuleHasIssues", null, "The 7z compression module has known issues and should only be used for experimental purposes"); - //Inform the user about the deprecated Tardigrade-Backend. They should switch to Storj DCS instead. - if (string.Equals(new Library.Utility.Uri(m_backend).Scheme, "tardigrade", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(new Library.Utility.Uri(m_backendUrl).Scheme, "tardigrade", StringComparison.OrdinalIgnoreCase)) Logging.Log.WriteWarningMessage(LOGTAG, "TardigradeRename", null, "The Tardigrade-backend got renamed to Storj DCS - please migrate your backups to the new configuration by changing the destination storage type to Storj DCS."); + //Inform the user about the unmaintained Mega support library + if (string.Equals(new Library.Utility.Uri(m_backendUrl).Scheme, "mega", StringComparison.OrdinalIgnoreCase)) + Logging.Log.WriteWarningMessage(LOGTAG, "MegaUnmaintained", null, "The Mega support library is currently unmaintained and may not work as expected. Mega has not published an official API so it may break at any moment. Please consider migrating to another backend."); + //TODO: Based on the action, see if all options are relevant } @@ -875,11 +891,11 @@ namespace Duplicati.Library.Main private string[] ExpandInputSources(string[] inputsources, IFilter filter) { if (inputsources == null || inputsources.Length == 0) - throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.NoSourceFoldersError, "NoSourceFolders"); + throw new UserInformationException(Strings.Controller.NoSourceFoldersError, "NoSourceFolders"); var sources = new List(inputsources.Length); - System.IO.DriveInfo[] drives = null; + DriveInfo[] drives = null; //Make sure they all have the same format and exist foreach (var inputsource in inputsources) @@ -890,11 +906,11 @@ namespace Duplicati.Library.Main { // *: drive paths are only supported on Windows clients // Lazily load the drive info - drives = drives ?? System.IO.DriveInfo.GetDrives(); + drives = drives ?? DriveInfo.GetDrives(); // Replace the drive letter with each available drive string sourcePath = inputsource.Substring(1); - foreach (System.IO.DriveInfo drive in drives) + foreach (DriveInfo drive in drives) { string expandedSource = drive.Name[0] + sourcePath; Logging.Log.WriteVerboseMessage(LOGTAG, "AddingSourcePathFromWildcard", @"Adding source path ""{0}"" due to wildcard source path ""{1}""", expandedSource, inputsource); @@ -921,14 +937,14 @@ namespace Duplicati.Library.Main { // If we aren't allow to have missing sources, throw an exception indicating we couldn't find a drive where this volume is mounted if (!m_options.AllowMissingSource) - throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameNotFoundError(inputsource, volumeGuid), "MissingSourceFolder"); + throw new UserInformationException(Strings.Controller.SourceVolumeNameNotFoundError(inputsource, volumeGuid), "MissingSourceFolder"); } } else { // If we aren't allow to have missing sources, throw an exception indicating we couldn't find this volume if (!m_options.AllowMissingSource) - throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.SourceVolumeNameInvalidError(inputsource), "SourceVolumeNameInvalid"); + throw new UserInformationException(Strings.Controller.SourceVolumeNameInvalidError(inputsource), "SourceVolumeNameInvalid"); } } else @@ -943,17 +959,30 @@ namespace Duplicati.Library.Main string source; try { + // Check if this is a mounted path + if (expandedSource.StartsWith("@")) + { + // TODO: If the remote source fails to load, + // this will be an enumeration warning, but will result + // in the backup being recorded without files from the source + // Eventually, this could lead to retention deletion, + // causing the last backup with the data from the source to be deleted + foundAnyPaths = true; + sources.Add(expandedSource); + continue; + } + // TODO: This expands "C:" to CWD, but not C:\ - source = System.IO.Path.GetFullPath(expandedSource); + source = Path.GetFullPath(expandedSource); } catch (Exception ex) { // Note that we use the original source (with the *) in the error - throw new Duplicati.Library.Interface.UserInformationException(Strings.Controller.InvalidPathError(expandedSource, ex.Message), "InputSourceInvalid", ex); + throw new UserInformationException(Strings.Controller.InvalidPathError(expandedSource, ex.Message), "InputSourceInvalid", ex); } - var fi = new System.IO.FileInfo(source); - var di = new System.IO.DirectoryInfo(source); + var fi = new FileInfo(source); + var di = new DirectoryInfo(source); if (fi.Exists || di.Exists) { foundAnyPaths = true; @@ -985,16 +1014,16 @@ namespace Duplicati.Library.Main { if (unauthorized) { - throw new System.IO.IOException(Strings.Controller.SourceUnauthorizedError(inputsource)); + throw new IOException(Strings.Controller.SourceUnauthorizedError(inputsource)); } - throw new System.IO.IOException(Strings.Controller.SourceIsMissingError(inputsource)); + throw new IOException(Strings.Controller.SourceIsMissingError(inputsource)); } } //Sanity check for duplicate files/folders ISet pathDuplicates; - sources = Library.Utility.Utility.GetUniqueItems(sources, Library.Utility.Utility.ClientFilenameStringComparer, out pathDuplicates).OrderBy(a => a).ToList(); + sources = Library.Utility.Utility.GetUniqueItems(sources, Library.Utility.Utility.ClientFilenameStringComparer, out pathDuplicates).ToList(); foreach (var pathDuplicate in pathDuplicates) Logging.Log.WriteVerboseMessage(LOGTAG, "RemoveDuplicateSource", "Removing duplicate source: {0}", pathDuplicate); @@ -1014,7 +1043,7 @@ namespace Duplicati.Library.Main if (excludes) { Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingSubfolderSource", "Removing source \"{0}\" because it is a subfolder of \"{1}\", and using it as an include filter", sources[i], sources[j]); - filter = Library.Utility.JoinedFilterExpression.Join(new FilterExpression(sources[i]), filter); + filter = JoinedFilterExpression.Join(new FilterExpression(sources[i]), filter); } else Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingSubfolderSource", "Removing source \"{0}\" because it is a subfolder or subfile of \"{1}\"", sources[i], sources[j]); @@ -1030,130 +1059,33 @@ namespace Duplicati.Library.Main return sources.ToArray(); } - /// - /// Checks if the value passed to an option is actually valid. - /// - /// The argument being validated - /// The name of the option to validate - /// The value to check - /// Null if no errors are found, an error message otherwise - private static string ValidateOptionValue(Library.Interface.ICommandLineArgument arg, string optionname, string value) + public void Pause(bool alsoTransfers) { - if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration) - { - bool found = false; - foreach (string v in arg.ValidValues ?? new string[0]) - if (string.Equals(v, value, StringComparison.OrdinalIgnoreCase)) - { - found = true; - break; - } - - if (!found) - return Strings.Controller.UnsupportedEnumerationValue(optionname, value, arg.ValidValues ?? new string[0]); - - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Flags) - { - bool validatedAllFlags = false; - var flags = (value ?? string.Empty).ToLowerInvariant().Split(new[] { "," }, StringSplitOptions.None).Select(flag => flag.Trim()).Distinct(); - var validFlags = arg.ValidValues ?? new string[0]; - - foreach (var flag in flags) - { - if (!validFlags.Any(validFlag => string.Equals(validFlag, flag, StringComparison.OrdinalIgnoreCase))) - { - validatedAllFlags = false; - break; - } - - validatedAllFlags = true; - } - - if (!validatedAllFlags) - { - return Strings.Controller.UnsupportedFlagsValue(optionname, value, validFlags); - } - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean) - { - if (!string.IsNullOrEmpty(value) && Library.Utility.Utility.ParseBool(value, true) != Library.Utility.Utility.ParseBool(value, false)) - return Strings.Controller.UnsupportedBooleanValue(optionname, value); - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Integer) - { - if (!long.TryParse(value, out _)) - return Strings.Controller.UnsupportedIntegerValue(optionname, value); - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path) - { - foreach (string p in value.Split(System.IO.Path.DirectorySeparatorChar)) - if (p.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) - return Strings.Controller.UnsupportedPathValue(optionname, p); - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Size) - { - try - { - Library.Utility.Sizeparser.ParseSize(value); - } - catch - { - return Strings.Controller.UnsupportedSizeValue(optionname, value); - } - - if (!string.IsNullOrWhiteSpace(value) && char.IsDigit(value.Last())) - return Strings.Controller.NonQualifiedSizeValue(optionname, value); - } - else if (arg.Type == Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan) - { - try - { - Library.Utility.Timeparser.ParseTimeSpan(value); - } - catch - { - return Strings.Controller.UnsupportedTimeValue(optionname, value); - } - } - - return null; - } - - public void Pause() - { - var ct = m_currentTask; + var ct = m_currentTaskControl; if (ct != null) - ct.Pause(); + ct.Pause(alsoTransfers); } public void Resume() { - var ct = m_currentTask; + var ct = m_currentTaskControl; if (ct != null) ct.Resume(); } - public void Stop(bool allowCurrentFileToFinish) + public void Stop() { - var ct = m_currentTask; + var ct = m_currentTaskControl; if (ct == null) return; Logging.Log.WriteVerboseMessage(LOGTAG, "CancellationRequested", "Cancellation Requested"); - m_cancellationTokenSource.Cancel(); - ct.Stop(allowCurrentFileToFinish); + ct.Stop(); } public void Abort() { - var ct = m_currentTask; - if (ct != null) - ct.Abort(); - - var t = m_currentTaskThread; - if (t != null) - t.Interrupt(); + m_currentTaskControl?.Terminate(); } public long MaxUploadSpeed diff --git a/Duplicati/Library/Main/ControllerMultiLogTarget.cs b/Duplicati/Library/Main/ControllerMultiLogTarget.cs index a0896addf..fe8ddc464 100644 --- a/Duplicati/Library/Main/ControllerMultiLogTarget.cs +++ b/Duplicati/Library/Main/ControllerMultiLogTarget.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Database/Database schema/13. Unique index on DuplicateBlock.sql b/Duplicati/Library/Main/Database/Database schema/13. Unique index on DuplicateBlock.sql new file mode 100644 index 000000000..50362367b --- /dev/null +++ b/Duplicati/Library/Main/Database/Database schema/13. Unique index on DuplicateBlock.sql @@ -0,0 +1,16 @@ +CREATE TEMPORARY TABLE "UniqueReassignTable" AS +SELECT "BlockID", "VolumeID" +FROM "DuplicateBlock" +GROUP BY "BlockID", "VolumeID"; + +DELETE FROM "DuplicateBlock"; + +INSERT INTO "DuplicateBlock"("BlockID", "VolumeID") +SELECT "BlockID", "VolumeID" FROM "UniqueReassignTable"; + +DROP TABLE "UniqueReassignTable"; + +CREATE UNIQUE INDEX IF NOT EXISTS "UniqueBlockVolumeDuplicateBlock" +ON "DuplicateBlock" ("BlockID", "VolumeID"); + +UPDATE "Version" SET "Version" = 13; \ No newline at end of file diff --git a/Duplicati/Library/Main/Database/Database schema/Schema.sql b/Duplicati/Library/Main/Database/Database schema/Schema.sql index 2ee13404b..fd7003280 100644 --- a/Duplicati/Library/Main/Database/Database schema/Schema.sql +++ b/Duplicati/Library/Main/Database/Database schema/Schema.sql @@ -217,6 +217,9 @@ CREATE TABLE "DuplicateBlock" ( "VolumeID" INTEGER NOT NULL ); +CREATE UNIQUE INDEX "UniqueBlockVolumeDuplicateBlock" +ON "DuplicateBlock" ("BlockID", "VolumeID"); + /* A metadata set, essentially a placeholder to easily extend metadatasets with new properties @@ -287,4 +290,4 @@ CREATE TABLE "ChangeJournalData" ( "ConfigHash" TEXT NOT NULL ); -INSERT INTO "Version" ("Version") VALUES (12); +INSERT INTO "Version" ("Version") VALUES (13); diff --git a/Duplicati/Library/Main/Database/ExtensionMethods.cs b/Duplicati/Library/Main/Database/ExtensionMethods.cs index 327265e0d..3e7a071e4 100644 --- a/Duplicati/Library/Main/Database/ExtensionMethods.cs +++ b/Duplicati/Library/Main/Database/ExtensionMethods.cs @@ -1,235 +1,614 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.Data; using System.Linq; -using System.Text; -using Duplicati.Library.Localization.Short; +using System.Text.RegularExpressions; -namespace Duplicati.Library.Main.Database +#nullable enable + +namespace Duplicati.Library.Main.Database; + +/// +/// Extension method for +/// +public static class ExtensionMethods { - public static class ExtensionMethods + /// + /// The tag used for logging + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods)); + + /// + /// Adds and sets the parameters for the given command. + /// + /// The type of the command + /// The command to add and set the parameters for + /// The parameters to add and set + /// The command with the parameters added and set + public static T AddAndSetParameters(this T cmd, params object?[]? parameters) + where T : IDbCommand { - /// - /// The tag used for logging - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ExtensionMethods)); - - public static void AddParameters(this System.Data.IDbCommand self, int count) - { - for(var i = 0; i < count; i++) - self.Parameters.Add(self.CreateParameter()); - } - - public static void AddParameter(this System.Data.IDbCommand self) - { - self.Parameters.Add(self.CreateParameter()); - } - - public static void AddParameter(this System.Data.IDbCommand self, T value, string name = null) - { - var p = self.CreateParameter(); - p.Value = value; - if (!string.IsNullOrEmpty(name)) - p.ParameterName = name; - self.Parameters.Add(p); - } - - public static void SetParameterValue(this System.Data.IDbCommand self, int index, T value) - { - ((System.Data.IDataParameter)self.Parameters[index]).Value = value; - } - - public static string GetPrintableCommandText(this System.Data.IDbCommand self) - { - var txt = self.CommandText; - - foreach(var p in self.Parameters.Cast()) + if (parameters != null) + for (var i = 0; i < parameters.Length; i++) { - var ix = txt.IndexOf('?'); - if (ix >= 0) - { - string v; - if (p.Value is string) - v = string.Format("\"{0}\"", p.Value); - else if (p.Value == null) - v = "NULL"; - else - v = string.Format("{0}", p.Value); - - txt = txt.Substring(0, ix) + v + txt.Substring(ix + 1); - } + var p = cmd.CreateParameter(); + p.Value = parameters[i]; + cmd.Parameters.Add(p); } - return txt; - } - - public static int ExecuteNonQuery(this System.Data.IDbCommand self, bool writeLog) - { - return ExecuteNonQuery(self, writeLog, null, null); - } - - public static int ExecuteNonQuery(this System.Data.IDbCommand self, string cmd, params object[] values) - { - return ExecuteNonQuery(self, true, cmd, values); - } - - public static int ExecuteNonQuery(this System.Data.IDbCommand self, bool writeLog, string cmd, params object[] values) - { - if (cmd != null) - self.CommandText = cmd; - - if (values != null && values.Length > 0) - { - self.Parameters.Clear(); - foreach (var n in values) - self.AddParameter(n); - } - - using(writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null) - return self.ExecuteNonQuery(); - } - - public static object ExecuteScalar(this System.Data.IDbCommand self, string cmd, params object[] values) - { - return ExecuteScalar(self, true, cmd, values); - } - - public static object ExecuteScalar(this System.Data.IDbCommand self, bool writeLog, string cmd, params object[] values) - { - if (cmd != null) - self.CommandText = cmd; - - if (values != null && values.Length > 0) - { - self.Parameters.Clear(); - foreach (var n in values) - self.AddParameter(n); - } - - using(writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalar", string.Format("ExecuteScalar: {0}", self.GetPrintableCommandText())) : null) - return self.ExecuteScalar(); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, bool writeLog, long defaultvalue = -1) - { - return ExecuteScalarInt64(self, writeLog, null, defaultvalue); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, long defaultvalue = -1) - { - return ExecuteScalarInt64(self, true, null, defaultvalue); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, bool writeLog, string cmd, long defaultvalue = -1) - { - return ExecuteScalarInt64(self, writeLog, cmd, defaultvalue, null); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, string cmd, long defaultvalue = -1) - { - return ExecuteScalarInt64(self, true, cmd, defaultvalue, null); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, string cmd, long defaultvalue, params object[] values) - { - return ExecuteScalarInt64(self, true, cmd, defaultvalue, values); - } - - public static long ExecuteScalarInt64(this System.Data.IDbCommand self, bool writeLog, string cmd, long defaultvalue, params object[] values) - { - if (cmd != null) - self.CommandText = cmd; - - if (values != null && values.Length > 0) - { - self.Parameters.Clear(); - foreach (var n in values) - self.AddParameter(n); - } - - using(writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalarInt64", string.Format("ExecuteScalarInt64: {0}", self.GetPrintableCommandText())) : null) - using(var rd = self.ExecuteReader()) - if (rd.Read()) - return ConvertValueToInt64(rd, 0, defaultvalue); - - return defaultvalue; - } - - public static System.Data.IDataReader ExecuteReader(this System.Data.IDbCommand self, string cmd, params object[] values) - { - return ExecuteReader(self, true, cmd, values); - } - - public static System.Data.IDataReader ExecuteReader(this System.Data.IDbCommand self, bool writeLog, string cmd, params object[] values) - { - if (cmd != null) - self.CommandText = cmd; - - if (values != null && values.Length > 0) - { - self.Parameters.Clear(); - foreach (var n in values) - self.AddParameter(n); - } - - using(writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null) - return self.ExecuteReader(); - } - - public static IEnumerable ExecuteReaderEnumerable(this System.Data.IDbCommand self, string cmd, params object[] values) - { - using(var rd = ExecuteReader(self, cmd, values)) - while (rd.Read()) - yield return rd; - } - - public static string ConvertValueToString(this System.Data.IDataReader reader, int index) - { - var v = reader.GetValue(index); - if (v == null || v == DBNull.Value) - return null; - - return v.ToString(); - } - - public static long ConvertValueToInt64(this System.Data.IDataReader reader, int index, long defaultvalue = -1) - { - try - { - if (!reader.IsDBNull(index)) - return reader.GetInt64(index); - } - catch - { - } - - return defaultvalue; - } - - public static System.Data.IDbCommand CreateCommand(this System.Data.IDbConnection self, System.Data.IDbTransaction transaction) - { - var con = self.CreateCommand(); - con.Transaction = transaction; - return con; - } + return cmd; } + + /// + /// Adds the given number of parameters to the command. + /// + /// The type of the command + /// The command to add the parameters to + /// The number of parameters to add + /// The command with the parameters added + public static T AddParameters(this T self, int count) + where T : IDbCommand + { + for (var i = 0; i < count; i++) + self.Parameters.Add(self.CreateParameter()); + + return self; + } + + /// + /// Adds a parameter to the command. + /// + /// The type of the command + /// The command to add the parameter to + /// The command with the parameter added + public static T AddParameter(this T self) + where T : IDbCommand + { + self.Parameters.Add(self.CreateParameter()); + return self; + } + + /// + /// Adds a parameter to the command with the given value. + /// + /// The type of the command + /// The command to add the parameter to + /// The value of the parameter + /// The optional name of the parameter + /// The command with the parameter added + public static T AddParameter(this T self, object? value, string? name = null) + where T : IDbCommand + { + var p = self.CreateParameter(); + p.Value = value; + if (!string.IsNullOrEmpty(name)) + p.ParameterName = name; + self.Parameters.Add(p); + return self; + } + + /// + /// Adds a parameter to the command with the given value. + /// + /// The type of the command + /// The command to add the parameter to + /// The name of the parameter + /// The optional value of the parameter + /// The command with the parameter added + public static T AddNamedParameter(this T self, string? name, object? value = null) + where T : IDbCommand + { + var p = self.CreateParameter(); + p.ParameterName = name; + if (value != null) + p.Value = value; + self.Parameters.Add(p); + return self; + } + + /// + /// Sets the parameter values for the command, the parameters must already be added. + /// + /// The type of the command + /// The command to set the parameter values for + /// The values to set the parameters to + /// The command with the parameter values set + public static T SetParameterValues(this T self, params object?[] values) + where T : IDbCommand + { + for (var i = 0; i < values.Length; i++) + ((IDataParameter)self.Parameters[i]!).Value = values[i]; + return self; + } + + /// + /// Sets the parameter values for the command, the parameters must already be added. + /// + /// The type of the command + /// The command to set the parameter values for + /// The values to set the parameters to + /// The command with the parameter values set + public static T SetParameterValues(this T self, Dictionary values) + where T : IDbCommand + { + foreach (var kvp in values) + ((IDataParameter)self.Parameters[kvp.Key]!).Value = kvp.Value; + return self; + } + + /// + /// Sets the parameter value for the command at the given index. + /// + /// The type of the command + /// The command to set the parameter value for + /// The index of the parameter to set the value for + /// The value to set the parameter to + /// The command with the parameter value set + public static T SetParameterValue(this T self, int index, object? value) + where T : IDbCommand + { + ((IDataParameter)self.Parameters[index]!).Value = value; + return self; + } + + /// + /// Sets the parameter value for the command at the given index. + /// + /// The type of the command + /// The command to set the parameter value for + /// The name of the parameter to set the value for + /// The value to set the parameter to + /// The command with the parameter value set + public static T SetParameterValue(this T self, string name, object? value) + where T : IDbCommand + { + ((IDataParameter)self.Parameters[name]!).Value = value; + return self; + } + + /// + /// Gets the printable command text for the given command. + /// The command to get the printable command text for + /// The printable command text + public static string GetPrintableCommandText(this IDbCommand self) + { + var txt = self.CommandText; + + foreach (var p in self.Parameters.Cast()) + { + var ix = txt.IndexOf('?'); + if (ix >= 0) + { + string v; + if (p.Value is string) + v = string.Format("\"{0}\"", p.Value); + else if (p.Value == null) + v = "NULL"; + else + v = string.Format("{0}", p.Value); + + txt = txt.Substring(0, ix) + v + txt.Substring(ix + 1); + } + } + + return txt; + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, bool writeLog) + { + return ExecuteNonQuery(self, writeLog, null, (object?[]?)null); + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, string cmd, params object?[]? values) + { + return ExecuteNonQuery(self, true, cmd, values); + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, string cmd, Dictionary values) + { + return ExecuteNonQuery(self, true, cmd, values); + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, bool writeLog, string? cmd, Dictionary values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Count > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null) + return self.ExecuteNonQuery(); + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, bool writeLog, string? cmd, params object?[]? values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Length > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteNonQuery", string.Format("ExecuteNonQuery: {0}", self.GetPrintableCommandText())) : null) + return self.ExecuteNonQuery(); + } + + /// + /// Executes the command and returns the number of rows affected. + /// + /// The command instance to execute on + /// The transaction to use for the command + /// The number of rows affected + public static int ExecuteNonQuery(this IDbCommand self, IDbTransaction transaction) + { + self.Transaction = transaction; + return self.ExecuteNonQuery(); + } + + /// + /// Executes the command and returns the scalar value of the first row. + /// + /// The command instance to execute on + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The scalar value of the first row + public static object? ExecuteScalar(this IDbCommand self, string cmd, params object[] values) + { + return ExecuteScalar(self, true, cmd, values); + } + + /// + /// Executes the command and returns the scalar value of the first row. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// The scalar value of the first row + public static object? ExecuteScalar(this IDbCommand self, bool writeLog, string cmd, params object[] values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Length > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalar", string.Format("ExecuteScalar: {0}", self.GetPrintableCommandText())) : null) + return self.ExecuteScalar(); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The default value to return if no value is found + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, bool writeLog, long defaultvalue = -1) + { + return ExecuteScalarInt64(self, writeLog, null, defaultvalue); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// The default value to return if no value is found + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, long defaultvalue = -1) + { + return ExecuteScalarInt64(self, true, null, defaultvalue); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// The transaction to use for the command + /// The default value to return if no value is found + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, IDbTransaction transaction, long defaultvalue = -1) + { + self.Transaction = transaction; + return ExecuteScalarInt64(self, true, null, defaultvalue); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The default value to return if no value is found + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, bool writeLog, string? cmd, long defaultvalue = -1) + { + return ExecuteScalarInt64(self, writeLog, cmd, defaultvalue, null); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// The command string to execute + /// The default value to return if no value is found + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, string? cmd, long defaultvalue = -1) + { + return ExecuteScalarInt64(self, true, cmd, defaultvalue, null); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// The command string to execute + /// The default value to return if no value is found + /// The values to use as parameters. The parameters must already be added. + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, string? cmd, long defaultvalue, params object?[]? values) + { + return ExecuteScalarInt64(self, true, cmd, defaultvalue, values); + } + + /// + /// Executes the command and returns the scalar int64 value of the first row as a string. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The default value to return if no value is found + /// The values to use as parameters. The parameters must already be added. + /// The scalar int64 value of the first row as a string + public static long ExecuteScalarInt64(this IDbCommand self, bool writeLog, string? cmd, long defaultvalue, params object?[]? values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Length > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteScalarInt64", string.Format("ExecuteScalarInt64: {0}", self.GetPrintableCommandText())) : null) + using (var rd = self.ExecuteReader()) + if (rd.Read()) + return ConvertValueToInt64(rd, 0, defaultvalue); + + return defaultvalue; + } + + /// + /// Executes the command and returns a data reader. + /// + /// The command instance to execute on + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// A instance + public static IDataReader ExecuteReader(this IDbCommand self, string cmd, Dictionary values) + { + return ExecuteReader(self, true, cmd, values); + } + + + /// + /// Executes the command and returns a data reader. + /// + /// The command instance to execute on + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// A instance + public static IDataReader ExecuteReader(this IDbCommand self, string cmd, params object[] values) + { + return ExecuteReader(self, true, cmd, values); + } + + /// + /// Executes the command and returns a data reader. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// A instance + public static IDataReader ExecuteReader(this IDbCommand self, bool writeLog, string cmd, params object[] values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Length > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null) + return self.ExecuteReader(); + } + + /// + /// Executes the command and returns a data reader. + /// + /// The command instance to execute on + /// Whether to write a log entry + /// The command string to execute + /// The values to use as parameters. The parameters must already be added. + /// A instance + public static IDataReader ExecuteReader(this IDbCommand self, bool writeLog, string cmd, Dictionary values) + { + if (cmd != null) + self.SetCommandAndParameters(cmd); + + if (values != null && values.Count > 0) + self.SetParameterValues(values); + + using (writeLog ? new Logging.Timer(LOGTAG, "ExecuteReader", string.Format("ExecuteReader: {0}", self.GetPrintableCommandText())) : null) + return self.ExecuteReader(); + } + + /// + /// Executes the given command string `cmd` on the given database command `self` with the given values `values` and returns an enumerable of data readers. + /// + /// The database command to execute on. + /// The command string to execute. + /// The values that the command string should be parameterized with. + /// + public static IEnumerable ExecuteReaderEnumerable(this IDbCommand self, string cmd, params object[] values) + { + using var rd = ExecuteReader(self, cmd, values); + while (rd.Read()) + yield return rd; + } + + /// + /// Converts the value at the given index of the given data reader to a string. + /// + /// The data reader to convert the value from. + /// The index of the value to convert. + /// The value at the given index as a string. + public static string? ConvertValueToString(this IDataReader reader, int index) + { + var v = reader.GetValue(index); + if (v == null || v == DBNull.Value) + return null; + + return v.ToString(); + } + + /// + /// Converts the value at the given index of the given data reader to a long. + /// + /// The data reader to convert the value from. + /// The index of the value to convert. + /// The default value to return if the value is null or cannot be converted. + /// The value at the given index as a long. + public static long ConvertValueToInt64(this IDataReader reader, int index, long defaultvalue = -1) + { + try + { + if (!reader.IsDBNull(index)) + return reader.GetInt64(index); + } + catch + { + } + + return defaultvalue; + } + + /// + /// Creates a command with the given transaction. + /// + /// The connection to create the command on. + /// The transaction to use for the command. + /// The command string to create the command with. + /// A new command with the given transaction. + public static IDbCommand CreateCommand(this IDbConnection self, IDbTransaction? transaction, string? cmdtext = null) + { + var cmd = self.CreateCommand(); + cmd.Transaction = transaction; + if (!string.IsNullOrEmpty(cmdtext)) + cmd.SetCommandAndParameters(cmdtext); + return cmd; + } + + /// + /// Sets the command text and adds parameters to the command. + /// + /// The command to set the command text and add parameters to. + /// The transaction to use for the command. + /// The command text to set. + /// The command with the command text set and parameters added. + public static IDbCommand SetCommandAndParameters(this IDbCommand cmd, IDbTransaction transaction, string cmdtext) + { + cmd.Transaction = transaction; + return cmd.SetCommandAndParameters(cmdtext); + } + + /// + /// Sets the command text and adds parameters to the command. + /// + /// The command to set the command text and add parameters to. + /// The command text to set. + /// The command with the command text set and parameters added. + public static IDbCommand SetCommandAndParameters(this IDbCommand cmd, string cmdtext) + { + cmd.CommandText = cmdtext; + cmd.Parameters.Clear(); + cmd.AddParameters(cmdtext.Count(c => c == '?')); + + var parameters = Regex.Matches(cmdtext, @"@\w+"); + var found = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (Match match in parameters) + { + if (found.Contains(match.Value)) + continue; + found.Add(match.Value); + cmd.AddNamedParameter(match.Value); + } + + return cmd; + } + + /// + /// Creates a command with the given command string, and adds parameters to fit the input + /// + /// The connection to create the command on + /// The command string to create the command with + /// The command with the parameters added + public static IDbCommand CreateCommand(this IDbConnection self, string cmdtext) + => CreateCommand(self, null, cmdtext); } + diff --git a/Duplicati/Library/Main/Database/HashLookupHelper.cs b/Duplicati/Library/Main/Database/HashLookupHelper.cs index de7d1884b..52d28ac83 100644 --- a/Duplicati/Library/Main/Database/HashLookupHelper.cs +++ b/Duplicati/Library/Main/Database/HashLookupHelper.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Database/IRemoteVolume.cs b/Duplicati/Library/Main/Database/IRemoteVolume.cs index 6bc6075fc..b5047e2dc 100644 --- a/Duplicati/Library/Main/Database/IRemoteVolume.cs +++ b/Duplicati/Library/Main/Database/IRemoteVolume.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,17 +18,12 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Duplicati.Library.Main.Database -{ - public interface IRemoteVolume - { - string Name { get; } - string Hash { get; } - long Size { get; } - } -} +namespace Duplicati.Library.Main.Database +{ + public interface IRemoteVolume + { + string Name { get; } + string Hash { get; } + long Size { get; } + } +} diff --git a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs index 47fda793d..4bc61e3ae 100644 --- a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -33,32 +33,33 @@ namespace Duplicati.Library.Main.Database /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private readonly System.Data.IDbCommand m_findblockCommand; - private readonly System.Data.IDbCommand m_findblocksetCommand; - private readonly System.Data.IDbCommand m_findfilesetCommand; - private readonly System.Data.IDbCommand m_findmetadatasetCommand; + private readonly IDbCommand m_findblockCommand; + private readonly IDbCommand m_findblocksetCommand; + private readonly IDbCommand m_findfilesetCommand; + private readonly IDbCommand m_findmetadatasetCommand; - private readonly System.Data.IDbCommand m_insertblockCommand; + private readonly IDbCommand m_insertblockCommand; - private readonly System.Data.IDbCommand m_insertfileCommand; + private readonly IDbCommand m_insertfileCommand; - private readonly System.Data.IDbCommand m_insertblocksetCommand; - private readonly System.Data.IDbCommand m_insertblocksetentryFastCommand; - private readonly System.Data.IDbCommand m_insertblocksetentryCommand; - private readonly System.Data.IDbCommand m_insertblocklistHashesCommand; + private readonly IDbCommand m_insertblocksetCommand; + private readonly IDbCommand m_insertblocksetentryFastCommand; + private readonly IDbCommand m_insertblocksetentryCommand; + private readonly IDbCommand m_insertblocklistHashesCommand; - private readonly System.Data.IDbCommand m_insertmetadatasetCommand; + private readonly IDbCommand m_insertmetadatasetCommand; - private readonly System.Data.IDbCommand m_findfileCommand; - private readonly System.Data.IDbCommand m_selectfilelastmodifiedCommand; - private readonly System.Data.IDbCommand m_selectfilelastmodifiedWithSizeCommand; - private readonly System.Data.IDbCommand m_selectfileHashCommand; - private readonly System.Data.IDbCommand m_selectblocklistHashesCommand; + private readonly IDbCommand m_findfileCommand; + private readonly IDbCommand m_selectfilelastmodifiedCommand; + private readonly IDbCommand m_selectfilelastmodifiedWithSizeCommand; + private readonly IDbCommand m_selectfileHashCommand; + private readonly IDbCommand m_selectblocklistHashesCommand; - private readonly System.Data.IDbCommand m_insertfileOperationCommand; - private readonly System.Data.IDbCommand m_selectfilemetadatahashandsizeCommand; + private readonly IDbCommand m_insertfileOperationCommand; + private readonly IDbCommand m_selectfilemetadatahashandsizeCommand; + private readonly IDbCommand m_getfirstfilesetwithblockinblockset; - private Dictionary m_blockCache; + private HashSet m_blocklistHashes; private long m_filesetId; @@ -93,51 +94,23 @@ namespace Duplicati.Library.Main.Database m_selectfileHashCommand = m_connection.CreateCommand(); m_insertblocksetentryFastCommand = m_connection.CreateCommand(); m_selectfilemetadatahashandsizeCommand = m_connection.CreateCommand(); + m_getfirstfilesetwithblockinblockset = m_connection.CreateCommand(); - m_findblockCommand.CommandText = @"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"; - m_findblockCommand.AddParameters(2); - - m_findblocksetCommand.CommandText = @"SELECT ""ID"" FROM ""Blockset"" WHERE ""Fullhash"" = ? AND ""Length"" = ?"; - m_findblocksetCommand.AddParameters(2); - - m_findmetadatasetCommand.CommandText = @"SELECT ""A"".""ID"" FROM ""Metadataset"" A, ""BlocksetEntry"" B, ""Block"" C WHERE ""A"".""BlocksetID"" = ""B"".""BlocksetID"" AND ""B"".""BlockID"" = ""C"".""ID"" AND ""C"".""Hash"" = ? AND ""C"".""Size"" = ?"; - m_findmetadatasetCommand.AddParameters(2); - - m_findfilesetCommand.CommandText = @"SELECT ""ID"" FROM ""FileLookup"" WHERE ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""Path"" = ? AND ""PrefixID"" = ?"; - m_findfilesetCommand.AddParameters(4); - - m_insertblockCommand.CommandText = @"INSERT INTO ""Block"" (""Hash"", ""VolumeID"", ""Size"") VALUES (?, ?, ?); SELECT last_insert_rowid();"; - m_insertblockCommand.AddParameters(3); - - m_insertfileOperationCommand.CommandText = @"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (?, ?, ?)"; - m_insertfileOperationCommand.AddParameters(3); - - m_insertfileCommand.CommandText = @"INSERT INTO ""FileLookup"" (""PrefixID"", ""Path"",""BlocksetID"", ""MetadataID"") VALUES (?, ?, ? ,?); SELECT last_insert_rowid();"; - m_insertfileCommand.AddParameters(4); - - m_insertblocksetCommand.CommandText = @"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?, ?); SELECT last_insert_rowid();"; - m_insertblocksetCommand.AddParameters(2); - - m_insertblocksetentryFastCommand.CommandText = @"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") VALUES (?,?,?)"; - m_insertblocksetentryFastCommand.AddParameters(3); - - m_insertblocksetentryCommand.CommandText = @"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") SELECT ? AS A, ? AS B, ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"; - m_insertblocksetentryCommand.AddParameters(4); - - m_insertblocklistHashesCommand.CommandText = @"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?)"; - m_insertblocklistHashesCommand.AddParameters(3); - - m_insertmetadatasetCommand.CommandText = @"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();"; - m_insertmetadatasetCommand.AddParameter(); - - m_selectfilelastmodifiedCommand.CommandText = @"SELECT ""A"".""ID"", ""B"".""LastModified"" FROM (SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?"; - m_selectfilelastmodifiedCommand.AddParameters(3); - - m_selectfilelastmodifiedWithSizeCommand.CommandText = @"SELECT ""C"".""ID"", ""C"".""LastModified"", ""D"".""Length"" FROM (SELECT ""A"".""ID"", ""B"".""LastModified"", ""A"".""BlocksetID"" FROM (SELECT ""ID"", ""BlocksetID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?) AS ""C"", ""Blockset"" AS ""D"" WHERE ""C"".""BlocksetID"" == ""D"".""ID"" "; - m_selectfilelastmodifiedWithSizeCommand.AddParameters(3); - - m_selectfilemetadatahashandsizeCommand.CommandText = @"SELECT ""Blockset"".""Length"", ""Blockset"".""FullHash"" FROM ""Blockset"", ""Metadataset"", ""File"" WHERE ""File"".""ID"" = ? AND ""Blockset"".""ID"" = ""Metadataset"".""BlocksetID"" AND ""Metadataset"".""ID"" = ""File"".""MetadataID"" "; - m_selectfilemetadatahashandsizeCommand.AddParameters(1); + m_findblockCommand = m_connection.CreateCommand(@"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"); + m_findblocksetCommand = m_connection.CreateCommand(@"SELECT ""ID"" FROM ""Blockset"" WHERE ""Fullhash"" = ? AND ""Length"" = ?"); + m_findmetadatasetCommand = m_connection.CreateCommand(@"SELECT ""A"".""ID"" FROM ""Metadataset"" A, ""BlocksetEntry"" B, ""Block"" C WHERE ""A"".""BlocksetID"" = ""B"".""BlocksetID"" AND ""B"".""BlockID"" = ""C"".""ID"" AND ""C"".""Hash"" = ? AND ""C"".""Size"" = ?"); + m_findfilesetCommand = m_connection.CreateCommand(@"SELECT ""ID"" FROM ""FileLookup"" WHERE ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""Path"" = ? AND ""PrefixID"" = ?"); + m_insertblockCommand = m_connection.CreateCommand(@"INSERT INTO ""Block"" (""Hash"", ""VolumeID"", ""Size"") VALUES (?, ?, ?); SELECT last_insert_rowid();"); + m_insertfileOperationCommand = m_connection.CreateCommand(@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (?, ?, ?)"); + m_insertfileCommand = m_connection.CreateCommand(@"INSERT INTO ""FileLookup"" (""PrefixID"", ""Path"",""BlocksetID"", ""MetadataID"") VALUES (?, ?, ? ,?); SELECT last_insert_rowid();"); + m_insertblocksetCommand = m_connection.CreateCommand(@"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?, ?); SELECT last_insert_rowid();"); + m_insertblocksetentryFastCommand = m_connection.CreateCommand(@"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") VALUES (?,?,?)"); + m_insertblocksetentryCommand = m_connection.CreateCommand(@"INSERT INTO ""BlocksetEntry"" (""BlocksetID"", ""Index"", ""BlockID"") SELECT ? AS A, ? AS B, ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"); + m_insertblocklistHashesCommand = m_connection.CreateCommand(@"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?)"); + m_insertmetadatasetCommand = m_connection.CreateCommand(@"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();"); + m_selectfilelastmodifiedCommand = m_connection.CreateCommand(@"SELECT ""A"".""ID"", ""B"".""LastModified"" FROM (SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?"); + m_selectfilelastmodifiedWithSizeCommand = m_connection.CreateCommand(@"SELECT ""C"".""ID"", ""C"".""LastModified"", ""D"".""Length"" FROM (SELECT ""A"".""ID"", ""B"".""LastModified"", ""A"".""BlocksetID"" FROM (SELECT ""ID"", ""BlocksetID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ?) ""A"" CROSS JOIN ""FilesetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""B"".""FilesetID"" = ?) AS ""C"", ""Blockset"" AS ""D"" WHERE ""C"".""BlocksetID"" == ""D"".""ID"" "); + m_selectfilemetadatahashandsizeCommand = m_connection.CreateCommand(@"SELECT ""Blockset"".""Length"", ""Blockset"".""FullHash"" FROM ""Blockset"", ""Metadataset"", ""File"" WHERE ""File"".""ID"" = ? AND ""Blockset"".""ID"" = ""Metadataset"".""BlocksetID"" AND ""Metadataset"".""ID"" = ""File"".""MetadataID"" "); // Allow users to test on real-world data // to get feedback on potential performance @@ -156,11 +129,12 @@ namespace Duplicati.Library.Main.Database // but allow users to switch back via an environment variable // such that we can get performance feedback + string findQuery; switch (testqueryversion) { // The query used in Duplicati until 2.0.3.9 case 1: - m_findfileCommand.CommandText = + findQuery = @" SELECT ""FileLookup"".""ID"" AS ""FileID"", ""FilesetEntry"".""Lastmodified"", ""FileBlockset"".""Length"", ""MetaBlockset"".""Fullhash"" AS ""Metahash"", ""MetaBlockset"".""Length"" AS ""Metasize"" " + @" FROM ""FileLookup"", ""FilesetEntry"", ""Fileset"", ""Blockset"" ""FileBlockset"", ""Metadataset"", ""Blockset"" ""MetaBlockset"" " + @" WHERE ""FileLookup"".""PrefixID"" = ? AND ""FileLookup"".""Path"" = ? " + @@ -182,19 +156,15 @@ namespace Duplicati.Library.Main.Database @" WHERE ""A"".""ID"" = ""B"".""FileID"" " + @" AND ""B"".""FilesetID"" = ? "; - m_findfileCommand.CommandText = string.Format( - @"SELECT ""C"".""ID"" AS ""FileID"", ""C"".""LastModified"", ""D"".""Length"", ""E"".""FullHash"" as ""Metahash"", ""E"".""Length"" AS ""Metasize"" " + - @" FROM " + - @" ({0}) AS ""C"", ""Blockset"" AS ""D"", ""Blockset"" AS ""E"", ""Metadataset"" ""F"" " + - @" WHERE ""C"".""BlocksetID"" == ""D"".""ID"" AND ""C"".""MetadataID"" == ""F"".""ID"" AND ""F"".""BlocksetID"" = ""E"".""ID"" " + - @" LIMIT 1", - getLastFileEntryForPath - ); + findQuery = FormatInvariant($@"SELECT ""C"".""ID"" AS ""FileID"", ""C"".""LastModified"", ""D"".""Length"", ""E"".""FullHash"" as ""Metahash"", ""E"".""Length"" AS ""Metasize"" +FROM ({getLastFileEntryForPath}) AS ""C"", ""Blockset"" AS ""D"", ""Blockset"" AS ""E"", ""Metadataset"" ""F"" +WHERE ""C"".""BlocksetID"" == ""D"".""ID"" AND ""C"".""MetadataID"" == ""F"".""ID"" AND ""F"".""BlocksetID"" = ""E"".""ID"" +LIMIT 1"); break; // Potentially faster query: https://forum.duplicati.com/t/release-2-0-3-10-canary-2018-08-30/4497/25 case 3: - m_findfileCommand.CommandText = + findQuery = @" SELECT FileLookup.ID as FileID, FilesetEntry.Lastmodified, FileBlockset.Length, " + @" MetaBlockset.FullHash AS Metahash, MetaBlockset.Length as Metasize " + @" FROM FilesetEntry " + @@ -209,7 +179,7 @@ namespace Duplicati.Library.Main.Database // The slow query used in Duplicati 2.0.3.10, but with "LIMIT 1" added case 4: - m_findfileCommand.CommandText = + findQuery = @" SELECT ""FileLookup"".""ID"" AS ""FileID"", ""FilesetEntry"".""Lastmodified"", ""FileBlockset"".""Length"", ""MetaBlockset"".""Fullhash"" AS ""Metahash"", ""MetaBlockset"".""Length"" AS ""Metasize"" " + @" FROM ""FileLookup"", ""FilesetEntry"", ""Fileset"", ""Blockset"" ""FileBlockset"", ""Metadataset"", ""Blockset"" ""MetaBlockset"" " + @" WHERE ""FileLookup"".""PrefixID"" = ? AND ""FileLookup"".""Path"" = ? " + @@ -222,13 +192,15 @@ namespace Duplicati.Library.Main.Database } - m_findfileCommand.AddParameters(3); + m_findfileCommand = m_connection.CreateCommand(findQuery); - m_selectfileHashCommand.CommandText = @"SELECT ""Blockset"".""Fullhash"" FROM ""Blockset"", ""FileLookup"" WHERE ""Blockset"".""ID"" = ""FileLookup"".""BlocksetID"" AND ""FileLookup"".""ID"" = ? "; - m_selectfileHashCommand.AddParameters(1); + m_selectfileHashCommand = m_connection.CreateCommand(@"SELECT ""Blockset"".""Fullhash"" FROM ""Blockset"", ""FileLookup"" WHERE ""Blockset"".""ID"" = ""FileLookup"".""BlocksetID"" AND ""FileLookup"".""ID"" = ? "); + m_selectblocklistHashesCommand = m_connection.CreateCommand(@"SELECT ""Hash"" FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ? ORDER BY ""Index"" ASC "); + m_getfirstfilesetwithblockinblockset = m_connection.CreateCommand(@"SELECT MIN(""FilesetEntry"".""FilesetID"") FROM ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" IN ( +SELECT ""File"".""ID"" FROM ""File"" WHERE ""File"".""BlocksetID"" IN( +SELECT ""BlocklistHash"".""BlocksetID"" FROM ""BlocklistHash"" WHERE ""BlocklistHash"".""Hash"" = ?))"); - m_selectblocklistHashesCommand.CommandText = @"SELECT ""Hash"" FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ? ORDER BY ""Index"" ASC "; - m_selectblocklistHashesCommand.AddParameters(1); + m_blocklistHashes = new HashSet(); } /// @@ -237,7 +209,7 @@ namespace Duplicati.Library.Main.Database /// The block key /// The size of the block /// True if the block should be added to the current output - public long FindBlockID(string key, long size, System.Data.IDbTransaction transaction = null) + public long FindBlockID(string key, long size, IDbTransaction transaction = null) { m_findblockCommand.Transaction = transaction; m_findblockCommand.SetParameterValue(0, key); @@ -251,19 +223,8 @@ namespace Duplicati.Library.Main.Database /// The block key /// The size of the block /// True if the block should be added to the current output - public bool AddBlock(string key, long size, long volumeid, System.Data.IDbTransaction transaction = null) + public bool AddBlock(string key, long size, long volumeid, IDbTransaction transaction = null) { - long exsize; - - if (m_blockCache != null && m_blockCache.TryGetValue(key, out exsize)) - { - if (exsize == size) - return false; - - Logging.Log.WriteWarningMessage(LOGTAG, "HashCollisionsFound", null, "Found hash collision on {0}, sizes {1} vs {2}. Disabling cache from now on.", key, size, exsize); - m_blockCache = null; - } - m_findblockCommand.Transaction = transaction; m_findblockCommand.SetParameterValue(0, key); m_findblockCommand.SetParameterValue(1, size); @@ -276,8 +237,6 @@ namespace Duplicati.Library.Main.Database m_insertblockCommand.SetParameterValue(1, volumeid); m_insertblockCommand.SetParameterValue(2, size); m_insertblockCommand.ExecuteScalarInt64(m_logQueries); - if (m_blockCache != null) - m_blockCache.Add(key, size); return true; } else @@ -296,7 +255,7 @@ namespace Duplicati.Library.Main.Database /// The list of hashes /// The id of the blockset, new or old /// True if the blockset was created, false otherwise - public bool AddBlockset(string filehash, long size, int blocksize, IEnumerable hashes, IEnumerable blocklistHashes, out long blocksetid, System.Data.IDbTransaction transaction = null) + public bool AddBlockset(string filehash, long size, int blocksize, IEnumerable hashes, IEnumerable blocklistHashes, out long blocksetid, IDbTransaction transaction = null) { m_findblocksetCommand.Transaction = transaction; blocksetid = m_findblocksetCommand.ExecuteScalarInt64(m_logQueries, null, -1, filehash, size); @@ -346,12 +305,12 @@ namespace Duplicati.Library.Main.Database { var bid = cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ?", -1, h); if (bid == -1) - throw new Exception(string.Format("Could not find any blocks with the given hash: {0}", h)); + throw new Exception($"Could not find any blocks with the given hash: {h}"); foreach (var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Size"" FROM ""Block"" WHERE ""Hash"" = ?", h)) Logging.Log.WriteErrorMessage(LOGTAG, "FoundIssue1400Error", null, "Found block with ID {0} and hash {1} and size {2}", bid, h, rd.ConvertValueToInt64(0, -1)); } - throw new Exception(string.Format("Unexpected result count: {0}, expected {1}, check log for more messages", c, 1)); + throw new Exception($"Unexpected result count: {c}, expected {1}, check log for more messages"); } ix++; @@ -372,7 +331,7 @@ namespace Duplicati.Library.Main.Database /// The size of the metadata. /// The ID of the metadataset. /// An optional transaction. - public bool GetMetadatasetID(string filehash, long size, out long metadataid, System.Data.IDbTransaction transaction = null) + public bool GetMetadatasetID(string filehash, long size, out long metadataid, IDbTransaction transaction = null) { if (size > 0) { @@ -394,7 +353,7 @@ namespace Duplicati.Library.Main.Database /// The id of the blockset to add /// The id of the metadata set /// True if the set was added to the database, false otherwise - public bool AddMetadataset(string filehash, long size, long blocksetid, out long metadataid, System.Data.IDbTransaction transaction = null) + public bool AddMetadataset(string filehash, long size, long blocksetid, out long metadataid, IDbTransaction transaction = null) { if (GetMetadatasetID(filehash, size, out metadataid, transaction)) return false; @@ -418,7 +377,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the hashkey for the file /// The ID for the metadata /// The transaction to use for insertion, or null for no transaction - public void AddFile(long pathprefixid, string filename, DateTime lastmodified, long blocksetID, long metadataID, System.Data.IDbTransaction transaction) + public void AddFile(long pathprefixid, string filename, DateTime lastmodified, long blocksetID, long metadataID, IDbTransaction transaction) { var fileidobj = -1L; m_findfilesetCommand.Transaction = transaction; @@ -457,13 +416,13 @@ namespace Duplicati.Library.Main.Database /// The ID of the hashkey for the file /// The ID for the metadata /// The transaction to use for insertion, or null for no transaction - public void AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID, System.Data.IDbTransaction transaction) + public void AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID, IDbTransaction transaction) { var split = SplitIntoPrefixAndName(filename); AddFile(GetOrCreatePathPrefix(split.Key, transaction), split.Value, lastmodified, blocksetID, metadataID, transaction); } - public void AddUnmodifiedFile(long fileid, DateTime lastmodified, System.Data.IDbTransaction transaction = null) + public void AddUnmodifiedFile(long fileid, DateTime lastmodified, IDbTransaction transaction = null) { m_insertfileOperationCommand.Transaction = transaction; m_insertfileOperationCommand.SetParameterValue(0, m_filesetId); @@ -472,17 +431,17 @@ namespace Duplicati.Library.Main.Database m_insertfileOperationCommand.ExecuteNonQuery(m_logQueries); } - public void AddDirectoryEntry(string path, long metadataID, DateTime lastmodified, System.Data.IDbTransaction transaction = null) + public void AddDirectoryEntry(string path, long metadataID, DateTime lastmodified, IDbTransaction transaction = null) { AddFile(path, lastmodified, FOLDER_BLOCKSET_ID, metadataID, transaction); } - public void AddSymlinkEntry(string path, long metadataID, DateTime lastmodified, System.Data.IDbTransaction transaction = null) + public void AddSymlinkEntry(string path, long metadataID, DateTime lastmodified, IDbTransaction transaction = null) { AddFile(path, lastmodified, SYMLINK_BLOCKSET_ID, metadataID, transaction); } - public long GetFileLastModified(long prefixid, string path, long filesetid, bool includeLength, out DateTime oldModified, out long length, System.Data.IDbTransaction transaction = null) + public long GetFileLastModified(long prefixid, string path, long filesetid, bool includeLength, out DateTime oldModified, out long length, IDbTransaction transaction = null) { if (includeLength) { @@ -519,7 +478,7 @@ namespace Duplicati.Library.Main.Database } - public long GetFileEntry(long prefixid, string path, long filesetid, out DateTime oldModified, out long lastFileSize, out string oldMetahash, out long oldMetasize, System.Data.IDbTransaction transaction) + public long GetFileEntry(long prefixid, string path, long filesetid, out DateTime oldModified, out long lastFileSize, out string oldMetahash, out long oldMetasize, IDbTransaction transaction) { m_findfileCommand.SetParameterValue(0, prefixid); m_findfileCommand.SetParameterValue(1, path); @@ -545,7 +504,7 @@ namespace Duplicati.Library.Main.Database } } - public Tuple GetMetadataHashAndSizeForFile(long fileid, System.Data.IDbTransaction transaction) + public Tuple GetMetadataHashAndSizeForFile(long fileid, IDbTransaction transaction) { m_selectfilemetadatahashandsizeCommand.SetParameterValue(0, fileid); m_selectfilemetadatahashandsizeCommand.Transaction = transaction; @@ -558,7 +517,7 @@ namespace Duplicati.Library.Main.Database } - public string GetFileHash(long fileid, System.Data.IDbTransaction transaction) + public string GetFileHash(long fileid, IDbTransaction transaction) { m_selectfileHashCommand.SetParameterValue(0, fileid); m_selectfileHashCommand.Transaction = transaction; @@ -574,12 +533,12 @@ namespace Duplicati.Library.Main.Database base.Dispose(); } - private long GetPreviousFilesetID(System.Data.IDbCommand cmd) + private long GetPreviousFilesetID(IDbCommand cmd) { return GetPreviousFilesetID(cmd, OperationTimestamp, m_filesetId); } - private long GetPreviousFilesetID(System.Data.IDbCommand cmd, DateTime timestamp, long filesetid) + private long GetPreviousFilesetID(IDbCommand cmd, DateTime timestamp, long filesetid) { var lastFilesetId = cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""Fileset"" WHERE ""Timestamp"" < ? AND ""ID"" != ? ORDER BY ""Timestamp"" DESC ", -1, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(timestamp), filesetid); return lastFilesetId; @@ -597,7 +556,7 @@ namespace Duplicati.Library.Main.Database } } - internal void UpdateChangeStatistics(BackupResults results, System.Data.IDbTransaction transaction) + internal void UpdateChangeStatistics(BackupResults results, IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand(transaction)) { @@ -619,22 +578,22 @@ namespace Duplicati.Library.Main.Database { var subqueryFiles = @"SELECT ""File"".""Path"" AS ""Path"", ""A"".""Fullhash"" AS ""Filehash"", ""B"".""Fullhash"" AS ""Metahash"" FROM ""File"", ""FilesetEntry"", ""Blockset"" A, ""Blockset"" B, ""Metadataset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""A"".""ID"" = ""File"".""BlocksetID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""B"".""ID"" "; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS " + subqueryFiles, tmpName1), lastFilesetId); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS " + subqueryFiles, tmpName2), m_filesetId); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName1"" ON ""{0}"" (""Path"")", tmpName1)); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""nn_tmpName2"" ON ""{0}"" (""Path"")", tmpName2)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tmpName1}"" AS {subqueryFiles}"), lastFilesetId); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tmpName2}"" AS {subqueryFiles}"), m_filesetId); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""nn_tmpName1"" ON ""{tmpName1}"" (""Path"")")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""nn_tmpName2"" ON ""{tmpName2}"" (""Path"")")); - results.AddedFiles = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""{0}"")", tmpName1), 0, m_filesetId, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID); - results.DeletedFiles = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""{0}"" WHERE ""{0}"".""Path"" NOT IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)", tmpName1), 0, m_filesetId); - results.ModifiedFiles = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""{0}"" A, ""{1}"" B WHERE ""A"".""Path"" = ""B"".""Path"" AND (""A"".""Filehash"" != ""B"".""Filehash"" OR ""A"".""Metahash"" != ""B"".""Metahash"")", tmpName1, tmpName2), 0); + results.AddedFiles = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""File"".""BlocksetID"" != ? AND ""File"".""BlocksetID"" != ? AND NOT ""File"".""Path"" IN (SELECT ""Path"" FROM ""{tmpName1}"")"), 0, m_filesetId, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID); + results.DeletedFiles = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""{tmpName1}"" WHERE ""{tmpName1}"".""Path"" NOT IN (SELECT ""Path"" FROM ""File"" INNER JOIN ""FilesetEntry"" ON ""File"".""ID"" = ""FilesetEntry"".""FileID"" WHERE ""FilesetEntry"".""FilesetID"" = ?)"), 0, m_filesetId); + results.ModifiedFiles = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""{tmpName1}"" A, ""{tmpName2}"" B WHERE ""A"".""Path"" = ""B"".""Path"" AND (""A"".""Filehash"" != ""B"".""Filehash"" OR ""A"".""Metahash"" != ""B"".""Metahash"")"), 0); } finally { - try { cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"";", tmpName1)); } + try { cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tmpName1}"";")); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Dispose temp table error"); } - try { cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"";", tmpName2)); } + try { cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tmpName2}"";")); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DisposeError", ex, "Dispose temp table error"); } } } @@ -646,7 +605,7 @@ namespace Duplicati.Library.Main.Database /// /// Transaction /// List of deleted paths, or null - public void AppendFilesFromPreviousSet(System.Data.IDbTransaction transaction, IEnumerable deleted = null) + public void AppendFilesFromPreviousSet(IDbTransaction transaction, IEnumerable deleted = null) { AppendFilesFromPreviousSet(transaction, deleted, m_filesetId, -1, OperationTimestamp); } @@ -660,7 +619,7 @@ namespace Duplicati.Library.Main.Database /// Current file-set ID /// Source file-set ID /// If filesetid == -1, used to locate previous file-set - public void AppendFilesFromPreviousSet(System.Data.IDbTransaction transaction, IEnumerable deleted, long filesetid, long prevId, DateTime timestamp) + public void AppendFilesFromPreviousSet(IDbTransaction transaction, IEnumerable deleted, long filesetid, long prevId, DateTime timestamp) { using (var cmd = m_connection.CreateCommand()) using (var cmdDelete = m_connection.CreateCommand()) @@ -696,7 +655,7 @@ namespace Duplicati.Library.Main.Database /// /// Transaction /// Optional exclusion predicate (true = exclude file) - public void AppendFilesFromPreviousSetWithPredicate(System.Data.IDbTransaction transaction, Func exclusionPredicate) + public void AppendFilesFromPreviousSetWithPredicate(IDbTransaction transaction, Func exclusionPredicate) { AppendFilesFromPreviousSetWithPredicate(transaction, exclusionPredicate, m_filesetId, -1, OperationTimestamp); } @@ -711,7 +670,7 @@ namespace Duplicati.Library.Main.Database /// Current fileset ID /// Source fileset ID /// If prevFileSetId == -1, used to locate previous fileset - public void AppendFilesFromPreviousSetWithPredicate(System.Data.IDbTransaction transaction, + public void AppendFilesFromPreviousSetWithPredicate(IDbTransaction transaction, Func exclusionPredicate, long fileSetId, long prevFileSetId, DateTime timestamp) { if (exclusionPredicate == null) @@ -729,21 +688,21 @@ namespace Duplicati.Library.Main.Database // copy entries from previous file set into a temporary table, except those file IDs already added by the current backup var tempFileSetTable = "FilesetEntry-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); cmd.Transaction = tr.Parent; - cmd.ExecuteNonQuery($@"CREATE TEMPORARY TABLE ""{tempFileSetTable}"" AS SELECT ""FileID"", ""Lastmodified"" FROM (SELECT DISTINCT ""FilesetID"", ""FileID"", ""Lastmodified"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?))", lastFilesetId, fileSetId); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tempFileSetTable}"" AS SELECT ""FileID"", ""Lastmodified"" FROM (SELECT DISTINCT ""FilesetID"", ""FileID"", ""Lastmodified"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?))"), lastFilesetId, fileSetId); // now we need to remove, from the above, any entries that were enumerated by the // UNC-driven backup cmdDelete.Transaction = tr.Parent; - cmdDelete.CommandText = $@"DELETE FROM ""{tempFileSetTable}"" WHERE ""FileID"" = ?"; + cmdDelete.CommandText = FormatInvariant($@"DELETE FROM ""{tempFileSetTable}"" WHERE ""FileID"" = ?"); cmdDelete.AddParameters(1); // enumerate files from new temporary file set, and remove any entries handled by UNC cmd.Transaction = tr.Parent; - foreach (var row in cmd.ExecuteReaderEnumerable( + foreach (var row in cmd.ExecuteReaderEnumerable(FormatInvariant( $@"SELECT f.""Path"", fs.""FileID"", fs.""Lastmodified"", COALESCE(bs.""Length"", -1) FROM (SELECT DISTINCT ""FileID"", ""Lastmodified"" FROM ""{tempFileSetTable}"") AS fs LEFT JOIN ""File"" AS f ON fs.""FileID"" = f.""ID"" - LEFT JOIN ""Blockset"" AS bs ON f.""BlocksetID"" = bs.""ID"";")) + LEFT JOIN ""Blockset"" AS bs ON f.""BlocksetID"" = bs.""ID"";"))) { var path = row.GetString(0); var size = row.GetInt64(3); @@ -756,8 +715,8 @@ namespace Duplicati.Library.Main.Database } // now copy the temporary table into the FileSetEntry table - cmd.ExecuteNonQuery($@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") - SELECT ?, ""FileID"", ""Lastmodified"" FROM ""{tempFileSetTable}""", fileSetId); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") + SELECT ?, ""FileID"", ""Lastmodified"" FROM ""{tempFileSetTable}"""), fileSetId); tr.Commit(); } @@ -769,44 +728,11 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset volume to update /// The timestamp of the operation to create /// An optional external transaction - public override long CreateFileset(long volumeid, DateTime timestamp, System.Data.IDbTransaction transaction = null) + public override long CreateFileset(long volumeid, DateTime timestamp, IDbTransaction transaction = null) { return m_filesetId = base.CreateFileset(volumeid, timestamp, transaction); } - public IEnumerable> GetIncompleteFilesets(System.Data.IDbTransaction transaction) - { - using (var cmd = m_connection.CreateCommand(transaction)) - { - using (var rd = cmd.ExecuteReader(@$"SELECT DISTINCT ""Fileset"".""ID"", ""Fileset"".""Timestamp"" FROM ""Fileset"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND (""RemoteVolume"".""State"" = '{RemoteVolumeState.Uploading}' OR ""RemoteVolume"".""State"" = '{RemoteVolumeState.Temporary}')")) - while (rd.Read()) - { - yield return new KeyValuePair( - rd.GetInt64(0), - ParseFromEpochSeconds(rd.GetInt64(1)).ToLocalTime() - ); - } - } - } - - public RemoteVolumeEntry GetRemoteVolumeFromFilesetID(long filesetID, IDbTransaction transaction = null) - { - using (var cmd = m_connection.CreateCommand(transaction)) - using (var rd = cmd.ExecuteReader(@"SELECT ""RemoteVolume"".""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""RemoteVolume"", ""Fileset"" WHERE ""Fileset"".""VolumeID"" = ""RemoteVolume"".""ID"" AND ""Fileset"".""ID"" = ?", filesetID)) - if (rd.Read()) - return new RemoteVolumeEntry( - rd.ConvertValueToInt64(0, -1), - rd.GetValue(1).ToString(), - (rd.GetValue(4) == null || rd.GetValue(4) == DBNull.Value) ? null : rd.GetValue(4).ToString(), - rd.ConvertValueToInt64(3, -1), - (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(2).ToString()), - (RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(5).ToString()), - new DateTime(rd.ConvertValueToInt64(6, 0), DateTimeKind.Utc) - ); - else - return default(RemoteVolumeEntry); - } - public IEnumerable GetTemporaryFilelistVolumeNames(bool latestOnly, IDbTransaction transaction = null) { var incompleteFilesetIDs = GetIncompleteFilesets(transaction).OrderBy(x => x.Value).Select(x => x.Key).ToArray(); @@ -824,7 +750,7 @@ namespace Duplicati.Library.Main.Database return volumeNames; } - public IEnumerable GetMissingIndexFiles(System.Data.IDbTransaction transaction) + public IEnumerable GetMissingIndexFiles(IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand(transaction)) using (var rd = cmd.ExecuteReader(@"SELECT ""Name"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND NOT ""ID"" IN (SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"") AND ""State"" IN (?,?)", RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) @@ -832,29 +758,18 @@ namespace Duplicati.Library.Main.Database yield return rd.GetValue(0).ToString(); } - public void LinkFilesetToVolume(long filesetid, long volumeid, System.Data.IDbTransaction transaction) - { - using (var cmd = m_connection.CreateCommand()) - { - cmd.Transaction = transaction; - var c = cmd.ExecuteNonQuery(@"UPDATE ""Fileset"" SET ""VolumeID"" = ? WHERE ""ID"" = ?", volumeid, filesetid); - if (c != 1) - throw new Exception(string.Format("Failed to link filesetid {0} to volumeid {1}", filesetid, volumeid)); - } - } - - public void MoveBlockToVolume(string blockkey, long size, long sourcevolumeid, long targetvolumeid, System.Data.IDbTransaction transaction) + public void MoveBlockToVolume(string blockkey, long size, long sourcevolumeid, long targetvolumeid, IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand()) { cmd.Transaction = transaction; var c = cmd.ExecuteNonQuery(@"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ? AND ""VolumeID"" = ? ", targetvolumeid, blockkey, size, sourcevolumeid); if (c != 1) - throw new Exception(string.Format("Failed to move block {0}:{1} from volume {2}, count: {3}", blockkey, size, sourcevolumeid, c)); + throw new Exception($"Failed to move block {blockkey}:{size} from volume {sourcevolumeid}, count: {c}"); } } - public void SafeDeleteRemoteVolume(string name, System.Data.IDbTransaction transaction) + public void SafeDeleteRemoteVolume(string name, IDbTransaction transaction) { var volumeid = GetRemoteVolumeID(name, transaction); @@ -862,13 +777,13 @@ namespace Duplicati.Library.Main.Database { var c = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""Block"" WHERE ""VolumeID"" = ? ", -1, volumeid); if (c != 0) - throw new Exception(string.Format("Failed to safe-delete volume {0}, blocks: {1}", name, c)); + throw new Exception($"Failed to safe-delete volume {name}, blocks: {c}"); RemoveRemoteVolume(name, transaction); } } - public string[] GetBlocklistHashes(string name, System.Data.IDbTransaction transaction) + public string[] GetBlocklistHashes(string name, IDbTransaction transaction) { var volumeid = GetRemoteVolumeID(name, transaction); using (var cmd = m_connection.CreateCommand(transaction)) @@ -929,7 +844,7 @@ namespace Duplicati.Library.Main.Database /// /// Data to add /// An optional external transaction - public void CreateChangeJournalData(IEnumerable data, System.Data.IDbTransaction transaction = null) + public void CreateChangeJournalData(IEnumerable data, IDbTransaction transaction = null) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) { @@ -957,7 +872,7 @@ namespace Duplicati.Library.Main.Database /// Data to add /// Existing file set to update /// An optional external transaction - public void UpdateChangeJournalData(IEnumerable data, long fileSetId, System.Data.IDbTransaction transaction = null) + public void UpdateChangeJournalData(IEnumerable data, long fileSetId, IDbTransaction transaction = null) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) { @@ -975,5 +890,22 @@ namespace Duplicati.Library.Main.Database tr.Commit(); } } + + /// + /// Checks if a blocklist hash is known + /// + /// The hash to check + /// An optional external transaction + /// True if the hash is known, false otherwise + public bool IsBlocklistHashKnown(string hash, IDbTransaction transaction) + { + m_getfirstfilesetwithblockinblockset.Transaction = transaction; + m_getfirstfilesetwithblockinblockset.SetParameterValue(0, hash); + var res = m_getfirstfilesetwithblockinblockset.ExecuteScalarInt64(); + if (res != -1 && res != m_filesetId) + return true; + else + return !m_blocklistHashes.Add(hash); + } } } diff --git a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs index 6a85e5b77..bcf56106b 100644 --- a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -40,22 +40,22 @@ namespace Duplicati.Library.Main.Database public void Fix() { - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand()) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand()) { cmd.Transaction = tr; var tablename = "PathMap-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); // TODO: Rewrite this to use PathPrefix // TODO: Needs to be much faster - using(var upcmd = m_connection.CreateCommand()) + using (var upcmd = m_connection.CreateCommand()) { - + upcmd.Transaction = tr; - upcmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY, ""RealPath"" TEXT NOT NULL, ""Obfuscated"" TEXT NULL)", tablename)); - upcmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" (""RealPath"") SELECT DISTINCT ""Path"" FROM ""File"" ORDER BY ""Path"" ", tablename)); - upcmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Obfuscated"" = ? || length(""RealPath"") || ? || ""ID"" || (CASE WHEN substr(""RealPath"", length(""RealPath"")) = ? THEN ? ELSE ? END) ", tablename), !OperatingSystem.IsWindows() ? "/" : "X:\\", Util.DirectorySeparatorString, Util.DirectorySeparatorString, Util.DirectorySeparatorString, ".bin"); - + upcmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tablename}"" (""ID"" INTEGER PRIMARY KEY, ""RealPath"" TEXT NOT NULL, ""Obfuscated"" TEXT NULL)")); + upcmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{tablename}"" (""RealPath"") SELECT DISTINCT ""Path"" FROM ""File"" ORDER BY ""Path"" ")); + upcmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{tablename}"" SET ""Obfuscated"" = ? || length(""RealPath"") || ? || ""ID"" || (CASE WHEN substr(""RealPath"", length(""RealPath"")) = ? THEN ? ELSE ? END) "), !OperatingSystem.IsWindows() ? "/" : "X:\\", Util.DirectorySeparatorString, Util.DirectorySeparatorString, Util.DirectorySeparatorString, ".bin"); + /*long id = 1; using(var rd = cmd.ExecuteReader(string.Format(@"SELECT ""RealPath"", ""Obfuscated"" FROM ""{0}"" ", tablename))) while(rd.Read()) @@ -66,21 +66,21 @@ namespace Duplicati.Library.Main.Database */ } - cmd.ExecuteNonQuery(@"UPDATE ""LogData"" SET ""Message"" = 'ERASED!' WHERE ""Message"" LIKE '%/%' OR ""Message"" LIKE '%:\%' "); - cmd.ExecuteNonQuery(@"UPDATE ""LogData"" SET ""Exception"" = 'ERASED!' WHERE ""Exception"" LIKE '%/%' OR ""Exception"" LIKE '%:\%' "); + cmd.ExecuteNonQuery(@"UPDATE ""LogData"" SET ""Message"" = 'ERASED!' WHERE ""Message"" LIKE '%/%' OR ""Message"" LIKE '%:\%' "); + cmd.ExecuteNonQuery(@"UPDATE ""LogData"" SET ""Exception"" = 'ERASED!' WHERE ""Exception"" LIKE '%/%' OR ""Exception"" LIKE '%:\%' "); cmd.ExecuteNonQuery(@"UPDATE ""Configuration"" SET ""Value"" = 'ERASED!' WHERE ""Key"" = 'passphrase' "); - cmd.ExecuteNonQuery(string.Format(@"CREATE TABLE ""FixedFile"" AS SELECT ""B"".""ID"" AS ""ID"", ""A"".""Obfuscated"" AS ""Path"", ""B"".""BlocksetID"" AS ""BlocksetID"", ""B"".""MetadataID"" AS ""MetadataID"" FROM ""{0}"" ""A"", ""File"" ""B"" WHERE ""A"".""RealPath"" = ""B"".""Path"" ", tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TABLE ""FixedFile"" AS SELECT ""B"".""ID"" AS ""ID"", ""A"".""Obfuscated"" AS ""Path"", ""B"".""BlocksetID"" AS ""BlocksetID"", ""B"".""MetadataID"" AS ""MetadataID"" FROM ""{tablename}"" ""A"", ""File"" ""B"" WHERE ""A"".""RealPath"" = ""B"".""Path"" ")); cmd.ExecuteNonQuery(@"DROP VIEW ""File"" "); cmd.ExecuteNonQuery(@"DROP TABLE ""FileLookup"" "); cmd.ExecuteNonQuery(@"DROP TABLE ""PathPrefix"" "); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", tablename)); - - using(new Logging.Timer(LOGTAG, "CommitUpdateBugReport", "CommitUpdateBugReport")) + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tablename}"" ")); + + using (new Logging.Timer(LOGTAG, "CommitUpdateBugReport", "CommitUpdateBugReport")) tr.Commit(); - + cmd.Transaction = null; cmd.ExecuteNonQuery("VACUUM"); diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index de08ccd9e..a03c71dcd 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,6 +26,12 @@ using System.Text; using System.IO; using Duplicati.Library.Modules.Builtin.ResultSerialization; using Duplicati.Library.Utility; +using System.Runtime.CompilerServices; +using Duplicati.Library.Interface; + + +// Expose internal classes to UnitTests, so that Database classes can be tested +[assembly: InternalsVisibleTo("Duplicati.UnitTest")] namespace Duplicati.Library.Main.Database { @@ -36,49 +42,48 @@ namespace Duplicati.Library.Main.Database /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalDatabase)); - protected readonly System.Data.IDbConnection m_connection; + protected readonly IDbConnection m_connection; protected readonly long m_operationid = -1; + private bool m_hasExecutedVacuum; - private readonly System.Data.IDbCommand m_updateremotevolumeCommand; - private readonly System.Data.IDbCommand m_selectremotevolumesCommand; - private readonly System.Data.IDbCommand m_selectremotevolumeCommand; - private readonly System.Data.IDbCommand m_removeremotevolumeCommand; - private readonly System.Data.IDbCommand m_removedeletedremotevolumeCommand; - private readonly System.Data.IDbCommand m_selectremotevolumeIdCommand; - private readonly System.Data.IDbCommand m_createremotevolumeCommand; - private readonly System.Data.IDbCommand m_selectduplicateRemoteVolumesCommand; + private readonly IDbCommand m_updateremotevolumeCommand; + private readonly IDbCommand m_selectremotevolumesCommand; + private readonly IDbCommand m_selectremotevolumeCommand; + private readonly IDbCommand m_removeremotevolumeCommand; + private readonly IDbCommand m_removedeletedremotevolumeCommand; + private readonly IDbCommand m_selectremotevolumeIdCommand; + private readonly IDbCommand m_createremotevolumeCommand; + private readonly IDbCommand m_selectduplicateRemoteVolumesCommand; - private readonly System.Data.IDbCommand m_insertlogCommand; - private readonly System.Data.IDbCommand m_insertremotelogCommand; - private readonly System.Data.IDbCommand m_insertIndexBlockLink; + private readonly IDbCommand m_insertlogCommand; + private readonly IDbCommand m_insertremotelogCommand; + private readonly IDbCommand m_insertIndexBlockLink; - private readonly System.Data.IDbCommand m_findpathprefixCommand; - private readonly System.Data.IDbCommand m_insertpathprefixCommand; - - protected BasicResults m_result; + private readonly IDbCommand m_findpathprefixCommand; + private readonly IDbCommand m_insertpathprefixCommand; public const long FOLDER_BLOCKSET_ID = -100; public const long SYMLINK_BLOCKSET_ID = -200; public DateTime OperationTimestamp { get; private set; } - internal System.Data.IDbConnection Connection { get { return m_connection; } } + internal IDbConnection Connection { get { return m_connection; } } public bool IsDisposed { get; private set; } public bool ShouldCloseConnection { get; set; } - protected static System.Data.IDbConnection CreateConnection(string path) + protected static IDbConnection CreateConnection(string path) { - path = System.IO.Path.GetFullPath(path); - if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(path))) - System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + path = Path.GetFullPath(path); + if (!Directory.Exists(Path.GetDirectoryName(path))) + Directory.CreateDirectory(Path.GetDirectoryName(path)); - var c = Duplicati.Library.SQLiteHelper.SQLiteLoader.LoadConnection(path); + var c = SQLiteHelper.SQLiteLoader.LoadConnection(path); try { - Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(c, path, typeof(LocalDatabase)); + SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(c, path, typeof(LocalDatabase)); } catch { @@ -90,6 +95,14 @@ namespace Duplicati.Library.Main.Database return c; } + /// + /// Formats the string using the invariant culture + /// + /// The formattable string + /// The formatted string + public static string FormatInvariant(FormattableString formattable) + => Library.Utility.Utility.FormatInvariant(formattable); + public static bool Exists(string path) { return File.Exists(path); @@ -112,29 +125,31 @@ namespace Duplicati.Library.Main.Database public LocalDatabase(LocalDatabase db) : this(db.m_connection) { - this.OperationTimestamp = db.OperationTimestamp; - this.m_connection = db.m_connection; - this.m_operationid = db.m_operationid; - this.m_result = db.m_result; + OperationTimestamp = db.OperationTimestamp; + m_connection = db.m_connection; + m_operationid = db.m_operationid; } /// /// Creates a new database instance and starts a new operation /// /// The name of the operation. If null, continues last operation - public LocalDatabase(System.Data.IDbConnection connection, string operation) + public LocalDatabase(IDbConnection connection, string operation) : this(connection) { - this.OperationTimestamp = DateTime.UtcNow; + OperationTimestamp = DateTime.UtcNow; m_connection = connection; - if (m_connection.State != System.Data.ConnectionState.Open) + if (m_connection.State != ConnectionState.Open) m_connection.Open(); if (operation != null) { using (var cmd = m_connection.CreateCommand()) - m_operationid = cmd.ExecuteScalarInt64(@"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES (?, ?); SELECT last_insert_rowid();", -1, operation, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(OperationTimestamp)); + m_operationid = cmd.SetCommandAndParameters(@"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES (@Description, @Timestamp); SELECT last_insert_rowid();") + .SetParameterValue("@Description", operation) + .SetParameterValue("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(OperationTimestamp)) + .ExecuteScalarInt64(-1); } else { @@ -143,71 +158,29 @@ namespace Duplicati.Library.Main.Database using (var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Timestamp"" FROM ""Operation"" ORDER BY ""Timestamp"" DESC LIMIT 1")) { if (!rd.Read()) - { throw new Exception("LocalDatabase does not contain a previous operation."); - } + m_operationid = rd.GetInt64(0); OperationTimestamp = ParseFromEpochSeconds(rd.GetInt64(1)); } } } - private LocalDatabase(System.Data.IDbConnection connection) + private LocalDatabase(IDbConnection connection) { - m_updateremotevolumeCommand = connection.CreateCommand(); - m_selectremotevolumesCommand = connection.CreateCommand(); - m_selectduplicateRemoteVolumesCommand = connection.CreateCommand(); - m_selectremotevolumeCommand = connection.CreateCommand(); - m_insertlogCommand = connection.CreateCommand(); - m_insertremotelogCommand = connection.CreateCommand(); - m_removeremotevolumeCommand = connection.CreateCommand(); - m_removedeletedremotevolumeCommand = connection.CreateCommand(); - m_selectremotevolumeIdCommand = connection.CreateCommand(); - m_createremotevolumeCommand = connection.CreateCommand(); - m_insertIndexBlockLink = connection.CreateCommand(); - m_findpathprefixCommand = connection.CreateCommand(); - m_insertpathprefixCommand = connection.CreateCommand(); - - m_insertlogCommand.CommandText = @"INSERT INTO ""LogData"" (""OperationID"", ""Timestamp"", ""Type"", ""Message"", ""Exception"") VALUES (?, ?, ?, ?, ?)"; - m_insertlogCommand.AddParameters(5); - - m_insertremotelogCommand.CommandText = @"INSERT INTO ""RemoteOperation"" (""OperationID"", ""Timestamp"", ""Operation"", ""Path"", ""Data"") VALUES (?, ?, ?, ?, ?)"; - m_insertremotelogCommand.AddParameters(5); - - m_updateremotevolumeCommand.CommandText = @"UPDATE ""Remotevolume"" SET ""OperationID"" = ?, ""State"" = ?, ""Hash"" = ?, ""Size"" = ? WHERE ""Name"" = ?"; - m_updateremotevolumeCommand.AddParameters(5); - - m_selectremotevolumesCommand.CommandText = @"SELECT ""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""Remotevolume"""; - - m_selectremotevolumeCommand.CommandText = m_selectremotevolumesCommand.CommandText + @" WHERE ""Name"" = ?"; - m_selectduplicateRemoteVolumesCommand.CommandText = string.Format(@"SELECT DISTINCT ""Name"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" IN (SELECT ""Name"" FROM ""Remotevolume"" WHERE ""State"" IN ('{0}', '{1}')) AND NOT ""State"" IN ('{0}', '{1}')", RemoteVolumeState.Deleted.ToString(), RemoteVolumeState.Deleting.ToString()); - - m_selectremotevolumeCommand.AddParameter(); - - m_removeremotevolumeCommand.CommandText = @"DELETE FROM ""Remotevolume"" WHERE ""Name"" = ? AND (""DeleteGraceTime"" < ? OR ""State"" != ?)"; - m_removeremotevolumeCommand.AddParameters(3); - - m_removedeletedremotevolumeCommand.CommandText = $@"DELETE FROM ""Remotevolume"" WHERE ""State"" == '{RemoteVolumeState.Deleted.ToString()}' AND (""DeleteGraceTime"" < ? OR LENGTH(""DeleteGraceTime"") > 12) "; // >12 is to handle removal of old records that were in ticks - m_removedeletedremotevolumeCommand.AddParameter(); - - m_selectremotevolumeIdCommand.CommandText = @"SELECT ""ID"" FROM ""Remotevolume"" WHERE ""Name"" = ?"; - - m_createremotevolumeCommand.CommandText = @"INSERT INTO ""Remotevolume"" (""OperationID"", ""Name"", ""Type"", ""State"", ""Size"", ""VerificationCount"", ""DeleteGraceTime"") VALUES (?, ?, ?, ?, ?, ?, ?); SELECT last_insert_rowid();"; - m_createremotevolumeCommand.AddParameters(7); - - m_insertIndexBlockLink.CommandText = @"INSERT INTO ""IndexBlockLink"" (""IndexVolumeID"", ""BlockVolumeID"") VALUES (?, ?)"; - m_insertIndexBlockLink.AddParameters(2); - - m_findpathprefixCommand.CommandText = @"SELECT ""ID"" FROM ""PathPrefix"" WHERE ""Prefix"" = ?"; - m_findpathprefixCommand.AddParameter(); - - m_insertpathprefixCommand.CommandText = @"INSERT INTO ""PathPrefix"" (""Prefix"") VALUES (?); SELECT last_insert_rowid(); "; - m_insertpathprefixCommand.AddParameter(); - } - - internal void SetResult(BasicResults result) - { - m_result = result; + m_insertlogCommand = connection.CreateCommand(@"INSERT INTO ""LogData"" (""OperationID"", ""Timestamp"", ""Type"", ""Message"", ""Exception"") VALUES (@OperationID, @Timestamp, @Type, @Message, @Exception)"); + m_insertremotelogCommand = connection.CreateCommand(@"INSERT INTO ""RemoteOperation"" (""OperationID"", ""Timestamp"", ""Operation"", ""Path"", ""Data"") VALUES (@OperationID, @Timestamp, @Operation, @Path, @Data)"); + m_updateremotevolumeCommand = connection.CreateCommand(@"UPDATE ""Remotevolume"" SET ""OperationID"" = @OperationID, ""State"" = @State, ""Hash"" = @Hash, ""Size"" = @Size WHERE ""Name"" = @Name"); + m_selectremotevolumesCommand = connection.CreateCommand(@"SELECT ""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""Remotevolume"""); + m_selectremotevolumeCommand = connection.CreateCommand(m_selectremotevolumesCommand.CommandText + @" WHERE ""Name"" = @Name"); + m_selectduplicateRemoteVolumesCommand = connection.CreateCommand(FormatInvariant($@"SELECT DISTINCT ""Name"", ""State"" FROM ""Remotevolume"" WHERE ""Name"" IN (SELECT ""Name"" FROM ""Remotevolume"" WHERE ""State"" IN ('{RemoteVolumeState.Deleted.ToString()}', '{RemoteVolumeState.Deleting.ToString()}')) AND NOT ""State"" IN ('{RemoteVolumeState.Deleted.ToString()}', '{RemoteVolumeState.Deleting.ToString()}')")); + m_removeremotevolumeCommand = connection.CreateCommand(@"DELETE FROM ""Remotevolume"" WHERE ""Name"" = @Name AND (""DeleteGraceTime"" < @Now OR ""State"" != @State)"); + m_removedeletedremotevolumeCommand = connection.CreateCommand(FormatInvariant($@"DELETE FROM ""Remotevolume"" WHERE ""State"" == '{RemoteVolumeState.Deleted.ToString()}' AND (""DeleteGraceTime"" < @Now OR LENGTH(""DeleteGraceTime"") > 12) ")); // >12 is to handle removal of old records that were in ticks + m_selectremotevolumeIdCommand = connection.CreateCommand(@"SELECT ""ID"" FROM ""Remotevolume"" WHERE ""Name"" = @Name"); + m_createremotevolumeCommand = connection.CreateCommand(@"INSERT INTO ""Remotevolume"" (""OperationID"", ""Name"", ""Type"", ""State"", ""Size"", ""VerificationCount"", ""DeleteGraceTime"") VALUES (@OperationID, @Name, @Type, @State, @Size, @VerificationCount, @DeleteGraceTime); SELECT last_insert_rowid();"); + m_insertIndexBlockLink = connection.CreateCommand(@"INSERT INTO ""IndexBlockLink"" (""IndexVolumeID"", ""BlockVolumeID"") VALUES (@IndexVolumeId, @BlockVolumeId)"); + m_findpathprefixCommand = connection.CreateCommand(@"SELECT ""ID"" FROM ""PathPrefix"" WHERE ""Prefix"" = @Prefix"); + m_insertpathprefixCommand = connection.CreateCommand(@"INSERT INTO ""PathPrefix"" (""Prefix"") VALUES (@Prefix); SELECT last_insert_rowid(); "); } /// @@ -218,26 +191,25 @@ namespace Duplicati.Library.Main.Database return Library.Utility.Utility.EPOCH.AddSeconds(seconds); } - public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, System.Data.IDbTransaction transaction = null) + public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, IDbTransaction transaction = null) { UpdateRemoteVolume(name, state, size, hash, false, transaction); } - public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup, System.Data.IDbTransaction transaction = null) + public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup, IDbTransaction transaction = null) { UpdateRemoteVolume(name, state, size, hash, suppressCleanup, new TimeSpan(0), transaction); } - public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup, TimeSpan deleteGraceTime, System.Data.IDbTransaction transaction = null) + public void UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup, TimeSpan deleteGraceTime, IDbTransaction transaction = null) { m_updateremotevolumeCommand.Transaction = transaction; - m_updateremotevolumeCommand.SetParameterValue(0, m_operationid); - m_updateremotevolumeCommand.SetParameterValue(1, state.ToString()); - m_updateremotevolumeCommand.SetParameterValue(2, hash); - m_updateremotevolumeCommand.SetParameterValue(3, size); - m_updateremotevolumeCommand.SetParameterValue(4, name); - - var c = m_updateremotevolumeCommand.ExecuteNonQuery(); + var c = m_updateremotevolumeCommand.SetParameterValue("@OperationID", m_operationid) + .SetParameterValue("@State", state.ToString()) + .SetParameterValue("@Hash", hash) + .SetParameterValue("@Size", size) + .SetParameterValue("@Name", name) + .ExecuteNonQuery(); if (c != 1) { @@ -248,13 +220,13 @@ namespace Duplicati.Library.Main.Database { using (var cmd = m_connection.CreateCommand(transaction)) { - if ((c = cmd.ExecuteNonQuery( - @"UPDATE ""RemoteVolume"" SET ""DeleteGraceTime"" = ? WHERE ""Name"" = ? ", - Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow + deleteGraceTime), - name)) != 1) - { + c = cmd.SetCommandAndParameters(@"UPDATE ""RemoteVolume"" SET ""DeleteGraceTime"" = @DeleteGraceTime WHERE ""Name"" = @Name ") + .SetParameterValue("@DeleteGraceTime", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow + deleteGraceTime)) + .SetParameterValue("@Name", name) + .ExecuteNonQuery(); + + if (c != 1) throw new Exception($"Unexpected number of updates when recording remote volume updates: {c}!"); - } } } @@ -275,11 +247,11 @@ namespace Duplicati.Library.Main.Database } } - public Tuple GetFilelistWhereClause(DateTime time, long[] versions, IEnumerable> filesetslist = null, bool singleTimeMatch = false) + public (string Query, Dictionary Values) GetFilelistWhereClause(DateTime time, long[] versions, IEnumerable> filesetslist = null, bool singleTimeMatch = false) { - var filesets = (filesetslist ?? this.FilesetTimes).ToArray(); - StringBuilder query = new StringBuilder(); - var args = new List(); + var filesets = (filesetslist ?? FilesetTimes).ToArray(); + var query = new StringBuilder(); + var args = new Dictionary(); if (time.Ticks > 0 || (versions != null && versions.Length > 0)) { var hasTime = false; @@ -288,22 +260,23 @@ namespace Duplicati.Library.Main.Database if (time.Kind == DateTimeKind.Unspecified) throw new Exception("Invalid DateTime given, must be either local or UTC"); - query.Append(singleTimeMatch ? @" ""Timestamp"" = ?" : @" ""Timestamp"" <= ?"); + query.Append(singleTimeMatch ? @" ""Timestamp"" = @Timestamp" : @" ""Timestamp"" <= @Timestamp"); // Make sure the resolution is the same (i.e. no milliseconds) - args.Add(Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(time)); + args.Add("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(time)); hasTime = true; } if (versions != null && versions.Length > 0) { - StringBuilder qs = new StringBuilder(); - + var qs = new StringBuilder(); foreach (var v in versions) { if (v >= 0 && v < filesets.Length) { - args.Add(filesets[v].Key); - qs.Append("?,"); + var argName = "@Fileset" + v; + args.Add(argName, filesets[v].Key); + qs.Append(argName); + qs.Append(","); } else Logging.Log.WriteWarningMessage(LOGTAG, "SkipInvalidVersion", null, "Skipping invalid version: {0}", v); @@ -324,19 +297,32 @@ namespace Duplicati.Library.Main.Database } } - return new Tuple(query.ToString(), args.ToArray()); + return (query.ToString(), args); } - public long GetRemoteVolumeID(string file, System.Data.IDbTransaction transaction = null) + public long GetRemoteVolumeID(string file, IDbTransaction transaction = null) { m_selectremotevolumeIdCommand.Transaction = transaction; - return m_selectremotevolumeIdCommand.ExecuteScalarInt64(null, -1, file); + return m_selectremotevolumeIdCommand.SetParameterValue("@Name", file).ExecuteScalarInt64(-1); } - public RemoteVolumeEntry GetRemoteVolume(string file, System.Data.IDbTransaction transaction = null) + public IEnumerable> GetRemoteVolumeIDs(IEnumerable files, IDbTransaction transaction = null) + { + using (var cmd = m_connection.CreateCommand(transaction)) + { + cmd.SetCommandAndParameters(@"SELECT ""Name"", ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN (@Name)") + .SetParameterValue("@Name", files); + + using (var rd = cmd.ExecuteReader()) + while (rd.Read()) + yield return new KeyValuePair(rd.GetString(0), rd.GetInt64(1)); + } + } + + public RemoteVolumeEntry GetRemoteVolume(string file, IDbTransaction transaction = null) { m_selectremotevolumeCommand.Transaction = transaction; - m_selectremotevolumeCommand.SetParameterValue(0, file); + m_selectremotevolumeCommand.SetParameterValue("@Name", file); using (var rd = m_selectremotevolumeCommand.ExecuteReader()) if (rd.Read()) return new RemoteVolumeEntry( @@ -346,7 +332,7 @@ namespace Duplicati.Library.Main.Database rd.ConvertValueToInt64(3, -1), (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(2).ToString()), (RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(5).ToString()), - new DateTime(rd.ConvertValueToInt64(6, 0), DateTimeKind.Utc) + ParseFromEpochSeconds(rd.ConvertValueToInt64(6, 0)) ); return RemoteVolumeEntry.Empty; @@ -363,7 +349,7 @@ namespace Duplicati.Library.Main.Database } } - public IEnumerable GetRemoteVolumes(System.Data.IDbTransaction transaction = null) + public IEnumerable GetRemoteVolumes(IDbTransaction transaction = null) { m_selectremotevolumesCommand.Transaction = transaction; using (var rd = m_selectremotevolumesCommand.ExecuteReader()) @@ -377,7 +363,7 @@ namespace Duplicati.Library.Main.Database rd.ConvertValueToInt64(3, -1), (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(2).ToString()), (RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(5).ToString()), - new DateTime(rd.ConvertValueToInt64(6, 0), DateTimeKind.Utc) + ParseFromEpochSeconds(rd.ConvertValueToInt64(6, 0)) ); } } @@ -389,15 +375,15 @@ namespace Duplicati.Library.Main.Database /// The operation performed /// The path involved /// Any data relating to the operation - public void LogRemoteOperation(string operation, string path, string data, System.Data.IDbTransaction transaction) + public void LogRemoteOperation(string operation, string path, string data, IDbTransaction transaction) { - m_insertremotelogCommand.Transaction = transaction; - m_insertremotelogCommand.SetParameterValue(0, m_operationid); - m_insertremotelogCommand.SetParameterValue(1, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); - m_insertremotelogCommand.SetParameterValue(2, operation); - m_insertremotelogCommand.SetParameterValue(3, path); - m_insertremotelogCommand.SetParameterValue(4, data); - m_insertremotelogCommand.ExecuteNonQuery(); + m_insertremotelogCommand + .SetParameterValue("@OperationID", m_operationid) + .SetParameterValue("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)) + .SetParameterValue("@Operation", operation) + .SetParameterValue("@Path", path) + .SetParameterValue("@Data", data) + .ExecuteNonQuery(transaction); } /// @@ -406,154 +392,157 @@ namespace Duplicati.Library.Main.Database /// The message type /// The message /// An optional exception - public void LogMessage(string type, string message, Exception exception, System.Data.IDbTransaction transaction) + public void LogMessage(string type, string message, Exception exception, IDbTransaction transaction) { - m_insertlogCommand.Transaction = transaction; - m_insertlogCommand.SetParameterValue(0, m_operationid); - m_insertlogCommand.SetParameterValue(1, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); - m_insertlogCommand.SetParameterValue(2, type); - m_insertlogCommand.SetParameterValue(3, message); - m_insertlogCommand.SetParameterValue(4, exception == null ? null : exception.ToString()); - m_insertlogCommand.ExecuteNonQuery(); + m_insertlogCommand.SetParameterValue("@OperationID", m_operationid) + .SetParameterValue("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)) + .SetParameterValue("@Type", type) + .SetParameterValue("@Message", message) + .SetParameterValue("@Exception", exception?.ToString()) + .ExecuteNonQuery(transaction); } - public void UnlinkRemoteVolume(string name, RemoteVolumeState state, System.Data.IDbTransaction transaction = null) + public void UnlinkRemoteVolume(string name, RemoteVolumeState state, IDbTransaction transaction = null) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - cmd.Transaction = tr.Parent; - var c = cmd.ExecuteNonQuery(@"DELETE FROM ""RemoteVolume"" WHERE ""Name"" = ? AND ""State"" = ? ", name, state.ToString()); + var c = cmd.SetCommandAndParameters(@"DELETE FROM ""RemoteVolume"" WHERE ""Name"" = @Name AND ""State"" = @State ") + .SetParameterValue("@Name", name) + .SetParameterValue("@State", state.ToString()) + .ExecuteNonQuery(); + if (c != 1) - throw new Exception(string.Format("Unexpected number of remote volumes deleted: {0}, expected {1}", c, 1)); + throw new Exception($"Unexpected number of remote volumes deleted: {c}, expected {1}"); tr.Commit(); } } - public void RemoveRemoteVolume(string name, System.Data.IDbTransaction transaction = null) + public void RemoveRemoteVolume(string name, IDbTransaction transaction = null) { - RemoveRemoteVolumes(new string[] { name }, transaction); + RemoveRemoteVolumes([name], transaction); } - public void RemoveRemoteVolumes(IEnumerable names, System.Data.IDbTransaction transaction = null) + public void RemoveRemoteVolumes(IEnumerable names, IDbTransaction transaction = null) { if (names == null || !names.Any()) return; using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) - using (var deletecmd = m_connection.CreateCommand()) + using (var deletecmd = m_connection.CreateCommand(tr.Parent)) { - deletecmd.Transaction = tr.Parent; - string temptransguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); var volidstable = "DelVolSetIds-" + temptransguid; var blocksetidstable = "DelBlockSetIds-" + temptransguid; // Create and fill a temp table with the volids to delete. We avoid using too many parameters that way. - deletecmd.ExecuteNonQuery(string.Format(@"CREATE TEMP TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY)", volidstable)); - deletecmd.CommandText = string.Format(@"INSERT OR IGNORE INTO ""{0}"" (""ID"") VALUES (?)", volidstable); - deletecmd.Parameters.Clear(); - deletecmd.AddParameters(1); + deletecmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMP TABLE ""{volidstable}"" (""ID"" INTEGER PRIMARY KEY)")); + deletecmd.SetCommandAndParameters(FormatInvariant($@"INSERT OR IGNORE INTO ""{volidstable}"" (""ID"") VALUES (@Id)")); foreach (var name in names) { var volumeid = GetRemoteVolumeID(name, tr.Parent); - deletecmd.SetParameterValue(0, volumeid); - deletecmd.ExecuteNonQuery(); + deletecmd.SetParameterValue("@Id", volumeid) + .ExecuteNonQuery(); } - var volIdsSubQuery = string.Format(@"SELECT ""ID"" FROM ""{0}"" ", volidstable); + var volIdsSubQuery = FormatInvariant($@"SELECT ""ID"" FROM ""{volidstable}"" "); deletecmd.Parameters.Clear(); - var bsIdsSubQuery = string.Format( - @"SELECT DISTINCT ""BlocksetEntry"".""BlocksetID"" FROM ""BlocksetEntry"", ""Block""" - + @" WHERE ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""Block"".""VolumeID"" IN ({0}) " - + @"UNION ALL " - + @"SELECT DISTINCT ""BlocksetID"" FROM ""BlocklistHash""" - + @" WHERE ""Hash"" IN (SELECT ""Hash"" FROM ""Block"" WHERE ""VolumeID"" IN ({0}))" - , volIdsSubQuery); + var bsIdsSubQuery = FormatInvariant(@$" +SELECT DISTINCT ""BlocksetEntry"".""BlocksetID"" FROM ""BlocksetEntry"", ""Block"" +WHERE ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""Block"".""VolumeID"" IN ({volIdsSubQuery}) +UNION ALL +SELECT DISTINCT ""BlocksetID"" FROM ""BlocklistHash"" +WHERE ""Hash"" IN (SELECT ""Hash"" FROM ""Block"" WHERE ""VolumeID"" IN ({volIdsSubQuery}))"); // Create a temporary table to cache subquery result, as it might take long (SQLite does not cache at all). - deletecmd.ExecuteNonQuery(string.Format(@"CREATE TEMP TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY)", blocksetidstable)); - deletecmd.ExecuteNonQuery(string.Format(@"INSERT OR IGNORE INTO ""{0}"" (""ID"") {1}", blocksetidstable, bsIdsSubQuery)); - bsIdsSubQuery = string.Format(@"SELECT DISTINCT ""ID"" FROM ""{0}"" ", blocksetidstable); + deletecmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMP TABLE ""{blocksetidstable}"" (""ID"" INTEGER PRIMARY KEY)")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"INSERT OR IGNORE INTO ""{blocksetidstable}"" (""ID"") {bsIdsSubQuery}")); + bsIdsSubQuery = FormatInvariant($@"SELECT DISTINCT ""ID"" FROM ""{blocksetidstable}"" "); deletecmd.Parameters.Clear(); // Create a temp table to associate metadata that is being deleted to a fileset - var metadataFilesetQuery = $@"SELECT Metadataset.ID, FilesetEntry.FilesetID + var metadataFilesetQuery = FormatInvariant($@"SELECT Metadataset.ID, FilesetEntry.FilesetID FROM Metadataset INNER JOIN FileLookup ON FileLookup.MetadataID = Metadataset.ID INNER JOIN FilesetEntry ON FilesetEntry.FileID = FileLookup.ID WHERE Metadataset.BlocksetID IN ({bsIdsSubQuery}) -OR Metadataset.ID IN (SELECT MetadataID FROM FileLookup WHERE BlocksetID IN ({bsIdsSubQuery}))"; +OR Metadataset.ID IN (SELECT MetadataID FROM FileLookup WHERE BlocksetID IN ({bsIdsSubQuery}))"); var metadataFilesetTable = @"DelMetadataFilesetIds-" + temptransguid; - deletecmd.ExecuteNonQuery($@"CREATE TEMP TABLE ""{metadataFilesetTable}"" (MetadataID INTEGER PRIMARY KEY, FilesetID INTEGER)"); - deletecmd.ExecuteNonQuery($@"INSERT OR IGNORE INTO ""{metadataFilesetTable}"" (MetadataID, FilesetID) {metadataFilesetQuery}"); + deletecmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMP TABLE ""{metadataFilesetTable}"" (MetadataID INTEGER PRIMARY KEY, FilesetID INTEGER)")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"INSERT OR IGNORE INTO ""{metadataFilesetTable}"" (MetadataID, FilesetID) {metadataFilesetQuery}")); // Delete FilesetEntry rows that had their metadata deleted - deletecmd.ExecuteNonQuery($@"DELETE FROM FilesetEntry + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM FilesetEntry WHERE FilesetEntry.FilesetID IN (SELECT DISTINCT FilesetID FROM ""{metadataFilesetTable}"") AND FilesetEntry.FileID IN ( SELECT FilesetEntry.FileID FROM FilesetEntry INNER JOIN FileLookup ON FileLookup.ID = FilesetEntry.FileID - WHERE FileLookup.MetadataID IN (SELECT MetadataID FROM ""{metadataFilesetTable}""))"); + WHERE FileLookup.MetadataID IN (SELECT MetadataID FROM ""{metadataFilesetTable}""))")); // Delete FilesetEntry rows that had their blocks deleted - deletecmd.ExecuteNonQuery($@"DELETE FROM FilesetEntry WHERE FilesetEntry.FileID IN ( + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM FilesetEntry WHERE FilesetEntry.FileID IN ( SELECT ID FROM FileLookup -WHERE FileLookup.BlocksetID IN ({bsIdsSubQuery}))"); - deletecmd.ExecuteNonQuery($@"DELETE FROM FileLookup WHERE FileLookup.MetadataID IN (SELECT MetadataID FROM ""{metadataFilesetTable}"")"); +WHERE FileLookup.BlocksetID IN ({bsIdsSubQuery}))")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM FileLookup WHERE FileLookup.MetadataID IN (SELECT MetadataID FROM ""{metadataFilesetTable}"")")); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""Metadataset"" WHERE ""BlocksetID"" IN ({0})", bsIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""FileLookup"" WHERE ""BlocksetID"" IN ({0})", bsIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""Blockset"" WHERE ""ID"" IN ({0})", bsIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ({0})", bsIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""BlocklistHash"" WHERE ""BlocklistHash"".""BlocksetID"" IN ({0})", bsIdsSubQuery)); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""Metadataset"" WHERE ""BlocksetID"" IN ({bsIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""FileLookup"" WHERE ""BlocksetID"" IN ({bsIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""Blockset"" WHERE ""ID"" IN ({bsIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ({bsIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""BlocklistHash"" WHERE ""BlocklistHash"".""BlocksetID"" IN ({bsIdsSubQuery})")); // If the volume is a block or index volume, this will update the crosslink table, otherwise nothing will happen - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN ({0}) OR ""IndexVolumeID"" IN ({0})", volIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""Block"" WHERE ""VolumeID"" IN ({0})", volIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""DeletedBlock"" WHERE ""VolumeID"" IN ({0})", volIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""ChangeJournalData"" WHERE ""FilesetID"" IN (SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ({0}))", volIdsSubQuery)); - deletecmd.ExecuteNonQuery($@"DELETE FROM FilesetEntry WHERE FilesetID IN (SELECT ID FROM Fileset WHERE VolumeID IN ({volIdsSubQuery}))"); - deletecmd.ExecuteNonQuery($@"DELETE FROM Fileset WHERE VolumeID IN ({volIdsSubQuery})"); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN ({volIdsSubQuery}) OR ""IndexVolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""Block"" WHERE ""VolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""DeletedBlock"" WHERE ""VolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""ChangeJournalData"" WHERE ""FilesetID"" IN (SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ({volIdsSubQuery}))")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM FilesetEntry WHERE FilesetID IN (SELECT ID FROM Fileset WHERE VolumeID IN ({volIdsSubQuery}))")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM Fileset WHERE VolumeID IN ({volIdsSubQuery})")); // Delete from Fileset if FilesetEntry rows were deleted by related metadata and there are no references in FilesetEntry anymore - deletecmd.ExecuteNonQuery($@"DELETE FROM Fileset WHERE Fileset.ID IN + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM Fileset WHERE Fileset.ID IN (SELECT DISTINCT FilesetID FROM ""{metadataFilesetTable}"") AND Fileset.ID NOT IN - (SELECT DISTINCT FilesetID FROM FilesetEntry)"); + (SELECT DISTINCT FilesetID FROM FilesetEntry)")); // Clean up temp tables for subqueries. We truncate content and then try to delete. // Drop in try-block, as it fails in nested transactions (SQLite problem) - // System.Data.SQLite.SQLiteException (0x80004005): database table is locked - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" ", blocksetidstable)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" ", volidstable)); + // SQLite.SQLiteException (0x80004005): database table is locked + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{blocksetidstable}"" ")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{volidstable}"" ")); try { deletecmd.CommandTimeout = 2; - deletecmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", blocksetidstable)); - deletecmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", volidstable)); - deletecmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", metadataFilesetTable)); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{blocksetidstable}"" ")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{volidstable}"" ")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{metadataFilesetTable}"" ")); } catch { /* Ignore, will be deleted on close anyway. */ } m_removeremotevolumeCommand.Transaction = tr.Parent; - m_removeremotevolumeCommand.SetParameterValue(1, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); - m_removeremotevolumeCommand.SetParameterValue(2, RemoteVolumeState.Deleted.ToString()); + m_removeremotevolumeCommand.SetParameterValue("@Now", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); + m_removeremotevolumeCommand.SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()); foreach (var name in names) { - m_removeremotevolumeCommand.SetParameterValue(0, name); + m_removeremotevolumeCommand.SetParameterValue("@Name", name); m_removeremotevolumeCommand.ExecuteNonQuery(); } + // Validate before commiting changes + var nonAttachedFiles = deletecmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FileID"" NOT IN (SELECT ""ID"" FROM ""FileLookup"")"); + if (nonAttachedFiles > 0) + throw new ConstraintException($"Detected {nonAttachedFiles} file(s) in FilesetEntry without corresponding FileLookup entry"); + tr.Commit(); } } public void Vacuum() { + m_hasExecutedVacuum = true; using (var cmd = m_connection.CreateCommand()) cmd.ExecuteNonQuery("VACUUM"); } @@ -563,34 +552,29 @@ AND Fileset.ID NOT IN return RegisterRemoteVolume(name, type, state, size, new TimeSpan(0), null); } - public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, System.Data.IDbTransaction transaction) + public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, IDbTransaction transaction) { return RegisterRemoteVolume(name, type, state, new TimeSpan(0), transaction); } - public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, TimeSpan deleteGraceTime, System.Data.IDbTransaction transaction) + public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, TimeSpan deleteGraceTime, IDbTransaction transaction) { return RegisterRemoteVolume(name, type, state, -1, deleteGraceTime, transaction); } - public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, long size, TimeSpan deleteGraceTime, System.Data.IDbTransaction transaction) + public long RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, long size, TimeSpan deleteGraceTime, IDbTransaction transaction) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) { - m_createremotevolumeCommand.SetParameterValue(0, m_operationid); - m_createremotevolumeCommand.SetParameterValue(1, name); - m_createremotevolumeCommand.SetParameterValue(2, type.ToString()); - m_createremotevolumeCommand.SetParameterValue(3, state.ToString()); - m_createremotevolumeCommand.SetParameterValue(4, size); - m_createremotevolumeCommand.SetParameterValue(5, 0); + var r = m_createremotevolumeCommand.SetParameterValue("@OperationId", m_operationid) + .SetParameterValue("@Name", name) + .SetParameterValue("@Type", type.ToString()) + .SetParameterValue("@State", state.ToString()) + .SetParameterValue("@Size", size) + .SetParameterValue("@VerificationCount", 0) + .SetParameterValue("@DeleteGraceTime", deleteGraceTime.Ticks <= 0 ? 0 : (DateTime.UtcNow + deleteGraceTime).Ticks) + .ExecuteScalarInt64(tr.Parent); - if (deleteGraceTime.Ticks <= 0) - m_createremotevolumeCommand.SetParameterValue(6, 0); - else - m_createremotevolumeCommand.SetParameterValue(6, (DateTime.UtcNow + deleteGraceTime).Ticks); - - m_createremotevolumeCommand.Transaction = tr.Parent; - var r = m_createremotevolumeCommand.ExecuteScalarInt64(); tr.Commit(); return r; } @@ -608,7 +592,7 @@ AND Fileset.ID NOT IN var res = new List(); using (var cmd = m_connection.CreateCommand()) { - using (var rd = cmd.ExecuteReader(@"SELECT ""ID"" FROM ""Fileset"" " + query + @" ORDER BY ""Timestamp"" DESC", args)) + using (var rd = cmd.ExecuteReader($@"SELECT ""ID"" FROM ""Fileset"" {query} ORDER BY ""Timestamp"" DESC", args)) while (rd.Read()) res.Add(rd.GetInt64(0)); @@ -650,31 +634,27 @@ AND Fileset.ID NOT IN public bool IsFilesetFullBackup(DateTime filesetTime) { using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.SetCommandAndParameters($@"SELECT ""IsFullBackup"" FROM ""Fileset"" WHERE ""Timestamp"" = @Timestamp").SetParameterValue("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(filesetTime)).ExecuteReader()) { - using (var rd = cmd.ExecuteReader($@"SELECT ""IsFullBackup"" FROM ""Fileset"" WHERE ""Timestamp"" = {Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(filesetTime)}")) - { - if (!rd.Read()) - { - return false; - } - var isFullBackup = rd.GetInt32(0); - return isFullBackup == BackupType.FULL_BACKUP; - } + if (!rd.Read()) + return false; + var isFullBackup = rd.GetInt32(0); + return isFullBackup == BackupType.FULL_BACKUP; } } // TODO: Remove this - public System.Data.IDbTransaction BeginTransaction() + public IDbTransaction BeginTransaction() { return m_connection.BeginTransaction(); } protected class TemporaryTransactionWrapper : IDisposable { - private readonly System.Data.IDbTransaction m_parent; + private readonly IDbTransaction m_parent; private readonly bool m_isTemporary; - public TemporaryTransactionWrapper(System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) + public TemporaryTransactionWrapper(IDbConnection connection, IDbTransaction transaction) { if (transaction != null) { @@ -700,10 +680,10 @@ AND Fileset.ID NOT IN m_parent.Dispose(); } - public System.Data.IDbTransaction Parent { get { return m_parent; } } + public IDbTransaction Parent { get { return m_parent; } } } - private IEnumerable> GetDbOptionList(System.Data.IDbTransaction transaction = null) + private IEnumerable> GetDbOptionList(IDbTransaction transaction = null) { using (var cmd = m_connection.CreateCommand(transaction)) using (var rd = cmd.ExecuteReader(@"SELECT ""Key"", ""Value"" FROM ""Configuration"" ")) @@ -711,58 +691,71 @@ AND Fileset.ID NOT IN yield return new KeyValuePair(rd.GetValue(0).ToString(), rd.GetValue(1).ToString()); } - public IDictionary GetDbOptions(System.Data.IDbTransaction transaction = null) + public IDictionary GetDbOptions(IDbTransaction transaction = null) { return GetDbOptionList(transaction).ToDictionary(x => x.Key, x => x.Value); } + /// + /// Updates a database option + /// + /// The key to update + /// The value to set + private void UpdateDbOption(string key, bool value) + { + var opts = GetDbOptions(); + + if (value) + opts[key] = "true"; + else + opts.Remove(key); + + SetDbOptions(opts); + } + + /// + /// Flag indicating if a repair is in progress + /// public bool RepairInProgress { - get - { - return GetDbOptions().ContainsKey("repair-in-progress"); - } - set - { - var opts = GetDbOptions(); - - if (value) - opts["repair-in-progress"] = "true"; - else - opts.Remove("repair-in-progress"); - - SetDbOptions(opts); - } + get => GetDbOptions().ContainsKey("repair-in-progress"); + set => UpdateDbOption("repair-in-progress", value); } + /// + /// Flag indicating if a repair is in progress + /// public bool PartiallyRecreated { - get - { - return GetDbOptions().ContainsKey("partially-recreated"); - } - set - { - var opts = GetDbOptions(); - - if (value) - opts["partially-recreated"] = "true"; - else - opts.Remove("partially-recreated"); - - SetDbOptions(opts); - } + get => GetDbOptions().ContainsKey("partially-recreated"); + set => UpdateDbOption("partially-recreated", value); } - public void SetDbOptions(IDictionary options, System.Data.IDbTransaction transaction = null) + /// + /// Flag indicating if the database can contain partial uploads + /// + public bool TerminatedWithActiveUploads + { + get => GetDbOptions().ContainsKey("terminated-with-active-uploads"); + set => UpdateDbOption("terminated-with-active-uploads", value); + } + + /// + /// Sets the database options + /// + /// The options to set + /// An optional transaction + public void SetDbOptions(IDictionary options, IDbTransaction transaction = null) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - cmd.Transaction = tr.Parent; cmd.ExecuteNonQuery(@"DELETE FROM ""Configuration"" "); foreach (var kp in options) - cmd.ExecuteNonQuery(@"INSERT INTO ""Configuration"" (""Key"", ""Value"") VALUES (?, ?) ", kp.Key, kp.Value); + cmd.SetCommandAndParameters(@"INSERT INTO ""Configuration"" (""Key"", ""Value"") VALUES (@Key, @Value) ") + .SetParameterValue("@Key", kp.Key) + .SetParameterValue("@Value", kp.Value) + .ExecuteNonQuery(); tr.Commit(); } @@ -771,10 +764,40 @@ AND Fileset.ID NOT IN public long GetBlocksLargerThan(long fhblocksize) { using (var cmd = m_connection.CreateCommand()) - return cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""Block"" WHERE ""Size"" > ?", -1, fhblocksize); + return cmd.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""Block"" WHERE ""Size"" > @Size") + .SetParameterValue("@Size", fhblocksize) + .ExecuteScalarInt64(-1); } - public void VerifyConsistency(long blocksize, long hashsize, bool verifyfilelists, System.Data.IDbTransaction transaction) + /// + /// Verifies the consistency of the database + /// + /// The block size in bytes + /// The hash size in byts + /// Also verify filelists (can be slow) + /// The transaction to run in + public void VerifyConsistency(long blocksize, long hashsize, bool verifyfilelists, IDbTransaction transaction) + => VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, false, transaction); + + /// + /// Verifies the consistency of the database prior to repair + /// + /// The block size in bytes + /// The hash size in byts + /// Also verify filelists (can be slow) + /// The transaction to run in + public void VerifyConsistencyForRepair(long blocksize, long hashsize, bool verifyfilelists, IDbTransaction transaction) + => VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, true, transaction); + + /// + /// Verifies the consistency of the database + /// + /// The block size in bytes + /// The hash size in byts + /// Also verify filelists (can be slow) + /// Disable verify for errors that will be fixed by repair + /// The transaction to run in + private void VerifyConsistencyInner(long blocksize, long hashsize, bool verifyfilelists, bool laxVerifyForRepair, IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand(transaction)) { @@ -824,50 +847,130 @@ ON sb.AppendFormat("... and {0} more", c); sb.Append(". Run repair to fix it."); - throw new InvalidDataException(sb.ToString()); + throw new DatabaseInconsistencyException(sb.ToString()); } var real_count = cmd.ExecuteScalarInt64(@"SELECT Count(*) FROM ""BlocklistHash""", 0); var unique_count = cmd.ExecuteScalarInt64(@"SELECT Count(*) FROM (SELECT DISTINCT ""BlocksetID"", ""Index"" FROM ""BlocklistHash"")", 0); if (real_count != unique_count) - throw new InvalidDataException(string.Format("Found {0} blocklist hashes, but there should be {1}. Run repair to fix it.", real_count, unique_count)); + throw new DatabaseInconsistencyException($"Found {real_count} blocklist hashes, but there should be {unique_count}. Run repair to fix it."); - var itemswithnoblocklisthash = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM (SELECT * FROM (SELECT ""N"".""BlocksetID"", ((""N"".""BlockCount"" + {0} - 1) / {0}) AS ""BlocklistHashCountExpected"", CASE WHEN ""G"".""BlocklistHashCount"" IS NULL THEN 0 ELSE ""G"".""BlocklistHashCount"" END AS ""BlocklistHashCountActual"" FROM (SELECT ""BlocksetID"", COUNT(*) AS ""BlockCount"" FROM ""BlocksetEntry"" GROUP BY ""BlocksetID"") ""N"" LEFT OUTER JOIN (SELECT ""BlocksetID"", COUNT(*) AS ""BlocklistHashCount"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"") ""G"" ON ""N"".""BlocksetID"" = ""G"".""BlocksetID"" WHERE ""N"".""BlockCount"" > 1) WHERE ""BlocklistHashCountExpected"" != ""BlocklistHashCountActual"")", blocksize / hashsize), 0); + var itemswithnoblocklisthash = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM (SELECT * FROM (SELECT ""N"".""BlocksetID"", ((""N"".""BlockCount"" + {blocksize / hashsize} - 1) / {blocksize / hashsize}) AS ""BlocklistHashCountExpected"", CASE WHEN ""G"".""BlocklistHashCount"" IS NULL THEN 0 ELSE ""G"".""BlocklistHashCount"" END AS ""BlocklistHashCountActual"" FROM (SELECT ""BlocksetID"", COUNT(*) AS ""BlockCount"" FROM ""BlocksetEntry"" GROUP BY ""BlocksetID"") ""N"" LEFT OUTER JOIN (SELECT ""BlocksetID"", COUNT(*) AS ""BlocklistHashCount"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"") ""G"" ON ""N"".""BlocksetID"" = ""G"".""BlocksetID"" WHERE ""N"".""BlockCount"" > 1) WHERE ""BlocklistHashCountExpected"" != ""BlocklistHashCountActual"")"), 0); if (itemswithnoblocklisthash != 0) - throw new InvalidDataException(string.Format("Found {0} file(s) with missing blocklist hashes", itemswithnoblocklisthash)); + throw new DatabaseInconsistencyException($"Found {itemswithnoblocklisthash} file(s) with missing blocklist hashes"); if (cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""Blockset"" WHERE ""Length"" > 0 AND ""ID"" NOT IN (SELECT ""BlocksetId"" FROM ""BlocksetEntry"")") != 0) + throw new DatabaseInconsistencyException("Detected non-empty blocksets with no associated blocks!"); + + if (cmd.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""FileLookup"" WHERE ""BlocksetID"" != @FolderBlocksetId AND ""BlocksetID"" != @SymlinkBlocksetId AND NOT ""BlocksetID"" IN (SELECT ""ID"" FROM ""Blockset"")") + .SetParameterValue("@FolderBlocksetId", FOLDER_BLOCKSET_ID) + .SetParameterValue("@SymlinkBlocksetId", SYMLINK_BLOCKSET_ID) + .ExecuteScalarInt64(0) != 0) + throw new DatabaseInconsistencyException("Detected files associated with non-existing blocksets!"); + + if (!laxVerifyForRepair) { - throw new Exception("Detected non-empty blocksets with no associated blocks!"); + var filesetsMissingVolumes = cmd.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""Fileset"" WHERE ""VolumeID"" NOT IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = @Type AND ""State"" != @State)") + .SetParameterValue("@Type", RemoteVolumeType.Files.ToString()) + .SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()) + .ExecuteScalarInt64(0); + + if (filesetsMissingVolumes != 0) + { + if (filesetsMissingVolumes == 1) + using (var reader = cmd.SetCommandAndParameters(@"SELECT ""ID"", ""Timestamp"", ""VolumeID"" FROM ""Fileset"" WHERE ""VolumeID"" NOT IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = @Type AND ""State"" != @State)") + .SetParameterValue("@Type", RemoteVolumeType.Files.ToString()) + .SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()) + .ExecuteReader()) + if (reader.Read()) + throw new DatabaseInconsistencyException($"Detected 1 fileset with missing volume: FilesetId = {reader.ConvertValueToInt64(0)}, Time = ({ParseFromEpochSeconds(reader.ConvertValueToInt64(1))}), unmatched VolumeID {reader.ConvertValueToInt64(2)}"); + + throw new DatabaseInconsistencyException($"Detected {filesetsMissingVolumes} filesets with missing volumes"); + } + + var volumesMissingFilests = cmd.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""RemoteVolume"" WHERE ""Type"" = @Type AND ""State"" != @State AND ""ID"" NOT IN (SELECT ""VolumeID"" FROM ""Fileset"")") + .SetParameterValue("@Type", RemoteVolumeType.Files.ToString()) + .SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()) + .ExecuteScalarInt64(0); + if (volumesMissingFilests != 0) + { + if (volumesMissingFilests == 1) + using (var reader = cmd.SetCommandAndParameters(@"SELECT ""ID"", ""Name"", ""State"" FROM ""RemoteVolume"" WHERE ""Type"" = @Type AND ""State"" != @State AND ""ID"" NOT IN (SELECT ""VolumeID"" FROM ""Fileset"")") + .SetParameterValue("@Type", RemoteVolumeType.Files.ToString()) + .SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()) + .ExecuteReader()) + if (reader.Read()) + throw new DatabaseInconsistencyException($"Detected 1 volume with missing filesets: VolumeId = {reader.ConvertValueToInt64(0)}, Name = {reader.ConvertValueToString(1)}, State = {reader.ConvertValueToString(2)}"); + + throw new DatabaseInconsistencyException($"Detected {volumesMissingFilests} volumes with missing filesets"); + } } - if (cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FileLookup"" WHERE ""BlocksetID"" != ? AND ""BlocksetID"" != ? AND NOT ""BlocksetID"" IN (SELECT ""ID"" FROM ""Blockset"")", 0, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID) != 0) - throw new Exception("Detected files associated with non-existing blocksets!"); + var nonAttachedFiles = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FileID"" NOT IN (SELECT ""ID"" FROM ""FileLookup"")"); + if (nonAttachedFiles != 0) + { + // Attempt to create a better error message by finding the first 10 fileset ids with the issue + using var filesetIdReader = cmd.ExecuteReader(@"SELECT DISTINCT(FilesetID) FROM ""FilesetEntry"" WHERE ""FileID"" NOT IN (SELECT ""ID"" FROM ""FileLookup"") LIMIT 11"); + var filesetIds = new HashSet(); + var overflow = false; + while (filesetIdReader.Read()) + { + if (filesetIds.Count >= 10) + { + overflow = true; + break; + } + filesetIds.Add(filesetIdReader.ConvertValueToInt64(0)); + } + + var pairs = FilesetTimes + .Select((x, i) => new { FilesetId = x.Key, Version = i, Time = x.Value }) + .Where(x => filesetIds.Contains(x.FilesetId)) + .Select(x => $"Fileset {x.Version}: {x.Time} (id = {x.FilesetId})"); + + // Fall back to a generic error message if we can't find the fileset ids + if (!pairs.Any()) + throw new DatabaseInconsistencyException($"Detected {nonAttachedFiles} file(s) in FilesetEntry without corresponding FileLookup entry"); + + if (overflow) + pairs = pairs.Append("... and more"); + + throw new DatabaseInconsistencyException($"Detected {nonAttachedFiles} file(s) in FilesetEntry without corresponding FileLookup entry in the following filesets:{Environment.NewLine}{string.Join(Environment.NewLine, pairs)}"); + } if (verifyfilelists) { var anyError = new List(); using (var cmd2 = m_connection.CreateCommand(transaction)) + { foreach (var filesetid in cmd.ExecuteReaderEnumerable(@"SELECT ""ID"" FROM ""Fileset"" ").Select(x => x.ConvertValueToInt64(0, -1))) { - var expandedCmd = string.Format(@"SELECT COUNT(*) FROM (SELECT DISTINCT ""Path"" FROM ({0}) UNION SELECT DISTINCT ""Path"" FROM ({1}))", LocalDatabase.LIST_FILESETS, LocalDatabase.LIST_FOLDERS_AND_SYMLINKS); - var expandedlist = cmd2.ExecuteScalarInt64(expandedCmd, 0, filesetid, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID, filesetid); - //var storedfilelist = cmd2.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""FilesetEntry"", ""FileLookup"" WHERE ""FilesetEntry"".""FilesetID"" = ? AND ""FileLookup"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FileLookup"".""BlocksetID"" != ? AND ""FileLookup"".""BlocksetID"" != ?"), 0, filesetid, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID); - var storedlist = cmd2.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetEntry"".""FilesetID"" = ?", 0, filesetid); + var expandedCmd = FormatInvariant($@"SELECT COUNT(*) FROM (SELECT DISTINCT ""Path"" FROM ({LocalDatabase.LIST_FILESETS}) UNION SELECT DISTINCT ""Path"" FROM ({LocalDatabase.LIST_FOLDERS_AND_SYMLINKS}))"); + var expandedlist = cmd2 + .SetCommandAndParameters(expandedCmd) + .SetParameterValue("@FilesetId", filesetid) + .SetParameterValue("@FolderBlocksetId", FOLDER_BLOCKSET_ID) + .SetParameterValue("@SymlinkBlocksetId", SYMLINK_BLOCKSET_ID) + .ExecuteScalarInt64(0); + //var storedfilelist = cmd2.ExecuteScalarInt64(FormatInvariant(@"SELECT COUNT(*) FROM ""FilesetEntry"", ""FileLookup"" WHERE ""FilesetEntry"".""FilesetID"" = @FilesetId AND ""FileLookup"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FileLookup"".""BlocksetID"" != @FolderBlocksetId AND ""FileLookup"".""BlocksetID"" != @SymlinkBlocksetId"), 0, filesetid, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID); + var storedlist = cmd2.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetEntry"".""FilesetID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetid) + .ExecuteScalarInt64(0); if (expandedlist != storedlist) { var filesetname = filesetid.ToString(); var fileset = FilesetTimes.Zip(Enumerable.Range(0, FilesetTimes.Count()), (a, b) => new Tuple(b, a.Key, a.Value)).FirstOrDefault(x => x.Item2 == filesetid); if (fileset != null) - filesetname = string.Format("version {0}: {1} (database id: {2})", fileset.Item1, fileset.Item3, fileset.Item2); - anyError.Add(string.Format("Unexpected difference in fileset {0}, found {1} entries, but expected {2}", filesetname, expandedlist, storedlist)); + filesetname = $"version {fileset.Item1}: {fileset.Item3} (database id: {fileset.Item2})"; + anyError.Add($"Unexpected difference in fileset {filesetname}, found {expandedlist} entries, but expected {storedlist}"); } } + } if (anyError.Any()) { - throw new Interface.UserInformationException(string.Join("\n\r", anyError), "FilesetDifferences"); + throw new DatabaseInconsistencyException(string.Join("\n\r", anyError), "FilesetDifferences"); } } } @@ -886,15 +989,17 @@ ON public Block(string hash, long size) { - this.Hash = hash; - this.Size = size; + Hash = hash; + Size = size; } } - public IEnumerable GetBlocks(long volumeid, System.Data.IDbTransaction transaction = null) + public IEnumerable GetBlocks(long volumeid, IDbTransaction transaction = null) { using (var cmd = m_connection.CreateCommand(transaction)) - using (var rd = cmd.ExecuteReader(@"SELECT DISTINCT ""Hash"", ""Size"" FROM ""Block"" WHERE ""VolumeID"" = ?", volumeid)) + using (var rd = cmd.SetCommandAndParameters(@"SELECT DISTINCT ""Hash"", ""Size"" FROM ""Block"" WHERE ""VolumeID"" = @VolumeId") + .SetParameterValue("@VolumeId", volumeid) + .ExecuteReader()) while (rd.Read()) yield return new Block(rd.GetValue(0).ToString(), rd.GetInt64(1)); } @@ -903,13 +1008,13 @@ ON { private class BlocklistHashEnumerator : IEnumerator { - private readonly System.Data.IDataReader m_reader; + private readonly IDataReader m_reader; private readonly BlocklistHashEnumerable m_parent; private string m_path = null; private bool m_first = true; private string m_current = null; - public BlocklistHashEnumerator(BlocklistHashEnumerable parent, System.Data.IDataReader reader) + public BlocklistHashEnumerator(BlocklistHashEnumerable parent, IDataReader reader) { m_reader = reader; m_parent = parent; @@ -921,7 +1026,7 @@ ON { } - object System.Collections.IEnumerator.Current { get { return this.Current; } } + object System.Collections.IEnumerator.Current { get { return Current; } } public bool MoveNext() { @@ -966,12 +1071,12 @@ ON } } - private readonly System.Data.IDataReader m_reader; + private readonly IDataReader m_reader; - public BlocklistHashEnumerable(System.Data.IDataReader reader) + public BlocklistHashEnumerable(IDataReader reader) { m_reader = reader; - this.MoreData = true; + MoreData = true; } public bool MoreData { get; protected set; } @@ -983,7 +1088,7 @@ ON System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { - return this.GetEnumerator(); + return GetEnumerator(); } } @@ -1051,7 +1156,7 @@ FROM ON ""I"".""BlockID"" = ""H"".""ID"" WHERE ""A"".""BlocksetId"" >= 0 AND - ""D"".""FilesetID"" = ? AND + ""D"".""FilesetID"" = @FilesetId AND (""I"".""Index"" = 0 OR ""I"".""Index"" IS NULL) AND (""G"".""Index"" = 0 OR ""G"".""Index"" IS NULL) ) J @@ -1103,8 +1208,8 @@ FROM AND ""E"".""BlocksetID"" = ""C"".""BlocksetID"" AND ""E"".""BlockID"" = ""F"".""ID"" AND ""E"".""Index"" = 0 - AND (""B"".""BlocksetID"" = ? OR ""B"".""BlocksetID"" = ?) - AND ""A"".""FilesetID"" = ? + AND (""B"".""BlocksetID"" = @FolderBlocksetId OR ""B"".""BlocksetID"" = @SymlinkBlocksetId) + AND ""A"".""FilesetID"" = @FilesetId ) G LEFT OUTER JOIN ""BlocklistHash"" H @@ -1115,15 +1220,14 @@ ORDER BY "; - public void WriteFileset(Volumes.FilesetVolumeWriter filesetvolume, long filesetId, System.Data.IDbTransaction transaction) + public void WriteFileset(Volumes.FilesetVolumeWriter filesetvolume, long filesetId, IDbTransaction transaction) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; - cmd.CommandText = LIST_FOLDERS_AND_SYMLINKS; - cmd.AddParameter(FOLDER_BLOCKSET_ID); - cmd.AddParameter(SYMLINK_BLOCKSET_ID); - cmd.AddParameter(filesetId); + cmd.SetCommandAndParameters(LIST_FOLDERS_AND_SYMLINKS) + .SetParameterValue("@FilesetId", filesetId) + .SetParameterValue("@FolderBlocksetId", FOLDER_BLOCKSET_ID) + .SetParameterValue("@SymlinkBlocksetId", SYMLINK_BLOCKSET_ID); string lastpath = null; using (var rd = cmd.ExecuteReader()) @@ -1149,9 +1253,8 @@ ORDER BY // TODO: Perhaps run the above query after recreate and compare count(*) with count(*) from filesetentry where id = x - cmd.CommandText = LIST_FILESETS; - cmd.Parameters.Clear(); - cmd.AddParameter(filesetId); + cmd.SetCommandAndParameters(LIST_FILESETS) + .SetParameterValue("@FilesetId", filesetId); using (var rd = cmd.ExecuteReader()) if (rd.Read()) @@ -1190,59 +1293,81 @@ ORDER BY } } + public void LinkFilesetToVolume(long filesetid, long volumeid, IDbTransaction transaction) + { + using (var cmd = m_connection.CreateCommand(transaction)) + { + var c = cmd.SetCommandAndParameters(@"UPDATE ""Fileset"" SET ""VolumeID"" = @VolumeId WHERE ""ID"" = @FilesetId") + .SetParameterValue("@VolumeId", volumeid) + .SetParameterValue("@FilesetId", filesetid) + .ExecuteNonQuery(); + + if (c != 1) + throw new Exception($"Failed to link filesetid {filesetid} to volumeid {volumeid}"); + } + } + + public void PushTimestampChangesToPreviousVersion(long filesetId, IDbTransaction transaction) + { + var query = @" +UPDATE FilesetEntry AS oldVersion +SET Lastmodified = tempVersion.Lastmodified +FROM FilesetEntry AS tempVersion +WHERE oldVersion.FileID = tempVersion.FileID +AND tempVersion.FilesetID = @FilesetId +AND oldVersion.FilesetID = (SELECT ID FROM Fileset WHERE ID != @FilesetId ORDER BY Timestamp DESC LIMIT 1)"; + + using (var cmd = m_connection.CreateCommand(transaction, query)) + cmd.SetParameterValue("@FilesetId", filesetId) + .ExecuteNonQuery(); + } + /// /// Keeps a list of filenames in a temporary table with a single column Path /// public class FilteredFilenameTable : IDisposable { public string Tablename { get; private set; } - private readonly System.Data.IDbConnection m_connection; + private readonly IDbConnection m_connection; - public FilteredFilenameTable(System.Data.IDbConnection connection, Library.Utility.IFilter filter, System.Data.IDbTransaction transaction) + public FilteredFilenameTable(IDbConnection connection, IFilter filter, IDbTransaction transaction) { m_connection = connection; Tablename = "Filenames-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - var type = Library.Utility.FilterType.Regexp; + var type = FilterType.Regexp; if (filter is FilterExpression expression) type = expression.Type; // Bugfix: SQLite does not handle case-insensitive LIKE with non-ascii characters - if (type != Library.Utility.FilterType.Regexp && !Library.Utility.Utility.IsFSCaseSensitive && filter.ToString().Any(x => x > 127)) - type = Library.Utility.FilterType.Regexp; + if (type != FilterType.Regexp && !Library.Utility.Utility.IsFSCaseSensitive && filter.ToString().Any(x => x > 127)) + type = FilterType.Regexp; if (filter.Empty) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS SELECT DISTINCT ""Path"" FROM ""File"" ", Tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{Tablename}"" AS SELECT DISTINCT ""Path"" FROM ""File"" ")); return; } } - if (type == Library.Utility.FilterType.Regexp || type == Library.Utility.FilterType.Group) + if (type == FilterType.Regexp || type == FilterType.Group) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { // TODO: Optimize this to not rely on the "File" view, and not instantiate the paths in full - - cmd.Transaction = transaction; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)", Tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{Tablename}"" (""Path"" TEXT NOT NULL)")); using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) { - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"") VALUES (?)", Tablename); - cmd.AddParameter(); - cmd.Transaction = tr.Parent; + cmd.SetCommandAndParameters(tr.Parent, FormatInvariant($@"INSERT INTO ""{Tablename}"" (""Path"") VALUES (@Path)")); using (var c2 = m_connection.CreateCommand()) using (var rd = c2.ExecuteReader(@"SELECT DISTINCT ""Path"" FROM ""File"" ")) while (rd.Read()) { var p = rd.GetValue(0).ToString(); - if (Library.Utility.FilterExpression.Matches(filter, p)) - { - cmd.SetParameterValue(0, p); - cmd.ExecuteNonQuery(); - } + if (FilterExpression.Matches(filter, p)) + cmd.SetParameterValue("@Path", p) + .ExecuteNonQuery(); } @@ -1253,29 +1378,30 @@ ORDER BY else { var sb = new StringBuilder(); - var args = new List(); - foreach (var f in ((Library.Utility.FilterExpression)filter).GetSimpleList()) + var args = new Dictionary(); + foreach (var f in ((FilterExpression)filter).GetSimpleList()) { + if (sb.Length != 0) + sb.Append(" OR "); + + var argName = $"@Arg{args.Count}"; if (type == FilterType.Wildcard) { - sb.Append(@"""Path"" LIKE ? OR "); - args.Add(f.Replace('*', '%').Replace('?', '_')); + sb.Append(FormatInvariant(@$"""Path"" LIKE {argName}")); + args.Add(argName, f.Replace('*', '%').Replace('?', '_')); } else { - sb.Append(@"""Path"" = ? OR "); - args.Add(f); + sb.Append(FormatInvariant(@$"""Path"" = {argName}")); + args.Add(argName, f); } } - sb.Length = sb.Length - " OR ".Length; - - using (var cmd = m_connection.CreateCommand()) using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - cmd.Transaction = tr.Parent; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)", Tablename)); - cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" SELECT DISTINCT ""Path"" FROM ""File"" WHERE " + sb, Tablename), args.ToArray()); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{Tablename}"" (""Path"" TEXT NOT NULL)")); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{Tablename}"" SELECT DISTINCT ""Path"" FROM ""File"" WHERE {sb}"), args); tr.Commit(); } } @@ -1287,27 +1413,35 @@ ORDER BY try { using (var cmd = m_connection.CreateCommand()) - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", Tablename)); + cmd.ExecuteNonQuery(FormatInvariant(@$"DROP TABLE IF EXISTS ""{Tablename}"" ")); } catch { } finally { Tablename = null; } } } - public void RenameRemoteFile(string oldname, string newname, System.Data.IDbTransaction transaction) + public void RenameRemoteFile(string oldname, string newname, IDbTransaction transaction) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - cmd.Transaction = tr.Parent; - //Rename the old entry, to preserve ID links - var c = cmd.ExecuteNonQuery(@"UPDATE ""Remotevolume"" SET ""Name"" = ? WHERE ""Name"" = ?", newname, oldname); + var c = cmd.SetCommandAndParameters(@"UPDATE ""Remotevolume"" SET ""Name"" = @Newname WHERE ""Name"" = @Oldname") + .SetParameterValue("@Newname", newname) + .SetParameterValue("@Oldname", oldname) + .ExecuteNonQuery(); + if (c != 1) - throw new Exception(string.Format("Unexpected result from renaming \"{0}\" to \"{1}\", expected {2} got {3}", oldname, newname, 1, c)); + throw new Exception($"Unexpected result from renaming \"{oldname}\" to \"{newname}\", expected {1} got {c}"); // Grab the type of entry - var type = (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), cmd.ExecuteScalar(@"SELECT ""Type"" FROM ""Remotevolume"" WHERE ""Name"" = ?", newname).ToString(), true); + var type = (RemoteVolumeType)Enum.Parse( + typeof(RemoteVolumeType), + cmd.SetCommandAndParameters(@"SELECT ""Type"" FROM ""Remotevolume"" WHERE ""Name"" = @Name") + .SetParameterValue("@Name", newname) + .ExecuteScalar() + .ToString(), + true); //Create a fake new entry with the old name and mark as deleting // as this ensures we will remove it, if it shows up in some later listing @@ -1323,24 +1457,27 @@ ORDER BY /// The ID of the fileset volume to update /// The timestamp of the operation to create /// An optional external transaction - public virtual long CreateFileset(long volumeid, DateTime timestamp, System.Data.IDbTransaction transaction = null) + public virtual long CreateFileset(long volumeid, DateTime timestamp, IDbTransaction transaction = null) { - using (var cmd = m_connection.CreateCommand()) using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - cmd.Transaction = tr.Parent; - var id = cmd.ExecuteScalarInt64(@"INSERT INTO ""Fileset"" (""OperationID"", ""Timestamp"", ""VolumeID"", ""IsFullBackup"") VALUES (?, ?, ?, ?); SELECT last_insert_rowid();", -1, m_operationid, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(timestamp), volumeid, BackupType.PARTIAL_BACKUP); + var id = cmd.SetCommandAndParameters(@"INSERT INTO ""Fileset"" (""OperationID"", ""Timestamp"", ""VolumeID"", ""IsFullBackup"") VALUES (@OperationId, @Timestamp, @VolumeId, @IsFullBackup); SELECT last_insert_rowid();") + .SetParameterValue("@OperationId", m_operationid) + .SetParameterValue("@Timestamp", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(timestamp)) + .SetParameterValue("@VolumeId", volumeid) + .SetParameterValue("@IsFullBackup", BackupType.PARTIAL_BACKUP) + .ExecuteScalarInt64(-1); tr.Commit(); return id; } } - public void AddIndexBlockLink(long indexVolumeID, long blockVolumeID, System.Data.IDbTransaction transaction) + public void AddIndexBlockLink(long indexVolumeID, long blockVolumeID, IDbTransaction transaction) { - m_insertIndexBlockLink.Transaction = transaction; - m_insertIndexBlockLink.SetParameterValue(0, indexVolumeID); - m_insertIndexBlockLink.SetParameterValue(1, blockVolumeID); - m_insertIndexBlockLink.ExecuteNonQuery(); + m_insertIndexBlockLink.SetParameterValue("@IndexVolumeId", indexVolumeID) + .SetParameterValue("@BlockVolumeId", blockVolumeID) + .ExecuteNonQuery(transaction); } /// @@ -1351,24 +1488,22 @@ ORDER BY /// The size of the hash /// An optional external transaction /// An enumerable of tuples containing the blocklist hash, the blocklist data and the length of the data - public IEnumerable> GetBlocklists(long volumeid, long blocksize, int hashsize, System.Data.IDbTransaction transaction = null) + public IEnumerable> GetBlocklists(long volumeid, long blocksize, int hashsize, IDbTransaction transaction = null) { using (var cmd = m_connection.CreateCommand(transaction)) { // Group subquery by hash to ensure that each blocklist hash appears only once in the result - var sql = string.Format(@"SELECT ""A"".""Hash"", ""C"".""Hash"" FROM " + - @"(SELECT ""BlocklistHash"".""BlocksetID"", ""Block"".""Hash"", ""BlocklistHash"".""Index"" FROM ""BlocklistHash"",""Block"" WHERE ""BlocklistHash"".""Hash"" = ""Block"".""Hash"" AND ""Block"".""VolumeID"" = ? GROUP BY ""Block"".""Hash"", ""Block"".""Size"") A, " + - @" ""BlocksetEntry"" B, ""Block"" C WHERE ""B"".""BlocksetID"" = ""A"".""BlocksetID"" AND " + - @" ""B"".""Index"" >= (""A"".""Index"" * {0}) AND ""B"".""Index"" < ((""A"".""Index"" + 1) * {0}) AND ""C"".""ID"" = ""B"".""BlockID"" " + - @" ORDER BY ""A"".""BlocksetID"", ""B"".""Index""", - blocksize / hashsize - ); + var sql = FormatInvariant($@"SELECT ""A"".""Hash"", ""C"".""Hash"" FROM +(SELECT ""BlocklistHash"".""BlocksetID"", ""Block"".""Hash"", ""BlocklistHash"".""Index"" FROM ""BlocklistHash"",""Block"" WHERE ""BlocklistHash"".""Hash"" = ""Block"".""Hash"" AND ""Block"".""VolumeID"" = @VolumeId GROUP BY ""Block"".""Hash"", ""Block"".""Size"") A, + ""BlocksetEntry"" B, ""Block"" C WHERE ""B"".""BlocksetID"" = ""A"".""BlocksetID"" AND + ""B"".""Index"" >= (""A"".""Index"" * {blocksize / hashsize}) AND ""B"".""Index"" < ((""A"".""Index"" + 1) * {blocksize / hashsize}) AND ""C"".""ID"" = ""B"".""BlockID"" + ORDER BY ""A"".""BlocksetID"", ""B"".""Index"""); string curHash = null; int count = 0; byte[] buffer = new byte[blocksize]; - using (var rd = cmd.ExecuteReader(sql, volumeid)) + using (var rd = cmd.SetCommandAndParameters(sql).SetParameterValue("@VolumeId", volumeid).ExecuteReader()) while (rd.Read()) { var blockhash = rd.GetValue(0).ToString(); @@ -1399,25 +1534,86 @@ ORDER BY public void UpdateFullBackupStateInFileset(long fileSetId, bool isFullBackup, IDbTransaction transaction = null) { using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) + using (var cmd = m_connection.CreateCommand(tr.Parent)) { - using (var cmd = m_connection.CreateCommand()) - { - cmd.Transaction = tr.Parent; - cmd.ExecuteNonQuery(@"UPDATE ""Fileset"" SET ""IsFullBackup"" = ? WHERE ""ID"" = ?;", isFullBackup, fileSetId); - } - + cmd.SetCommandAndParameters(@"UPDATE ""Fileset"" SET ""IsFullBackup"" = @IsFullBackup WHERE ""ID"" = @FilesetId;") + .SetParameterValue("@FilesetId", fileSetId) + .SetParameterValue("@IsFullBackup", isFullBackup ? BackupType.FULL_BACKUP : BackupType.PARTIAL_BACKUP) + .ExecuteNonQuery(); tr.Commit(); } } + /// + /// Gets the last previous fileset that was incomplete + /// + /// The transaction to use + /// The last incomplete fileset or default + public RemoteVolumeEntry GetLastIncompleteFilesetVolume(IDbTransaction transaction) + { + var candidates = GetIncompleteFilesets(transaction).OrderBy(x => x.Value).ToArray(); + if (candidates.Any()) + return GetRemoteVolumeFromFilesetID(candidates.Last().Key, transaction); + + return default; + } + + /// + /// Gets a list of incomplete filesets + /// + /// An optional transaction + /// A list of fileset IDs and timestamps + public IEnumerable> GetIncompleteFilesets(IDbTransaction transaction) + { + using (var cmd = m_connection.CreateCommand(transaction)) + using (var rd = cmd.ExecuteReader(FormatInvariant(@$"SELECT DISTINCT ""Fileset"".""ID"", ""Fileset"".""Timestamp"" FROM ""Fileset"", ""RemoteVolume"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" IN (SELECT ""FilesetID"" FROM ""FilesetEntry"") AND (""RemoteVolume"".""State"" = '{RemoteVolumeState.Uploading}' OR ""RemoteVolume"".""State"" = '{RemoteVolumeState.Temporary}')"))) + while (rd.Read()) + { + yield return new KeyValuePair( + rd.GetInt64(0), + ParseFromEpochSeconds(rd.GetInt64(1)).ToLocalTime() + ); + } + } + + /// + /// Gets the remote volume entry from the fileset ID + /// + /// The fileset ID + /// An optional transaction + /// The remote volume entry or default + public RemoteVolumeEntry GetRemoteVolumeFromFilesetID(long filesetID, IDbTransaction transaction = null) + { + using (var cmd = m_connection.CreateCommand(transaction)) + using (var rd = cmd.SetCommandAndParameters(@"SELECT ""RemoteVolume"".""ID"", ""Name"", ""Type"", ""Size"", ""Hash"", ""State"", ""DeleteGraceTime"" FROM ""RemoteVolume"", ""Fileset"" WHERE ""Fileset"".""VolumeID"" = ""RemoteVolume"".""ID"" AND ""Fileset"".""ID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetID) + .ExecuteReader()) + if (rd.Read()) + return new RemoteVolumeEntry( + rd.ConvertValueToInt64(0, -1), + rd.GetValue(1).ToString(), + (rd.GetValue(4) == null || rd.GetValue(4) == DBNull.Value) ? null : rd.GetValue(4).ToString(), + rd.ConvertValueToInt64(3, -1), + (RemoteVolumeType)Enum.Parse(typeof(RemoteVolumeType), rd.GetValue(2).ToString()), + (RemoteVolumeState)Enum.Parse(typeof(RemoteVolumeState), rd.GetValue(5).ToString()), + ParseFromEpochSeconds(rd.GetInt64(6)).ToLocalTime() + ); + else + return default; + } + public void PurgeLogData(DateTime threshold) { using (var tr = m_connection.BeginTransaction()) using (var cmd = m_connection.CreateCommand(tr)) { var t = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold); - cmd.ExecuteNonQuery(@"DELETE FROM ""LogData"" WHERE ""Timestamp"" < ?", t); - cmd.ExecuteNonQuery(@"DELETE FROM ""RemoteOperation"" WHERE ""Timestamp"" < ?", t); + cmd.SetCommandAndParameters(@"DELETE FROM ""LogData"" WHERE ""Timestamp"" < @Timestamp") + .SetParameterValue("@Timestamp", t) + .ExecuteNonQuery(); + cmd.SetCommandAndParameters(@"DELETE FROM ""RemoteOperation"" WHERE ""Timestamp"" < @Timestamp") + .SetParameterValue("@Timestamp", t) + .ExecuteNonQuery(); tr.Commit(); } @@ -1428,9 +1624,8 @@ ORDER BY using (var tr = m_connection.BeginTransaction()) using (var cmd = m_connection.CreateCommand(tr)) { - m_removedeletedremotevolumeCommand.Transaction = tr; - m_removedeletedremotevolumeCommand.SetParameterValue(0, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold)); - m_removedeletedremotevolumeCommand.ExecuteNonQuery(); + m_removedeletedremotevolumeCommand.SetParameterValue("@Now", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold)) + .ExecuteNonQuery(tr); tr.Commit(); } } @@ -1440,20 +1635,26 @@ ORDER BY if (IsDisposed) return; - DisposeAllFields(this, false); + DisposeAllFields(this, false); if (ShouldCloseConnection && m_connection != null) { - if (m_connection.State == System.Data.ConnectionState.Open) + if (m_connection.State == ConnectionState.Open && !m_hasExecutedVacuum) { - using (IDbTransaction transaction = m_connection.BeginTransaction()) + using (var transaction = m_connection.BeginTransaction()) + using (var command = m_connection.CreateCommand(transaction)) { - using (IDbCommand command = m_connection.CreateCommand(transaction)) + // SQLite recommends that PRAGMA optimize is run just before closing each database connection. + command.ExecuteNonQuery("PRAGMA optimize"); + + try { - // SQLite recommends that PRAGMA optimize is run just before closing each database connection. - command.ExecuteNonQuery("PRAGMA optimize"); transaction.Commit(); } + catch (SQLite.SQLiteException ex) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "FailedToCommitTransaction", ex, "Failed to commit transaction after pragma optimize, usually caused by the a no-op transaction"); + } } m_connection.Close(); @@ -1506,20 +1707,23 @@ ORDER BY throw new AggregateException(exceptions); } - public void WriteResults() + public void WriteResults(IBasicResults result) { if (IsDisposed) return; - if (m_connection != null && m_result != null) + if (m_connection != null && result != null) { - m_result.FlushLog(); - if (m_result.EndTime.Ticks == 0) - m_result.EndTime = DateTime.UtcNow; + if (result is BasicResults basicResults) + { + basicResults.FlushLog(this); + if (basicResults.EndTime.Ticks == 0) + basicResults.EndTime = DateTime.UtcNow; + } var serializer = new JsonFormatSerializer(); LogMessage("Result", - serializer.SerializeResults(m_result), + serializer.SerializeResults(result), null, null ); @@ -1541,7 +1745,7 @@ ORDER BY /// The path prefix ID. /// The path to get the prefix for. /// The transaction to use for insertion, or null for no transaction - public long GetOrCreatePathPrefix(string prefix, System.Data.IDbTransaction transaction) + public long GetOrCreatePathPrefix(string prefix, IDbTransaction transaction) { // Ring-buffer style lookup for (var i = 0; i < m_pathPrefixLookup.Length; i++) @@ -1552,14 +1756,12 @@ ORDER BY } m_findpathprefixCommand.Transaction = transaction; - m_findpathprefixCommand.SetParameterValue(0, prefix); - var id = m_findpathprefixCommand.ExecuteScalarInt64(); + var id = m_findpathprefixCommand.SetParameterValue("@Prefix", prefix) + .ExecuteScalarInt64(transaction); + if (id < 0) - { - m_insertpathprefixCommand.Transaction = transaction; - m_insertpathprefixCommand.SetParameterValue(0, prefix); - id = m_insertpathprefixCommand.ExecuteScalarInt64(); - } + id = m_insertpathprefixCommand.SetParameterValue("@Prefix", prefix) + .ExecuteScalarInt64(transaction); m_pathPrefixIndex = (m_pathPrefixIndex + 1) % m_pathPrefixLookup.Length; m_pathPrefixLookup[m_pathPrefixIndex] = new KeyValuePair(prefix, id); @@ -1571,8 +1773,8 @@ ORDER BY /// The path separators on this system /// private static readonly char[] _pathseparators = new char[] { - System.IO.Path.DirectorySeparatorChar, - System.IO.Path.AltDirectorySeparatorChar, + Path.DirectorySeparatorChar, + Path.AltDirectorySeparatorChar, }; /// diff --git a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs index 184efd37d..dab070c7e 100644 --- a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,8 +23,6 @@ using System; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Text; -using System.IO; using Duplicati.Library.Interface; namespace Duplicati.Library.Main.Database @@ -36,7 +34,12 @@ namespace Duplicati.Library.Main.Database /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private System.Data.IDbCommand m_moveBlockToNewVolumeCommand; + /// + /// Flag for toggling temporary tables; set to empty string if debugging + /// + private const string TEMPORARY = "TEMPORARY"; + + private IDbCommand m_registerDuplicateBlockCommand; public LocalDeleteDatabase(string path, string operation) : base(path, operation, true) @@ -52,10 +55,7 @@ namespace Duplicati.Library.Main.Database private void InitializeCommands() { - m_moveBlockToNewVolumeCommand = m_connection.CreateCommand(); - - m_moveBlockToNewVolumeCommand.CommandText = @"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ?"; - m_moveBlockToNewVolumeCommand.AddParameters(3); + m_registerDuplicateBlockCommand = m_connection.CreateCommand(@"INSERT OR IGNORE INTO ""DuplicateBlock"" (""BlockID"", ""VolumeID"") SELECT ""ID"", ? FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? "); } /// @@ -64,29 +64,27 @@ namespace Duplicati.Library.Main.Database /// The fileset entries to delete /// The transaction to execute the commands in /// A list of filesets to delete - public IEnumerable> DropFilesetsFromTable(DateTime[] toDelete, System.Data.IDbTransaction transaction) + public IEnumerable> DropFilesetsFromTable(DateTime[] toDelete, IDbTransaction transaction) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; - var deleted = 0; //Process array in slices to prevent exceeding SQLITE_MAX_VARIABLE_NUMBER (default 999) const int SLICE_SIZE = 128; - for (int sliceStart = 0; sliceStart < toDelete.Length; sliceStart += SLICE_SIZE) + for (var sliceStart = 0; sliceStart < toDelete.Length; sliceStart += SLICE_SIZE) { - int sliceEnd = Math.Min(toDelete.Length, sliceStart + SLICE_SIZE) - 1; - int sliceLen = sliceEnd - sliceStart + 1; + var sliceEnd = Math.Min(toDelete.Length, sliceStart + SLICE_SIZE) - 1; + var sliceLen = sliceEnd - sliceStart + 1; - string q = string.Join(",", Enumerable.Repeat("?", sliceLen)); + var q = string.Join(",", Enumerable.Repeat("?", sliceLen)); //First we remove unwanted entries deleted += cmd.ExecuteNonQuery(@"DELETE FROM ""Fileset"" WHERE ""Timestamp"" IN (" + q + @") ", toDelete.Skip(sliceStart).Take(sliceLen).Select(Library.Utility.Utility.NormalizeDateTimeToEpochSeconds).Cast().ToArray()); } if (deleted != toDelete.Length) - throw new Exception(string.Format("Unexpected number of deleted filesets {0} vs {1}", deleted, toDelete.Length)); + throw new Exception($"Unexpected number of deleted filesets {deleted} vs {toDelete.Length}"); //Then we delete anything that is no longer being referenced cmd.ExecuteNonQuery(@"DELETE FROM ""FilesetEntry"" WHERE ""FilesetID"" NOT IN (SELECT DISTINCT ""ID"" FROM ""Fileset"")"); @@ -101,19 +99,15 @@ namespace Duplicati.Library.Main.Database cmd.ExecuteNonQuery(@"INSERT INTO ""DeletedBlock"" (""Hash"", ""Size"", ""VolumeID"") SELECT ""Hash"", ""Size"", ""VolumeID"" FROM ""Block"" WHERE ""ID"" NOT IN (SELECT DISTINCT ""BlockID"" AS ""BlockID"" FROM ""BlocksetEntry"" UNION SELECT DISTINCT ""ID"" FROM ""Block"", ""BlocklistHash"" WHERE ""Block"".""Hash"" = ""BlocklistHash"".""Hash"") "); cmd.ExecuteNonQuery(@"DELETE FROM ""Block"" WHERE ""ID"" NOT IN (SELECT DISTINCT ""BlockID"" FROM ""BlocksetEntry"" UNION SELECT DISTINCT ""ID"" FROM ""Block"", ""BlocklistHash"" WHERE ""Block"".""Hash"" = ""BlocklistHash"".""Hash"") "); - //Find all remote filesets that are no longer required, and mark them as delete - var updated = cmd.ExecuteNonQuery(@"UPDATE ""RemoteVolume"" SET ""State"" = ? WHERE ""Type"" = ? AND ""State"" IN (?, ?, ?) AND ""ID"" NOT IN (SELECT ""VolumeID"" FROM ""Fileset"") ", RemoteVolumeState.Deleting.ToString(), RemoteVolumeType.Files.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString(), RemoteVolumeState.Temporary.ToString()); + //Find all remote filesets that are no longer required, and mark them as deleting + var updated = cmd.ExecuteNonQuery(@"UPDATE ""RemoteVolume"" SET ""State"" = ? WHERE ""Type"" = ? AND ""State"" IN (?, ?, ?, ?) AND ""ID"" NOT IN (SELECT ""VolumeID"" FROM ""Fileset"") ", RemoteVolumeState.Deleting.ToString(), RemoteVolumeType.Files.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString(), RemoteVolumeState.Temporary.ToString(), RemoteVolumeState.Deleting.ToString()); if (deleted != updated) - throw new Exception(string.Format("Unexpected number of remote volumes marked as deleted. Found {0} filesets, but {1} volumes", deleted, updated)); + throw new Exception($"Unexpected number of remote volumes marked as deleted. Found {deleted} filesets, but {updated} volumes"); using (var rd = cmd.ExecuteReader(@"SELECT ""Name"", ""Size"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND ""State"" = ? ", RemoteVolumeType.Files.ToString(), RemoteVolumeState.Deleting.ToString())) - { while (rd.Read()) - { yield return new KeyValuePair(rd.GetString(0), rd.ConvertValueToInt64(1)); - } - } } } @@ -126,8 +120,8 @@ namespace Duplicati.Library.Main.Database { get { - List filesets = new List(); - using (IDbCommand cmd = this.m_connection.CreateCommand()) + var filesets = new List(); + using (var cmd = m_connection.CreateCommand()) { // We can also use the ROW_NUMBER() window function to generate the backup versions, // but this requires at least SQLite 3.25, which is not available in some common @@ -155,10 +149,10 @@ namespace Duplicati.Library.Main.Database public VolumeUsage(string name, long datasize, long wastedsize, long compressedsize) { - this.Name = name; - this.DataSize = datasize; - this.WastedSize = wastedsize; - this.CompressedSize = compressedsize; + Name = name; + DataSize = datasize; + WastedSize = wastedsize; + CompressedSize = compressedsize; } } @@ -168,7 +162,7 @@ namespace Duplicati.Library.Main.Database /// The sizes are the uncompressed values. /// /// A list of tuples with name, datasize, wastedbytes. - private IEnumerable GetWastedSpaceReport(System.Data.IDbTransaction transaction) + private IEnumerable GetWastedSpaceReport(IDbTransaction transaction) { var tmptablename = "UsageReport-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); @@ -183,21 +177,20 @@ namespace Duplicati.Library.Main.Database var combined = active + " UNION " + inactive + " UNION " + empty; var collected = @"SELECT ""VolumeID"" AS ""VolumeID"", SUM(""ActiveSize"") AS ""ActiveSize"", SUM(""InactiveSize"") AS ""InactiveSize"", MAX(""Sorttime"") AS ""Sorttime"" FROM (" + combined + @") GROUP BY ""VolumeID"" "; - var createtable = @"CREATE TEMPORARY TABLE """ + tmptablename + @""" AS " + collected; + var createtable = FormatInvariant($"{@$"CREATE {TEMPORARY} TABLE ""{tmptablename}"" AS "}{collected}"); - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; try { cmd.ExecuteNonQuery(createtable, RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString()); - using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""A"".""Name"", ""B"".""ActiveSize"", ""B"".""InactiveSize"", ""A"".""Size"" FROM ""Remotevolume"" A, ""{0}"" B WHERE ""A"".""ID"" = ""B"".""VolumeID"" ORDER BY ""B"".""Sorttime"" ASC ", tmptablename))) + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT ""A"".""Name"", ""B"".""ActiveSize"", ""B"".""InactiveSize"", ""A"".""Size"" FROM ""Remotevolume"" A, ""{tmptablename}"" B WHERE ""A"".""ID"" = ""B"".""VolumeID"" ORDER BY ""B"".""Sorttime"" ASC "))) while (rd.Read()) yield return new VolumeUsage(rd.GetValue(0).ToString(), rd.ConvertValueToInt64(1, 0) + rd.ConvertValueToInt64(2, 0), rd.ConvertValueToInt64(2, 0), rd.ConvertValueToInt64(3, 0)); } finally { - try { cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", tmptablename)); } + try { cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tmptablename}"" ")); } catch { } } } @@ -301,7 +294,7 @@ namespace Duplicati.Library.Main.Database } } - public ICompactReport GetCompactReport(long volsize, long wastethreshold, long smallfilesize, long maxsmallfilecount, System.Data.IDbTransaction transaction) + public ICompactReport GetCompactReport(long volsize, long wastethreshold, long smallfilesize, long maxsmallfilecount, IDbTransaction transaction) { return new CompactReport(volsize, wastethreshold, smallfilesize, maxsmallfilecount, GetWastedSpaceReport(transaction).ToList()); } @@ -322,16 +315,11 @@ namespace Duplicati.Library.Main.Database private class BlockQuery : IBlockQuery { - private System.Data.IDbCommand m_command; + private IDbCommand m_command; - public BlockQuery(System.Data.IDbConnection con, System.Data.IDbTransaction transaction) + public BlockQuery(IDbConnection con, IDbTransaction transaction) { - m_command = con.CreateCommand(); - m_command.Transaction = transaction; - - m_command.Parameters.Clear(); - m_command.CommandText = @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? "; - m_command.AddParameters(2); + m_command = con.CreateCommand(transaction, @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? "); } /// @@ -352,7 +340,7 @@ namespace Duplicati.Library.Main.Database else { // Check that the volume id matches - return (long)r == volumeId; + return r == volumeId; } } @@ -367,30 +355,81 @@ namespace Duplicati.Library.Main.Database /// /// Builds a lookup table to enable faster response to block queries /// - public IBlockQuery CreateBlockQueryHelper(System.Data.IDbTransaction transaction) + public IBlockQuery CreateBlockQueryHelper(IDbTransaction transaction) { return new BlockQuery(m_connection, transaction); } - public void MoveBlockToNewVolume(string hash, long size, long volumeID, System.Data.IDbTransaction tr) + /// + /// Registers a block as moved to a new volume + /// + /// The hash of the block + /// The size of the block + /// The new volume ID + /// The transaction to execute the command in + public void RegisterDuplicatedBlock(string hash, long size, long volumeID, IDbTransaction tr) { - m_moveBlockToNewVolumeCommand.SetParameterValue(0, volumeID); - m_moveBlockToNewVolumeCommand.SetParameterValue(1, hash); - m_moveBlockToNewVolumeCommand.SetParameterValue(2, size); - m_moveBlockToNewVolumeCommand.Transaction = tr; - var c = m_moveBlockToNewVolumeCommand.ExecuteNonQuery(); - if (c != 1) - throw new Exception("Unexpected update result"); + m_registerDuplicateBlockCommand.Transaction = tr; + m_registerDuplicateBlockCommand.SetParameterValue(0, volumeID); + m_registerDuplicateBlockCommand.SetParameterValue(1, hash); + m_registerDuplicateBlockCommand.SetParameterValue(2, size); + // Using INSERT OR IGNORE to avoid duplicate entries, result may be 1 or 0 + m_registerDuplicateBlockCommand.ExecuteNonQuery(); + } + + /// + /// After new volumes are uploaded, this method will update the blocks from the old volumes to point to the new volumes + /// + /// The file to remove + /// The volume IDs that will be removed + /// The transaction to execute the command in + public void PrepareForDelete(string filename, IEnumerable volumeIdsToBeRemoved, IDbTransaction transaction) + { + var deletedVolume = GetRemoteVolume(filename, transaction); + if (deletedVolume.Type != RemoteVolumeType.Blocks) + return; + + using (var cmd = m_connection.CreateCommand(transaction)) + { + var updatedBlocks = "BlocksToUpdate-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + var replacementBlocks = "ReplacementBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + try + { + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE {TEMPORARY} TABLE ""{updatedBlocks}"" AS SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" = ? "), deletedVolume.ID); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE {TEMPORARY} TABLE ""{replacementBlocks}"" AS SELECT ""BlockID"", MAX(""VolumeID"") AS ""VolumeID"" FROM ""DuplicateBlock"" WHERE ""VolumeID"" NOT IN (?) AND ""BlockID"" IN (SELECT ""ID"" FROM ""{updatedBlocks}"") GROUP BY ""BlockID"" "), volumeIdsToBeRemoved.ToArray()); + var targetCount = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""{updatedBlocks}"" ")); + if (targetCount == 0) + return; + + var replacementCount = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM """ + replacementBlocks + @""" "); + var updateCount = cmd.ExecuteNonQuery(FormatInvariant(@$"UPDATE ""Block"" SET ""VolumeID"" = (SELECT ""VolumeID"" FROM ""{replacementBlocks}"" WHERE ""{replacementBlocks}"".""BlockID"" = ""Block"".""ID"" AND ""Block"".""VolumeID"" = ?) WHERE ""Block"".""VolumeID"" = ? "), deletedVolume.ID, deletedVolume.ID); + var deleteCount = cmd.ExecuteNonQuery(FormatInvariant(@$"DELETE FROM ""DuplicateBlock"" WHERE (""DuplicateBlock"".""BlockID"" || ':' || ""DuplicateBlock"".""VolumeID"") IN (SELECT ""RB"".""BlockID"" || ':' || ""RB"".""VolumeID"" FROM ""{replacementBlocks}"" RB)")); + if (targetCount != updateCount || replacementCount != deleteCount || updateCount != deleteCount) + throw new Exception($"Unexpected number of rows updated. Expected {targetCount} but got updated {updateCount}, deleted {deleteCount}, and replaced {replacementCount}"); + + // Remove knowledge of any old blocks + cmd.ExecuteNonQuery(FormatInvariant(@$"DELETE FROM ""DuplicateBlock"" WHERE ""VolumeID"" = ?"), deletedVolume.ID); + } + finally + { + try { cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{updatedBlocks}"" ")); } + catch { } + try { cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{replacementBlocks}"" ")); } + catch { } + } + + } } /// /// Calculates the sequence in which files should be deleted based on their relations. /// - /// The deletable volumes. /// Block volumes slated for deletion. - public IEnumerable GetDeletableVolumes(IEnumerable deleteableVolumes, System.Data.IDbTransaction transaction) + /// The transaction to execute the command in + /// The deletable volumes. + public IEnumerable ReOrderDeleteableVolumes(IEnumerable deleteableVolumes, IDbTransaction transaction) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { // Although the generated index volumes are always in pairs, // this code handles many-to-many relations between @@ -398,14 +437,11 @@ namespace Duplicati.Library.Main.Database var lookupBlock = new Dictionary>(); var lookupIndexfiles = new Dictionary>(); - cmd.Transaction = transaction; - using (var rd = cmd.ExecuteReader(@"SELECT ""C"".""Name"", ""B"".""Name"", ""B"".""Hash"", ""B"".""Size"" FROM ""IndexBlockLink"" A, ""RemoteVolume"" B, ""RemoteVolume"" C WHERE ""A"".""IndexVolumeID"" = ""B"".""ID"" AND ""A"".""BlockVolumeID"" = ""C"".""ID"" AND ""B"".""Hash"" IS NOT NULL AND ""B"".""Size"" IS NOT NULL ")) while (rd.Read()) { var name = rd.GetValue(0).ToString(); - List indexfileList; - if (!lookupBlock.TryGetValue(name, out indexfileList)) + if (!lookupBlock.TryGetValue(name, out var indexfileList)) { indexfileList = new List(); lookupBlock.Add(name, indexfileList); @@ -414,8 +450,7 @@ namespace Duplicati.Library.Main.Database var v = new RemoteVolume(rd.GetString(1), rd.GetString(2), rd.GetInt64(3)); indexfileList.Add(v); - List blockList; - if (!lookupIndexfiles.TryGetValue(v.Name, out blockList)) + if (!lookupIndexfiles.TryGetValue(v.Name, out var blockList)) { blockList = new List(); lookupIndexfiles.Add(v.Name, blockList); @@ -427,12 +462,10 @@ namespace Duplicati.Library.Main.Database { // Return the input yield return r; - List indexfileList; - if (lookupBlock.TryGetValue(r.Name, out indexfileList)) + if (lookupBlock.TryGetValue(r.Name, out var indexfileList)) foreach (var sh in indexfileList) { - List backref; - if (lookupIndexfiles.TryGetValue(sh.Name, out backref)) + if (lookupIndexfiles.TryGetValue(sh.Name, out var backref)) { //If this is the last reference, // remove the index file as well diff --git a/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs b/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs index 0f36a8158..7a3dd5448 100644 --- a/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -33,7 +33,7 @@ namespace Duplicati.Library.Main.Database ShouldCloseConnection = true; } - private class ListResultFileset : Duplicati.Library.Interface.IListResultFileset + private class ListResultFileset : Interface.IListResultFileset { public long Version { get; set; } public int IsFullBackup { get; set; } @@ -42,120 +42,111 @@ namespace Duplicati.Library.Main.Database public long FileSizes { get; set; } } - private class ListResultFile : Duplicati.Library.Interface.IListResultFile + private class ListResultFile : Interface.IListResultFile { public string Path { get; set; } public IEnumerable Sizes { get; set; } } - private class ListResultRemoteLog : Duplicati.Library.Interface.IListResultRemoteLog + private class ListResultRemoteLog : Interface.IListResultRemoteLog { public DateTime Timestamp { get; set; } public string Message { get; set; } } - private class ListResultRemoteVolume : Duplicati.Library.Interface.IListResultRemoteVolume + private class ListResultRemoteVolume : Interface.IListResultRemoteVolume { public string Name { get; set; } } - public IEnumerable GetFilesets(IEnumerable items) + public IEnumerable GetFilesets(IEnumerable items) { var filesets = FilesetTimes.ToArray(); var dict = new Dictionary(); - for(var i = 0; i < filesets.Length; i++) + for (var i = 0; i < filesets.Length; i++) dict[filesets[i].Key] = i; - var sql = string.Format( - @"SELECT DISTINCT ""FilesetID"" FROM (" + - @"SELECT ""FilesetID"" FROM ""FilesetEntry"" WHERE ""FileID"" IN ( SELECT ""ID"" FROM ""FileLookup"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN ( SELECT ""ID"" From ""Block"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({0})))))" + - " UNION " + - @"SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({0}))" + - ")", - string.Join(",", items.Select(x => "?")) - ); + var sql = FormatInvariant($@"SELECT DISTINCT ""FilesetID"" FROM ( +SELECT ""FilesetID"" FROM ""FilesetEntry"" WHERE ""FileID"" IN ( SELECT ""ID"" FROM ""FileLookup"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN ( SELECT ""ID"" From ""Block"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))}))))) +UNION +SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))})) +)"); var it = new List(items); it.AddRange(items); - using(var cmd = m_connection.CreateCommand()) - using(var rd = cmd.ExecuteReader(sql, it.ToArray())) + using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.ExecuteReader(sql, it.ToArray())) while (rd.Read()) { var v = dict[rd.GetInt64(0)]; - yield return new ListResultFileset() { + yield return new ListResultFileset() + { Version = v, Time = filesets[v].Value }; } } - public IEnumerable GetFiles(IEnumerable items) + public IEnumerable GetFiles(IEnumerable items) { - var sql = string.Format( - @"SELECT DISTINCT ""Path"" FROM (" + - @"SELECT ""Path"" FROM ""File"" WHERE ""BlocksetID"" IN (SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" IN (SELECT ""ID"" from ""RemoteVolume"" WHERE ""Name"" IN ({0}))))" + - @" UNION " + - @"SELECT ""Path"" FROM ""File"" WHERE ""MetadataID"" IN (SELECT ""ID"" FROM ""Metadataset"" WHERE ""BlocksetID"" IN (SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" IN (SELECT ""ID"" from ""RemoteVolume"" WHERE ""Name"" IN ({0})))))" + - @" UNION " + - @"SELECT ""Path"" FROM ""File"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({0}))))" + - @") ORDER BY ""Path"" ", - string.Join(",", items.Select(x => "?")) - ); - + var sql = FormatInvariant($@"SELECT DISTINCT ""Path"" FROM ( +SELECT ""Path"" FROM ""File"" WHERE ""BlocksetID"" IN (SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" IN (SELECT ""ID"" from ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))})))) + UNION +SELECT ""Path"" FROM ""File"" WHERE ""MetadataID"" IN (SELECT ""ID"" FROM ""Metadataset"" WHERE ""BlocksetID"" IN (SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" IN (SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" IN (SELECT ""ID"" from ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))}))))) + UNION +SELECT ""Path"" FROM ""File"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))})))) +) ORDER BY ""Path"""); + var it = new List(items); it.AddRange(items); it.AddRange(items); - using(var cmd = m_connection.CreateCommand()) - using(var rd = cmd.ExecuteReader(sql, it.ToArray())) + using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.ExecuteReader(sql, it.ToArray())) while (rd.Read()) - yield return new ListResultFile() { + yield return new ListResultFile() + { Path = Convert.ToString(rd.GetValue(0)) }; - + } - public IEnumerable GetLogLines(IEnumerable items) + public IEnumerable GetLogLines(IEnumerable items) { - var sql = string.Format( - @"SELECT ""TimeStamp"", ""Message"" || ' ' || CASE WHEN ""Exception"" IS NULL THEN '' ELSE ""Exception"" END FROM ""LogData"" WHERE {0}" + - @" UNION " + - @"SELECT ""Timestamp"", ""Data"" FROM ""RemoteOperation"" WHERE ""Path"" IN ({1})", - string.Join(" OR ", items.Select(x => @"""Message"" LIKE ?")), - string.Join(",", items.Select(x => "?")) - ); + var sql = FormatInvariant($@" +SELECT ""TimeStamp"", ""Message"" || ' ' || CASE WHEN ""Exception"" IS NULL THEN '' ELSE ""Exception"" END FROM ""LogData"" WHERE {string.Join(" OR ", items.Select(x => @"""Message"" LIKE ?"))} +UNION +SELECT ""Timestamp"", ""Data"" FROM ""RemoteOperation"" WHERE ""Path"" IN ({string.Join(",", items.Select(x => "?"))})"); var it = new List(from n in items select "%" + n + "%"); it.AddRange(items); - using(var cmd = m_connection.CreateCommand()) - using(var rd = cmd.ExecuteReader(sql, it.ToArray())) + using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.ExecuteReader(sql, it.ToArray())) while (rd.Read()) - yield return new ListResultRemoteLog() { + yield return new ListResultRemoteLog() + { Timestamp = ParseFromEpochSeconds(rd.GetInt64(0)), Message = rd.GetString(1) }; } - public IEnumerable GetVolumes(IEnumerable items) + public IEnumerable GetVolumes(IEnumerable items) { - var sql = string.Format( - @"SELECT DISTINCT ""Name"" FROM ( " + - @" SELECT ""Name"" FROM ""Remotevolume"" WHERE ""ID"" IN ( SELECT ""VolumeID"" FROM ""Block"" WHERE ""ID"" IN ( SELECT ""BlockID"" FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""FileLookup"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({0}))))))) " + - @" UNION " + - @" SELECT ""Name"" FROM ""Remotevolume"" WHERE ""ID"" IN ( SELECT ""VolumeID"" FROM ""Block"" WHERE ""ID"" IN ( SELECT ""BlockID"" FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""Metadataset"" WHERE ""ID"" IN ( SELECT ""MetadataID"" FROM ""FileLookup"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({0}))))))))" + - @")", - string.Join(",", items.Select(x => "?")) - ); - + var sql = FormatInvariant($@"SELECT DISTINCT ""Name"" FROM ( +SELECT ""Name"" FROM ""Remotevolume"" WHERE ""ID"" IN ( SELECT ""VolumeID"" FROM ""Block"" WHERE ""ID"" IN ( SELECT ""BlockID"" FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""FileLookup"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))}))))))) +UNION +SELECT ""Name"" FROM ""Remotevolume"" WHERE ""ID"" IN ( SELECT ""VolumeID"" FROM ""Block"" WHERE ""ID"" IN ( SELECT ""BlockID"" FROM ""BlocksetEntry"" WHERE ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""Metadataset"" WHERE ""ID"" IN ( SELECT ""MetadataID"" FROM ""FileLookup"" WHERE ""ID"" IN ( SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" IN ( SELECT ""ID"" FROM ""Fileset"" WHERE ""VolumeID"" IN ( SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" IN ({string.Join(",", items.Select(x => "?"))})))))))) +)"); var it = new List(items); it.AddRange(items); - using(var cmd = m_connection.CreateCommand()) - using(var rd = cmd.ExecuteReader(sql, it.ToArray())) + using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.ExecuteReader(sql, it.ToArray())) while (rd.Read()) - yield return new ListResultRemoteVolume() { + yield return new ListResultRemoteVolume() + { Name = Convert.ToString(rd.GetValue(0)) }; } diff --git a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs index d1ed59cbb..531da1d54 100644 --- a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,23 +21,24 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; namespace Duplicati.Library.Main.Database { - internal class LocalListBrokenFilesDatabase : LocalDatabase - { - private const string BLOCK_VOLUME_IDS = @"SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = '{2}'"; + internal class LocalListBrokenFilesDatabase : LocalDatabase + { + private static readonly string BLOCK_VOLUME_IDS = FormatInvariant($@"SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = '{RemoteVolumeType.Blocks.ToString()}'"); - // Invalid blocksets include those that: - // - Have BlocksetEntries with unknown/invalid blocks (meaning the data to rebuild the blockset isn't available) - // - Invalid blocks include those that appear to be in non-Blocks volumes (e.g., are listed as being in an Index or Files volume) or that appear in an unknown volume (-1) - // - Have BlocklistHash entries with unknown/invalid blocks (meaning the data which defines the list of hashes that makes up the blockset isn't available) - // - Are defined in the Blockset table but have no entries in the BlocksetEntries table (this can happen during recreate if Files volumes reference blocksets that are not found in any Index files) - // However, blocksets with a length of 0 are excluded from this check, as the corresponding blocks for these are not needed. - private const string BROKEN_FILE_IDS = @" + // Invalid blocksets include those that: + // - Have BlocksetEntries with unknown/invalid blocks (meaning the data to rebuild the blockset isn't available) + // - Invalid blocks include those that appear to be in non-Blocks volumes (e.g., are listed as being in an Index or Files volume) or that appear in an unknown volume (-1) + // - Have BlocklistHash entries with unknown/invalid blocks (meaning the data which defines the list of hashes that makes up the blockset isn't available) + // - Are defined in the Blockset table but have no entries in the BlocksetEntries table (this can happen during recreate if Files volumes reference blocksets that are not found in any Index files) + // However, blocksets with a length of 0 are excluded from this check, as the corresponding blocks for these are not needed. + private static readonly string BROKEN_FILE_IDS = FormatInvariant($@" SELECT DISTINCT ""ID"" FROM ( - SELECT ""ID"" AS ""ID"", ""BlocksetID"" AS ""BlocksetID"" FROM ""FileLookup"" WHERE ""BlocksetID"" != {0} AND ""BlocksetID"" != {1} + SELECT ""ID"" AS ""ID"", ""BlocksetID"" AS ""BlocksetID"" FROM ""FileLookup"" WHERE ""BlocksetID"" != {FOLDER_BLOCKSET_ID} AND ""BlocksetID"" != {SYMLINK_BLOCKSET_ID} UNION SELECT ""A"".""ID"" AS ""ID"", ""B"".""BlocksetID"" AS ""BlocksetID"" FROM ""FileLookup"" A LEFT JOIN ""Metadataset"" B ON ""A"".""MetadataID"" = ""B"".""ID"" ) @@ -47,113 +48,111 @@ WHERE ""BlocksetID"" IS NULL OR ""BlocksetID"" IN ( SELECT ""BlocksetID"" FROM ""BlocksetEntry"" WHERE ""BlockID"" NOT IN (SELECT ""ID"" FROM ""Block"" WHERE ""VolumeID"" IN - (" + BLOCK_VOLUME_IDS + @")) + ({BLOCK_VOLUME_IDS})) UNION SELECT ""BlocksetID"" FROM ""BlocklistHash"" WHERE ""Hash"" NOT IN (SELECT ""Hash"" FROM ""Block"" WHERE ""VolumeID"" IN - (" + BLOCK_VOLUME_IDS + @")) + ({BLOCK_VOLUME_IDS})) UNION SELECT ""A"".""ID"" AS ""BlocksetID"" FROM ""Blockset"" A LEFT JOIN ""BlocksetEntry"" B ON ""A"".""ID"" = ""B"".""BlocksetID"" WHERE ""A"".""Length"" > 0 AND ""B"".""BlocksetID"" IS NULL ) WHERE ""BlocksetID"" NOT IN (SELECT ""ID"" FROM ""Blockset"" WHERE ""Length"" == 0) ) -"; - private const string BROKEN_FILE_SETS = @"SELECT DISTINCT ""B"".""Timestamp"", ""A"".""FilesetID"", COUNT(""A"".""FileID"") AS ""FileCount"" FROM ""FilesetEntry"" A, ""Fileset"" B WHERE ""A"".""FilesetID"" = ""B"".""ID"" AND ""A"".""FileID"" IN (" + BROKEN_FILE_IDS + @")"; +"); + private static readonly string BROKEN_FILE_SETS = FormatInvariant($@"SELECT DISTINCT ""B"".""Timestamp"", ""A"".""FilesetID"", COUNT(""A"".""FileID"") AS ""FileCount"" FROM ""FilesetEntry"" A, ""Fileset"" B WHERE ""A"".""FilesetID"" = ""B"".""ID"" AND ""A"".""FileID"" IN ({BROKEN_FILE_IDS})"); - private const string BROKEN_FILE_NAMES = @" -SELECT ""A"".""Path"", ""B"".""Length"" FROM ""File"" A, ""Blockset"" B WHERE ""A"".""BlocksetID"" = ""B"".""ID"" AND ""A"".""ID"" IN (" -+ BROKEN_FILE_IDS + -@") AND ""A"".""ID"" IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?)"; + private static readonly string BROKEN_FILE_NAMES = FormatInvariant($@"SELECT ""A"".""Path"", ""B"".""Length"" FROM ""File"" A, ""Blockset"" B WHERE ""A"".""BlocksetID"" = ""B"".""ID"" AND ""A"".""ID"" IN ({BROKEN_FILE_IDS}) AND ""A"".""ID"" IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?)"); - private const string INSERT_BROKEN_IDS = @"INSERT INTO ""{3}"" (""{4}"") " + BROKEN_FILE_IDS - + @" AND ""ID"" IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?)"; + private static string INSERT_BROKEN_IDS(string tablename, string IDfieldname) => FormatInvariant($@"INSERT INTO ""{tablename}"" (""{IDfieldname}"") {BROKEN_FILE_IDS} AND ""ID"" IN (SELECT ""FileID"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?)"); - public LocalListBrokenFilesDatabase(string path) - : base(path, "ListBrokenFiles", false) - { - ShouldCloseConnection = true; - } - - public LocalListBrokenFilesDatabase(LocalDatabase parent) - : base(parent) - { - ShouldCloseConnection = false; - } - - public IEnumerable> GetBrokenFilesets(DateTime time, long[] versions, System.Data.IDbTransaction transaction) - { - var query = string.Format(BROKEN_FILE_SETS, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID, RemoteVolumeType.Blocks.ToString()); - var clause = GetFilelistWhereClause(time, versions); - if (!string.IsNullOrWhiteSpace(clause.Item1)) - query += @" AND ""A"".""FilesetID"" IN (SELECT ""ID"" FROM ""Fileset"" " + clause.Item1 + ")"; - - query += @" GROUP BY ""A"".""FilesetID"""; - - using (var cmd = Connection.CreateCommand(transaction)) - foreach (var rd in cmd.ExecuteReaderEnumerable(query, clause.Item2)) - if (!rd.IsDBNull(0)) - yield return new Tuple(ParseFromEpochSeconds(rd.ConvertValueToInt64(0, 0)), rd.ConvertValueToInt64(1, -1), rd.ConvertValueToInt64(2, 0)); - } - - public IEnumerable> GetBrokenFilenames(long filesetid, System.Data.IDbTransaction transaction) - { - using (var cmd = Connection.CreateCommand(transaction)) - foreach (var rd in cmd.ExecuteReaderEnumerable(string.Format(BROKEN_FILE_NAMES, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID, RemoteVolumeType.Blocks.ToString()), filesetid)) - if (!rd.IsDBNull(0)) - yield return new Tuple(rd.ConvertValueToString(0), rd.ConvertValueToInt64(1)); - } - - public void InsertBrokenFileIDsIntoTable(long filesetid, string tablename, string IDfieldname, System.Data.IDbTransaction transaction) - { - using (var cmd = Connection.CreateCommand(transaction)) - cmd.ExecuteNonQuery(string.Format(INSERT_BROKEN_IDS, FOLDER_BLOCKSET_ID, SYMLINK_BLOCKSET_ID, RemoteVolumeType.Blocks.ToString(), tablename, IDfieldname), filesetid); - } - - public void RemoveMissingBlocks(IEnumerable names, System.Data.IDbTransaction transaction) - { - if (names == null || !names.Any()) return; - if (transaction == null) - throw new Exception("This function cannot be called when not in a transaction, as it leaves the database in an inconsistent state"); - - using (var deletecmd = m_connection.CreateCommand(transaction)) - { - string temptransguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - var volidstable = "DelVolSetIds-" + temptransguid; - - // Create and fill a temp table with the volids to delete. We avoid using too many parameters that way. - deletecmd.ExecuteNonQuery(string.Format(@"CREATE TEMP TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY)", volidstable)); - deletecmd.CommandText = string.Format(@"INSERT OR IGNORE INTO ""{0}"" (""ID"") VALUES (?)", volidstable); - deletecmd.Parameters.Clear(); - deletecmd.AddParameters(1); - foreach (var name in names) - { - var volumeid = GetRemoteVolumeID(name, transaction); - deletecmd.SetParameterValue(0, volumeid); - deletecmd.ExecuteNonQuery(); - } - var volIdsSubQuery = string.Format(@"SELECT ""ID"" FROM ""{0}"" ", volidstable); - deletecmd.Parameters.Clear(); - - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN ({0}) OR ""IndexVolumeID"" IN ({0})", volIdsSubQuery)); - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""Block"" WHERE ""VolumeID"" IN ({0})", volIdsSubQuery)); - - // Clean up temp tables for subqueries. We truncate content and then try to delete. - // Drop in try-block, as it fails in nested transactions (SQLite problem) - // System.Data.SQLite.SQLiteException (0x80004005): database table is locked - deletecmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" ", volidstable)); - try - { - deletecmd.CommandTimeout = 2; - deletecmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", volidstable)); - } - catch { /* Ignore, will be deleted on close anyway. */ } - } - } - - public long GetFilesetFileCount(long filesetid, System.Data.IDbTransaction transaction) - { - using (var cmd = m_connection.CreateCommand(transaction)) - return cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?", 0, filesetid); - } + public LocalListBrokenFilesDatabase(string path) + : base(path, "ListBrokenFiles", false) + { + ShouldCloseConnection = true; } + + public LocalListBrokenFilesDatabase(LocalDatabase parent) + : base(parent) + { + ShouldCloseConnection = false; + } + + public IEnumerable> GetBrokenFilesets(DateTime time, long[] versions, IDbTransaction transaction) + { + var query = BROKEN_FILE_SETS; + var clause = GetFilelistWhereClause(time, versions); + if (!string.IsNullOrWhiteSpace(clause.Item1)) + query += FormatInvariant($@" AND ""A"".""FilesetID"" IN (SELECT ""ID"" FROM ""Fileset"" {clause.Item1})"); + + query += @" GROUP BY ""A"".""FilesetID"""; + + using (var cmd = Connection.CreateCommand(transaction)) + foreach (var rd in cmd.SetCommandAndParameters(query).SetParameterValues(clause.Values).ExecuteReaderEnumerable(null)) + if (!rd.IsDBNull(0)) + yield return new Tuple(ParseFromEpochSeconds(rd.ConvertValueToInt64(0, 0)), rd.ConvertValueToInt64(1, -1), rd.ConvertValueToInt64(2, 0)); + } + + public IEnumerable> GetBrokenFilenames(long filesetid, IDbTransaction transaction) + { + using (var cmd = Connection.CreateCommand(transaction)) + foreach (var rd in cmd.ExecuteReaderEnumerable(BROKEN_FILE_NAMES, filesetid)) + if (!rd.IsDBNull(0)) + yield return new Tuple(rd.ConvertValueToString(0), rd.ConvertValueToInt64(1)); + } + + public void InsertBrokenFileIDsIntoTable(long filesetid, string tablename, string IDfieldname, IDbTransaction transaction) + { + using (var cmd = Connection.CreateCommand(transaction)) + cmd.ExecuteNonQuery(INSERT_BROKEN_IDS(tablename, IDfieldname), filesetid); + } + + public void RemoveMissingBlocks(IEnumerable names, IDbTransaction transaction) + { + if (names == null || !names.Any()) return; + if (transaction == null) + throw new Exception("This function cannot be called when not in a transaction, as it leaves the database in an inconsistent state"); + + using (var deletecmd = m_connection.CreateCommand(transaction)) + { + var temptransguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + var volidstable = "DelVolSetIds-" + temptransguid; + + // Create and fill a temp table with the volids to delete. We avoid using too many parameters that way. + deletecmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMP TABLE ""{volidstable}"" (""ID"" INTEGER PRIMARY KEY)")); + deletecmd.CommandText = FormatInvariant($@"INSERT OR IGNORE INTO ""{volidstable}"" (""ID"") VALUES (?)"); + deletecmd.Parameters.Clear(); + deletecmd.AddParameters(1); + foreach (var name in names) + { + var volumeid = GetRemoteVolumeID(name, transaction); + deletecmd.SetParameterValue(0, volumeid); + deletecmd.ExecuteNonQuery(); + } + var volIdsSubQuery = FormatInvariant($@"SELECT ""ID"" FROM ""{volidstable}"" "); + deletecmd.Parameters.Clear(); + + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN ({volIdsSubQuery}) OR ""IndexVolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""Block"" WHERE ""VolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""DeletedBlock"" WHERE ""VolumeID"" IN ({volIdsSubQuery})")); + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""DuplicateBlock"" WHERE ""VolumeID"" IN ({volIdsSubQuery})")); + + // Clean up temp tables for subqueries. We truncate content and then try to delete. + // Drop in try-block, as it fails in nested transactions (SQLite problem) + // SQLite.SQLiteException (0x80004005): database table is locked + deletecmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{volidstable}"" ")); + try + { + deletecmd.CommandTimeout = 2; + deletecmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{volidstable}"" ")); + } + catch { /* Ignore, will be deleted on close anyway. */ } + } + } + + public long GetFilesetFileCount(long filesetid, IDbTransaction transaction) + { + using (var cmd = m_connection.CreateCommand(transaction)) + return cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetID"" = ?", 0, filesetid); + } + } } diff --git a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs index 09a94c7b8..fda6f663d 100644 --- a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,6 +20,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Data; using System.Collections.Generic; using Duplicati.Library.Utility; @@ -32,16 +33,16 @@ namespace Duplicati.Library.Main.Database { ShouldCloseConnection = true; } - + public interface IStorageHelper : IDisposable { - void AddElement(string path, string filehash, string metahash, long size, Library.Interface.ListChangesElementType type, bool asNew); - - void AddFromDb(long filesetId, bool asNew, Library.Utility.IFilter filter); - + void AddElement(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew); + + void AddFromDb(long filesetId, bool asNew, IFilter filter); + IChangeCountReport CreateChangeCountReport(); IChangeSizeReport CreateChangeSizeReport(); - IEnumerable> CreateChangedFileReport(); + IEnumerable> CreateChangedFileReport(); } public interface IChangeCountReport @@ -49,11 +50,11 @@ namespace Duplicati.Library.Main.Database long AddedFolders { get; } long AddedSymlinks { get; } long AddedFiles { get; } - + long DeletedFolders { get; } long DeletedSymlinks { get; } long DeletedFiles { get; } - + long ModifiedFolders { get; } long ModifiedSymlinks { get; } long ModifiedFiles { get; } @@ -63,21 +64,21 @@ namespace Duplicati.Library.Main.Database { long AddedSize { get; } long DeletedSize { get; } - + long PreviousSize { get; } long CurrentSize { get; } } - + internal class ChangeCountReport : IChangeCountReport { - public long AddedFolders { get; internal set; } - public long AddedSymlinks { get; internal set; } + public long AddedFolders { get; internal set; } + public long AddedSymlinks { get; internal set; } public long AddedFiles { get; internal set; } - + public long DeletedFolders { get; internal set; } - public long DeletedSymlinks { get; internal set; } - public long DeletedFiles { get; internal set; } - + public long DeletedSymlinks { get; internal set; } + public long DeletedFiles { get; internal set; } + public long ModifiedFolders { get; internal set; } public long ModifiedSymlinks { get; internal set; } public long ModifiedFiles { get; internal set; } @@ -87,66 +88,56 @@ namespace Duplicati.Library.Main.Database { public long AddedSize { get; internal set; } public long DeletedSize { get; internal set; } - + public long PreviousSize { get; internal set; } public long CurrentSize { get; internal set; } } - + private class StorageHelper : IStorageHelper { - private readonly System.Data.IDbConnection m_connection; - private System.Data.IDbTransaction m_transaction; - - private System.Data.IDbCommand m_insertPreviousElementCommand; - private System.Data.IDbCommand m_insertCurrentElementCommand; - + private readonly IDbConnection m_connection; + private IDbTransaction m_transaction; + + private IDbCommand m_insertPreviousElementCommand; + private IDbCommand m_insertCurrentElementCommand; + private string m_previousTable; private string m_currentTable; - - public StorageHelper(System.Data.IDbConnection con) + + public StorageHelper(IDbConnection con) { m_connection = con; m_previousTable = "Previous-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); m_currentTable = "Current-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - + m_transaction = m_connection.BeginTransaction(); - - using(var cmd = m_connection.CreateCommand()) + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) ", m_previousTable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) ", m_currentTable)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_previousTable}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_currentTable}"" (""Path"" TEXT NOT NULL, ""FileHash"" TEXT NULL, ""MetaHash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Type"" INTEGER NOT NULL) ")); } - - m_insertPreviousElementCommand = m_connection.CreateCommand(); - m_insertPreviousElementCommand.Transaction = m_transaction; - m_insertPreviousElementCommand.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)", m_previousTable); - m_insertPreviousElementCommand.AddParameters(5); - - m_insertCurrentElementCommand = m_connection.CreateCommand(); - m_insertCurrentElementCommand.Transaction = m_transaction; - m_insertCurrentElementCommand.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)", m_currentTable); - m_insertCurrentElementCommand.AddParameters(5); + + m_insertPreviousElementCommand = m_connection.CreateCommand(m_transaction, FormatInvariant($@"INSERT INTO ""{m_previousTable}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)")); + m_insertCurrentElementCommand = m_connection.CreateCommand(FormatInvariant($@"INSERT INTO ""{m_currentTable}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)")); } - - public void AddFromDb(long filesetId, bool asNew, Library.Utility.IFilter filter) + + public void AddFromDb(long filesetId, bool asNew, IFilter filter) { var tablename = asNew ? m_currentTable : m_previousTable; - - var folders = string.Format(@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {0} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -100 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" ", (int)Library.Interface.ListChangesElementType.Folder); - var symlinks = string.Format(@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {0} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -200 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" ", (int)Library.Interface.ListChangesElementType.Symlink); - var files = string.Format(@"SELECT ""File"".""Path"" AS ""Path"", ""FB"".""FullHash"" AS ""FileHash"", ""MB"".""Fullhash"" AS ""MetaHash"", ""FB"".""Length"" AS ""Size"", {0} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" MB, ""Blockset"" FB WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" >= 0 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""MB"".""ID"" AND ""File"".""BlocksetID"" = ""FB"".""ID"" ", (int)Library.Interface.ListChangesElementType.File); + + var folders = FormatInvariant($@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {(int)Interface.ListChangesElementType.Folder} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -100 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" "); + var symlinks = FormatInvariant($@"SELECT ""File"".""Path"" AS ""Path"", NULL AS ""FileHash"", ""Blockset"".""Fullhash"" AS ""MetaHash"", -1 AS ""Size"", {(int)Interface.ListChangesElementType.Symlink} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" = -200 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" "); + var files = FormatInvariant($@"SELECT ""File"".""Path"" AS ""Path"", ""FB"".""FullHash"" AS ""FileHash"", ""MB"".""Fullhash"" AS ""MetaHash"", ""FB"".""Length"" AS ""Size"", {(int)Interface.ListChangesElementType.File} AS ""Type"", ""FilesetEntry"".""FilesetID"" AS ""FilesetID"" FROM ""File"",""FilesetEntry"",""Metadataset"",""Blockset"" MB, ""Blockset"" FB WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""BlocksetID"" >= 0 AND ""Metadataset"".""ID""=""File"".""MetadataID"" AND ""Metadataset"".""BlocksetID"" = ""MB"".""ID"" AND ""File"".""BlocksetID"" = ""FB"".""ID"" "); var combined = "(" + folders + " UNION " + symlinks + " UNION " + files + ")"; - - - using(var cmd = m_connection.CreateCommand()) + + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; if (filter == null || filter.Empty) { // Simple case, select everything - cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {1} A WHERE ""A"".""FilesetID"" = ? ", tablename, combined), filesetId); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {combined} A WHERE ""A"".""FilesetID"" = ? "), filesetId); } else if (Library.Utility.Utility.IsFSCaseSensitive && filter is FilterExpression expression && expression.Type == Duplicati.Library.Utility.FilterType.Simple) { @@ -155,11 +146,11 @@ namespace Duplicati.Library.Main.Database // SQLite only supports ASCII compares var p = expression.GetSimpleList(); var filenamestable = "Filenames-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL) ", filenamestable)); - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"") VALUES (?)", filenamestable); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{filenamestable}"" (""Path"" TEXT NOT NULL) ")); + cmd.CommandText = FormatInvariant($@"INSERT INTO ""{filenamestable}"" (""Path"") VALUES (?)"); cmd.AddParameter(); - - foreach(var s in p) + + foreach (var s in p) { cmd.SetParameterValue(0, s); cmd.ExecuteNonQuery(); @@ -169,46 +160,40 @@ namespace Duplicati.Library.Main.Database if (expression.Result) { // Include filter - whereClause = string.Format(@"""A"".""FilesetID"" = ? AND ""A"".""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{0}"")", filenamestable); + whereClause = FormatInvariant($@"""A"".""FilesetID"" = ? AND ""A"".""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{filenamestable}"")"); } else { // Exclude filter - whereClause = string.Format(@"""A"".""FilesetID"" = ? AND ""A"".""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""{0}"")", filenamestable); + whereClause = FormatInvariant($@"""A"".""FilesetID"" = ? AND ""A"".""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""{filenamestable}"")"); } - cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {1} A WHERE {2} ", tablename, combined, whereClause), filesetId); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", filenamestable)); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") SELECT ""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"" FROM {combined} A WHERE {whereClause} "), filesetId); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{filenamestable}"" ")); } else { // Do row-wise iteration - object[] values = new object[5]; - using(var cmd2 = m_connection.CreateCommand()) - { - cmd2.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)", tablename); - cmd2.AddParameters(5); - cmd2.Transaction = m_transaction; - - using(var rd = cmd.ExecuteReader(string.Format(@"SELECT ""A"".""Path"", ""A"".""FileHash"", ""A"".""MetaHash"", ""A"".""Size"", ""A"".""Type"" FROM {0} A WHERE ""A"".""FilesetID"" = ?", combined), filesetId)) - while (rd.Read()) + var values = new object[5]; + using (var cmd2 = m_connection.CreateCommand(m_transaction, FormatInvariant($@"INSERT INTO ""{tablename}"" (""Path"", ""FileHash"", ""MetaHash"", ""Size"", ""Type"") VALUES (?,?,?,?,?)"))) + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT ""A"".""Path"", ""A"".""FileHash"", ""A"".""MetaHash"", ""A"".""Size"", ""A"".""Type"" FROM {combined} A WHERE ""A"".""FilesetID"" = ?"), filesetId)) + while (rd.Read()) + { + rd.GetValues(values); + if (values[0] != null && values[0] != DBNull.Value && FilterExpression.Matches(filter, values[0].ToString())) { - rd.GetValues(values); - if (values[0] != null && values[0] != DBNull.Value && Library.Utility.FilterExpression.Matches(filter, values[0].ToString())) - { - cmd2.SetParameterValue(0, values[0]); - cmd2.SetParameterValue(1, values[1]); - cmd2.SetParameterValue(2, values[2]); - cmd2.SetParameterValue(3, values[3]); - cmd2.SetParameterValue(4, values[4]); - cmd2.ExecuteNonQuery(); - } + cmd2.SetParameterValue(0, values[0]); + cmd2.SetParameterValue(1, values[1]); + cmd2.SetParameterValue(2, values[2]); + cmd2.SetParameterValue(3, values[3]); + cmd2.SetParameterValue(4, values[4]); + cmd2.ExecuteNonQuery(); } - } + } } } } - - public void AddElement(string path, string filehash, string metahash, long size, Library.Interface.ListChangesElementType type, bool asNew) + + public void AddElement(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew) { var cmd = asNew ? m_insertCurrentElementCommand : m_insertPreviousElementCommand; cmd.SetParameterValue(0, path); @@ -219,9 +204,9 @@ namespace Duplicati.Library.Main.Database cmd.ExecuteNonQuery(); } - private static IEnumerable ReaderToStringList(System.Data.IDataReader rd) + private static IEnumerable ReaderToStringList(IDataReader rd) { - using(rd) + using (rd) while (rd.Read()) { var v = rd.GetValue(0); @@ -231,13 +216,13 @@ namespace Duplicati.Library.Main.Database yield return v.ToString(); } } - + private Tuple GetSqls(bool allTypes) { return new Tuple( - string.Format(@"SELECT ""Path"" FROM ""{0}"" WHERE {2} ""{0}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{1}"")", m_currentTable, m_previousTable, string.Format(allTypes ? "" : @" ""{0}"".""Type"" = ? AND ", m_currentTable)), - string.Format(@"SELECT ""Path"" FROM ""{0}"" WHERE {2} ""{0}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{1}"")", m_previousTable, m_currentTable, string.Format(allTypes ? "" : @" ""{0}"".""Type"" = ? AND ", m_previousTable)), - string.Format(@"SELECT ""{0}"".""Path"" FROM ""{0}"",""{1}"" WHERE {2} ""{0}"".""Path"" = ""{1}"".""Path"" AND (""{0}"".""FileHash"" != ""{1}"".""FileHash"" OR ""{0}"".""MetaHash"" != ""{1}"".""MetaHash"" OR ""{0}"".""Type"" != ""{1}"".""Type"") ", m_currentTable, m_previousTable, string.Format(allTypes ? "" : @" ""{0}"".""Type"" = ? AND ", m_currentTable)) + FormatInvariant($@"SELECT ""Path"" FROM ""{m_currentTable}"" WHERE {(allTypes ? "" : FormatInvariant(@$" ""{m_currentTable}"".""Type"" = ? AND "))} ""{m_currentTable}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{m_previousTable}"")"), + FormatInvariant($@"SELECT ""Path"" FROM ""{m_previousTable}"" WHERE {(allTypes ? "" : FormatInvariant(@$" ""{m_previousTable}"".""Type"" = ? AND "))} ""{m_previousTable}"".""Path"" NOT IN (SELECT ""Path"" FROM ""{m_currentTable}"")"), + FormatInvariant($@"SELECT ""{m_currentTable}"".""Path"" FROM ""{m_currentTable}"",""{m_previousTable}"" WHERE {(allTypes ? "" : FormatInvariant($@" ""{m_currentTable}"".""Type"" = ? AND "))} ""{m_currentTable}"".""Path"" = ""{m_previousTable}"".""Path"" AND (""{m_currentTable}"".""FileHash"" != ""{m_previousTable}"".""FileHash"" OR ""{m_currentTable}"".""MetaHash"" != ""{m_previousTable}"".""MetaHash"" OR ""{m_currentTable}"".""Type"" != ""{m_previousTable}"".""Type"") ") ); } @@ -247,114 +232,109 @@ namespace Duplicati.Library.Main.Database var added = sqls.Item1; var deleted = sqls.Item2; //var modified = sqls.Item3; - - using(var cmd = m_connection.CreateCommand()) + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - var result = new ChangeSizeReport(); - - result.PreviousSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" ", m_previousTable), 0); - result.CurrentSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" ", m_currentTable), 0); - - result.AddedSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" WHERE ""{0}"".""Path"" IN ({1}) ", m_currentTable, added), 0); - result.DeletedSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" WHERE ""{0}"".""Path"" IN ({1}) ", m_previousTable, deleted), 0); - + + result.PreviousSize = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""Size"") FROM ""{m_previousTable}"" "), 0); + result.CurrentSize = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""Size"") FROM ""{m_currentTable}"" "), 0); + + result.AddedSize = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""Size"") FROM ""{m_currentTable}"" WHERE ""{m_currentTable}"".""Path"" IN ({added}) "), 0); + result.DeletedSize = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""Size"") FROM ""{m_previousTable}"" WHERE ""{m_previousTable}"".""Path"" IN ({deleted}) "), 0); + return result; } } - + public IChangeCountReport CreateChangeCountReport() { var sqls = GetSqls(false); var added = @"SELECT COUNT(*) FROM (" + sqls.Item1 + ")"; var deleted = @"SELECT COUNT(*) FROM (" + sqls.Item2 + ")"; var modified = @"SELECT COUNT(*) FROM (" + sqls.Item3 + ")"; - - using(var cmd = m_connection.CreateCommand()) - { - cmd.Transaction = m_transaction; - - var result = new ChangeCountReport(); - result.AddedFolders = cmd.ExecuteScalarInt64(added, 0, (int)Library.Interface.ListChangesElementType.Folder); - result.AddedSymlinks = cmd.ExecuteScalarInt64(added, 0, (int)Library.Interface.ListChangesElementType.Symlink); - result.AddedFiles = cmd.ExecuteScalarInt64(added, 0, (int)Library.Interface.ListChangesElementType.File); - result.DeletedFolders = cmd.ExecuteScalarInt64(deleted, 0, (int)Library.Interface.ListChangesElementType.Folder); - result.DeletedSymlinks = cmd.ExecuteScalarInt64(deleted, 0, (int)Library.Interface.ListChangesElementType.Symlink); - result.DeletedFiles = cmd.ExecuteScalarInt64(deleted, 0, (int)Library.Interface.ListChangesElementType.File); - - result.ModifiedFolders = cmd.ExecuteScalarInt64(modified, 0, (int)Library.Interface.ListChangesElementType.Folder); - result.ModifiedSymlinks = cmd.ExecuteScalarInt64(modified, 0, (int)Library.Interface.ListChangesElementType.Symlink); - result.ModifiedFiles = cmd.ExecuteScalarInt64(modified, 0, (int)Library.Interface.ListChangesElementType.File); - + using (var cmd = m_connection.CreateCommand(m_transaction)) + { + var result = new ChangeCountReport(); + result.AddedFolders = cmd.ExecuteScalarInt64(added, 0, (int)Interface.ListChangesElementType.Folder); + result.AddedSymlinks = cmd.ExecuteScalarInt64(added, 0, (int)Interface.ListChangesElementType.Symlink); + result.AddedFiles = cmd.ExecuteScalarInt64(added, 0, (int)Interface.ListChangesElementType.File); + + result.DeletedFolders = cmd.ExecuteScalarInt64(deleted, 0, (int)Interface.ListChangesElementType.Folder); + result.DeletedSymlinks = cmd.ExecuteScalarInt64(deleted, 0, (int)Interface.ListChangesElementType.Symlink); + result.DeletedFiles = cmd.ExecuteScalarInt64(deleted, 0, (int)Interface.ListChangesElementType.File); + + result.ModifiedFolders = cmd.ExecuteScalarInt64(modified, 0, (int)Interface.ListChangesElementType.Folder); + result.ModifiedSymlinks = cmd.ExecuteScalarInt64(modified, 0, (int)Interface.ListChangesElementType.Symlink); + result.ModifiedFiles = cmd.ExecuteScalarInt64(modified, 0, (int)Interface.ListChangesElementType.File); + return result; - } + } } - - public IEnumerable> CreateChangedFileReport() + + public IEnumerable> CreateChangedFileReport() { var sqls = GetSqls(false); var added = sqls.Item1; var deleted = sqls.Item2; var modified = sqls.Item3; - - using(var cmd = m_connection.CreateCommand()) + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - foreach(var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Library.Interface.ListChangesElementType.Folder))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Added, Library.Interface.ListChangesElementType.Folder, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Library.Interface.ListChangesElementType.Symlink))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Added, Library.Interface.ListChangesElementType.Symlink, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Library.Interface.ListChangesElementType.File))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Added, Library.Interface.ListChangesElementType.File, s); - - foreach(var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Library.Interface.ListChangesElementType.Folder))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Deleted, Library.Interface.ListChangesElementType.Folder, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Library.Interface.ListChangesElementType.Symlink))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Deleted, Library.Interface.ListChangesElementType.Symlink, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Library.Interface.ListChangesElementType.File))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Deleted, Library.Interface.ListChangesElementType.File, s); - - foreach(var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Library.Interface.ListChangesElementType.Folder))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Modified, Library.Interface.ListChangesElementType.Folder, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Library.Interface.ListChangesElementType.Symlink))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Modified, Library.Interface.ListChangesElementType.Symlink, s); - foreach(var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Library.Interface.ListChangesElementType.File))) - yield return new Tuple(Library.Interface.ListChangesChangeType.Modified, Library.Interface.ListChangesElementType.File, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Interface.ListChangesElementType.Folder))) + yield return new Tuple(Interface.ListChangesChangeType.Added, Interface.ListChangesElementType.Folder, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Interface.ListChangesElementType.Symlink))) + yield return new Tuple(Interface.ListChangesChangeType.Added, Interface.ListChangesElementType.Symlink, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(added, (int)Interface.ListChangesElementType.File))) + yield return new Tuple(Interface.ListChangesChangeType.Added, Interface.ListChangesElementType.File, s); + + foreach (var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Interface.ListChangesElementType.Folder))) + yield return new Tuple(Interface.ListChangesChangeType.Deleted, Interface.ListChangesElementType.Folder, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Interface.ListChangesElementType.Symlink))) + yield return new Tuple(Interface.ListChangesChangeType.Deleted, Interface.ListChangesElementType.Symlink, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(deleted, (int)Interface.ListChangesElementType.File))) + yield return new Tuple(Interface.ListChangesChangeType.Deleted, Interface.ListChangesElementType.File, s); + + foreach (var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Interface.ListChangesElementType.Folder))) + yield return new Tuple(Interface.ListChangesChangeType.Modified, Interface.ListChangesElementType.Folder, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Interface.ListChangesElementType.Symlink))) + yield return new Tuple(Interface.ListChangesChangeType.Modified, Interface.ListChangesElementType.Symlink, s); + foreach (var s in ReaderToStringList(cmd.ExecuteReader(modified, (int)Interface.ListChangesElementType.File))) + yield return new Tuple(Interface.ListChangesChangeType.Modified, Interface.ListChangesElementType.File, s); } } - + public void Dispose() { if (m_insertPreviousElementCommand != null) { try { m_insertPreviousElementCommand.Dispose(); } - catch {} + catch { } finally { m_insertPreviousElementCommand = null; } } if (m_insertCurrentElementCommand != null) { try { m_insertCurrentElementCommand.Dispose(); } - catch {} + catch { } finally { m_insertCurrentElementCommand = null; } } - + if (m_transaction != null) { try { m_transaction.Rollback(); } - catch {} + catch { } finally { m_previousTable = null; - m_currentTable= null; + m_currentTable = null; m_transaction = null; } } } } - + public IStorageHelper CreateStorageHelper() { return new StorageHelper(m_connection); diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index d346fbaa5..b13208ef8 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,10 +20,11 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Data; using System.Linq; using System.Collections.Generic; -using System.Text; using Duplicati.Library.Common.IO; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Database { @@ -34,13 +35,13 @@ namespace Duplicati.Library.Main.Database { ShouldCloseConnection = true; } - + public interface IFileversion { string Path { get; } IEnumerable Sizes { get; } } - + public interface IFileset { long Version { get; } @@ -49,39 +50,39 @@ namespace Duplicati.Library.Main.Database long FileCount { get; } long FileSizes { get; } } - + public interface IFileSets : IDisposable { IEnumerable Sets { get; } IEnumerable QuickSets { get; } - IEnumerable SelectFiles(Library.Utility.IFilter filter); - IEnumerable GetLargestPrefix(Library.Utility.IFilter filter); - IEnumerable SelectFolderContents(Library.Utility.IFilter filter); - void TakeFirst (); + IEnumerable SelectFiles(IFilter filter); + IEnumerable GetLargestPrefix(IFilter filter); + IEnumerable SelectFolderContents(IFilter filter); + void TakeFirst(); } private class FileSets : IFileSets { - private readonly System.Data.IDbConnection m_connection; + private readonly IDbConnection m_connection; private string m_tablename; private readonly KeyValuePair[] m_filesets; - + public FileSets(LocalListDatabase owner, DateTime time, long[] versions) { m_connection = owner.m_connection; m_filesets = owner.FilesetTimes.ToArray(); m_tablename = "Filesets-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); var tmp = owner.GetFilelistWhereClause(time, versions, m_filesets); - string query = tmp.Item1; + var query = tmp.Item1; var args = tmp.Item2; - - using(var cmd = m_connection.CreateCommand()) + + using (var cmd = m_connection.CreateCommand()) { - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS SELECT DISTINCT ""ID"" AS ""FilesetID"", ""IsFullBackup"" AS ""IsFullBackup"" , ""Timestamp"" AS ""Timestamp"" FROM ""Fileset"" " + query, m_tablename), args); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}_FilesetIDTimestampIndex"" ON ""{0}"" (""FilesetID"", ""Timestamp"" DESC)", m_tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tablename}"" AS SELECT DISTINCT ""ID"" AS ""FilesetID"", ""IsFullBackup"" AS ""IsFullBackup"" , ""Timestamp"" AS ""Timestamp"" FROM ""Fileset"" {query}"), args); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tablename}_FilesetIDTimestampIndex"" ON ""{m_tablename}"" (""FilesetID"", ""Timestamp"" DESC)")); } } - + private class Fileset : IFileset { public long Version { get; private set; } @@ -89,90 +90,90 @@ namespace Duplicati.Library.Main.Database public DateTime Time { get; private set; } public long FileCount { get; private set; } public long FileSizes { get; private set; } - + public Fileset(long version, int isFullBackup, DateTime time, long filecount, long filesizes) { - this.Version = version; - this.IsFullBackup = isFullBackup; - this.Time = time; - this.FileCount = filecount; - this.FileSizes = filesizes; + Version = version; + IsFullBackup = isFullBackup; + Time = time; + FileCount = filecount; + FileSizes = filesizes; } } - + private class Fileversion : IFileversion { - private readonly System.Data.IDataReader m_reader; + private readonly IDataReader m_reader; public string Path { get; private set; } public bool More { get; private set; } - - public Fileversion(System.Data.IDataReader reader) - { - m_reader = reader; - this.Path = reader.GetValue(0).ToString(); - this.More = true; + + public Fileversion(IDataReader reader) + { + m_reader = reader; + Path = reader.GetValue(0).ToString(); + More = true; } - + public IEnumerable Sizes { get { - while(this.More && this.Path == m_reader.GetValue(0).ToString()) + while (More && Path == m_reader.GetValue(0).ToString()) { yield return m_reader.ConvertValueToInt64(1, -1); - this.More = m_reader.Read(); + More = m_reader.Read(); } } } } - + private class FileversionFixed : IFileversion { public string Path { get; internal set; } public IEnumerable Sizes { get { return new long[0]; } } } - public IEnumerable GetLargestPrefix(Library.Utility.IFilter filter) + public IEnumerable GetLargestPrefix(IFilter filter) { return GetLargestPrefix(filter, null); } - private IEnumerable GetLargestPrefix(Library.Utility.IFilter filter, string prefixrule) + private IEnumerable GetLargestPrefix(IFilter filter, string prefixrule) { - using(var tmpnames = new FilteredFilenameTable(m_connection, filter, null)) - using(var cmd = m_connection.CreateCommand()) + using (var tmpnames = new FilteredFilenameTable(m_connection, filter, null)) + using (var cmd = m_connection.CreateCommand()) { //First we trim the filelist to exclude filenames not found in any of the filesets - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) ", tmpnames.Tablename, m_tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) ")); //If we have a prefix rule, apply it if (!string.IsNullOrWhiteSpace(prefixrule)) - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" WHERE SUBSTR(""Path"", 1, {1}) != ?", tmpnames.Tablename, prefixrule.Length), prefixrule); + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{tmpnames.Tablename}"" WHERE SUBSTR(""Path"", 1, {prefixrule.Length}) != ?"), prefixrule); // Then we recursively find the largest prefix - cmd.CommandText = string.Format(@"SELECT ""Path"" FROM ""{0}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1", tmpnames.Tablename); + cmd.CommandText = FormatInvariant($@"SELECT ""Path"" FROM ""{tmpnames.Tablename}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1"); var v0 = cmd.ExecuteScalar(); - string maxpath = ""; + var maxpath = ""; if (v0 != null) maxpath = v0.ToString(); var dirsep = Util.GuessDirSeparator(maxpath); - - cmd.CommandText = string.Format(@"SELECT COUNT(*) FROM ""{0}""", tmpnames.Tablename); + + cmd.CommandText = FormatInvariant($@"SELECT COUNT(*) FROM ""{tmpnames.Tablename}"""); var filecount = cmd.ExecuteScalarInt64(0); - long foundfiles = -1; - + var foundfiles = -1L; + //TODO: Handle FS case-sensitive? - cmd.CommandText = string.Format(@"SELECT COUNT(*) FROM ""{0}"" WHERE SUBSTR(""Path"", 1, ?) = ?", tmpnames.Tablename); + cmd.CommandText = FormatInvariant($@"SELECT COUNT(*) FROM ""{tmpnames.Tablename}"" WHERE SUBSTR(""Path"", 1, ?) = ?"); cmd.AddParameter(); cmd.AddParameter(); - + while (filecount != foundfiles && maxpath.Length > 0) { var mp = Util.AppendDirSeparator(maxpath, dirsep); cmd.SetParameterValue(0, mp.Length); cmd.SetParameterValue(1, mp); - + foundfiles = cmd.ExecuteScalarInt64(0); if (filecount != foundfiles) @@ -189,7 +190,7 @@ namespace Duplicati.Library.Main.Database // Special handling for Windows and multi-drive/UNC backups as they do not have a single common root if (string.IsNullOrWhiteSpace(maxpath) && string.IsNullOrWhiteSpace(prefixrule)) { - var paths = cmd.ExecuteReaderEnumerable(string.Format(@"SELECT Path FROM ""{0}""", tmpnames.Tablename)).Select(x => x.ConvertValueToString(0)).ToArray(); + var paths = cmd.ExecuteReaderEnumerable(FormatInvariant($@"SELECT Path FROM ""{tmpnames.Tablename}""")).Select(x => x.ConvertValueToString(0)).ToArray(); var roots = paths.Select(x => x.Substring(0, 1)).Distinct().Where(x => x != "\\").ToArray(); //unc path like \\server.domain\ @@ -199,20 +200,17 @@ namespace Duplicati.Library.Main.Database return roots.Concat(rootsUNC).Select(x => GetLargestPrefix(filter, x).First()).Distinct().ToArray(); } - return - new IFileversion[] { - new FileversionFixed { Path = maxpath == "" ? "" : Util.AppendDirSeparator(maxpath, dirsep) } - }; + return [new FileversionFixed { Path = maxpath == "" ? "" : Util.AppendDirSeparator(maxpath, dirsep) }]; } } - - private IEnumerable SelectFolderEntries(System.Data.IDbCommand cmd, string prefix, string table) + + private IEnumerable SelectFolderEntries(IDbCommand cmd, string prefix, string table) { if (!string.IsNullOrEmpty(prefix)) prefix = Util.AppendDirSeparator(prefix, Util.GuessDirSeparator(prefix)); - + var ppl = prefix.Length; - using(var rd = cmd.ExecuteReader(string.Format(@"SELECT DISTINCT ""Path"" FROM ""{0}"" ", table))) + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT DISTINCT ""Path"" FROM ""{table}"" "))) while (rd.Read()) { var s = rd.GetString(0); @@ -228,8 +226,8 @@ namespace Duplicati.Library.Main.Database yield return prefix + s; } } - - public IEnumerable SelectFolderContents(Library.Utility.IFilter filter) + + public IEnumerable SelectFolderContents(IFilter filter) { var tbname = "Filenames-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); try @@ -237,22 +235,22 @@ namespace Duplicati.Library.Main.Database string pathprefix; if (filter == null || filter.Empty) pathprefix = ""; - else if (filter as Library.Utility.FilterExpression == null || ((Library.Utility.FilterExpression)filter).Type != Duplicati.Library.Utility.FilterType.Simple || ((Library.Utility.FilterExpression)filter).GetSimpleList().Length != 1) + else if (filter as FilterExpression == null || ((FilterExpression)filter).Type != FilterType.Simple || ((FilterExpression)filter).GetSimpleList().Length != 1) throw new ArgumentException("Filter for list-folder-contents must be a path prefix with no wildcards", nameof(filter)); else - pathprefix = ((Library.Utility.FilterExpression)filter).GetSimpleList().First(); + pathprefix = ((FilterExpression)filter).GetSimpleList().First(); var dirsep = Util.GuessDirSeparator(pathprefix); if (pathprefix.Length > 0 || dirsep == "/") pathprefix = Util.AppendDirSeparator(pathprefix, dirsep); - - using(var tmpnames = new FilteredFilenameTable(m_connection, new Library.Utility.FilterExpression(new string[] { pathprefix + "*" }, true), null)) - using(var cmd = m_connection.CreateCommand()) + + using (var tmpnames = new FilteredFilenameTable(m_connection, new FilterExpression(new string[] { pathprefix + "*" }, true), null)) + using (var cmd = m_connection.CreateCommand()) { //First we trim the filelist to exclude filenames not found in any of the filesets - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) ", tmpnames.Tablename, m_tablename)); - + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) ")); + // If we had instr support this would work: /*var distinctPaths = @"SELECT DISTINCT :1 || " + @"CASE(INSTR(SUBSTR(""Path"", :2), '/')) " + @@ -262,33 +260,33 @@ namespace Duplicati.Library.Main.Database @" FROM (" + cartesianPathFileset + @")";*/ // Instead we manually iterate the paths - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL)", tbname)); - - using(var c2 = m_connection.CreateCommand()) + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tbname}"" (""Path"" TEXT NOT NULL)")); + + using (var c2 = m_connection.CreateCommand()) { - c2.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"") VALUES (?)", tbname); + c2.CommandText = FormatInvariant($@"INSERT INTO ""{tbname}"" (""Path"") VALUES (?)"); c2.AddParameter(); - - foreach(var n in SelectFolderEntries(cmd, pathprefix, tmpnames.Tablename).Distinct()) + + foreach (var n in SelectFolderEntries(cmd, pathprefix, tmpnames.Tablename).Distinct()) { c2.SetParameterValue(0, n); c2.ExecuteNonQuery(); } - c2.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}_PathIndex"" ON ""{0}"" (""Path"")", tbname)); + c2.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{tbname}_PathIndex"" ON ""{tbname}"" (""Path"")")); } - - //Then we select the matching results - var filesets = string.Format(@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC", m_tablename); - var cartesianPathFileset = string.Format(@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC", tbname, m_tablename); - var filesWithSizes = string.Format(@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") ", m_tablename); + //Then we select the matching results + var filesets = FormatInvariant($@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC"); + var cartesianPathFileset = FormatInvariant($@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{tbname}"" A, ({filesets}) B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC"); + + var filesWithSizes = FormatInvariant($@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{m_tablename}"") "); var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path"""; - - cmd.AddParameter(pathprefix, "1"); + + cmd.AddParameter(pathprefix, "1"); cmd.AddParameter(pathprefix.Length + 1, "2"); - - using(var rd = cmd.ExecuteReader(query)) + + using (var rd = cmd.ExecuteReader(query)) if (rd.Read()) { bool more; @@ -304,39 +302,39 @@ namespace Duplicati.Library.Main.Database { more = rd.Read(); } - - } while(more); + + } while (more); } - } + } } finally { try { - using(var c = m_connection.CreateCommand()) - c.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}""", tbname)); + using (var c = m_connection.CreateCommand()) + c.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tbname}""")); } catch { } } } - - public IEnumerable SelectFiles(Library.Utility.IFilter filter) + + public IEnumerable SelectFiles(IFilter filter) { - using(var tmpnames = new FilteredFilenameTable(m_connection, filter, null)) - using(var cmd = m_connection.CreateCommand()) + using (var tmpnames = new FilteredFilenameTable(m_connection, filter, null)) + using (var cmd = m_connection.CreateCommand()) { //First we trim the filelist to exclude filenames not found in any of the filesets - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{1}"") ) ", tmpnames.Tablename, m_tablename)); - + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{tmpnames.Tablename}"" WHERE ""Path"" NOT IN (SELECT DISTINCT ""Path"" FROM ""File"", ""FilesetEntry"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" IN (SELECT ""FilesetID"" FROM ""{m_tablename}"") ) ")); + //Then we select the matching results - var filesets = string.Format(@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC", m_tablename); - var cartesianPathFileset = string.Format(@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{0}"" A, (" + filesets + @") B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC", tmpnames.Tablename, m_tablename); - var filesWithSizes = string.Format(@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{0}"") ", m_tablename); + var filesets = FormatInvariant($@"SELECT ""FilesetID"", ""Timestamp"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC"); + var cartesianPathFileset = FormatInvariant($@"SELECT ""A"".""Path"", ""B"".""FilesetID"" FROM ""{tmpnames.Tablename}"" A, ({filesets}) B ORDER BY ""A"".""Path"" ASC, ""B"".""Timestamp"" DESC"); + var filesWithSizes = FormatInvariant($@"SELECT ""Length"", ""FilesetEntry"".""FilesetID"", ""File"".""Path"" FROM ""Blockset"", ""FilesetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND FilesetEntry.""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{m_tablename}"") "); var query = @"SELECT ""C"".""Path"", ""D"".""Length"", ""C"".""FilesetID"" FROM (" + cartesianPathFileset + @") C LEFT OUTER JOIN (" + filesWithSizes + @") D ON ""C"".""FilesetID"" = ""D"".""FilesetID"" AND ""C"".""Path"" = ""D"".""Path"""; - using(var rd = cmd.ExecuteReader(query)) - if(rd.Read()) + using (var rd = cmd.ExecuteReader(query)) + if (rd.Read()) { bool more; do @@ -344,15 +342,15 @@ namespace Duplicati.Library.Main.Database var f = new Fileversion(rd); yield return f; more = f.More; - } while(more); + } while (more); } } } - + public void TakeFirst() { - using(var cmd = m_connection.CreateCommand()) - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""{0}"" WHERE ""FilesetID"" NOT IN (SELECT ""FilesetID"" FROM ""{0}"" ORDER BY ""Timestamp"" DESC LIMIT 1 )", m_tablename)); + using (var cmd = m_connection.CreateCommand()) + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""{m_tablename}"" WHERE ""FilesetID"" NOT IN (SELECT ""FilesetID"" FROM ""{m_tablename}"" ORDER BY ""Timestamp"" DESC LIMIT 1 )")); } public IEnumerable QuickSets @@ -361,83 +359,69 @@ namespace Duplicati.Library.Main.Database { var dict = new Dictionary(); for (var i = 0; i < m_filesets.Length; i++) - { dict[m_filesets[i].Key] = i; - } using (var cmd = m_connection.CreateCommand()) - { - using (var rd = cmd.ExecuteReader(@"SELECT DISTINCT ""ID"", ""IsFullBackup"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC ")) + using (var rd = cmd.ExecuteReader(@"SELECT DISTINCT ""ID"", ""IsFullBackup"" FROM ""Fileset"" ORDER BY ""Timestamp"" DESC ")) + while (rd.Read()) { - while (rd.Read()) - { - var id = rd.GetInt64(0); - var backupType = rd.GetInt32(1); - var e = dict[id]; - yield return new Fileset(e, backupType, m_filesets[e].Value, -1L, -1L); - } + var id = rd.GetInt64(0); + var backupType = rd.GetInt32(1); + var e = dict[id]; + yield return new Fileset(e, backupType, m_filesets[e].Value, -1L, -1L); } - } } } - public IEnumerable Sets - { + public IEnumerable Sets + { get { var dict = new Dictionary(); for (var i = 0; i < m_filesets.Length; i++) - { dict[m_filesets[i].Key] = i; - } - - var summation = - $@"SELECT ""A"".""FilesetID"" AS ""FilesetID"", COUNT(*) AS ""FileCount"", SUM(""C"".""Length"") AS ""FileSizes"" FROM ""FilesetEntry"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" AND ""A"".""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{m_tablename}"") GROUP BY ""A"".""FilesetID"" "; + + var summation = FormatInvariant( + $@"SELECT ""A"".""FilesetID"" AS ""FilesetID"", COUNT(*) AS ""FileCount"", SUM(""C"".""Length"") AS ""FileSizes"" FROM ""FilesetEntry"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" AND ""A"".""FilesetID"" IN (SELECT DISTINCT ""FilesetID"" FROM ""{m_tablename}"") GROUP BY ""A"".""FilesetID"" "); using (var cmd = m_connection.CreateCommand()) - { - using (var rd = cmd.ExecuteReader( - $@"SELECT DISTINCT ""A"".""FilesetID"", ""A"".""IsFullBackup"", ""B"".""FileCount"", ""B"".""FileSizes"" FROM ""{m_tablename}"" A LEFT OUTER JOIN ( {summation} ) B ON ""A"".""FilesetID"" = ""B"".""FilesetID"" ORDER BY ""A"".""Timestamp"" DESC ") - ) + using (var rd = cmd.ExecuteReader(FormatInvariant( + $@"SELECT DISTINCT ""A"".""FilesetID"", ""A"".""IsFullBackup"", ""B"".""FileCount"", ""B"".""FileSizes"" FROM ""{m_tablename}"" A LEFT OUTER JOIN ( {summation} ) B ON ""A"".""FilesetID"" = ""B"".""FilesetID"" ORDER BY ""A"".""Timestamp"" DESC ")) + ) + while (rd.Read()) { - while (rd.Read()) - { - var id = rd.GetInt64(0); - var isFullBackup = rd.GetInt32(1); - var e = dict[id]; - var filecount = rd.ConvertValueToInt64(2, -1L); - var filesizes = rd.ConvertValueToInt64(3, -1L); + var id = rd.GetInt64(0); + var isFullBackup = rd.GetInt32(1); + var e = dict[id]; + var filecount = rd.ConvertValueToInt64(2, -1L); + var filesizes = rd.ConvertValueToInt64(3, -1L); - yield return new Fileset(e, isFullBackup, m_filesets[e].Value, filecount, filesizes); - } + yield return new Fileset(e, isFullBackup, m_filesets[e].Value, filecount, filesizes); } - } } } - + public void Dispose() { if (m_tablename != null) { - try + try { using (var cmd = m_connection.CreateCommand()) - { - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_tablename)); - } + cmd.ExecuteNonQuery(FormatInvariant(@$"DROP TABLE IF EXISTS ""{m_tablename}"" ")); } - catch {} + catch { } finally { m_tablename = null; } } - + } } - + public IFileSets SelectFileSets(DateTime time, long[] versions) { return new FileSets(this, time, versions); } - + } } diff --git a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs index 4e4948571..c539164f8 100644 --- a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,6 +20,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Data; using System.Collections.Generic; using Duplicati.Library.Utility; @@ -38,22 +39,22 @@ namespace Duplicati.Library.Main.Database { } - public ITemporaryFileset CreateTemporaryFileset(long parentid, System.Data.IDbTransaction transaction) + public ITemporaryFileset CreateTemporaryFileset(long parentid, IDbTransaction transaction) { return new TemporaryFileset(parentid, this, m_connection, transaction); } - public string GetRemoteVolumeNameForFileset(long id, System.Data.IDbTransaction transaction) + public string GetRemoteVolumeNameForFileset(long id, IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand(transaction)) using (var rd = cmd.ExecuteReader(@"SELECT ""B"".""Name"" FROM ""Fileset"" A, ""RemoteVolume"" B WHERE ""A"".""VolumeID"" = ""B"".""ID"" AND ""A"".""ID"" = ? ", id)) if (!rd.Read()) - throw new Exception(string.Format("No remote volume found for fileset with id {0}", id)); + throw new Exception($"No remote volume found for fileset with id {id}"); else return rd.ConvertValueToString(0); } - internal long CountOrphanFiles(System.Data.IDbTransaction transaction) + internal long CountOrphanFiles(IDbTransaction transaction) { using (var cmd = m_connection.CreateCommand(transaction)) using (var rd = cmd.ExecuteReader(@"SELECT COUNT(*) FROM ""FileLookup"" WHERE ""ID"" NOT IN (SELECT DISTINCT ""FileID"" FROM ""FilesetEntry"")")) @@ -70,15 +71,15 @@ namespace Duplicati.Library.Main.Database long RemovedFileSize { get; } void ApplyFilter(Library.Utility.IFilter filter); - void ApplyFilter(Action filtercommand); + void ApplyFilter(Action filtercommand); Tuple ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup); IEnumerable> ListAllDeletedFiles(); } private class TemporaryFileset : ITemporaryFileset { - private readonly System.Data.IDbConnection m_connection; - private readonly System.Data.IDbTransaction m_transaction; + private readonly IDbConnection m_connection; + private readonly IDbTransaction m_transaction; private readonly string m_tablename; private readonly LocalPurgeDatabase m_parentdb; @@ -86,23 +87,23 @@ namespace Duplicati.Library.Main.Database public long RemovedFileCount { get; private set; } public long RemovedFileSize { get; private set; } - public TemporaryFileset(long parentid, LocalPurgeDatabase parentdb, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) + public TemporaryFileset(long parentid, LocalPurgeDatabase parentdb, IDbConnection connection, IDbTransaction transaction) { - this.ParentID = parentid; + ParentID = parentid; m_parentdb = parentdb; m_connection = connection; m_transaction = transaction; m_tablename = "TempDeletedFilesTable-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); using (var cmd = m_connection.CreateCommand(m_transaction)) - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""FileID"" INTEGER PRIMARY KEY) ", m_tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tablename}"" (""FileID"" INTEGER PRIMARY KEY) ")); } - public void ApplyFilter(Action filtercommand) + public void ApplyFilter(Action filtercommand) { using (var cmd = m_connection.CreateCommand(m_transaction)) filtercommand(cmd, ParentID, m_tablename); - + PostFilterChecks(); } @@ -117,8 +118,8 @@ namespace Duplicati.Library.Main.Database var filenamestable = "Filenames-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL) ", filenamestable)); - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"") VALUES (?)", filenamestable); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{filenamestable}"" (""Path"" TEXT NOT NULL) ")); + cmd.CommandText = FormatInvariant($@"INSERT INTO ""{filenamestable}"" (""Path"") VALUES (?)"); cmd.AddParameter(); foreach (var s in p) @@ -127,18 +128,18 @@ namespace Duplicati.Library.Main.Database cmd.ExecuteNonQuery(); } - cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" (""FileID"") SELECT DISTINCT ""A"".""FileID"" FROM ""FilesetEntry"" A, ""File"" B WHERE ""A"".""FilesetID"" = ? AND ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""Path"" IN ""{1}""", m_tablename, filenamestable), ParentID); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", filenamestable)); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{m_tablename}"" (""FileID"") SELECT DISTINCT ""A"".""FileID"" FROM ""FilesetEntry"" A, ""File"" B WHERE ""A"".""FilesetID"" = ? AND ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""Path"" IN ""{filenamestable}"""), ParentID); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{filenamestable}"" ")); } } else { // Do row-wise iteration - object[] values = new object[2]; + var values = new object[2]; using (var cmd = m_connection.CreateCommand(m_transaction)) using (var cmd2 = m_connection.CreateCommand(m_transaction)) { - cmd2.CommandText = string.Format(@"INSERT INTO ""{0}"" (""FileID"") VALUES (?)", m_tablename); + cmd2.CommandText = FormatInvariant($@"INSERT INTO ""{m_tablename}"" (""FileID"") VALUES (?)"); cmd2.AddParameters(1); using (var rd = cmd.ExecuteReader(@"SELECT ""B"".""Path"", ""A"".""FileID"" FROM ""FilesetEntry"" A, ""File"" B WHERE ""A"".""FilesetID"" = ? AND ""A"".""FileID"" = ""B"".""ID"" ", ParentID)) @@ -161,11 +162,11 @@ namespace Duplicati.Library.Main.Database { using (var cmd = m_connection.CreateCommand(m_transaction)) { - RemovedFileCount = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""{0}""", m_tablename), 0); - RemovedFileSize = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""C"".""Length"") FROM ""{0}"" A, ""FileLookup"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" ", m_tablename), 0); - var filesetcount = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetID"" = " + ParentID), 0); + RemovedFileCount = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""{m_tablename}"""), 0); + RemovedFileSize = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""C"".""Length"") FROM ""{m_tablename}"" A, ""FileLookup"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" "), 0); + var filesetcount = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM ""FilesetEntry"" WHERE ""FilesetID"" = {ParentID}"), 0); if (filesetcount == RemovedFileCount) - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Refusing to purge {0} files from fileset with ID {1}, as that would remove the entire fileset.\nTo delete a fileset, use the \"delete\" command.", RemovedFileCount, ParentID), "PurgeWouldRemoveEntireFileset"); + throw new Interface.UserInformationException($"Refusing to purge {RemovedFileCount} files from fileset with ID {ParentID}, as that would remove the entire fileset.\nTo delete a fileset, use the \"delete\" command.", "PurgeWouldRemoveEntireFileset"); } } @@ -176,15 +177,15 @@ namespace Duplicati.Library.Main.Database m_parentdb.UpdateFullBackupStateInFileset(filesetid, isFullBackup); using (var cmd = m_connection.CreateCommand(m_transaction)) - cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") SELECT ?, ""FileID"", ""LastModified"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN ""{0}"" ", m_tablename), filesetid, ParentID); + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") SELECT ?, ""FileID"", ""LastModified"" FROM ""FilesetEntry"" WHERE ""FilesetID"" = ? AND ""FileID"" NOT IN ""{m_tablename}"" "), filesetid, ParentID); return new Tuple(remotevolid, filesetid); } - + public IEnumerable> ListAllDeletedFiles() { using (var cmd = m_connection.CreateCommand(m_transaction)) - using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""B"".""Path"", ""C"".""Length"" FROM ""{0}"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" ", m_tablename))) + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT ""B"".""Path"", ""C"".""Length"" FROM ""{m_tablename}"" A, ""File"" B, ""Blockset"" C WHERE ""A"".""FileID"" = ""B"".""ID"" AND ""B"".""BlocksetID"" = ""C"".""ID"" "))) while (rd.Read()) yield return new KeyValuePair(rd.ConvertValueToString(0), rd.ConvertValueToInt64(1)); } @@ -194,7 +195,7 @@ namespace Duplicati.Library.Main.Database try { using (var cmd = m_connection.CreateCommand(m_transaction)) - cmd.ExecuteNonQuery(@"DROP TABLE IF EXISTS ""{0}""", m_tablename); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tablename}""")); } catch { @@ -202,5 +203,5 @@ namespace Duplicati.Library.Main.Database } } - } + } } diff --git a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs index fd542f8d0..4ff961c9e 100644 --- a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,10 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; +using System.Data; using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; namespace Duplicati.Library.Main.Database { @@ -48,21 +46,21 @@ namespace Duplicati.Library.Main.Database } } - private readonly System.Data.IDbCommand m_insertFileCommand; - private readonly System.Data.IDbCommand m_insertFilesetEntryCommand; - private readonly System.Data.IDbCommand m_insertMetadatasetCommand; - private readonly System.Data.IDbCommand m_insertBlocksetCommand; - private readonly System.Data.IDbCommand m_insertBlocklistHashCommand; - private readonly System.Data.IDbCommand m_updateBlockVolumeCommand; - private readonly System.Data.IDbCommand m_insertTempBlockListHash; - private readonly System.Data.IDbCommand m_insertSmallBlockset; - private readonly System.Data.IDbCommand m_findBlocksetCommand; - private readonly System.Data.IDbCommand m_findMetadatasetCommand; - private readonly System.Data.IDbCommand m_findFilesetCommand; - private readonly System.Data.IDbCommand m_findTempBlockListHashCommand; - private readonly System.Data.IDbCommand m_findHashBlockCommand; - private readonly System.Data.IDbCommand m_insertBlockCommand; - private readonly System.Data.IDbCommand m_insertDuplicateBlockCommand; + private readonly IDbCommand m_insertFileCommand; + private readonly IDbCommand m_insertFilesetEntryCommand; + private readonly IDbCommand m_insertMetadatasetCommand; + private readonly IDbCommand m_insertBlocksetCommand; + private readonly IDbCommand m_insertBlocklistHashCommand; + private readonly IDbCommand m_updateBlockVolumeCommand; + private readonly IDbCommand m_insertTempBlockListHash; + private readonly IDbCommand m_insertSmallBlockset; + private readonly IDbCommand m_findBlocksetCommand; + private readonly IDbCommand m_findMetadatasetCommand; + private readonly IDbCommand m_findFilesetCommand; + private readonly IDbCommand m_findTempBlockListHashCommand; + private readonly IDbCommand m_findHashBlockCommand; + private readonly IDbCommand m_insertBlockCommand; + private readonly IDbCommand m_insertDuplicateBlockCommand; private string m_tempblocklist; private string m_tempsmalllist; @@ -78,13 +76,13 @@ namespace Duplicati.Library.Main.Database // {1} --> BlockHash-Size // {2} --> Temp-Table // {3} --> FullBlocklist-BlockCount [equals ({0} / {1}), if SQLite pays respect to ints] - private const string SELECT_BLOCKLIST_ENTRIES = - @" + private static string SELECT_BLOCKLIST_ENTRIES(long blocksize, long blockhashsize, string temptable, long fullBlockListBlockCount) => + FormatInvariant($@" SELECT ""E"".""BlocksetID"", - ""F"".""Index"" + (""E"".""BlocklistIndex"" * {3}) AS ""FullIndex"", + ""F"".""Index"" + (""E"".""BlocklistIndex"" * {fullBlockListBlockCount}) AS ""FullIndex"", ""F"".""BlockHash"", - MIN({0}, ""E"".""Length"" - ((""F"".""Index"" + (""E"".""BlocklistIndex"" * {3})) * {0})) AS ""BlockSize"", + MIN({blocksize}, ""E"".""Length"" - ((""F"".""Index"" + (""E"".""BlocklistIndex"" * {fullBlockListBlockCount})) * {blocksize})) AS ""BlockSize"", ""E"".""Hash"", ""E"".""BlocklistSize"", ""E"".""BlocklistHash"" @@ -95,7 +93,7 @@ namespace Duplicati.Library.Main.Database SELECT ""A"".""BlocksetID"", ""A"".""Index"" AS ""BlocklistIndex"", - MIN({3} * {1}, (((""B"".""Length"" + {0} - 1) / {0}) - (""A"".""Index"" * ({3}))) * {1}) AS ""BlocklistSize"", + MIN({fullBlockListBlockCount} * {blockhashsize}, (((""B"".""Length"" + {blocksize} - 1) / {blocksize}) - (""A"".""Index"" * ({fullBlockListBlockCount}))) * {blockhashsize}) AS ""BlocklistSize"", ""A"".""Hash"" AS ""BlocklistHash"", ""B"".""Length"" FROM @@ -110,13 +108,13 @@ namespace Duplicati.Library.Main.Database AND ""C"".""BlocklistSize"" = ""D"".""Size"" ) E, - ""{2}"" F + ""{temptable}"" F WHERE ""F"".""BlocklistHash"" = ""E"".""Hash"" ORDER BY ""E"".""BlocksetID"", ""FullIndex"" -"; +"); public LocalRecreateDatabase(LocalDatabase parentdb, Options options) : base(parentdb) @@ -126,105 +124,47 @@ namespace Duplicati.Library.Main.Database using (var cmd = m_connection.CreateCommand()) { - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""BlockListHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""Index"" INTEGER NOT NULL)", m_tempblocklist)); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""Index_{0}"" ON ""{0}"" (""BlockListHash"");", m_tempblocklist)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tempblocklist}"" (""BlockListHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""Index"" INTEGER NOT NULL)")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""Index_{m_tempblocklist}"" ON ""{m_tempblocklist}"" (""BlockListHash"");")); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""FileHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""BlockSize"" INTEGER NOT NULL)", m_tempsmalllist)); - cmd.ExecuteNonQuery(string.Format(@"CREATE UNIQUE INDEX ""Index_File_{0}"" ON ""{0}"" (""FileHash"", ""BlockSize"");", m_tempsmalllist)); - cmd.ExecuteNonQuery(string.Format(@"CREATE UNIQUE INDEX ""Index_Block_{0}"" ON ""{0}"" (""BlockHash"", ""BlockSize"");", m_tempsmalllist)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tempsmalllist}"" (""FileHash"" TEXT NOT NULL, ""BlockHash"" TEXT NOT NULL, ""BlockSize"" INTEGER NOT NULL)")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE UNIQUE INDEX ""Index_File_{m_tempsmalllist}"" ON ""{m_tempsmalllist}"" (""FileHash"", ""BlockSize"");")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE UNIQUE INDEX ""Index_Block_{m_tempsmalllist}"" ON ""{m_tempsmalllist}"" (""BlockHash"", ""BlockSize"");")); } - m_insertFileCommand = m_connection.CreateCommand(); - m_insertFilesetEntryCommand = m_connection.CreateCommand(); - m_insertMetadatasetCommand = m_connection.CreateCommand(); - m_insertBlocksetCommand = m_connection.CreateCommand(); - m_insertBlocklistHashCommand = m_connection.CreateCommand(); - m_updateBlockVolumeCommand = m_connection.CreateCommand(); - m_insertTempBlockListHash = m_connection.CreateCommand(); - m_insertSmallBlockset = m_connection.CreateCommand(); - m_findBlocksetCommand = m_connection.CreateCommand(); - m_findMetadatasetCommand = m_connection.CreateCommand(); - m_findFilesetCommand = m_connection.CreateCommand(); - m_findTempBlockListHashCommand = m_connection.CreateCommand(); - m_findHashBlockCommand = m_connection.CreateCommand(); - m_insertBlockCommand = m_connection.CreateCommand(); - m_insertDuplicateBlockCommand = m_connection.CreateCommand(); - - m_insertFileCommand.CommandText = @"INSERT INTO ""FileLookup"" (""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") VALUES (?,?,?,?); SELECT last_insert_rowid();"; - m_insertFileCommand.AddParameters(4); - - m_insertFilesetEntryCommand.CommandText = @"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (?,?,?)"; - m_insertFilesetEntryCommand.AddParameters(3); - - m_insertMetadatasetCommand.CommandText = @"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();"; - m_insertMetadatasetCommand.AddParameters(1); - - m_insertBlocksetCommand.CommandText = @"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?,?); SELECT last_insert_rowid();"; - m_insertBlocksetCommand.AddParameters(2); - - m_insertBlocklistHashCommand.CommandText = @"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?,?,?)"; - m_insertBlocklistHashCommand.AddParameters(3); - - m_updateBlockVolumeCommand.CommandText = @"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ?"; - m_updateBlockVolumeCommand.AddParameters(3); - - m_insertTempBlockListHash.CommandText = string.Format(@"INSERT INTO ""{0}"" (""BlocklistHash"", ""BlockHash"", ""Index"") VALUES (?,?,?) ", m_tempblocklist); - m_insertTempBlockListHash.AddParameters(3); - - m_insertSmallBlockset.CommandText = string.Format(@"INSERT OR IGNORE INTO ""{0}"" (""FileHash"", ""BlockHash"", ""BlockSize"") VALUES (?,?,?) ", m_tempsmalllist); - m_insertSmallBlockset.AddParameters(3); - - m_findBlocksetCommand.CommandText = @"SELECT ""ID"" FROM ""Blockset"" WHERE ""Length"" = ? AND ""FullHash"" = ? "; - m_findBlocksetCommand.AddParameters(2); - - m_findMetadatasetCommand.CommandText = @"SELECT ""Metadataset"".""ID"" FROM ""Metadataset"",""Blockset"" WHERE ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" AND ""Blockset"".""FullHash"" = ? AND ""Blockset"".""Length"" = ? "; - m_findMetadatasetCommand.AddParameters(2); - - m_findFilesetCommand.CommandText = @"SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? "; - m_findFilesetCommand.AddParameters(4); - - m_findTempBlockListHashCommand.CommandText = string.Format(@"SELECT DISTINCT ""BlockListHash"" FROM ""{0}"" WHERE ""BlockListHash"" = ? ", m_tempblocklist); - m_findTempBlockListHashCommand.AddParameters(1); - - m_findHashBlockCommand.CommandText = @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? "; - m_findHashBlockCommand.AddParameters(2); - - m_insertBlockCommand.CommandText = @"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") VALUES (?,?,?)"; - m_insertBlockCommand.AddParameters(3); - - m_insertDuplicateBlockCommand.CommandText = @"INSERT INTO ""DuplicateBlock"" (""BlockID"", ""VolumeID"") VALUES ((SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?), ?)"; - m_insertDuplicateBlockCommand.AddParameters(3); + m_insertFileCommand = m_connection.CreateCommand(@"INSERT INTO ""FileLookup"" (""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") VALUES (?,?,?,?); SELECT last_insert_rowid();"); + m_insertFilesetEntryCommand = m_connection.CreateCommand(@"INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (?,?,?)"); + m_insertMetadatasetCommand = m_connection.CreateCommand(@"INSERT INTO ""Metadataset"" (""BlocksetID"") VALUES (?); SELECT last_insert_rowid();"); + m_insertBlocksetCommand = m_connection.CreateCommand(@"INSERT INTO ""Blockset"" (""Length"", ""FullHash"") VALUES (?,?); SELECT last_insert_rowid();"); + m_insertBlocklistHashCommand = m_connection.CreateCommand(@"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?,?,?)"); + m_updateBlockVolumeCommand = m_connection.CreateCommand(@"UPDATE ""Block"" SET ""VolumeID"" = ? WHERE ""Hash"" = ? AND ""Size"" = ?"); + m_insertTempBlockListHash = m_connection.CreateCommand(FormatInvariant($@"INSERT INTO ""{m_tempblocklist}"" (""BlocklistHash"", ""BlockHash"", ""Index"") VALUES (?,?,?) ")); + m_insertSmallBlockset = m_connection.CreateCommand(FormatInvariant($@"INSERT OR IGNORE INTO ""{m_tempsmalllist}"" (""FileHash"", ""BlockHash"", ""BlockSize"") VALUES (?,?,?) ")); + m_findBlocksetCommand = m_connection.CreateCommand(@"SELECT ""ID"" FROM ""Blockset"" WHERE ""Length"" = ? AND ""FullHash"" = ? "); + m_findMetadatasetCommand = m_connection.CreateCommand(@"SELECT ""Metadataset"".""ID"" FROM ""Metadataset"",""Blockset"" WHERE ""Metadataset"".""BlocksetID"" = ""Blockset"".""ID"" AND ""Blockset"".""FullHash"" = ? AND ""Blockset"".""Length"" = ? "); + m_findFilesetCommand = m_connection.CreateCommand(@"SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? "); + m_findTempBlockListHashCommand = m_connection.CreateCommand(FormatInvariant($@"SELECT DISTINCT ""BlockListHash"" FROM ""{m_tempblocklist}"" WHERE ""BlockListHash"" = ? ")); + m_findHashBlockCommand = m_connection.CreateCommand(@"SELECT ""VolumeID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ? "); + m_insertBlockCommand = m_connection.CreateCommand(@"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") VALUES (?,?,?)"); + m_insertDuplicateBlockCommand = m_connection.CreateCommand(@"INSERT OR IGNORE INTO ""DuplicateBlock"" (""BlockID"", ""VolumeID"") VALUES ((SELECT ""ID"" FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?), ?)"); } - public void FindMissingBlocklistHashes(long hashsize, long blocksize, System.Data.IDbTransaction transaction) + public void FindMissingBlocklistHashes(long hashsize, long blocksize, IDbTransaction transaction) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; - //Update all small blocklists and matching blocks - var selectSmallBlocks = string.Format(@"SELECT ""BlockHash"", ""BlockSize"" FROM ""{0}""", m_tempsmalllist); + var selectSmallBlocks = FormatInvariant($@"SELECT ""BlockHash"", ""BlockSize"" FROM ""{m_tempsmalllist}"""); - var selectBlockHashes = string.Format( - @"SELECT ""BlockHash"" AS ""FullHash"", ""BlockSize"" AS ""Length"" FROM ( " + - SELECT_BLOCKLIST_ENTRIES + - @" )", - blocksize, - hashsize, - m_tempblocklist, - blocksize / hashsize - ); + var selectBlockHashes = FormatInvariant($@"SELECT ""BlockHash"" AS ""FullHash"", ""BlockSize"" AS ""Length"" FROM ( {SELECT_BLOCKLIST_ENTRIES(blocksize, hashsize, m_tempblocklist, blocksize / hashsize)} )"); var selectAllBlocks = @"SELECT DISTINCT ""FullHash"", ""Length"" FROM (" + selectBlockHashes + " UNION " + selectSmallBlocks + " )"; - var selectNewBlocks = string.Format( - @"SELECT ""FullHash"" AS ""Hash"", ""Length"" AS ""Size"", -1 AS ""VolumeID"" " + - @" FROM (SELECT ""A"".""FullHash"", ""A"".""Length"", CASE WHEN ""B"".""Hash"" IS NULL THEN '' ELSE ""B"".""Hash"" END AS ""Hash"", CASE WHEN ""B"".""Size"" is NULL THEN -1 ELSE ""B"".""Size"" END AS ""Size"" FROM ({0}) A" + - @" LEFT OUTER JOIN ""Block"" B ON ""B"".""Hash"" = ""A"".""FullHash"" AND ""B"".""Size"" = ""A"".""Length"" )" + - @" WHERE ""FullHash"" != ""Hash"" AND ""Length"" != ""Size"" ", - selectAllBlocks - ); + var selectNewBlocks = FormatInvariant($@"SELECT ""FullHash"" AS ""Hash"", ""Length"" AS ""Size"", -1 AS ""VolumeID"" +FROM (SELECT ""A"".""FullHash"", ""A"".""Length"", CASE WHEN ""B"".""Hash"" IS NULL THEN '' ELSE ""B"".""Hash"" END AS ""Hash"", CASE WHEN ""B"".""Size"" is NULL THEN -1 ELSE ""B"".""Size"" END AS ""Size"" FROM ({selectAllBlocks}) A +LEFT OUTER JOIN ""Block"" B ON ""B"".""Hash"" = ""A"".""FullHash"" AND ""B"".""Size"" = ""A"".""Length"" ) +WHERE ""FullHash"" != ""Hash"" AND ""Length"" != ""Size"" "); var insertBlocksCommand = @"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") " + @@ -233,26 +173,14 @@ namespace Duplicati.Library.Main.Database // Insert all known blocks into block table with volumeid = -1 cmd.ExecuteNonQuery(insertBlocksCommand); - var selectBlocklistBlocksetEntries = string.Format( - @"SELECT ""E"".""BlocksetID"" AS ""BlocksetID"", ""D"".""FullIndex"" AS ""Index"", ""F"".""ID"" AS ""BlockID"" FROM ( " + - SELECT_BLOCKLIST_ENTRIES + - @") D, ""BlocklistHash"" E, ""Block"" F, ""Block"" G WHERE ""D"".""BlocksetID"" = ""E"".""BlocksetID"" AND ""D"".""BlocklistHash"" = ""E"".""Hash"" AND ""D"".""BlocklistSize"" = ""G"".""Size"" AND ""D"".""BlocklistHash"" = ""G"".""Hash"" AND ""D"".""Blockhash"" = ""F"".""Hash"" AND ""D"".""BlockSize"" = ""F"".""Size"" ", - + var selectBlocklistBlocksetEntries = FormatInvariant( // TODO: The BlocklistHash join seems to be unnecessary, but the join might be required to work around a from really old versions of Duplicati // this could be used instead //@") D, ""Block"" WHERE ""BlockQuery"".""BlockHash"" = ""Block"".""Hash"" AND ""BlockQuery"".""BlockSize"" = ""Block"".""Size"" "; + $@"SELECT ""E"".""BlocksetID"" AS ""BlocksetID"", ""D"".""FullIndex"" AS ""Index"", ""F"".""ID"" AS ""BlockID"" FROM ( {SELECT_BLOCKLIST_ENTRIES(blocksize, hashsize, m_tempblocklist, blocksize / hashsize)}) D, ""BlocklistHash"" E, ""Block"" F, ""Block"" G WHERE ""D"".""BlocksetID"" = ""E"".""BlocksetID"" AND ""D"".""BlocklistHash"" = ""E"".""Hash"" AND ""D"".""BlocklistSize"" = ""G"".""Size"" AND ""D"".""BlocklistHash"" = ""G"".""Hash"" AND ""D"".""Blockhash"" = ""F"".""Hash"" AND ""D"".""BlockSize"" = ""F"".""Size"" " + ); - blocksize, - hashsize, - m_tempblocklist, - blocksize / hashsize - ); - - var selectBlocksetEntries = string.Format( - @"SELECT ""Blockset"".""ID"" AS ""BlocksetID"", 0 AS ""Index"", ""Block"".""ID"" AS ""BlockID"" FROM ""Blockset"", ""Block"", ""{1}"" S WHERE ""Blockset"".""Fullhash"" = ""S"".""FileHash"" AND ""S"".""BlockHash"" = ""Block"".""Hash"" AND ""S"".""BlockSize"" = ""Block"".""Size"" AND ""Blockset"".""Length"" = ""S"".""BlockSize"" AND ""Blockset"".""Length"" <= {0} ", - blocksize, - m_tempsmalllist - ); + var selectBlocksetEntries = FormatInvariant($@"SELECT ""Blockset"".""ID"" AS ""BlocksetID"", 0 AS ""Index"", ""Block"".""ID"" AS ""BlockID"" FROM ""Blockset"", ""Block"", ""{m_tempsmalllist}"" S WHERE ""Blockset"".""Fullhash"" = ""S"".""FileHash"" AND ""S"".""BlockHash"" = ""Block"".""Hash"" AND ""S"".""BlockSize"" = ""Block"".""Size"" AND ""Blockset"".""Length"" = ""S"".""BlockSize"" AND ""Blockset"".""Length"" <= {blocksize} "); var selectAllBlocksetEntries = selectBlocklistBlocksetEntries + @@ -277,8 +205,8 @@ namespace Duplicati.Library.Main.Database using (var fixcmd = m_connection.CreateCommand()) { - fixcmd.ExecuteNonQuery(string.Format(@"CREATE TABLE ""{0}-Failure"" AS SELECT * FROM ""{0}"" ", m_tempblocklist)); - fixcmd.ExecuteNonQuery(string.Format(@"CREATE TABLE ""{0}-Failure"" AS SELECT * FROM ""{0}"" ", m_tempsmalllist)); + fixcmd.ExecuteNonQuery(FormatInvariant($@"CREATE TABLE ""{m_tempblocklist}-Failure"" AS SELECT * FROM ""{m_tempblocklist}"" ")); + fixcmd.ExecuteNonQuery(FormatInvariant($@"CREATE TABLE ""{m_tempsmalllist}-Failure"" AS SELECT * FROM ""{m_tempsmalllist}"" ")); } throw new Exception("The recreate failed, please create a bug-report from this database and send it to the developers for further analysis"); @@ -302,75 +230,55 @@ namespace Duplicati.Library.Main.Database /// temp small list structure: filehash, blockhash, blocksize: as the small files are defined /// by the fact that they are contained in a single block, blockhash is the same as the filehash, /// and blocksize can vary from 0 to the configured block size for the backup - public void AddBlockAndBlockSetEntryFromTemp(long hashsize, long blocksize, System.Data.IDbTransaction transaction, bool hashOnly = false) + public void AddBlockAndBlockSetEntryFromTemp(long hashsize, long blocksize, IDbTransaction transaction, bool hashOnly = false) { - using (var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.Transaction = transaction; - - var insertBlocksCommand = string.Format( - @"INSERT INTO BLOCK (Hash, Size, VolumeID) " + - @"SELECT DISTINCT BlockHash AS Hash, BlockSize AS Size, -1 AS VolumeID FROM " + - @"(" + - @"SELECT NB.BlockHash, " + - @"MIN({0}, BS.Length - ((NB.""Index"" + (BH.""Index"" * {1})) * {0})) AS BlockSize " + - @"FROM (" + - @" SELECT TBL.BlockListHash, TBL.BlockHash, TBL.""Index"" FROM {2} TBL " + - @" LEFT OUTER JOIN Block B ON (B.Hash = TBL.BlockHash) " + - @" WHERE B.Hash IS NULL " + - @" ) NB " + - @"JOIN BlocklistHash BH ON (BH.Hash = NB.BlocklistHash) " + - @"JOIN Blockset BS ON (BS.ID = BH.Blocksetid) " + - @"", - blocksize, - blocksize / hashsize, - m_tempblocklist - ); + var insertBlocksCommand = FormatInvariant($@"INSERT INTO BLOCK (Hash, Size, VolumeID) +SELECT DISTINCT BlockHash AS Hash, BlockSize AS Size, -1 AS VolumeID FROM +( +SELECT NB.BlockHash, +MIN({blocksize}, BS.Length - ((NB.""Index"" + (BH.""Index"" * {blocksize / hashsize})) * {blocksize})) AS BlockSize +FROM ( + SELECT TBL.BlockListHash, TBL.BlockHash, TBL.""Index"" FROM {m_tempblocklist} TBL + LEFT OUTER JOIN Block B ON (B.Hash = TBL.BlockHash) + WHERE B.Hash IS NULL + ) NB +JOIN BlocklistHash BH ON (BH.Hash = NB.BlocklistHash) +JOIN Blockset BS ON (BS.ID = BH.Blocksetid) "); if (!hashOnly) { - insertBlocksCommand += string.Format( - @" UNION " + - @"" + - @"SELECT TS.BlockHash, TS.BlockSize FROM " + - @"{0} TS " + - @"WHERE NOT EXISTS (SELECT ""X"" FROM Block AS B WHERE " + - @" B.Hash = TS.BlockHash AND " + - @" B.Size = TS.BlockSize) " + - @")", - m_tempsmalllist - ); + insertBlocksCommand += FormatInvariant($@" UNION +SELECT TS.BlockHash, TS.BlockSize FROM +{m_tempsmalllist} TS +WHERE NOT EXISTS (SELECT ""X"" FROM Block AS B WHERE + B.Hash = TS.BlockHash AND + B.Size = TS.BlockSize) +)"); } - var insertBlocksetEntriesCommand = string.Format( - @"INSERT INTO BlocksetEntry (BlocksetID, ""Index"", BlockID) " + - @"SELECT DISTINCT BH.blocksetid, (BH.""Index"" * {0})+TBL.""Index"" as FullIndex, BK.ID AS BlockID " + - @"FROM {1} TBL " + - @" JOIN blocklisthash BH ON (BH.hash = TBL.blocklisthash) " + - @" JOIN block BK ON (BK.Hash = TBL.BlockHash) " + - @" LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlockSetID = BH.BlocksetID AND BE.""Index"" = (BH.""Index"" * {0})+TBL.""Index"") " + - @"WHERE " + - @"BE.BlockSetID IS NULL ", - blocksize / hashsize, - m_tempblocklist - ); + var insertBlocksetEntriesCommand = FormatInvariant($@"INSERT INTO BlocksetEntry (BlocksetID, ""Index"", BlockID) +SELECT DISTINCT BH.blocksetid, (BH.""Index"" * {blocksize / hashsize})+TBL.""Index"" as FullIndex, BK.ID AS BlockID +FROM {m_tempblocklist} TBL + JOIN blocklisthash BH ON (BH.hash = TBL.blocklisthash) + JOIN block BK ON (BK.Hash = TBL.BlockHash) + LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlockSetID = BH.BlocksetID AND BE.""Index"" = (BH.""Index"" * {blocksize / hashsize})+TBL.""Index"") +WHERE +BE.BlockSetID IS NULL "); if (!hashOnly) { - insertBlocksetEntriesCommand += string.Format( - @" UNION " + - @"SELECT BS.ID AS BlocksetID, 0 AS ""Index"", BL.ID AS BlockID " + - @"FROM {1} TS " + - @" JOIN Blockset BS ON (BS.FullHash = TS.FileHash AND " + - @" BS.Length = TS.BlockSize AND " + - @" BS.Length <= {0}) " + - @" JOIN Block BL ON (BL.Hash = TS.BlockHash AND " + - @" BL.Size = TS.BlockSize) " + - @" LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlocksetID = BS.ID AND BE.""Index"" = 0) " + - @"WHERE " + - @"BE.BlocksetID IS NULL ", - blocksize, - m_tempsmalllist - ); + insertBlocksetEntriesCommand += FormatInvariant($@" UNION +SELECT BS.ID AS BlocksetID, 0 AS ""Index"", BL.ID AS BlockID +FROM {m_tempsmalllist} TS + JOIN Blockset BS ON (BS.FullHash = TS.FileHash AND + BS.Length = TS.BlockSize AND + BS.Length <= {blocksize}) + JOIN Block BL ON (BL.Hash = TS.BlockHash AND + BL.Size = TS.BlockSize) + LEFT OUTER JOIN BlocksetEntry BE ON (BE.BlocksetID = BS.ID AND BE.""Index"" = 0) +WHERE +BE.BlocksetID IS NULL "); } try @@ -386,8 +294,8 @@ namespace Duplicati.Library.Main.Database using (var fixcmd = m_connection.CreateCommand()) { - fixcmd.ExecuteNonQuery(string.Format(@"CREATE TABLE ""{0}_Failure"" AS SELECT * FROM ""{0}"" ", m_tempblocklist)); - fixcmd.ExecuteNonQuery(string.Format(@"CREATE TABLE ""{0}_Failure"" AS SELECT * FROM ""{0}"" ", m_tempsmalllist)); + fixcmd.ExecuteNonQuery(FormatInvariant($@"CREATE TABLE ""{m_tempblocklist}_Failure"" AS SELECT * FROM ""{m_tempblocklist}"" ")); + fixcmd.ExecuteNonQuery(FormatInvariant($@"CREATE TABLE ""{m_tempsmalllist}_Failure"" AS SELECT * FROM ""{m_tempsmalllist}"" ")); } throw new Exception("The recreate failed, please create a bug-report from this database and send it to the developers for further analysis"); @@ -395,31 +303,29 @@ namespace Duplicati.Library.Main.Database } } - public void AddDirectoryEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, System.Data.IDbTransaction transaction) + public void AddDirectoryEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, IDbTransaction transaction) { AddEntry(filesetid, pathprefixid, path, time, FOLDER_BLOCKSET_ID, metadataid, transaction); } - public void AddSymlinkEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, System.Data.IDbTransaction transaction) + public void AddSymlinkEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, IDbTransaction transaction) { AddEntry(filesetid, pathprefixid, path, time, SYMLINK_BLOCKSET_ID, metadataid, transaction); } - public void AddFileEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, System.Data.IDbTransaction transaction) + public void AddFileEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, IDbTransaction transaction) { AddEntry(filesetid, pathprefixid, path, time, blocksetid, metadataid, transaction); } - private void AddEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, System.Data.IDbTransaction transaction) + private void AddEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, IDbTransaction transaction) { - var fileid = -1L; - m_findFilesetCommand.Transaction = transaction; m_findFilesetCommand.SetParameterValue(0, pathprefixid); m_findFilesetCommand.SetParameterValue(1, path); m_findFilesetCommand.SetParameterValue(2, blocksetid); m_findFilesetCommand.SetParameterValue(3, metadataid); - fileid = m_findFilesetCommand.ExecuteScalarInt64(-1); + var fileid = m_findFilesetCommand.ExecuteScalarInt64(-1); if (fileid < 0) { @@ -438,7 +344,7 @@ namespace Duplicati.Library.Main.Database m_insertFilesetEntryCommand.ExecuteNonQuery(); } - public long AddMetadataset(string metahash, long metahashsize, IEnumerable metablocklisthashes, long expectedmetablocklisthashes, System.Data.IDbTransaction transaction) + public long AddMetadataset(string metahash, long metahashsize, IEnumerable metablocklisthashes, long expectedmetablocklisthashes, IDbTransaction transaction) { var metadataid = -1L; if (metahash == null) @@ -460,7 +366,7 @@ namespace Duplicati.Library.Main.Database return metadataid; } - public long AddBlockset(string fullhash, long size, IEnumerable blocklisthashes, long expectedblocklisthashes, System.Data.IDbTransaction transaction) + public long AddBlockset(string fullhash, long size, IEnumerable blocklisthashes, long expectedblocklisthashes, IDbTransaction transaction) { m_findBlocksetCommand.Transaction = transaction; m_findBlocksetCommand.SetParameterValue(0, size); @@ -502,7 +408,7 @@ namespace Duplicati.Library.Main.Database return blocksetid; } - public bool UpdateBlock(string hash, long size, long volumeID, System.Data.IDbTransaction transaction, ref bool anyChange) + public bool UpdateBlock(string hash, long size, long volumeID, IDbTransaction transaction, ref bool anyChange) { m_findHashBlockCommand.Transaction = transaction; m_findHashBlockCommand.SetParameterValue(0, hash); @@ -534,7 +440,7 @@ namespace Duplicati.Library.Main.Database m_updateBlockVolumeCommand.SetParameterValue(2, size); var c = m_updateBlockVolumeCommand.ExecuteNonQuery(); if (c != 1) - throw new Exception(string.Format("Failed to update table, found {0} entries for key {1} with size {2}", c, hash, size)); + throw new Exception($"Failed to update table, found {c} entries for key {hash} with size {size}"); return true; } @@ -550,7 +456,7 @@ namespace Duplicati.Library.Main.Database } } - public void AddSmallBlocksetLink(string filehash, string blockhash, long blocksize, System.Data.IDbTransaction transaction) + public void AddSmallBlocksetLink(string filehash, string blockhash, long blocksize, IDbTransaction transaction) { m_insertSmallBlockset.Transaction = transaction; m_insertSmallBlockset.SetParameterValue(0, filehash); @@ -559,7 +465,7 @@ namespace Duplicati.Library.Main.Database m_insertSmallBlockset.ExecuteNonQuery(); } - public bool AddTempBlockListHash(string hash, IEnumerable blocklisthashes, System.Data.IDbTransaction transaction) + public bool AddTempBlockListHash(string hash, IEnumerable blocklisthashes, IDbTransaction transaction) { m_findTempBlockListHashCommand.Transaction = transaction; m_findTempBlockListHashCommand.SetParameterValue(0, hash); @@ -603,30 +509,22 @@ namespace Duplicati.Library.Main.Database var selectCommand = @"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"", ""RemoteVolume"".""ID"" FROM ""RemoteVolume"""; var missingBlocklistEntries = - string.Format( - @"SELECT ""BlocklistHash"".""Hash"" FROM ""BlocklistHash"" LEFT OUTER JOIN ""BlocksetEntry"" ON ""BlocksetEntry"".""Index"" = (""BlocklistHash"".""Index"" * {0}) AND ""BlocksetEntry"".""BlocksetID"" = ""BlocklistHash"".""BlocksetID"" WHERE ""BlocksetEntry"".""BlocksetID"" IS NULL", - blocksize / hashsize - ); + FormatInvariant($@"SELECT ""BlocklistHash"".""Hash"" FROM ""BlocklistHash"" LEFT OUTER JOIN ""BlocksetEntry"" ON ""BlocksetEntry"".""Index"" = (""BlocklistHash"".""Index"" * {blocksize / hashsize}) AND ""BlocksetEntry"".""BlocksetID"" = ""BlocklistHash"".""BlocksetID"" WHERE ""BlocksetEntry"".""BlocksetID"" IS NULL"); var missingBlockInfo = @"SELECT ""VolumeID"" FROM ""Block"" WHERE ""VolumeID"" < 0 AND SIZE > 0"; - var missingBlocklistVolumes = string.Format( - @"SELECT ""VolumeID"" FROM ""Block"", (" + - missingBlocklistEntries + - @") A WHERE ""A"".""Hash"" = ""Block"".""Hash"" " + var missingBlocklistVolumes = FormatInvariant( + $@"SELECT ""VolumeID"" FROM ""Block"", ({missingBlocklistEntries}) A WHERE ""A"".""Hash"" = ""Block"".""Hash"" " ); - var countMissingInformation = string.Format( - @"SELECT COUNT(*) FROM (SELECT DISTINCT ""VolumeID"" FROM ({0} UNION {1}))", - missingBlockInfo, - missingBlocklistVolumes); + var countMissingInformation = FormatInvariant($@"SELECT COUNT(*) FROM (SELECT DISTINCT ""VolumeID"" FROM ({missingBlockInfo} UNION {missingBlocklistVolumes}))"); if (passNo == 0) { // On the first pass, we select all the volumes we know we need, // which may be an empty list - cmd.CommandText = string.Format(selectCommand + @" WHERE ""ID"" IN ({0})", missingBlocklistVolumes); + cmd.CommandText = selectCommand + FormatInvariant($@" WHERE ""ID"" IN ({missingBlocklistVolumes})"); // Reset the list m_proccessedVolumes = new Dictionary(); @@ -645,7 +543,7 @@ namespace Duplicati.Library.Main.Database var mentionedVolumes = @"SELECT DISTINCT ""VolumeID"" FROM ""Block"" "; - cmd.CommandText = string.Format(selectCommand + @" WHERE ""ID"" NOT IN ({0}) AND ""Type"" = ? ", mentionedVolumes); + cmd.CommandText = selectCommand + FormatInvariant($@" WHERE ""ID"" NOT IN ({mentionedVolumes}) AND ""Type"" = ? "); cmd.AddParameter(RemoteVolumeType.Blocks.ToString()); } else @@ -661,7 +559,6 @@ namespace Duplicati.Library.Main.Database { while (rd.Read()) { - var volumeID = rd.GetInt64(3); // Guard against multiple downloads of the same file @@ -677,8 +574,6 @@ namespace Duplicati.Library.Main.Database } } } - - } } @@ -692,39 +587,102 @@ namespace Duplicati.Library.Main.Database // existing ones (as recorded in the DuplicateBlock table) // The second part removes references to the non-present remote files, // and marks the index files that pointed to them, such that they will be removed later on - var sql = $@" + var sql = FormatInvariant($@" CREATE TEMPORARY TABLE ""{tablename}"" AS -SELECT ""A"".""ID"" AS ""BlockID"", ""A"".""VolumeID"" AS ""SourceVolumeID"", ""A"".""State"" AS ""SourceVolumeState"", ""B"".""VolumeID"" AS ""TargetVolumeID"", ""B"".""State"" AS ""TargetVolumeState"" FROM (SELECT ""Block"".""ID"", ""Block"".""VolumeID"", ""Remotevolume"".""State"" FROM ""Block"", ""Remotevolume"" WHERE ""Block"".""VolumeID"" = ""Remotevolume"".""ID"" and ""Remotevolume"".""State"" = '{RemoteVolumeState.Temporary}') A, (SELECT ""DuplicateBlock"".""BlockID"", ""DuplicateBlock"".""VolumeID"", ""Remotevolume"".""State"" FROM ""DuplicateBlock"", ""Remotevolume"" WHERE ""DuplicateBlock"".""VolumeID"" = ""Remotevolume"".""ID"" and ""Remotevolume"".""State"" = '{RemoteVolumeState.Verified}') B WHERE ""A"".""ID"" = ""B"".""BlockID""; +SELECT + ""A"".""ID"" AS ""BlockID"", ""A"".""VolumeID"" AS ""SourceVolumeID"", ""A"".""State"" AS ""SourceVolumeState"", ""B"".""VolumeID"" AS ""TargetVolumeID"", ""B"".""State"" AS ""TargetVolumeState"" +FROM + (SELECT + ""Block"".""ID"", ""Block"".""VolumeID"", ""Remotevolume"".""State"" + FROM ""Block"", ""Remotevolume"" + WHERE ""Block"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Remotevolume"".""State"" = '{RemoteVolumeState.Temporary}' + ) A, + (SELECT + ""DuplicateBlock"".""BlockID"", MIN(""DuplicateBlock"".""VolumeID"") AS ""VolumeID"", ""Remotevolume"".""State"" + FROM ""DuplicateBlock"", ""Remotevolume"" + WHERE ""DuplicateBlock"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Remotevolume"".""State"" = '{RemoteVolumeState.Verified}' + GROUP BY ""DuplicateBlock"".""BlockID"", ""Remotevolume"".""State"" + ) B +WHERE ""A"".""ID"" = ""B"".""BlockID""; -UPDATE ""Block"" SET ""VolumeID"" = (SELECT ""TargetVolumeID"" FROM ""{tablename}"" WHERE ""Block"".""ID"" = ""{tablename}"".""BlockID"") WHERE ""Block"".""ID"" IN (SELECT ""BlockID"" FROM ""{tablename}""); +UPDATE ""Block"" + SET ""VolumeID"" = (SELECT + ""TargetVolumeID"" FROM ""{tablename}"" + WHERE ""Block"".""ID"" = ""{tablename}"".""BlockID"" + ) + WHERE ""Block"".""ID"" IN (SELECT ""BlockID"" FROM ""{tablename}""); + +UPDATE ""DuplicateBlock"" + SET ""VolumeID"" = (SELECT + ""SourceVolumeID"" FROM ""{tablename}"" + WHERE ""DuplicateBlock"".""BlockID"" = ""{tablename}"".""BlockID"" + ) +WHERE + (""DuplicateBlock"".""BlockID"", ""DuplicateBlock"".""VolumeID"") + IN (SELECT ""BlockID"", ""TargetVolumeID"" FROM ""{tablename}""); -UPDATE ""DuplicateBlock"" SET ""VolumeID"" = (SELECT ""SourceVolumeID"" FROM ""{tablename}"" WHERE ""DuplicateBlock"".""BlockID"" = ""{tablename}"".""BlockID"") WHERE ""DuplicateBlock"".""BlockID"" IN (SELECT ""BlockID"" FROM ""{tablename}""); DROP TABLE ""{tablename}""; DELETE FROM ""IndexBlockLink"" WHERE ""BlockVolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = '{RemoteVolumeType.Blocks}' AND ""State"" = '{RemoteVolumeState.Temporary}' AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block"")); DELETE FROM ""DuplicateBlock"" WHERE ""VolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = '{RemoteVolumeType.Blocks}' AND ""State"" = '{RemoteVolumeState.Temporary}' AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block"")); DELETE FROM ""RemoteVolume"" WHERE ""Type"" = '{RemoteVolumeType.Blocks}' AND ""State"" = '{RemoteVolumeState.Temporary}' AND ""ID"" NOT IN (SELECT DISTINCT ""VolumeID"" FROM ""Block""); -"; +"); // We could delete these, but we don't have to, so we keep them around until the next compact is done // UPDATE ""RemoteVolume"" SET ""State"" = ""{3}"" WHERE ""Type"" = ""{5}"" AND ""ID"" NOT IN (SELECT ""IndexVolumeID"" FROM ""IndexBlockLink""); - var countsql = $@"SELECT COUNT(*) FROM ""RemoteVolume"" WHERE ""State"" = '{RemoteVolumeState.Temporary}' AND ""Type"" = '{RemoteVolumeType.Blocks}' "; + var countsql = FormatInvariant($@"SELECT COUNT(*) FROM ""RemoteVolume"" WHERE ""State"" = '{RemoteVolumeState.Temporary}' AND ""Type"" = '{RemoteVolumeType.Blocks}' "); using (var cmd = m_connection.CreateCommand()) { var cnt = cmd.ExecuteScalarInt64(countsql); if (cnt > 0) { - Logging.Log.WriteWarningMessage(LOGTAG, "MissingVolumesDetected", null, "Found {0} missing volumes; attempting to replace blocks from existing volumes", cnt); - cmd.ExecuteNonQuery(sql); - - var cnt2 = cmd.ExecuteScalarInt64(countsql); - Logging.Log.WriteVerboseMessage(LOGTAG, "ReplacedMissingVolumes", "Replaced blocks for {0} missing volumes; there are now {1} missing volumes", cnt, cnt2); + try + { + cmd.ExecuteNonQuery(sql); + var cnt2 = cmd.ExecuteScalarInt64(countsql); + Logging.Log.WriteWarningMessage(LOGTAG, "MissingVolumesDetected", null, "Replaced blocks for {0} missing volumes; there are now {1} missing volumes", cnt, cnt2); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "MissingVolumesDetected", ex, "Found {0} missing volumes; failed while attempting to replace blocks from existing volumes", cnt); + throw; + } } } } + /// + /// Move blocks that are not referenced by any files to DeletedBlock table. + /// + /// Needs to be called after the last FindMissingBlocklistHashes, otherwise the tables are not up to date. + public void CleanupDeletedBlocks(IDbTransaction transaction) + { + // Find out which blocks are deleted and move them into DeletedBlock, so that compact notices these blocks are empty + // Deleted blocks do not appear in the BlocksetEntry and not in the BlocklistHash table + + var tmptablename = "DeletedBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + + using (var tr = new TemporaryTransactionWrapper(m_connection, transaction)) + using (var cmd = m_connection.CreateCommand(tr.Parent)) + { + // 1. Select blocks not used by any file and not as a blocklist into temporary table + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tmptablename}"" + AS SELECT ""Block"".""ID"", ""Block"".""Hash"", ""Block"".""Size"", ""Block"".""VolumeID"" FROM ""Block"" + WHERE ""Block"".""ID"" NOT IN (SELECT ""BlocksetEntry"".""BlockID"" FROM ""BlocksetEntry"") + AND ""Block"".""Hash"" NOT IN (SELECT ""BlocklistHash"".""Hash"" FROM ""BlocklistHash"")")); + // 2. Insert blocks into DeletedBlock table + cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""DeletedBlock"" (""Hash"", ""Size"", ""VolumeID"") SELECT ""Hash"", ""Size"", ""VolumeID"" FROM ""{tmptablename}""")); + // 3. Remove blocks from Block table + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""Block"" WHERE ""ID"" IN (SELECT ""ID"" FROM ""{tmptablename}"")")); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tmptablename}""")); + tr.Commit(); + } + + + } + public override void Dispose() { using (var cmd = m_connection.CreateCommand()) @@ -732,20 +690,32 @@ DELETE FROM ""RemoteVolume"" WHERE ""Type"" = '{RemoteVolumeType.Blocks}' AND "" if (m_tempblocklist != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempblocklist); + cmd.CommandText = FormatInvariant(@$"DROP TABLE IF EXISTS ""{m_tempblocklist}"""); cmd.ExecuteNonQuery(); } - catch { } - finally { m_tempblocklist = null; } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToDropTempBlocklist", ex, "Failed to drop temporary blocklist table {0}", m_tempblocklist); + } + finally + { + m_tempblocklist = null; + } if (m_tempsmalllist != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempsmalllist); + cmd.CommandText = FormatInvariant(@$"DROP TABLE IF EXISTS ""{m_tempsmalllist}"""); cmd.ExecuteNonQuery(); } - catch { } - finally { m_tempsmalllist = null; } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToDropTempSmalllist", ex, "Failed to drop temporary smalllist table {0}", m_tempsmalllist); + } + finally + { + m_tempsmalllist = null; + } } diff --git a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs index d601da186..f66efcd38 100644 --- a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,9 +20,8 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Linq; +using System.Data; using System.Collections.Generic; -using System.Security.Cryptography; using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Database @@ -37,68 +36,100 @@ namespace Duplicati.Library.Main.Database public LocalRepairDatabase(string path) : base(path, "Repair", true) { - + } - + public long GetFilesetIdFromRemotename(string filelist) { - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { var filesetid = cmd.ExecuteScalarInt64(@"SELECT ""Fileset"".""ID"" FROM ""Fileset"",""RemoteVolume"" WHERE ""Fileset"".""VolumeID"" = ""RemoteVolume"".""ID"" AND ""RemoteVolume"".""Name"" = ?", -1, filelist); if (filesetid == -1) - throw new Exception(string.Format("No such remote file: {0}", filelist)); - + throw new Exception($"No such remote file: {filelist}"); + return filesetid; } } + /// + /// Gets a list of filesets that are missing files + /// + /// An optional transaction + /// A list of fileset IDs and timestamps + public IEnumerable> GetFilesetsWithMissingFiles(IDbTransaction transaction) + { + using (var cmd = m_connection.CreateCommand(transaction)) + using (var rd = cmd.ExecuteReader(@"SELECT ID, Timestamp FROM Fileset WHERE ID IN (SELECT FilesetID FROM ""FilesetEntry"" WHERE ""FileID"" NOT IN (SELECT ""ID"" FROM ""FileLookup""))")) + while (rd.Read()) + { + yield return new KeyValuePair( + rd.GetInt64(0), + ParseFromEpochSeconds(rd.GetInt64(1)).ToLocalTime() + ); + } + } + + /// + /// Deletes all fileset entries for a given fileset + /// + /// The fileset ID + /// An optional transaction + /// The number of deleted entries + public int DeleteFilesetEntries(long filesetid, IDbTransaction transaction = null) + { + using (var cmd = m_connection.CreateCommand(transaction)) + return cmd.SetCommandAndParameters(@"DELETE FROM ""FilesetEntry"" WHERE ""FilesetID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetid) + .ExecuteNonQuery(); + } + public interface IBlockSource { string File { get; } long Offset { get; } } - - public interface IBlockWithSources : LocalBackupDatabase.IBlock + + public interface IBlockWithSources : IBlock { IEnumerable Sources { get; } } - - private class BlockWithSources : LocalBackupDatabase.Block, IBlockWithSources + + private class BlockWithSources : Block, IBlockWithSources { private class BlockSource : IBlockSource { public string File { get; private set; } public long Offset { get; private set; } - + public BlockSource(string file, long offset) { this.File = file; this.Offset = offset; - + } } - private readonly System.Data.IDataReader m_rd; + private readonly IDataReader m_rd; public bool Done { get; private set; } - - public BlockWithSources(System.Data.IDataReader rd) + + public BlockWithSources(IDataReader rd) : base(rd.GetString(0), rd.GetInt64(1)) { m_rd = rd; Done = !m_rd.Read(); } - + public IEnumerable Sources { get { if (Done) yield break; - + var cur = new BlockSource(m_rd.GetString(2), m_rd.GetInt64(3)); var file = cur.File; - - while(!Done && cur.File == file) + + while (!Done && cur.File == file) { yield return cur; Done = m_rd.Read(); @@ -108,13 +139,13 @@ namespace Duplicati.Library.Main.Database } } } - + private class RemoteVolume : IRemoteVolume { public string Name { get; private set; } public string Hash { get; private set; } public long Size { get; private set; } - + public RemoteVolume(string name, string hash, long size) { this.Name = name; @@ -122,14 +153,14 @@ namespace Duplicati.Library.Main.Database this.Size = size; } } - + public IEnumerable GetBlockVolumesFromIndexName(string name) { - using(var cmd = m_connection.CreateCommand()) - foreach(var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Name"", ""Hash"", ""Size"" FROM ""RemoteVolume"" WHERE ""ID"" IN (SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"" WHERE ""IndexVolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" = ?))", name)) + using (var cmd = m_connection.CreateCommand()) + foreach (var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Name"", ""Hash"", ""Size"" FROM ""RemoteVolume"" WHERE ""ID"" IN (SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"" WHERE ""IndexVolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" = ?))", name)) yield return new RemoteVolume(rd.GetString(0), rd.ConvertValueToString(1), rd.ConvertValueToInt64(2)); } - + public interface IMissingBlockList : IDisposable { bool SetBlockRestored(string hash, long size); @@ -138,40 +169,36 @@ namespace Duplicati.Library.Main.Database IEnumerable GetFilesetsUsingMissingBlocks(); IEnumerable GetMissingBlockSources(); } - + private class MissingBlockList : IMissingBlockList { - private readonly System.Data.IDbConnection m_connection; + private readonly IDbConnection m_connection; private readonly TemporaryTransactionWrapper m_transaction; - private System.Data.IDbCommand m_insertCommand; + private IDbCommand m_insertCommand; private string m_tablename; private readonly string m_volumename; - - public MissingBlockList(string volumename, System.Data.IDbConnection connection, System.Data.IDbTransaction transaction) + + public MissingBlockList(string volumename, IDbConnection connection, IDbTransaction transaction) { m_connection = connection; m_transaction = new TemporaryTransactionWrapper(m_connection, transaction); m_volumename = volumename; var tablename = "MissingBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) { - cmd.Transaction = m_transaction.Parent; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Restored"" INTEGER NOT NULL) ", tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tablename}"" (""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Restored"" INTEGER NOT NULL) ")); m_tablename = tablename; - - var blockCount = cmd.ExecuteNonQuery(string.Format(@"INSERT INTO ""{0}"" (""Hash"", ""Size"", ""Restored"") SELECT DISTINCT ""Block"".""Hash"", ""Block"".""Size"", 0 AS ""Restored"" FROM ""Block"",""Remotevolume"" WHERE ""Block"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Remotevolume"".""Name"" = ? ", m_tablename), volumename); - if (blockCount == 0) - throw new Exception(string.Format("Unexpected empty block volume: {0}", volumename)); - cmd.ExecuteNonQuery(string.Format(@"CREATE UNIQUE INDEX ""{0}-Ix"" ON ""{0}"" (""Hash"", ""Size"", ""Restored"")", tablename)); + var blockCount = cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{m_tablename}"" (""Hash"", ""Size"", ""Restored"") SELECT DISTINCT ""Block"".""Hash"", ""Block"".""Size"", 0 AS ""Restored"" FROM ""Block"",""Remotevolume"" WHERE ""Block"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Remotevolume"".""Name"" = ? "), volumename); + if (blockCount == 0) + throw new Exception($"Unexpected empty block volume: {0}"); + + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE UNIQUE INDEX ""{tablename}-Ix"" ON ""{tablename}"" (""Hash"", ""Size"", ""Restored"")")); } - m_insertCommand = m_connection.CreateCommand(); - m_insertCommand.Transaction = m_transaction.Parent; - m_insertCommand.CommandText = string.Format(@"UPDATE ""{0}"" SET ""Restored"" = ? WHERE ""Hash"" = ? AND ""Size"" = ? AND ""Restored"" = ? ", tablename); - m_insertCommand.AddParameters(4); + m_insertCommand = m_connection.CreateCommand(m_transaction.Parent, FormatInvariant($@"UPDATE ""{tablename}"" SET ""Restored"" = ? WHERE ""Hash"" = ? AND ""Size"" = ? AND ""Restored"" = ? ")); } - + public bool SetBlockRestored(string hash, long size) { m_insertCommand.SetParameterValue(0, 1); @@ -180,78 +207,75 @@ namespace Duplicati.Library.Main.Database m_insertCommand.SetParameterValue(3, 0); return m_insertCommand.ExecuteNonQuery() == 1; } - + public IEnumerable GetSourceFilesWithBlocks(long blocksize) { - using(var cmd = m_connection.CreateCommand(m_transaction.Parent)) - using(var rd = cmd.ExecuteReader(string.Format(@"SELECT DISTINCT ""{0}"".""Hash"", ""{0}"".""Size"", ""File"".""Path"", ""BlocksetEntry"".""Index"" * {1} FROM ""{0}"", ""Block"", ""BlocksetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""Block"".""ID"" = ""BlocksetEntry"".""BlockID"" AND ""{0}"".""Hash"" = ""Block"".""Hash"" AND ""{0}"".""Size"" = ""Block"".""Size"" AND ""{0}"".""Restored"" = ? ", m_tablename, blocksize), 0)) - if (rd.Read()) - { - var bs = new BlockWithSources(rd); - while (!bs.Done) - yield return bs; - } + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT DISTINCT ""{m_tablename}"".""Hash"", ""{m_tablename}"".""Size"", ""File"".""Path"", ""BlocksetEntry"".""Index"" * {blocksize} FROM ""{m_tablename}"", ""Block"", ""BlocksetEntry"", ""File"" WHERE ""File"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""Block"".""ID"" = ""BlocksetEntry"".""BlockID"" AND ""{m_tablename}"".""Hash"" = ""Block"".""Hash"" AND ""{m_tablename}"".""Size"" = ""Block"".""Size"" AND ""{m_tablename}"".""Restored"" = ? "), 0)) + if (rd.Read()) + { + var bs = new BlockWithSources(rd); + while (!bs.Done) + yield return bs; + } } - + public IEnumerable> GetMissingBlocks() { - using(var cmd = m_connection.CreateCommand(m_transaction.Parent)) - foreach(var rd in cmd.ExecuteReaderEnumerable(string.Format(@"SELECT ""{0}"".""Hash"", ""{0}"".""Size"" FROM ""{0}"" WHERE ""{0}"".""Restored"" = ? ", m_tablename), 0)) + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) + foreach (var rd in cmd.ExecuteReaderEnumerable(FormatInvariant($@"SELECT ""{m_tablename}"".""Hash"", ""{m_tablename}"".""Size"" FROM ""{m_tablename}"" WHERE ""{m_tablename}"".""Restored"" = ? "), 0)) yield return new KeyValuePair(rd.ConvertValueToString(0), rd.ConvertValueToInt64(1)); } - + public IEnumerable GetFilesetsUsingMissingBlocks() { - var blocks = @"SELECT DISTINCT ""FileLookup"".""ID"" AS ID FROM ""{0}"", ""Block"", ""Blockset"", ""BlocksetEntry"", ""FileLookup"" WHERE ""Block"".""Hash"" = ""{0}"".""Hash"" AND ""Block"".""Size"" = ""{0}"".""Size"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""BlocksetEntry"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FileLookup"".""BlocksetID"" = ""Blockset"".""ID"" "; - var blocklists = @"SELECT DISTINCT ""FileLookup"".""ID"" AS ID FROM ""{0}"", ""Block"", ""Blockset"", ""BlocklistHash"", ""FileLookup"" WHERE ""Block"".""Hash"" = ""{0}"".""Hash"" AND ""Block"".""Size"" = ""{0}"".""Size"" AND ""BlocklistHash"".""Hash"" = ""Block"".""Hash"" AND ""BlocklistHash"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FileLookup"".""BlocksetID"" = ""Blockset"".""ID"" "; - - var cmdtxt = @"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"" FROM ""RemoteVolume"", ""FilesetEntry"", ""Fileset"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" = ""FilesetEntry"".""FilesetID"" AND ""RemoteVolume"".""Type"" = ? AND ""FilesetEntry"".""FileID"" IN (SELECT DISTINCT ""ID"" FROM ( " + blocks + " UNION " + blocklists + " ))"; - - using(var cmd = m_connection.CreateCommand(m_transaction.Parent)) - foreach(var rd in cmd.ExecuteReaderEnumerable(string.Format(cmdtxt, m_tablename), RemoteVolumeType.Files.ToString())) + var blocks = FormatInvariant($@"SELECT DISTINCT ""FileLookup"".""ID"" AS ID FROM ""{m_tablename}"", ""Block"", ""Blockset"", ""BlocksetEntry"", ""FileLookup"" WHERE ""Block"".""Hash"" = ""{m_tablename}"".""Hash"" AND ""Block"".""Size"" = ""{m_tablename}"".""Size"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""BlocksetEntry"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FileLookup"".""BlocksetID"" = ""Blockset"".""ID"" "); + var blocklists = FormatInvariant($@"SELECT DISTINCT ""FileLookup"".""ID"" AS ID FROM ""{m_tablename}"", ""Block"", ""Blockset"", ""BlocklistHash"", ""FileLookup"" WHERE ""Block"".""Hash"" = ""{m_tablename}"".""Hash"" AND ""Block"".""Size"" = ""{m_tablename}"".""Size"" AND ""BlocklistHash"".""Hash"" = ""Block"".""Hash"" AND ""BlocklistHash"".""BlocksetID"" = ""Blockset"".""ID"" AND ""FileLookup"".""BlocksetID"" = ""Blockset"".""ID"" "); + var cmdtxt = FormatInvariant($@"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"" FROM ""RemoteVolume"", ""FilesetEntry"", ""Fileset"" WHERE ""RemoteVolume"".""ID"" = ""Fileset"".""VolumeID"" AND ""Fileset"".""ID"" = ""FilesetEntry"".""FilesetID"" AND ""RemoteVolume"".""Type"" = ? AND ""FilesetEntry"".""FileID"" IN (SELECT DISTINCT ""ID"" FROM ( {blocks} UNION {blocklists} ))"); + + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) + foreach (var rd in cmd.ExecuteReaderEnumerable(cmdtxt, RemoteVolumeType.Files.ToString())) yield return new RemoteVolume(rd.GetString(0), rd.ConvertValueToString(1), rd.ConvertValueToInt64(2)); } - + public IEnumerable GetMissingBlockSources() { - using(var cmd = m_connection.CreateCommand(m_transaction.Parent)) - foreach(var rd in cmd.ExecuteReaderEnumerable(string.Format(@"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"" FROM ""RemoteVolume"", ""Block"", ""{0}"" WHERE ""Block"".""Hash"" = ""{0}"".""Hash"" AND ""Block"".""Size"" = ""{0}"".""Size"" AND ""Block"".""VolumeID"" = ""RemoteVolume"".""ID"" AND ""Remotevolume"".""Name"" != ? ", m_tablename), m_volumename)) + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) + foreach (var rd in cmd.ExecuteReaderEnumerable(FormatInvariant($@"SELECT DISTINCT ""RemoteVolume"".""Name"", ""RemoteVolume"".""Hash"", ""RemoteVolume"".""Size"" FROM ""RemoteVolume"", ""Block"", ""{m_tablename}"" WHERE ""Block"".""Hash"" = ""{m_tablename}"".""Hash"" AND ""Block"".""Size"" = ""{m_tablename}"".""Size"" AND ""Block"".""VolumeID"" = ""RemoteVolume"".""ID"" AND ""Remotevolume"".""Name"" != ? "), m_volumename)) yield return new RemoteVolume(rd.GetString(0), rd.ConvertValueToString(1), rd.ConvertValueToInt64(2)); } - + public void Dispose() { if (m_tablename != null) { try { - using(var cmd = m_connection.CreateCommand(m_transaction.Parent)) - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_transaction)); + using (var cmd = m_connection.CreateCommand(m_transaction.Parent)) + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tablename}"" "), m_transaction); } catch { } finally { m_tablename = null; } } - + if (m_insertCommand != null) try { m_insertCommand.Dispose(); } - catch {} + catch { } finally { m_insertCommand = null; } } } - - public IMissingBlockList CreateBlockList(string volumename, System.Data.IDbTransaction transaction = null) + + public IMissingBlockList CreateBlockList(string volumename, IDbTransaction transaction = null) { return new MissingBlockList(volumename, m_connection, transaction); } public void FixDuplicateMetahash() { - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand(tr)) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand(tr)) { - cmd.Transaction = tr; - - var sql_count = + var sql_count = @"SELECT COUNT(*) FROM (" + @" SELECT DISTINCT c1 FROM (" + @"SELECT COUNT(*) AS ""C1"" FROM (SELECT DISTINCT ""BlocksetID"" FROM ""Metadataset"") UNION SELECT COUNT(*) AS ""C1"" FROM ""Metadataset"" " + @@ -265,41 +289,40 @@ namespace Duplicati.Library.Main.Database var tablename = "TmpFile-" + Guid.NewGuid().ToString("N"); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS SELECT * FROM ""File""", tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tablename}"" AS SELECT * FROM ""File""")); var sql = @"SELECT ""A"".""ID"", ""B"".""BlocksetID"" FROM (SELECT MIN(""ID"") AS ""ID"", COUNT(""ID"") AS ""Duplicates"" FROM ""Metadataset"" GROUP BY ""BlocksetID"") ""A"", ""Metadataset"" ""B"" WHERE ""A"".""Duplicates"" > 1 AND ""A"".""ID"" = ""B"".""ID"""; - using(var c2 = m_connection.CreateCommand(tr)) + using (var c2 = m_connection.CreateCommand(tr)) { - c2.CommandText = string.Format(@"UPDATE ""{0}"" SET ""MetadataID"" = ? WHERE ""MetadataID"" IN (SELECT ""ID"" FROM ""Metadataset"" WHERE ""BlocksetID"" = ?)", tablename); + c2.CommandText = FormatInvariant($@"UPDATE ""{tablename}"" SET ""MetadataID"" = ? WHERE ""MetadataID"" IN (SELECT ""ID"" FROM ""Metadataset"" WHERE ""BlocksetID"" = ?)"); c2.CommandText += @"; DELETE FROM ""Metadataset"" WHERE ""BlocksetID"" = ? AND ""ID"" != ?"; - using(var rd = cmd.ExecuteReader(sql)) + using (var rd = cmd.ExecuteReader(sql)) while (rd.Read()) c2.ExecuteNonQuery(null, rd.GetValue(0), rd.GetValue(1), rd.GetValue(1), rd.GetValue(0)); } - sql = string.Format(@"SELECT ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""Entries"" FROM ( - SELECT MIN(""ID"") AS ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Entries"" FROM ""{0}"" GROUP BY ""Path"", ""BlocksetID"", ""MetadataID"") - WHERE ""Entries"" > 1 ORDER BY ""ID""", tablename); + sql = FormatInvariant($@"SELECT ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""Entries"" FROM ( + SELECT MIN(""ID"") AS ""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Entries"" FROM ""{tablename}"" GROUP BY ""Path"", ""BlocksetID"", ""MetadataID"") + WHERE ""Entries"" > 1 ORDER BY ""ID"""); - using(var c2 = m_connection.CreateCommand()) + using (var c2 = m_connection.CreateCommand(tr)) { - c2.Transaction = tr; - c2.CommandText = string.Format(@"UPDATE ""FilesetEntry"" SET ""FileID"" = ? WHERE ""FileID"" IN (SELECT ""ID"" FROM ""{0}"" WHERE ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ?)", tablename); - c2.CommandText += string.Format(@"; DELETE FROM ""{0}"" WHERE ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""ID"" != ?", tablename); - foreach(var rd in cmd.ExecuteReaderEnumerable(sql)) + c2.CommandText = FormatInvariant($@"UPDATE ""FilesetEntry"" SET ""FileID"" = ? WHERE ""FileID"" IN (SELECT ""ID"" FROM ""{tablename}"" WHERE ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ?)"); + c2.CommandText += FormatInvariant($@"; DELETE FROM ""{tablename}"" WHERE ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""ID"" != ?"); + foreach (var rd in cmd.ExecuteReaderEnumerable(sql)) c2.ExecuteNonQuery(null, rd.GetValue(0), rd.GetValue(1), rd.GetValue(2), rd.GetValue(3), rd.GetValue(1), rd.GetValue(2), rd.GetValue(3), rd.GetValue(0)); } - cmd.ExecuteNonQuery(string.Format(@"DELETE FROM ""FileLookup"" WHERE ""ID"" NOT IN (SELECT ""ID"" FROM ""{0}"") ", tablename)); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}-Ix"" ON ""{0}"" (""ID"", ""MetadataID"")", tablename)); - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""FileLookup"" SET ""MetadataID"" = (SELECT ""MetadataID"" FROM ""{0}"" A WHERE ""A"".""ID"" = ""FileLookup"".""ID"") ", tablename)); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE ""{0}"" ", tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"DELETE FROM ""FileLookup"" WHERE ""ID"" NOT IN (SELECT ""ID"" FROM ""{tablename}"") ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{tablename}-Ix"" ON ""{tablename}"" (""ID"", ""MetadataID"")")); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""FileLookup"" SET ""MetadataID"" = (SELECT ""MetadataID"" FROM ""{tablename}"" A WHERE ""A"".""ID"" = ""FileLookup"".""ID"") ")); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE ""{tablename}"" ")); cmd.CommandText = sql_count; x = cmd.ExecuteScalarInt64(0); if (x > 1) - throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate metadatahashes!", "DuplicateHashesRepairFailed"); + throw new Interface.UserInformationException("Repair failed, there are still duplicate metadatahashes!", "DuplicateHashesRepairFailed"); Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateMetadataHashesFixed", "Duplicate metadatahashes repaired succesfully"); tr.Commit(); @@ -309,8 +332,8 @@ namespace Duplicati.Library.Main.Database public void FixDuplicateFileentries() { - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand(tr)) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand(tr)) { var sql_count = @"SELECT COUNT(*) FROM (SELECT ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Duplicates"" FROM ""FileLookup"" GROUP BY ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") WHERE ""Duplicates"" > 1"; @@ -323,18 +346,18 @@ namespace Duplicati.Library.Main.Database SELECT MIN(""ID"") AS ""ID"", ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"", COUNT(*) as ""Entries"" FROM ""FileLookup"" GROUP BY ""PrefixID"", ""Path"", ""BlocksetID"", ""MetadataID"") WHERE ""Entries"" > 1 ORDER BY ""ID"""; - using(var c2 = m_connection.CreateCommand(tr)) + using (var c2 = m_connection.CreateCommand(tr)) { c2.CommandText = @"UPDATE ""FilesetEntry"" SET ""FileID"" = ? WHERE ""FileID"" IN (SELECT ""ID"" FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ?)"; c2.CommandText += @"; DELETE FROM ""FileLookup"" WHERE ""PrefixID"" = ? AND ""Path"" = ? AND ""BlocksetID"" = ? AND ""MetadataID"" = ? AND ""ID"" != ?"; - foreach(var rd in cmd.ExecuteReaderEnumerable(sql)) + foreach (var rd in cmd.ExecuteReaderEnumerable(sql)) c2.ExecuteNonQuery(null, rd.GetValue(0), rd.GetValue(1), rd.GetValue(2), rd.GetValue(3), rd.GetValue(4), rd.GetValue(1), rd.GetValue(2), rd.GetValue(3), rd.GetValue(4), rd.GetValue(0)); } cmd.CommandText = sql_count; x = cmd.ExecuteScalarInt64(0); if (x > 1) - throw new Duplicati.Library.Interface.UserInformationException("Repair failed, there are still duplicate file entries!", "DuplicateFilesRepairFailed"); + throw new Interface.UserInformationException("Repair failed, there are still duplicate file entries!", "DuplicateFilesRepairFailed"); Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateFileEntriesFixed", "Duplicate file entries repaired succesfully"); tr.Commit(); @@ -347,31 +370,31 @@ namespace Duplicati.Library.Main.Database { var blocklistbuffer = new byte[blocksize]; - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand(tr)) - using(var blockhasher = HashFactory.CreateHasher(blockhashalgorithm)) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand(tr)) + using (var blockhasher = HashFactory.CreateHasher(blockhashalgorithm)) { var hashsize = blockhasher.HashSize / 8; - var sql = string.Format(@"SELECT * FROM (SELECT ""N"".""BlocksetID"", ((""N"".""BlockCount"" + {0} - 1) / {0}) AS ""BlocklistHashCountExpected"", CASE WHEN ""G"".""BlocklistHashCount"" IS NULL THEN 0 ELSE ""G"".""BlocklistHashCount"" END AS ""BlocklistHashCountActual"" FROM (SELECT ""BlocksetID"", COUNT(*) AS ""BlockCount"" FROM ""BlocksetEntry"" GROUP BY ""BlocksetID"") ""N"" LEFT OUTER JOIN (SELECT ""BlocksetID"", COUNT(*) AS ""BlocklistHashCount"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"") ""G"" ON ""N"".""BlocksetID"" = ""G"".""BlocksetID"" WHERE ""N"".""BlockCount"" > 1) WHERE ""BlocklistHashCountExpected"" != ""BlocklistHashCountActual""", blocksize / hashsize); + var sql = FormatInvariant($@"SELECT * FROM (SELECT ""N"".""BlocksetID"", ((""N"".""BlockCount"" + {blocksize / hashsize} - 1) / {blocksize / hashsize}) AS ""BlocklistHashCountExpected"", CASE WHEN ""G"".""BlocklistHashCount"" IS NULL THEN 0 ELSE ""G"".""BlocklistHashCount"" END AS ""BlocklistHashCountActual"" FROM (SELECT ""BlocksetID"", COUNT(*) AS ""BlockCount"" FROM ""BlocksetEntry"" GROUP BY ""BlocksetID"") ""N"" LEFT OUTER JOIN (SELECT ""BlocksetID"", COUNT(*) AS ""BlocklistHashCount"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"") ""G"" ON ""N"".""BlocksetID"" = ""G"".""BlocksetID"" WHERE ""N"".""BlockCount"" > 1) WHERE ""BlocklistHashCountExpected"" != ""BlocklistHashCountActual"""); var countsql = @"SELECT COUNT(*) FROM (" + sql + @")"; var itemswithnoblocklisthash = cmd.ExecuteScalarInt64(countsql, 0); if (itemswithnoblocklisthash != 0) { Logging.Log.WriteInformationMessage(LOGTAG, "MissingBlocklistHashes", "Found {0} missing blocklisthash entries, repairing", itemswithnoblocklisthash); - using(var c2 = m_connection.CreateCommand(tr)) - using(var c3 = m_connection.CreateCommand(tr)) - using(var c4 = m_connection.CreateCommand(tr)) - using(var c5 = m_connection.CreateCommand(tr)) - using(var c6 = m_connection.CreateCommand(tr)) + using (var c2 = m_connection.CreateCommand(tr)) + using (var c3 = m_connection.CreateCommand(tr)) + using (var c4 = m_connection.CreateCommand(tr)) + using (var c5 = m_connection.CreateCommand(tr)) + using (var c6 = m_connection.CreateCommand(tr)) { - c3.CommandText = @"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?) "; - c4.CommandText = @"SELECT COUNT(*) FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"; - c5.CommandText = @"SELECT ""ID"" FROM ""DeletedBlock"" WHERE ""Hash"" = ? AND ""Size"" = ? AND ""VolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND (""State"" = ? OR ""State"" = ?))"; - c6.CommandText = @"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") SELECT ""Hash"", ""Size"", ""VolumeID"" FROM ""DeletedBlock"" WHERE ""ID"" = ? LIMIT 1; DELETE FROM ""DeletedBlock"" WHERE ""ID"" = ?;"; + c3.SetCommandAndParameters(@"INSERT INTO ""BlocklistHash"" (""BlocksetID"", ""Index"", ""Hash"") VALUES (?, ?, ?) "); + c4.SetCommandAndParameters(@"SELECT COUNT(*) FROM ""Block"" WHERE ""Hash"" = ? AND ""Size"" = ?"); + c5.SetCommandAndParameters(@"SELECT ""ID"" FROM ""DeletedBlock"" WHERE ""Hash"" = ? AND ""Size"" = ? AND ""VolumeID"" IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND (""State"" = ? OR ""State"" = ?))"); + c6.SetCommandAndParameters(@"INSERT INTO ""Block"" (""Hash"", ""Size"", ""VolumeID"") SELECT ""Hash"", ""Size"", ""VolumeID"" FROM ""DeletedBlock"" WHERE ""ID"" = ? LIMIT 1; DELETE FROM ""DeletedBlock"" WHERE ""ID"" = ?;"); - foreach(var e in cmd.ExecuteReaderEnumerable(sql)) + foreach (var e in cmd.ExecuteReaderEnumerable(sql)) { var blocksetid = e.ConvertValueToInt64(0); var ix = 0L; @@ -379,7 +402,7 @@ namespace Duplicati.Library.Main.Database c2.ExecuteNonQuery(@"DELETE FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ?", blocksetid); - foreach(var h in c2.ExecuteReaderEnumerable(@"SELECT ""A"".""Hash"" FROM ""Block"" ""A"", ""BlocksetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""BlockID"" AND ""B"".""BlocksetID"" = ? ORDER BY ""B"".""Index""", blocksetid)) + foreach (var h in c2.ExecuteReaderEnumerable(@"SELECT ""A"".""Hash"" FROM ""Block"" ""A"", ""BlocksetEntry"" ""B"" WHERE ""A"".""ID"" = ""B"".""BlockID"" AND ""B"".""BlocksetID"" = ? ORDER BY ""B"".""Index""", blocksetid)) { var tmp = Convert.FromBase64String(h.GetString(0)); if (blocklistbuffer.Length - blocklistoffset < tmp.Length) @@ -391,12 +414,12 @@ namespace Duplicati.Library.Main.Database { var c = c5.ExecuteScalarInt64(null, -1, blkey, blocklistoffset, RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString()); if (c <= 0) - throw new Exception(string.Format("Missing block for blocklisthash: {0}", blkey)); + throw new Exception($"Missing block for blocklisthash: {blkey}"); else { var rc = c6.ExecuteNonQuery(null, c, c); if (rc != 2) - throw new Exception(string.Format("Unexpected update count: {0}", rc)); + throw new Exception($"Unexpected update count: {rc}"); } } @@ -420,12 +443,12 @@ namespace Duplicati.Library.Main.Database { var c = c5.ExecuteScalarInt64(null, -1, blkeyfinal, blocklistoffset, RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString()); if (c == 0) - throw new Exception(string.Format("Missing block for blocklisthash: {0}", blkeyfinal)); + throw new Exception($"Missing block for blocklisthash: {blkeyfinal}"); else { var rc = c6.ExecuteNonQuery(null, c, c); if (rc != 2) - throw new Exception(string.Format("Unexpected update count: {0}", rc)); + throw new Exception($"Unexpected update count: {rc}"); } } @@ -438,7 +461,7 @@ namespace Duplicati.Library.Main.Database itemswithnoblocklisthash = cmd.ExecuteScalarInt64(countsql, 0); if (itemswithnoblocklisthash != 0) - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, after repair {0} blocklisthashes were missing", itemswithnoblocklisthash), "MissingBlocklistHashesRepairFailed"); + throw new Interface.UserInformationException($"Failed to repair, after repair {itemswithnoblocklisthash} blocklisthashes were missing", "MissingBlocklistHashesRepairFailed"); Logging.Log.WriteInformationMessage(LOGTAG, "MissingBlocklisthashesRepaired", "Missing blocklisthashes repaired succesfully"); tr.Commit(); @@ -449,8 +472,8 @@ namespace Duplicati.Library.Main.Database public void FixDuplicateBlocklistHashes(long blocksize, long hashsize) { - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand(tr)) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand(tr)) { var dup_sql = @"SELECT * FROM (SELECT ""BlocksetID"", ""Index"", COUNT(*) AS ""EC"" FROM ""BlocklistHash"" GROUP BY ""BlocksetID"", ""Index"") WHERE ""EC"" > 1"; @@ -463,15 +486,15 @@ namespace Duplicati.Library.Main.Database var unique_count = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM (SELECT DISTINCT ""BlocksetID"", ""Index"" FROM ""BlocklistHash"")", 0); - using(var c2 = m_connection.CreateCommand(tr)) + using (var c2 = m_connection.CreateCommand(tr)) { c2.CommandText = @"DELETE FROM ""BlocklistHash"" WHERE rowid IN (SELECT rowid FROM ""BlocklistHash"" WHERE ""BlocksetID"" = ? AND ""Index"" = ? LIMIT ?)"; - foreach(var rd in cmd.ExecuteReaderEnumerable(dup_sql)) + foreach (var rd in cmd.ExecuteReaderEnumerable(dup_sql)) { var expected = rd.GetInt32(2) - 1; var actual = c2.ExecuteNonQuery(null, rd.GetValue(0), rd.GetValue(1), expected); if (actual != expected) - throw new Exception(string.Format("Unexpected number of results after fix, got: {0}, expected: {1}", actual, expected)); + throw new Exception($"Unexpected number of results after fix, got: {actual}, expected: {expected}"); } } @@ -483,15 +506,15 @@ namespace Duplicati.Library.Main.Database var real_count = cmd.ExecuteScalarInt64(@"SELECT Count(*) FROM ""BlocklistHash""", 0); if (real_count != unique_count) - throw new Duplicati.Library.Interface.UserInformationException(string.Format("Failed to repair, result should have been {0} blocklist hashes, but result was {1} blocklist hashes", unique_count, real_count), "DuplicateBlocklistHashesRepairFailed"); + throw new Interface.UserInformationException($"Failed to repair, result should have been {unique_count} blocklist hashes, but result was {real_count} blocklist hashes", "DuplicateBlocklistHashesRepairFailed"); try { VerifyConsistency(blocksize, hashsize, true, tr); } - catch(Exception ex) + catch (Exception ex) { - throw new Duplicati.Library.Interface.UserInformationException("Repaired blocklisthashes, but the database was broken afterwards, rolled back changes", "DuplicateBlocklistHashesRepairFailed", ex); + throw new Interface.UserInformationException("Repaired blocklisthashes, but the database was broken afterwards, rolled back changes", "DuplicateBlocklistHashesRepairFailed", ex); } Logging.Log.WriteInformationMessage(LOGTAG, "DuplicateBlocklistHashesRepaired", "Duplicate blocklisthashes repaired succesfully"); @@ -502,16 +525,16 @@ namespace Duplicati.Library.Main.Database public void CheckAllBlocksAreInVolume(string filename, IEnumerable> blocks) { - using(var tr = m_connection.BeginTransaction()) - using(var cmd = m_connection.CreateCommand(tr)) + using (var tr = m_connection.BeginTransaction()) + using (var cmd = m_connection.CreateCommand(tr)) { var tablename = "ProbeBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL)", tablename)); - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Hash"", ""Size"") VALUES (?, ?)", tablename); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tablename}"" (""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL)")); + cmd.CommandText = FormatInvariant($@"INSERT INTO ""{tablename}"" (""Hash"", ""Size"") VALUES (?, ?)"); cmd.AddParameters(2); - foreach(var kp in blocks) + foreach (var kp in blocks) { cmd.SetParameterValue(0, kp.Key); cmd.SetParameterValue(1, kp.Value); @@ -519,20 +542,20 @@ namespace Duplicati.Library.Main.Database } var id = cmd.ExecuteScalarInt64(@"SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" = ?", -1, filename); - var aliens = cmd.ExecuteScalarInt64(string.Format(@"SELECT COUNT(*) FROM (SELECT ""A"".""VolumeID"" FROM ""{0}"" B LEFT OUTER JOIN ""Block"" A ON ""A"".""Hash"" = ""B"".""Hash"" AND ""A"".""Size"" = ""B"".""Size"") WHERE ""VolumeID"" != ? ", tablename), 0, id); + var aliens = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT COUNT(*) FROM (SELECT ""A"".""VolumeID"" FROM ""{tablename}"" B LEFT OUTER JOIN ""Block"" A ON ""A"".""Hash"" = ""B"".""Hash"" AND ""A"".""Size"" = ""B"".""Size"") WHERE ""VolumeID"" != ? "), 0, id); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", tablename)); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{tablename}"" ")); if (aliens != 0) - throw new Exception(string.Format("Not all blocks were found in {0}", filename)); + throw new Exception($"Not all blocks were found in {filename}"); } } public void CheckBlocklistCorrect(string hash, long length, IEnumerable blocklist, long blocksize, long blockhashlength) { - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { - var query = string.Format(@" + var query = FormatInvariant($@" SELECT ""C"".""Hash"", ""C"".""Size"" @@ -558,30 +581,50 @@ WHERE AND ""A"".""BlockID"" = ""C"".""ID"" AND - ""A"".""Index"" >= ""B"".""BlocklistHashIndex"" * ({0} / {1}) + ""A"".""Index"" >= ""B"".""BlocklistHashIndex"" * ({blocksize} / {blockhashlength}) AND - ""A"".""Index"" < (""B"".""BlocklistHashIndex"" + 1) * ({0} / {1}) + ""A"".""Index"" < (""B"".""BlocklistHashIndex"" + 1) * ({blocksize} / {blockhashlength}) ORDER BY ""A"".""Index"" -" - ,blocksize, blockhashlength); +"); using (var en = blocklist.GetEnumerator()) { foreach (var r in cmd.ExecuteReaderEnumerable(query, hash, length)) { if (!en.MoveNext()) - throw new Exception(string.Format("Too few entries in source blocklist with hash {0}", hash)); + throw new Exception($"Too few entries in source blocklist with hash {hash}"); if (en.Current != r.GetString(0)) - throw new Exception(string.Format("Mismatch in blocklist with hash {0}", hash)); + throw new Exception($"Mismatch in blocklist with hash {hash}"); } if (en.MoveNext()) - throw new Exception(string.Format("Too many source blocklist entries in {0}", hash)); + throw new Exception($"Too many source blocklist entries in {hash}"); } } } + + public IEnumerable MissingLocalFilesets() + { + using (var cmd = m_connection.CreateCommand()) + foreach (var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Name"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND ""State"" NOT IN (?, ?) AND ""ID"" NOT IN (SELECT ""VolumeID"" FROM ""Fileset"")", RemoteVolumeType.Files.ToString(), RemoteVolumeState.Deleting.ToString(), RemoteVolumeState.Deleted.ToString())) + yield return rd.ConvertValueToString(0); + } + + public IEnumerable<(long FilesetID, DateTime Timestamp, bool IsFull)> MissingRemoteFilesets() + { + using (var cmd = m_connection.CreateCommand()) + foreach (var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""ID"", ""Timestamp"", ""IsFullBackup"" FROM ""Fileset"" WHERE ""VolumeID"" NOT IN (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND ""State"" NOT IN (?, ?))", RemoteVolumeType.Files.ToString(), RemoteVolumeState.Deleting.ToString(), RemoteVolumeState.Deleted.ToString())) + yield return (rd.ConvertValueToInt64(0), ParseFromEpochSeconds(rd.ConvertValueToInt64(1)), rd.ConvertValueToInt64(2) == BackupType.FULL_BACKUP); + } + + public IEnumerable EmptyIndexFiles() + { + using (var cmd = m_connection.CreateCommand()) + foreach (var rd in cmd.ExecuteReaderEnumerable(@"SELECT ""Name"", ""Hash"", ""Size"" FROM ""RemoteVolume"" WHERE ""Type"" = ? AND ""State"" IN (?, ?, ?) AND ""ID"" NOT IN (SELECT ""IndexVolumeId"" FROM ""IndexBlockLink"")", RemoteVolumeType.Index.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) + yield return new RemoteVolume(rd.ConvertValueToString(0), rd.ConvertValueToString(1), rd.ConvertValueToInt64(2)); + } } } diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index 3da07a950..e2f268f23 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -1,29 +1,31 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in +// +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; +using Duplicati.Library.Main.Operation.Restore; using Duplicati.Library.Main.Volumes; using Duplicati.Library.Utility; @@ -37,15 +39,20 @@ namespace Duplicati.Library.Main.Database private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalRestoreDatabase)); protected readonly string m_temptabsetguid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + /// + /// The name of the temporary table in the database, which is used to store the list of files to restore. + /// protected string m_tempfiletable; protected string m_tempblocktable; + protected ConcurrentBag m_connection_pool = []; + protected string m_latestblocktable; protected string m_fileprogtable; protected string m_totalprogtable; protected string m_filesnewlydonetable; protected DateTime m_restoreTime; - public DateTime RestoreTime { get { return m_restoreTime; } } + public DateTime RestoreTime { get { return m_restoreTime; } } public LocalRestoreDatabase(string path) : this(new LocalDatabase(path, "Restore", false)) @@ -75,33 +82,33 @@ namespace Duplicati.Library.Main.Database /// public void CreateProgressTracker(bool createFilesNewlyDoneTracker) { - m_fileprogtable = "FileProgress-" + this.m_temptabsetguid; - m_totalprogtable = "TotalProgress-" + this.m_temptabsetguid; - m_filesnewlydonetable = createFilesNewlyDoneTracker ? "FilesNewlyDone-" + this.m_temptabsetguid : null; + m_fileprogtable = "FileProgress-" + m_temptabsetguid; + m_totalprogtable = "TotalProgress-" + m_temptabsetguid; + m_filesnewlydonetable = createFilesNewlyDoneTracker ? "FilesNewlyDone-" + m_temptabsetguid : null; using (var cmd = m_connection.CreateCommand()) { // How to handle METADATA? - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_fileprogtable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (" - + @" ""FileId"" INTEGER PRIMARY KEY " - + @", ""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL " - + @", ""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL " - + @")", m_fileprogtable)); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_fileprogtable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_fileprogtable}"" ( +""FileId"" INTEGER PRIMARY KEY, +""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL, +""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL +)")); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_totalprogtable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (" - + @" ""TotalFiles"" INTEGER NOT NULL, ""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL " - + @", ""FilesFullyRestored"" INTEGER NOT NULL, ""FilesPartiallyRestored"" INTEGER NOT NULL " - + @", ""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL " - + @")", m_totalprogtable)); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_totalprogtable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_totalprogtable}"" ( +""TotalFiles"" INTEGER NOT NULL, ""TotalBlocks"" INTEGER NOT NULL, ""TotalSize"" INTEGER NOT NULL, +""FilesFullyRestored"" INTEGER NOT NULL, ""FilesPartiallyRestored"" INTEGER NOT NULL, +""BlocksRestored"" INTEGER NOT NULL, ""SizeRestored"" INTEGER NOT NULL +)")); if (createFilesNewlyDoneTracker) { - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_filesnewlydonetable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (" - + @" ""ID"" INTEGER PRIMARY KEY " - + @")", m_filesnewlydonetable)); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_filesnewlydonetable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_filesnewlydonetable}"" ( +""ID"" INTEGER PRIMARY KEY +)")); } try @@ -111,31 +118,28 @@ namespace Duplicati.Library.Main.Database // Note: FilesNewlyDone is NOT initialized, as in initialization nothing is really new. string sql; - sql = string.Format( - @" INSERT INTO ""{0}"" (""FileId"", ""TotalBlocks"", ""TotalSize"", ""BlocksRestored"", ""SizeRestored"") " - + @" SELECT ""F"".""ID"", IFNULL(COUNT(""B"".""ID""), 0), IFNULL(SUM(""B"".""Size""), 0)" - + @" , IFNULL(COUNT(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""ID"" ELSE NULL END), 0) " - + @" , IFNULL(SUM(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""Size"" ELSE 0 END), 0) " - + @" FROM ""{1}"" ""F"" LEFT JOIN ""{2}"" ""B""" // allow for empty files (no data Blocks) - + @" ON ""B"".""FileID"" = ""F"".""ID"" " - + @" WHERE ""B"".""Metadata"" IS NOT 1 " // Use "IS" because of Left Join - + @" GROUP BY ""F"".""ID"" " - , m_fileprogtable, m_tempfiletable, m_tempblocktable); + // We use a LEFT JOIN to allow for empty files (no data Blocks) + sql = FormatInvariant($@" INSERT INTO ""{m_fileprogtable}"" (""FileId"", ""TotalBlocks"", ""TotalSize"", ""BlocksRestored"", ""SizeRestored"") +SELECT ""F"".""ID"", IFNULL(COUNT(""B"".""ID""), 0), IFNULL(SUM(""B"".""Size""), 0) + , IFNULL(COUNT(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""ID"" ELSE NULL END), 0) + , IFNULL(SUM(CASE ""B"".""Restored"" WHEN 1 THEN ""B"".""Size"" ELSE 0 END), 0) + FROM ""{m_tempfiletable}"" ""F"" LEFT JOIN ""{m_tempblocktable}"" ""B"" + ON ""B"".""FileID"" = ""F"".""ID"" + WHERE ""B"".""Metadata"" IS NOT 1 + GROUP BY ""F"".""ID"" "); // Will be one row per file. cmd.ExecuteNonQuery(sql); - sql = string.Format( - @"INSERT INTO ""{0}"" (" - + @" ""TotalFiles"", ""TotalBlocks"", ""TotalSize"" " - + @", ""FilesFullyRestored"", ""FilesPartiallyRestored"", ""BlocksRestored"", ""SizeRestored""" - + @" ) " - + @" SELECT IFNULL(COUNT(""P"".""FileId""), 0), IFNULL(SUM(""P"".""TotalBlocks""), 0), IFNULL(SUM(""P"".""TotalSize""), 0) " - + @" , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" = ""P"".""TotalBlocks"" THEN 1 ELSE NULL END), 0) " - + @" , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" BETWEEN 1 AND ""P"".""TotalBlocks"" - 1 THEN 1 ELSE NULL END), 0) " - + @" , IFNULL(SUM(""P"".""BlocksRestored""), 0), IFNULL(SUM(""P"".""SizeRestored""), 0) " - + @" FROM ""{1}"" ""P"" " - , m_totalprogtable, m_fileprogtable); + sql = FormatInvariant($@"INSERT INTO ""{m_totalprogtable}"" ( + ""TotalFiles"", ""TotalBlocks"", ""TotalSize"" +, ""FilesFullyRestored"", ""FilesPartiallyRestored"", ""BlocksRestored"", ""SizeRestored"" + ) + SELECT IFNULL(COUNT(""P"".""FileId""), 0), IFNULL(SUM(""P"".""TotalBlocks""), 0), IFNULL(SUM(""P"".""TotalSize""), 0) + , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" = ""P"".""TotalBlocks"" THEN 1 ELSE NULL END), 0) + , IFNULL(COUNT(CASE WHEN ""P"".""BlocksRestored"" BETWEEN 1 AND ""P"".""TotalBlocks"" - 1 THEN 1 ELSE NULL END), 0) + , IFNULL(SUM(""P"".""BlocksRestored""), 0), IFNULL(SUM(""P"".""SizeRestored""), 0) + FROM ""{m_fileprogtable}"" ""P"" "); // Will result in a single line (no support to also track metadata) cmd.ExecuteNonQuery(sql); @@ -147,44 +151,38 @@ namespace Duplicati.Library.Main.Database // with additional INSERT and DELETE triggers. // A trigger to update the file-stat entry each time a block changes restoration state. - sql = string.Format( - @"CREATE TEMPORARY TRIGGER ""TrackRestoredBlocks_{1}"" AFTER UPDATE OF ""Restored"" ON ""{1}"" " - + @" WHEN OLD.""Restored"" != NEW.""Restored"" AND NEW.""Metadata"" = 0 " - + @" BEGIN UPDATE ""{0}"" " - + @" SET ""BlocksRestored"" = ""{0}"".""BlocksRestored"" + (NEW.""Restored"" - OLD.""Restored"") " - + @" , ""SizeRestored"" = ""{0}"".""SizeRestored"" + ((NEW.""Restored"" - OLD.""Restored"") * NEW.Size) " - + @" WHERE ""{0}"".""FileId"" = NEW.""FileID"" " - + @" ; END " - , m_fileprogtable, m_tempblocktable); + sql = FormatInvariant($@"CREATE TEMPORARY TRIGGER ""TrackRestoredBlocks_{m_tempblocktable}"" AFTER UPDATE OF ""Restored"" ON ""{m_tempblocktable}"" +WHEN OLD.""Restored"" != NEW.""Restored"" AND NEW.""Metadata"" = 0 +BEGIN UPDATE ""{m_fileprogtable}"" + SET ""BlocksRestored"" = ""{m_fileprogtable}"".""BlocksRestored"" + (NEW.""Restored"" - OLD.""Restored"") + , ""SizeRestored"" = ""{m_fileprogtable}"".""SizeRestored"" + ((NEW.""Restored"" - OLD.""Restored"") * NEW.Size) + WHERE ""{m_fileprogtable}"".""FileId"" = NEW.""FileID"" +; END "); cmd.ExecuteNonQuery(sql); // A trigger to update total stats each time a file stat changed (nested triggering by file-stats) - sql = string.Format( - @"CREATE TEMPORARY TRIGGER ""UpdateTotalStats_{1}"" AFTER UPDATE ON ""{1}"" " - + @" BEGIN UPDATE ""{0}"" " - + @" SET ""FilesFullyRestored"" = ""{0}"".""FilesFullyRestored"" " - + @" + (CASE WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" THEN 1 ELSE 0 END) " - + @" - (CASE WHEN OLD.""BlocksRestored"" = OLD.""TotalBlocks"" THEN 1 ELSE 0 END) " - + @" , ""FilesPartiallyRestored"" = ""{0}"".""FilesPartiallyRestored"" " - + @" + (CASE WHEN NEW.""BlocksRestored"" BETWEEN 1 AND NEW.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) " - + @" - (CASE WHEN OLD.""BlocksRestored"" BETWEEN 1 AND OLD.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) " - + @" , ""BlocksRestored"" = ""{0}"".""BlocksRestored"" + NEW.""BlocksRestored"" - OLD.""BlocksRestored"" " // simple delta - + @" , ""SizeRestored"" = ""{0}"".""SizeRestored"" + NEW.""SizeRestored"" - OLD.""SizeRestored"" " // simple delta - + @" ; END " - , m_totalprogtable, m_fileprogtable); + sql = FormatInvariant($@"CREATE TEMPORARY TRIGGER ""UpdateTotalStats_{m_fileprogtable}"" AFTER UPDATE ON ""{m_fileprogtable}"" +BEGIN UPDATE ""{m_totalprogtable}"" + SET ""FilesFullyRestored"" = ""{m_totalprogtable}"".""FilesFullyRestored"" + + (CASE WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" THEN 1 ELSE 0 END) + - (CASE WHEN OLD.""BlocksRestored"" = OLD.""TotalBlocks"" THEN 1 ELSE 0 END) + , ""FilesPartiallyRestored"" = ""{m_totalprogtable}"".""FilesPartiallyRestored"" + + (CASE WHEN NEW.""BlocksRestored"" BETWEEN 1 AND NEW.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) + - (CASE WHEN OLD.""BlocksRestored"" BETWEEN 1 AND OLD.""TotalBlocks"" - 1 THEN 1 ELSE 0 END) + , ""BlocksRestored"" = ""{m_totalprogtable}"".""BlocksRestored"" + NEW.""BlocksRestored"" - OLD.""BlocksRestored"" + , ""SizeRestored"" = ""{m_totalprogtable}"".""SizeRestored"" + NEW.""SizeRestored"" - OLD.""SizeRestored"" +; END"); cmd.ExecuteNonQuery(sql); if (createFilesNewlyDoneTracker) { // A trigger checking if a file is done (all blocks restored in file-stat) (nested triggering by file-stats) - sql = string.Format( - @"CREATE TEMPORARY TRIGGER ""UpdateFilesNewlyDone_{1}"" AFTER UPDATE OF ""BlocksRestored"", ""TotalBlocks"" ON ""{1}"" " - + @" WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" " - + @" BEGIN " - + @" INSERT OR IGNORE INTO ""{0}"" (""ID"") VALUES (NEW.""FileId""); " - + @" END " - , m_filesnewlydonetable, m_fileprogtable); + sql = FormatInvariant($@"CREATE TEMPORARY TRIGGER ""UpdateFilesNewlyDone_{m_fileprogtable}"" AFTER UPDATE OF ""BlocksRestored"", ""TotalBlocks"" ON ""{m_fileprogtable}"" +WHEN NEW.""BlocksRestored"" = NEW.""TotalBlocks"" +BEGIN + INSERT OR IGNORE INTO ""{m_filesnewlydonetable}"" (""ID"") VALUES (NEW.""FileId""); +END "); cmd.ExecuteNonQuery(sql); } @@ -193,101 +191,99 @@ namespace Duplicati.Library.Main.Database { m_fileprogtable = null; m_totalprogtable = null; - Logging.Log.WriteWarningMessage(LOGTAG, "ProgressTrackerSetupError", ex, "Failed to set up progress tracking tables"); + Logging.Log.WriteWarningMessage(LOGTAG, "ProgressTrackerSetupError", ex, "Failed to set up progress tracking tables"); throw; } } - } - - public Tuple PrepareRestoreFilelist(DateTime restoretime, long[] versions, Library.Utility.IFilter filter) + public Tuple PrepareRestoreFilelist(DateTime restoretime, long[] versions, IFilter filter) { - var guid = Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); + m_tempfiletable = "Fileset-" + m_temptabsetguid; + m_tempblocktable = "Blocks-" + m_temptabsetguid; - m_tempfiletable = "Fileset-" + guid; - m_tempblocktable = "Blocks-" + guid; - - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { var filesetIds = GetFilesetIDs(Library.Utility.Utility.NormalizeDateTime(restoretime), versions).ToList(); - while(filesetIds.Count > 0) + while (filesetIds.Count > 0) { var filesetId = filesetIds[0]; filesetIds.RemoveAt(0); - - m_restoreTime = ParseFromEpochSeconds(cmd.ExecuteScalarInt64(@"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?", 0, filesetId)); - - var ix = this.FilesetTimes.Select((value, index) => new { value.Key, index }) + + m_restoreTime = ParseFromEpochSeconds(cmd + .SetCommandAndParameters(@"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetId) + .ExecuteScalarInt64(0)); + + var ix = FilesetTimes.Select((value, index) => new { value.Key, index }) .Where(n => n.Key == filesetId) .Select(pair => pair.index + 1) .FirstOrDefault() - 1; - + Logging.Log.WriteInformationMessage(LOGTAG, "SearchingBackup", "Searching backup {0} ({1}) ...", ix, m_restoreTime); - + cmd.Parameters.Clear(); - - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_tempfiletable)); - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_tempblocktable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY, ""Path"" TEXT NOT NULL, ""BlocksetID"" INTEGER NOT NULL, ""MetadataID"" INTEGER NOT NULL, ""TargetPath"" TEXT NULL, ""DataVerified"" BOOLEAN NOT NULL) ", m_tempfiletable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""ID"" INTEGER PRIMARY KEY, ""FileID"" INTEGER NOT NULL, ""Index"" INTEGER NOT NULL, ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Restored"" BOOLEAN NOT NULL, ""Metadata"" BOOLEAN NOT NULL)", m_tempblocktable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}_Index"" ON ""{0}"" (""TargetPath"")", m_tempfiletable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}_HashSizeIndex"" ON ""{0}"" (""Hash"", ""Size"")", m_tempblocktable)); - // better suited to speed up commit on UpdateBlocks - cmd.ExecuteNonQuery(string.Format(@"CREATE INDEX ""{0}_FileIdIndexIndex"" ON ""{0}"" (""FileId"", ""Index"")", m_tempblocktable)); + + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tempfiletable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tempblocktable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tempfiletable}"" (""ID"" INTEGER PRIMARY KEY, ""Path"" TEXT NOT NULL, ""BlocksetID"" INTEGER NOT NULL, ""MetadataID"" INTEGER NOT NULL, ""TargetPath"" TEXT NULL, ""DataVerified"" BOOLEAN NOT NULL, ""LatestBlocksetId"" INTEGER, ""LocalSourceExists"" BOOLEAN) ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tempblocktable}"" (""ID"" INTEGER PRIMARY KEY, ""FileID"" INTEGER NOT NULL, ""Index"" INTEGER NOT NULL, ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Restored"" BOOLEAN NOT NULL, ""Metadata"" BOOLEAN NOT NULL, ""VolumeID"" INTEGER NOT NULL, ""BlockID"" INTEGER NOT NULL)")); // TODO: Optimize to use the path prefix if (filter == null || filter.Empty) { // Simple case, restore everything - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? ", m_tempfiletable); - cmd.AddParameter(filesetId); - cmd.ExecuteNonQuery(); + cmd.SetCommandAndParameters(FormatInvariant($@"INSERT INTO ""{m_tempfiletable}"" (""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""ID"", ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = @FilesetId")) + .SetParameterValue("@FilesetId", filesetId) + .ExecuteNonQuery(); } - else if (Library.Utility.Utility.IsFSCaseSensitive && filter is FilterExpression expression && expression.Type == Duplicati.Library.Utility.FilterType.Simple) + else if (Library.Utility.Utility.IsFSCaseSensitive && filter is FilterExpression expression && expression.Type == FilterType.Simple) { // If we get a list of filenames, the lookup table is faster // unfortunately we cannot do this if the filesystem is case sensitive as // SQLite only supports ASCII compares - using(var tr = m_connection.BeginTransaction()) + using (var tr = m_connection.BeginTransaction()) { var p = expression.GetSimpleList(); - var m_filenamestable = "Filenames-" + guid; + var m_filenamestable = "Filenames-" + m_temptabsetguid; cmd.Transaction = tr; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" (""Path"" TEXT NOT NULL) ", m_filenamestable)); - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"") VALUES (?)", m_filenamestable); - cmd.AddParameter(); - - foreach(var s in p) + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_filenamestable}"" (""Path"" TEXT NOT NULL) ")); + cmd.SetCommandAndParameters(FormatInvariant($@"INSERT INTO ""{m_filenamestable}"" (""Path"") VALUES (@Path)")); + + foreach (var s in p) { - cmd.SetParameterValue(0, s); - cmd.ExecuteNonQuery(); + cmd.SetParameterValue("@Path", s) + .ExecuteNonQuery(); } - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = ? AND ""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{1}"") ", m_tempfiletable, m_filenamestable); - cmd.SetParameterValue(0, filesetId); - var c = cmd.ExecuteNonQuery(); - + var c = cmd.SetCommandAndParameters(FormatInvariant($@"INSERT INTO ""{m_tempfiletable}"" (""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") SELECT ""File"".""ID"", ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"", 0 FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetEntry"".""FilesetID"" = @FilesetId AND ""Path"" IN (SELECT DISTINCT ""Path"" FROM ""{m_filenamestable}"") ")) + .SetParameterValue("@FilesetId", filesetId) + .ExecuteNonQuery(); + cmd.Parameters.Clear(); - + if (c != p.Length && c != 0) { var sb = new StringBuilder(); sb.AppendLine(); - - using(var rd = cmd.ExecuteReader(string.Format(@"SELECT ""Path"" FROM ""{0}"" WHERE ""Path"" NOT IN (SELECT ""Path"" FROM ""{1}"")", m_filenamestable, m_tempfiletable))) + + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT ""Path"" FROM ""{m_filenamestable}"" WHERE ""Path"" NOT IN (SELECT ""Path"" FROM ""{m_tempfiletable}"")"))) while (rd.Read()) sb.AppendLine(rd.GetValue(0).ToString()); - - var actualrestoretime = ParseFromEpochSeconds(cmd.ExecuteScalarInt64(@"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = ?", 0, filesetId)); + + var actualrestoretime = ParseFromEpochSeconds( + cmd.SetCommandAndParameters(@"SELECT ""Timestamp"" FROM ""Fileset"" WHERE ""ID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetId) + .ExecuteScalarInt64(0)); + Logging.Log.WriteWarningMessage(LOGTAG, "FilesNotFoundInBackupList", null, "{0} File(s) were not found in list of files for backup at {1}, will not be restored: {2}", p.Length - c, actualrestoretime.ToLocalTime(), sb); cmd.Parameters.Clear(); } - - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", m_filenamestable)); - - using(new Logging.Timer(LOGTAG, "CommitPrepareFileset", "CommitPrepareFileset")) + + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_filenamestable}"" ")); + + using (new Logging.Timer(LOGTAG, "CommitPrepareFileset", "CommitPrepareFileset")) tr.Commit(); } } @@ -295,34 +291,32 @@ namespace Duplicati.Library.Main.Database { // Restore but filter elements based on the filter expression // If this is too slow, we could add a special handler for wildcard searches too - cmd.CommandText = @"SELECT ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetID"" = ?"; - cmd.AddParameter(filesetId); - - object[] values = new object[3]; - using(var cmd2 = m_connection.CreateCommand()) - { - cmd2.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") VALUES (?,?,?,0)", m_tempfiletable); - cmd2.AddParameter(); - cmd2.AddParameter(); - cmd2.AddParameter(); - - using(var rd = cmd.ExecuteReader()) - while (rd.Read()) + cmd.SetCommandAndParameters(@"SELECT ""File"".""ID"", ""File"".""Path"", ""File"".""BlocksetID"", ""File"".""MetadataID"" FROM ""File"", ""FilesetEntry"" WHERE ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""FilesetID"" = @FilesetId") + .SetParameterValue("@FilesetId", filesetId); + + object[] values = new object[4]; + using (var cmd2 = m_connection.CreateCommand(FormatInvariant($@"INSERT INTO ""{m_tempfiletable}"" (""ID"", ""Path"", ""BlocksetID"", ""MetadataID"", ""DataVerified"") VALUES (@ID, @Path, @BlocksetID, @MetadataID, 0)"))) + using (var rd = cmd.ExecuteReader()) + while (rd.Read()) + { + rd.GetValues(values); + if (values[1] != null && values[1] != DBNull.Value && FilterExpression.Matches(filter, values[1].ToString())) { - rd.GetValues(values); - if (values[0] != null && values[0] != DBNull.Value && Library.Utility.FilterExpression.Matches(filter, values[0].ToString())) - { - cmd2.SetParameterValue(0, values[0]); - cmd2.SetParameterValue(1, values[1]); - cmd2.SetParameterValue(2, values[2]); - cmd2.ExecuteNonQuery(); - } + cmd2.SetParameterValue("@ID", values[0]) + .SetParameterValue("@Path", values[1]) + .SetParameterValue("@BlocksetID", values[2]) + .SetParameterValue("@MetadataID", values[3]) + .ExecuteNonQuery(); } - } + } } - - - using(var rd = cmd.ExecuteReader(string.Format(@"SELECT COUNT(DISTINCT ""{0}"".""Path""), SUM(""Blockset"".""Length"") FROM ""{0}"", ""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" ", m_tempfiletable))) + + //creating indexes after insertion is much faster + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempfiletable}_ID"" ON ""{m_tempfiletable}"" (""ID"")")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempfiletable}_TargetPath"" ON ""{m_tempfiletable}"" (""TargetPath"")")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempfiletable}_Path"" ON ""{m_tempfiletable}"" (""Path"")")); + + using (var rd = cmd.ExecuteReader(FormatInvariant($@"SELECT COUNT(DISTINCT ""{m_tempfiletable}"".""Path""), SUM(""Blockset"".""Length"") FROM ""{m_tempfiletable}"", ""Blockset"" WHERE ""{m_tempfiletable}"".""BlocksetID"" = ""Blockset"".""ID"" "))) { var filecount = 0L; var filesize = 0L; @@ -338,10 +332,10 @@ namespace Duplicati.Library.Main.Database Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreTargetFileCount", "Needs to restore {0} files ({1})", filecount, Library.Utility.Utility.FormatSizeString(filesize)); return new Tuple(filecount, filesize); } - } + } } } - + return new Tuple(0, 0); } @@ -349,11 +343,11 @@ namespace Duplicati.Library.Main.Database { using (var cmd = m_connection.CreateCommand()) { - cmd.CommandText = string.Format(@"SELECT ""Path"" FROM ""{0}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1", m_tempfiletable); + cmd.CommandText = FormatInvariant($@"SELECT ""Path"" FROM ""{m_tempfiletable}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1"); var v0 = cmd.ExecuteScalar(); if (v0 == null || v0 == DBNull.Value) return null; - + return v0.ToString(); } } @@ -362,29 +356,27 @@ namespace Duplicati.Library.Main.Database { using (var cmd = m_connection.CreateCommand()) { - cmd.CommandText = string.Format(@"SELECT ""Path"" FROM ""{0}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1", m_tempfiletable); + cmd.CommandText = FormatInvariant($@"SELECT ""Path"" FROM ""{m_tempfiletable}"" ORDER BY LENGTH(""Path"") DESC LIMIT 1"); var v0 = cmd.ExecuteScalar(); - string maxpath = ""; + var maxpath = ""; if (v0 != null && v0 != DBNull.Value) maxpath = v0.ToString(); var dirsep = Util.GuessDirSeparator(maxpath); - cmd.CommandText = string.Format(@"SELECT COUNT(*) FROM ""{0}""", m_tempfiletable); + cmd.CommandText = FormatInvariant($@"SELECT COUNT(*) FROM ""{m_tempfiletable}"""); var filecount = cmd.ExecuteScalarInt64(-1); - long foundfiles = -1; + var foundfiles = -1L; //TODO: Handle FS case-sensitive? - cmd.CommandText = string.Format(@"SELECT COUNT(*) FROM ""{0}"" WHERE SUBSTR(""Path"", 1, ?) = ?", m_tempfiletable); - cmd.AddParameter(); - cmd.AddParameter(); + cmd.SetCommandAndParameters(FormatInvariant($@"SELECT COUNT(*) FROM ""{m_tempfiletable}"" WHERE SUBSTR(""Path"", 1, @PrefixLength) = @Prefix")); while (filecount != foundfiles && maxpath.Length > 0) { var mp = Util.AppendDirSeparator(maxpath, dirsep); - cmd.SetParameterValue(0, mp.Length); - cmd.SetParameterValue(1, mp); - foundfiles = cmd.ExecuteScalarInt64(-1); + foundfiles = cmd.SetParameterValue("@PrefixLength", mp.Length) + .SetParameterValue("@Prefix", mp) + .ExecuteScalarInt64(-1); if (filecount != foundfiles) { @@ -405,7 +397,7 @@ namespace Duplicati.Library.Main.Database { var dirsep = Util.GuessDirSeparator(string.IsNullOrWhiteSpace(largest_prefix) ? GetFirstPath() : largest_prefix); - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { if (string.IsNullOrEmpty(destination)) { @@ -416,83 +408,88 @@ namespace Duplicati.Library.Main.Database if ((OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) && dirsep == "\\") { // For Win -> Linux, we remove the colon from the drive letter, and use the drive letter as root folder - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN '\\' || SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable)); - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN '\\' || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN '\\' || SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END")); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN '\\' || SUBSTR(""Path"", 2) ELSE ""Path"" END")); } else if (OperatingSystem.IsWindows() && dirsep == "/") { // For Linux -> Win, we use the temporary folder's drive as the root path - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == '/' THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END", m_tempfiletable), Util.AppendDirSeparator(System.IO.Path.GetPathRoot(Library.Utility.TempFolder.SystemTempPath)).Replace("\\", "/")); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""Targetpath"" = CASE WHEN SUBSTR(""Path"", 1, 1) == '/' THEN ? || SUBSTR(""Path"", 2) ELSE ""Path"" END"), Util.AppendDirSeparator(System.IO.Path.GetPathRoot(Library.Utility.TempFolder.SystemTempPath)).Replace("\\", "/")); } else { // Same OS, just use the path directly - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""Targetpath"" = ""Path"" ", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""Targetpath"" = ""Path"" ")); } } else - { + { if (string.IsNullOrEmpty(largest_prefix)) { //Special case, restoring to new folder, but files are from different drives (no shared root on Windows) // We use the format / / - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 2, 1) == ':' THEN SUBSTR(""Path"", 1, 1) || SUBSTR(""Path"", 3) ELSE ""Path"" END")); // For UNC paths, we use \\server\folder -> / / - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN SUBSTR(""Path"", 2) ELSE ""TargetPath"" END", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = CASE WHEN SUBSTR(""Path"", 1, 2) == '\\' THEN SUBSTR(""Path"", 2) ELSE ""TargetPath"" END")); } else { largest_prefix = Util.AppendDirSeparator(largest_prefix, dirsep); - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = SUBSTR(""Path"", ?)", m_tempfiletable), largest_prefix.Length + 1); + cmd.SetCommandAndParameters(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = SUBSTR(""Path"", @PrefixLength)")) + .SetParameterValue("@PrefixLength", largest_prefix.Length + 1) + .ExecuteNonQuery(); } } // Cross-os path remapping support if ((OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) && dirsep == "\\") // For Win paths on Linux - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(""TargetPath"", '\', '/')", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = REPLACE(""TargetPath"", '\', '/')")); else if (OperatingSystem.IsWindows() && dirsep == "/") // For Linux paths on Windows - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = REPLACE(REPLACE(""TargetPath"", '\', '_'), '/', '\')", m_tempfiletable)); + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = REPLACE(REPLACE(""TargetPath"", '\', '_'), '/', '\')")); if (!string.IsNullOrEmpty(destination)) { // Paths are now relative with target-os naming system // so we prefix them with the target path - - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = ? || ""TargetPath"" ", m_tempfiletable), Util.AppendDirSeparator(destination)); + cmd.SetCommandAndParameters(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = @Destination || ""TargetPath"" ")) + .SetParameterValue("@Destination", Util.AppendDirSeparator(destination)) + .ExecuteNonQuery(); } - - } } public void FindMissingBlocks(bool skipMetadata) { - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"") SELECT DISTINCT ""{1}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 0 FROM ""{1}"", ""BlocksetEntry"", ""Block"" WHERE ""{1}"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ", m_tempblocktable, m_tempfiletable); - var p1 = cmd.ExecuteNonQuery(); + var p1 = cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{m_tempblocktable}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"", ""VolumeId"", ""BlockId"") SELECT DISTINCT ""{m_tempfiletable}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 0, ""Block"".""VolumeID"", ""Block"".""ID"" FROM ""{m_tempfiletable}"", ""BlocksetEntry"", ""Block"" WHERE ""{m_tempfiletable}"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ")); - int p2 = 0; + var p2 = 0; if (!skipMetadata) - { - cmd.CommandText = string.Format(@"INSERT INTO ""{0}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"") SELECT DISTINCT ""{1}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 1 FROM ""{1}"", ""BlocksetEntry"", ""Block"", ""Metadataset"" WHERE ""{1}"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ", m_tempblocktable, m_tempfiletable); - p2 = cmd.ExecuteNonQuery(); - } + p2 = cmd.ExecuteNonQuery(FormatInvariant($@"INSERT INTO ""{m_tempblocktable}"" (""FileID"", ""Index"", ""Hash"", ""Size"", ""Restored"", ""Metadata"", ""VolumeId"", ""BlockId"") SELECT DISTINCT ""{m_tempfiletable}"".""ID"", ""BlocksetEntry"".""Index"", ""Block"".""Hash"", ""Block"".""Size"", 0, 1, ""Block"".""VolumeID"", ""Block"".""ID"" FROM ""{m_tempfiletable}"", ""BlocksetEntry"", ""Block"", ""Metadataset"" WHERE ""{m_tempfiletable}"".""MetadataID"" = ""Metadataset"".""ID"" AND ""Metadataset"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ")); - var size = cmd.ExecuteScalarInt64(string.Format(@"SELECT SUM(""Size"") FROM ""{0}"" ", m_tempblocktable), 0); + //creating indexes after insertion is much faster + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempblocktable}_HashSizeIndex"" ON ""{m_tempblocktable}"" (""Hash"", ""Size"")")); + // better suited to speed up commit on UpdateBlocks + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempblocktable}_FileIdIndexIndex"" ON ""{m_tempblocktable}"" (""FileId"", ""Index"")")); + + var size = cmd.ExecuteScalarInt64(FormatInvariant($@"SELECT SUM(""Size"") FROM ""{m_tempblocktable}"" "), 0); Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreSourceSize", "Restore list contains {0} blocks with a total size of {1}", p1 + p2, Library.Utility.Utility.FormatSizeString(size)); } } public void UpdateTargetPath(long ID, string newname) { - using (var cmd = m_connection.CreateCommand()) - cmd.ExecuteNonQuery(string.Format(@"UPDATE ""{0}"" SET ""TargetPath"" = ? WHERE ""ID"" = ?", m_tempfiletable), newname, ID); + using var cmd = m_connection.CreateCommand(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""TargetPath"" = @TargetPath WHERE ""ID"" = @ID")) + .SetParameterValue("@TargetPath", newname) + .SetParameterValue("@ID", ID); + + cmd.ExecuteNonQuery(); } public interface IExistingFileBlock @@ -511,7 +508,6 @@ namespace Duplicati.Library.Main.Database IEnumerable Blocks { get; } } - public interface IBlockSource { string Path { get; } @@ -536,6 +532,9 @@ namespace Duplicati.Library.Main.Database IEnumerable Blocks { get; } } + /// + /// Interface for an object describing a file to restore. + /// public interface IFileToRestore { string Path { get; } @@ -559,34 +558,34 @@ namespace Duplicati.Library.Main.Database private class ExistingFile : IExistingFile { - private readonly System.Data.IDataReader m_reader; + private readonly IDataReader m_reader; - public ExistingFile(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } + public ExistingFile(IDataReader rd) { m_reader = rd; HasMore = true; } public string TargetPath { get { return m_reader.ConvertValueToString(0); } } public string TargetHash { get { return m_reader.ConvertValueToString(1); } } - public long TargetFileID { get { return m_reader.ConvertValueToInt64(2); } } + public long TargetFileID { get { return m_reader.ConvertValueToInt64(2); } } public long Length { get { return m_reader.ConvertValueToInt64(3); } } public bool HasMore { get; private set; } private class ExistingFileBlock : IExistingFileBlock { - private readonly System.Data.IDataReader m_reader; + private readonly IDataReader m_reader; - public ExistingFileBlock(System.Data.IDataReader rd) { m_reader = rd; } + public ExistingFileBlock(IDataReader rd) { m_reader = rd; } public string Hash { get { return m_reader.ConvertValueToString(4); } } public long Index { get { return m_reader.ConvertValueToInt64(5); } } public long Size { get { return m_reader.ConvertValueToInt64(6); } } } - public IEnumerable Blocks + public IEnumerable Blocks { get { - string p = this.TargetPath; - while (HasMore && p == this.TargetPath) + string p = TargetPath; + while (HasMore && p == TargetPath) { yield return new ExistingFileBlock(m_reader); HasMore = m_reader.Read(); @@ -594,27 +593,24 @@ namespace Duplicati.Library.Main.Database } } - public static IEnumerable GetExistingFilesWithBlocks(System.Data.IDbConnection connection, string tablename) + public static IEnumerable GetExistingFilesWithBlocks(IDbConnection connection, string tablename) { - using(var cmd = connection.CreateCommand()) - { - cmd.CommandText = string.Format(@"SELECT ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""{0}"".""ID"", ""Blockset"".""Length"", ""Block"".""Hash"", ""BlocksetEntry"".""Index"", ""Block"".""Size"" FROM ""{0}"", ""Blockset"", ""BlocksetEntry"", ""Block"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""BlocksetEntry"".""BlocksetID"" = ""{0}"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ORDER BY ""{0}"".""TargetPath"", ""BlocksetEntry"".""Index""", tablename); - using(var rd = cmd.ExecuteReader()) - if (rd.Read()) + using (var cmd = connection.CreateCommand(FormatInvariant($@"SELECT ""{tablename}"".""TargetPath"", ""Blockset"".""FullHash"", ""{tablename}"".""ID"", ""Blockset"".""Length"", ""Block"".""Hash"", ""BlocksetEntry"".""Index"", ""Block"".""Size"" FROM ""{tablename}"", ""Blockset"", ""BlocksetEntry"", ""Block"" WHERE ""{tablename}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""BlocksetEntry"".""BlocksetID"" = ""{tablename}"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" ORDER BY ""{tablename}"".""TargetPath"", ""BlocksetEntry"".""Index"""))) + using (var rd = cmd.ExecuteReader()) + if (rd.Read()) + { + var more = true; + while (more) { - var more = true; - while(more) - { - var f = new ExistingFile(rd); - string current = f.TargetPath; - yield return f; + var f = new ExistingFile(rd); + string current = f.TargetPath; + yield return f; - more = f.HasMore; - while (more && current == f.TargetPath) - more = rd.Read(); - } + more = f.HasMore; + while (more && current == f.TargetPath) + more = rd.Read(); } - } + } } } @@ -629,16 +625,16 @@ namespace Duplicati.Library.Main.Database { private class BlockSource : IBlockSource { - private readonly System.Data.IDataReader m_reader; - public BlockSource(System.Data.IDataReader rd) { m_reader = rd; } + private readonly IDataReader m_reader; + public BlockSource(IDataReader rd) { m_reader = rd; } public string Path { get { return m_reader.ConvertValueToString(6); } } public long Offset { get { return m_reader.ConvertValueToInt64(7); } } public bool IsMetadata { get { return false; } } } - private readonly System.Data.IDataReader m_reader; - public BlockDescriptor(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } + private readonly IDataReader m_reader; + public BlockDescriptor(IDataReader rd) { m_reader = rd; HasMore = true; } private string TargetPath { get { return m_reader.ConvertValueToString(0); } } @@ -654,11 +650,11 @@ namespace Duplicati.Library.Main.Database { get { - var p = this.TargetPath; - var h = this.Hash; - var s = this.Size; + var p = TargetPath; + var h = Hash; + var s = Size; - while (HasMore && p == this.TargetPath && h == this.Hash && s == this.Size) + while (HasMore && p == TargetPath && h == Hash && s == Size) { yield return new BlockSource(m_reader); HasMore = m_reader.Read(); @@ -667,8 +663,8 @@ namespace Duplicati.Library.Main.Database } } - private readonly System.Data.IDataReader m_reader; - public LocalBlockSource(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } + private readonly IDataReader m_reader; + public LocalBlockSource(IDataReader rd) { m_reader = rd; HasMore = true; } public string TargetPath { get { return m_reader.ConvertValueToString(0); } } public long TargetFileID { get { return m_reader.ConvertValueToInt64(1); } } @@ -677,10 +673,10 @@ namespace Duplicati.Library.Main.Database public IEnumerable Blocks { - get + get { - var p = this.TargetPath; - while (HasMore && p == this.TargetPath) + var p = TargetPath; + while (HasMore && p == TargetPath) { var c = new BlockDescriptor(m_reader); var h = c.Hash; @@ -689,33 +685,30 @@ namespace Duplicati.Library.Main.Database yield return c; HasMore = c.HasMore; - while (HasMore && c.Hash == h && c.Size == s && this.TargetPath == p) + while (HasMore && c.Hash == h && c.Size == s && TargetPath == p) HasMore = m_reader.Read(); } } } - public static IEnumerable GetFilesAndSourceBlocks(System.Data.IDbConnection connection, string filetablename, string blocktablename, long blocksize, bool skipMetadata) + public static IEnumerable GetFilesAndSourceBlocks(IDbConnection connection, string filetablename, string blocktablename, long blocksize, bool skipMetadata) { - using(var cmd = connection.CreateCommand()) + // TODO: Skip metadata as required + using (var cmd = connection.CreateCommand(FormatInvariant($@"SELECT DISTINCT ""A"".""TargetPath"", ""A"".""ID"", ""B"".""Hash"", (""B"".""Index"" * {blocksize}), ""B"".""Index"", ""B"".""Size"", ""C"".""Path"", (""D"".""Index"" * {blocksize}), ""E"".""Size"", ""B"".""Metadata"" FROM ""{filetablename}"" ""A"", ""{blocktablename}"" ""B"", ""File"" ""C"", ""BlocksetEntry"" ""D"", ""Block"" E WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""C"".""BlocksetID"" = ""D"".""BlocksetID"" AND ""D"".""BlockID"" = ""E"".""ID"" AND ""B"".""Hash"" = ""E"".""Hash"" AND ""B"".""Size"" = ""E"".""Size"" AND ""B"".""Restored"" = 0"))) + using (var rd = cmd.ExecuteReader()) { - // TODO: Skip metadata as required - cmd.CommandText = string.Format(@"SELECT DISTINCT ""A"".""TargetPath"", ""A"".""ID"", ""B"".""Hash"", (""B"".""Index"" * {2}), ""B"".""Index"", ""B"".""Size"", ""C"".""Path"", (""D"".""Index"" * {2}), ""E"".""Size"", ""B"".""Metadata"" FROM ""{0}"" ""A"", ""{1}"" ""B"", ""File"" ""C"", ""BlocksetEntry"" ""D"", ""Block"" E WHERE ""A"".""ID"" = ""B"".""FileID"" AND ""C"".""BlocksetID"" = ""D"".""BlocksetID"" AND ""D"".""BlockID"" = ""E"".""ID"" AND ""B"".""Hash"" = ""E"".""Hash"" AND ""B"".""Size"" = ""E"".""Size"" AND ""B"".""Restored"" = 0", filetablename, blocktablename, blocksize); - using(var rd = cmd.ExecuteReader()) + if (rd.Read()) { - if (rd.Read()) + var more = true; + while (more) { - var more = true; - while(more) - { - var f = new LocalBlockSource(rd); - string current = f.TargetPath; - yield return f; + var f = new LocalBlockSource(rd); + string current = f.TargetPath; + yield return f; - more = f.HasMore; - while (more && current == f.TargetPath) - more = rd.Read(); - } + more = f.HasMore; + while (more && current == f.TargetPath) + more = rd.Read(); } } } @@ -741,18 +734,14 @@ namespace Duplicati.Library.Main.Database // Now it is likely to restore all files from front to back. Large files will always be done last. // One could also use like the average block number in a volume, that needs to be measured. - cmd.CommandText = string.Format( - @"SELECT ""RV"".""Name"", ""RV"".""Hash"", ""RV"".""Size"", ""BB"".""MaxIndex"" " - + @" FROM ""RemoteVolume"" ""RV"" INNER JOIN " - + @" (SELECT ""B"".""VolumeID"", MAX(""TB"".""Index"") as ""MaxIndex"" " - + @" FROM ""Block"" ""B"", ""{0}"" ""TB"" " - + @" WHERE ""TB"".""Restored"" = 0 " - + @" AND ""B"".""Hash"" = ""TB"".""Hash"" " - + @" AND ""B"".""Size"" = ""TB"".""Size"" " - + @" GROUP BY ""B"".""VolumeID"" " - + @" ) as ""BB"" ON ""RV"".""ID"" = ""BB"".""VolumeID"" " - + @" ORDER BY ""BB"".""MaxIndex"" " - , m_tempblocktable); + cmd.CommandText = FormatInvariant($@"SELECT ""RV"".""Name"", ""RV"".""Hash"", ""RV"".""Size"", ""BB"".""MaxIndex"" +FROM ""RemoteVolume"" ""RV"" INNER JOIN + (SELECT ""TB"".""VolumeID"", MAX(""TB"".""Index"") as ""MaxIndex"" + FROM ""{m_tempblocktable}"" ""TB"" + WHERE ""TB"".""Restored"" = 0 + GROUP BY ""TB"".""VolumeID"" + ) as ""BB"" ON ""RV"".""ID"" = ""BB"".""VolumeID"" +ORDER BY ""BB"".""MaxIndex"" "); using (var rd = cmd.ExecuteReader()) { @@ -783,9 +772,9 @@ namespace Duplicati.Library.Main.Database private readonly string m_blocktablename; private readonly long m_blocksize; - private readonly System.Data.IDbConnection m_connection; + private readonly IDbConnection m_connection; - public FilesAndMetadata(System.Data.IDbConnection connection, string filetablename, string blocktablename, long blocksize, BlockVolumeReader curvolume) + public FilesAndMetadata(IDbConnection connection, string filetablename, string blocktablename, long blocksize, BlockVolumeReader curvolume) { m_filetablename = filetablename; m_blocktablename = blocktablename; @@ -795,21 +784,17 @@ namespace Duplicati.Library.Main.Database using (var c = m_connection.CreateCommand()) { m_tmptable = "VolumeFiles-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - c.CommandText = string.Format(@"CREATE TEMPORARY TABLE ""{0}"" ( ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL )", m_tmptable); - c.ExecuteNonQuery(); + c.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_tmptable}"" ( ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL )")); - - c.CommandText = string.Format(@"INSERT INTO ""{0}"" (""Hash"", ""Size"") VALUES (?,?)", m_tmptable); - c.AddParameters(2); + c.SetCommandAndParameters(FormatInvariant($@"INSERT INTO ""{m_tmptable}"" (""Hash"", ""Size"") VALUES (@Hash, @Size)")); foreach (var s in curvolume.Blocks) { - c.SetParameterValue(0, s.Key); - c.SetParameterValue(1, s.Value); + c.SetParameterValue("@Hash", s.Key); + c.SetParameterValue("@Size", s.Value); c.ExecuteNonQuery(); } // The index _HashSizeIndex is not needed anymore. Index on "Blocks-..." is used on Join in GetMissingBlocks - } } @@ -818,7 +803,7 @@ namespace Duplicati.Library.Main.Database if (m_tmptable != null) using (var c = m_connection.CreateCommand()) { - c.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tmptable); + c.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tmptable}"""); c.ExecuteNonQuery(); } } @@ -827,27 +812,27 @@ namespace Duplicati.Library.Main.Database { private class PatchBlock : IPatchBlock { - private readonly System.Data.IDataReader m_reader; - public PatchBlock(System.Data.IDataReader rd) { m_reader = rd; } + private readonly IDataReader m_reader; + public PatchBlock(IDataReader rd) { m_reader = rd; } public long Offset { get { return m_reader.ConvertValueToInt64(2); } } public long Size { get { return m_reader.ConvertValueToInt64(3); } } public string Key { get { return m_reader.ConvertValueToString(4); } } } - private readonly System.Data.IDataReader m_reader; - public VolumePatch(System.Data.IDataReader rd) { m_reader = rd; HasMore = true; } + private readonly IDataReader m_reader; + public VolumePatch(IDataReader rd) { m_reader = rd; HasMore = true; } public string Path { get { return m_reader.ConvertValueToString(0); } } public long FileID { get { return m_reader.ConvertValueToInt64(1); } } - public bool HasMore { get; private set; } + public bool HasMore { get; private set; } public IEnumerable Blocks { get { - string p = this.Path; - while (HasMore && p == this.Path) + string p = Path; + while (HasMore && p == Path) { yield return new PatchBlock(m_reader); HasMore = m_reader.Read(); @@ -860,25 +845,23 @@ namespace Duplicati.Library.Main.Database { get { - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { // The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here! - cmd.CommandText = string.Format( - @" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {3}), ""BB"".""Size"", ""BB"".""Hash"" " - + @" FROM ""{0}"" ""A"", ""{1}"" ""BB"" " - + @" WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {4}" - + @" AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{1}"" ""B"", ""{2}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") " - + @" ORDER BY ""A"".""TargetPath"", ""BB"".""Index""" - , m_filetablename, m_blocktablename, m_tmptable, m_blocksize, "0"); - using(var rd = cmd.ExecuteReader()) + cmd.CommandText = FormatInvariant($@" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {m_blocksize}), ""BB"".""Size"", ""BB"".""Hash"" +FROM ""{m_filetablename}"" ""A"", ""{m_blocktablename}"" ""BB"" +WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {"0"} +AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{m_blocktablename}"" ""B"", ""{m_tmptable}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") +ORDER BY ""A"".""TargetPath"", ""BB"".""Index"""); + using (var rd = cmd.ExecuteReader()) { if (rd.Read()) { var more = true; - while(more) + while (more) { var f = new VolumePatch(rd); - string current = f.Path; + var current = f.Path; yield return f; more = f.HasMore; @@ -894,18 +877,16 @@ namespace Duplicati.Library.Main.Database public IEnumerable MetadataWithMissingBlocks { get - { - using(var cmd = m_connection.CreateCommand()) + { + using (var cmd = m_connection.CreateCommand()) { // The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here! - cmd.CommandText = string.Format( - @" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {3}), ""BB"".""Size"", ""BB"".""Hash"" " - + @" FROM ""{0}"" ""A"", ""{1}"" ""BB"" " - + @" WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {4}" - + @" AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{1}"" ""B"", ""{2}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") " - + @" ORDER BY ""A"".""TargetPath"", ""BB"".""Index""" - , m_filetablename, m_blocktablename, m_tmptable, m_blocksize, "1"); - using(var rd = cmd.ExecuteReader()) + cmd.CommandText = FormatInvariant($@" SELECT DISTINCT ""A"".""TargetPath"", ""BB"".""FileID"", (""BB"".""Index"" * {m_blocksize}), ""BB"".""Size"", ""BB"".""Hash"" + FROM ""{m_filetablename}"" ""A"", ""{m_blocktablename}"" ""BB"" +WHERE ""A"".""ID"" = ""BB"".""FileID"" AND ""BB"".""Restored"" = 0 AND ""BB"".""Metadata"" = {"1"} + AND ""BB"".""ID"" IN (SELECT ""B"".""ID"" FROM ""{m_blocktablename}"" ""B"", ""{m_tmptable}"" ""C"" WHERE ""B"".""Hash"" = ""C"".""Hash"" AND ""B"".""Size"" = ""C"".""Size"") +ORDER BY ""A"".""TargetPath"", ""BB"".""Index"""); + using (var rd = cmd.ExecuteReader()) { if (rd.Read()) { @@ -932,29 +913,173 @@ namespace Duplicati.Library.Main.Database return new FilesAndMetadata(m_connection, m_tempfiletable, m_tempblocktable, blocksize, curvolume); } + /// + /// Returns a connection from the connection pool. + /// + /// A connection from the connection pool. + public IDbConnection GetConnectionFromPool() + { + if (!m_connection_pool.TryTake(out var connection)) + { + connection = SQLiteHelper.SQLiteLoader.LoadConnection(); + connection.ConnectionString = m_connection.ConnectionString + ";Cache=Shared;"; + connection.Open(); + + SQLiteHelper.SQLiteLoader.ApplyCustomPragmas(connection); + + using var cmd = connection.CreateCommand(); + cmd.ExecuteNonQuery("PRAGMA journal_mode = WAL;"); + cmd.ExecuteNonQuery("PRAGMA read_uncommitted = true;"); + } + + return connection; + } + private class FileToRestore : IFileToRestore { public string Path { get; private set; } public string Hash { get; private set; } public long Length { get; private set; } - + public FileToRestore(long id, string path, string hash, long length) { - this.Path = path; - this.Hash = hash; - this.Length = length; + Path = path; + Hash = hash; + Length = length; } } public IEnumerable GetFilesToRestore(bool onlyNonVerified) { - using (var cmd = m_connection.CreateCommand()) + using var cmd = m_connection.CreateCommand(FormatInvariant($@"SELECT ""{m_tempfiletable}"".""ID"", ""{m_tempfiletable}"".""TargetPath"", ""Blockset"".""FullHash"", ""Blockset"".""Length"" FROM ""{m_tempfiletable}"",""Blockset"" WHERE ""{m_tempfiletable}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""{m_tempfiletable}"".""DataVerified"" <= @Verified")) + .SetParameterValue("@Verified", !onlyNonVerified); + + using (var rd = cmd.ExecuteReader()) + while (rd.Read()) + yield return new FileToRestore(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToInt64(3)); + } + + /// + /// Returns a list of files and symlinks to restore. + /// + /// A list of files and symlinks to restore. + public IEnumerable GetFilesAndSymlinksToRestore() + { + using var cmd = m_connection.CreateCommand(FormatInvariant($@" + SELECT F.ID, F.Path, F.TargetPath, IFNULL(B.FullHash, ''), IFNULL(B.Length, 0), F.BlocksetID + FROM ""{m_tempfiletable}"" F + LEFT JOIN Blockset B ON F.BlocksetID = B.ID + WHERE F.BlocksetID != {FOLDER_BLOCKSET_ID}")); + using var rd = cmd.ExecuteReader(); + while (rd.Read()) + yield return new FileRequest(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToString(3), rd.ConvertValueToInt64(4), rd.ConvertValueToInt64(5)); + } + + /// + /// Returns a list of folders to restore. Used to restore folder metadata. + /// + /// A list of folders to restore. + public IEnumerable GetFolderMetadataToRestore() + { + using var cmd = m_connection.CreateCommand(); + using var rd = cmd.ExecuteReader(FormatInvariant($@" + SELECT F.ID, '', F.TargetPath, '', 0, {FOLDER_BLOCKSET_ID} + FROM ""{m_tempfiletable}"" F + WHERE F.BlocksetID = {FOLDER_BLOCKSET_ID} AND F.MetadataID IS NOT NULL AND F.MetadataID >= 0")); + + while (rd.Read()) + yield return new FileRequest(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToString(3), rd.ConvertValueToInt64(4), rd.ConvertValueToInt64(5)); + } + + /// + /// Returns a list of blocks and their volume IDs. Used by the to keep track of blocks and volumes to automatically evict them from the respective caches. + /// + /// Flag indicating whether the returned blocks should exclude the metadata blocks. + /// A list of tuples containing the block ID and the volume ID of the block. + public IEnumerable<(long, long)> GetBlocksAndVolumeIDs(bool skipMetadata) + { + using var cmd = Connection.CreateCommand(); + using var reader = cmd.ExecuteReader(FormatInvariant($@" + SELECT ""Block"".""ID"", ""Block"".""VolumeID"" + FROM ""BlocksetEntry"" + INNER JOIN ""{m_tempfiletable}"" ON ""BlocksetEntry"".""BlocksetID"" = ""{m_tempfiletable}"".""BlocksetID"" + INNER JOIN ""Block"" ON ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" + ") + + (skipMetadata ? "" : FormatInvariant($@" + UNION ALL + SELECT ""Block"".""ID"", ""Block"".""VolumeID"" + FROM ""{m_tempfiletable}"" + INNER JOIN ""Metadataset"" ON ""{m_tempfiletable}"".""MetadataID"" = ""Metadataset"".""ID"" + INNER JOIN ""BlocksetEntry"" ON ""Metadataset"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" + INNER JOIN ""Block"" ON ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" + "))); + while (reader.Read()) + yield return (reader.ConvertValueToInt64(0), reader.ConvertValueToInt64(1)); + } + + /// + /// Returns a list of for the given blockset ID. It is used by the to restore the blocks of a file. + /// + /// The BlocksetID of the file. + /// A list of needed to restore the given file. + public IEnumerable GetBlocksFromFile(long blocksetID) + { + var connection = GetConnectionFromPool(); + using var cmd = connection.CreateCommand(FormatInvariant(@$" + SELECT ""Block"".""ID"", ""Block"".""Hash"", ""Block"".""Size"", ""Block"".""VolumeID"" + FROM ""BlocksetEntry"" INNER JOIN ""Block"" + ON ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" + WHERE ""BlocksetEntry"".""BlocksetID"" = @BlocksetID")) + .SetParameterValue("@BlocksetID", blocksetID); + + using var reader = cmd.ExecuteReader(); + for (long i = 0; reader.Read(); i++) + yield return new BlockRequest(reader.ConvertValueToInt64(0), i, reader.ConvertValueToString(1), reader.ConvertValueToInt64(2), reader.ConvertValueToInt64(3), false); + + // Return the connection to the pool + m_connection_pool.Add(connection); + } + + /// + /// Returns a list of for the metadata blocks of the given file. It is used by the to restore the metadata of a file. + /// + /// The ID of the file. + /// A list of needed to restore the metadata of the given file. + public IEnumerable GetMetadataBlocksFromFile(long fileID) + { + var connection = GetConnectionFromPool(); + using var cmd = connection.CreateCommand(FormatInvariant($@" + SELECT ""Block"".""ID"", ""Block"".""Hash"", ""Block"".""Size"", ""Block"".""VolumeID"" + FROM ""File"" + INNER JOIN ""Metadataset"" ON ""File"".""MetadataID"" = ""Metadataset"".""ID"" + INNER JOIN ""BlocksetEntry"" ON ""Metadataset"".""BlocksetID"" = ""BlocksetEntry"".""BlocksetID"" + INNER JOIN ""Block"" ON ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" + WHERE ""File"".""ID"" = @FileID + ")) + .SetParameterValue("@FileID", fileID); + + using var reader = cmd.ExecuteReader(); + for (long i = 0; reader.Read(); i++) { - cmd.AddParameter(!onlyNonVerified); - using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""{0}"".""ID"", ""{0}"".""TargetPath"", ""Blockset"".""FullHash"", ""Blockset"".""Length"" FROM ""{0}"",""Blockset"" WHERE ""{0}"".""BlocksetID"" = ""Blockset"".""ID"" AND ""{0}"".""DataVerified"" <= ?", m_tempfiletable))) - while (rd.Read()) - yield return new FileToRestore(rd.ConvertValueToInt64(0), rd.ConvertValueToString(1), rd.ConvertValueToString(2), rd.ConvertValueToInt64(3)); + yield return new BlockRequest(reader.ConvertValueToInt64(0), i, reader.ConvertValueToString(1), reader.ConvertValueToInt64(2), reader.ConvertValueToInt64(3), false); } + + // Return the connection to the pool + m_connection_pool.Add(connection); + } + + /// + /// Returns the volume information for the given volume ID. It is used by the to get the volume information for the given volume ID. + /// + /// The ID of the volume. + /// A tuple containing the name, size, and hash of the volume. + public IEnumerable<(string, long, string)> GetVolumeInfo(long VolumeID) + { + using var cmd = m_connection.CreateCommand("SELECT Name, Size, Hash FROM RemoteVolume WHERE ID = @VolumeID") + .SetParameterValue("@VolumeID", VolumeID); + using var reader = cmd.ExecuteReader(); + while (reader.Read()) + yield return (reader.ConvertValueToString(0), reader.ConvertValueToInt64(1), reader.ConvertValueToString(2)); } public void DropRestoreTable() @@ -964,26 +1089,34 @@ namespace Duplicati.Library.Main.Database if (m_tempfiletable != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempfiletable); + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tempfiletable}"""); cmd.ExecuteNonQuery(); } - catch(Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } finally { m_tempfiletable = null; } if (m_tempblocktable != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tempblocktable); + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tempblocktable}"""); cmd.ExecuteNonQuery(); } - catch(Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } finally { m_tempblocktable = null; } + if (m_latestblocktable != null) + try + { + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_latestblocktable}"""); + cmd.ExecuteNonQuery(); + } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } + finally { m_latestblocktable = null; } if (m_fileprogtable != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_fileprogtable); + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_fileprogtable}"""); cmd.ExecuteNonQuery(); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } @@ -992,7 +1125,7 @@ namespace Duplicati.Library.Main.Database if (m_totalprogtable != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_totalprogtable); + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_totalprogtable}"""); cmd.ExecuteNonQuery(); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } @@ -1001,12 +1134,12 @@ namespace Duplicati.Library.Main.Database if (m_filesnewlydonetable != null) try { - cmd.CommandText = string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_filesnewlydonetable); + cmd.CommandText = FormatInvariant($@"DROP TABLE IF EXISTS ""{m_filesnewlydonetable}"""); cmd.ExecuteNonQuery(); } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "CleanupError", ex, "Cleanup error: {0}", ex.Message); } finally { m_filesnewlydonetable = null; } - + } } @@ -1027,62 +1160,38 @@ namespace Duplicati.Library.Main.Database /// private class DirectBlockMarker : IBlockMarker { - private System.Data.IDbCommand m_insertblockCommand; - private System.Data.IDbCommand m_resetfileCommand; - private System.Data.IDbCommand m_updateAsRestoredCommand; - private System.Data.IDbCommand m_updateFileAsDataVerifiedCommand; - private System.Data.IDbCommand m_statUpdateCommand; + private IDbCommand m_insertblockCommand; + private IDbCommand m_resetfileCommand; + private IDbCommand m_updateAsRestoredCommand; + private IDbCommand m_updateFileAsDataVerifiedCommand; + private IDbCommand m_statUpdateCommand; + private IDbTransaction m_transaction; private bool m_hasUpdates = false; private readonly string m_blocktablename; private readonly string m_filetablename; - public DirectBlockMarker(System.Data.IDbConnection connection, string blocktablename, string filetablename, string statstablename) + public DirectBlockMarker(IDbConnection connection, string blocktablename, string filetablename, string statstablename) { - m_insertblockCommand = connection.CreateCommand(); - m_resetfileCommand = connection.CreateCommand(); - m_updateAsRestoredCommand = connection.CreateCommand(); - m_updateFileAsDataVerifiedCommand = connection.CreateCommand(); - m_statUpdateCommand = connection.CreateCommand(); - - m_insertblockCommand.Transaction = connection.BeginTransaction(); - m_resetfileCommand.Transaction = m_insertblockCommand.Transaction; - m_updateAsRestoredCommand.Transaction = m_insertblockCommand.Transaction; - m_updateFileAsDataVerifiedCommand.Transaction = m_insertblockCommand.Transaction; - m_statUpdateCommand.Transaction = m_insertblockCommand.Transaction; - + m_transaction = connection.BeginTransaction(); m_blocktablename = blocktablename; m_filetablename = filetablename; - m_insertblockCommand.CommandText = string.Format( - @"UPDATE ""{0}"" SET ""Restored"" = 1 " - + @" WHERE ""FileID"" = ? AND ""Index"" = ? AND ""Hash"" = ? AND ""Size"" = ? AND ""Metadata"" = ? AND ""Restored"" = 0 " - , m_blocktablename); - m_insertblockCommand.AddParameters(5); + m_insertblockCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"UPDATE ""{m_blocktablename}"" SET ""Restored"" = 1 +WHERE ""FileID"" = @TargetFileId AND ""Index"" = @Index AND ""Hash"" = @Hash AND ""Size"" = @Size AND ""Metadata"" = @Metadata AND ""Restored"" = 0 ")); - m_resetfileCommand.CommandText = string.Format( - @"UPDATE ""{0}"" SET ""Restored"" = 0 WHERE ""FileID"" = ? " - , m_blocktablename); - m_resetfileCommand.AddParameters(1); - - m_updateAsRestoredCommand.CommandText = string.Format( - @"UPDATE ""{0}"" SET ""Restored"" = 1 WHERE ""FileID"" = ? AND ""Metadata"" <= ? " - , m_blocktablename); - m_updateAsRestoredCommand.AddParameters(2); - - m_updateFileAsDataVerifiedCommand.CommandText = string.Format( - @"UPDATE ""{0}"" SET ""DataVerified"" = 1 WHERE ""ID"" = ?" - , m_filetablename); - m_updateFileAsDataVerifiedCommand.AddParameters(1); + m_resetfileCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"UPDATE ""{m_blocktablename}"" SET ""Restored"" = 0 WHERE ""FileID"" = @TargetFileId ")); + m_updateAsRestoredCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"UPDATE ""{m_blocktablename}"" SET ""Restored"" = 1 WHERE ""FileID"" = @TargetFileId AND ""Metadata"" <= @Metadata ")); + m_updateFileAsDataVerifiedCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"UPDATE ""{m_filetablename}"" SET ""DataVerified"" = 1 WHERE ""ID"" = @TargetFileId")); if (statstablename != null) { // Fields in Stats: TotalFiles, TotalBlocks, TotalSize // FilesFullyRestored, FilesPartiallyRestored, BlocksRestored, SizeRestored - m_statUpdateCommand.CommandText = string.Format(@"SELECT SUM(""FilesFullyRestored""), SUM(""SizeRestored"") FROM ""{0}"" ", statstablename); + m_statUpdateCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"SELECT SUM(""FilesFullyRestored""), SUM(""SizeRestored"") FROM ""{statstablename}"" ")); } else // very slow fallback if stats tables were not created - m_statUpdateCommand.CommandText = string.Format(@"SELECT COUNT(DISTINCT ""FileID""), SUM(""Size"") FROM ""{0}"" WHERE ""Restored"" = 1 ", m_blocktablename); + m_statUpdateCommand = connection.CreateCommand(m_transaction, FormatInvariant($@"SELECT COUNT(DISTINCT ""FileID""), SUM(""Size"") FROM ""{m_blocktablename}"" WHERE ""Restored"" = 1 ")); } @@ -1110,8 +1219,8 @@ namespace Duplicati.Library.Main.Database public void SetAllBlocksMissing(long targetfileid) { m_hasUpdates = true; - m_resetfileCommand.SetParameterValue(0, targetfileid); - var r = m_resetfileCommand.ExecuteNonQuery(); + var r = m_resetfileCommand.SetParameterValue("@TargetFileId", targetfileid) + .ExecuteNonQuery(); if (r <= 0) throw new Exception("Unexpected reset result"); } @@ -1119,9 +1228,9 @@ namespace Duplicati.Library.Main.Database public void SetAllBlocksRestored(long targetfileid, bool includeMetadata) { m_hasUpdates = true; - m_updateAsRestoredCommand.SetParameterValue(0, targetfileid); - m_updateAsRestoredCommand.SetParameterValue(1, includeMetadata ? 1 : 0); - var r = m_updateAsRestoredCommand.ExecuteNonQuery(); + var r = m_updateAsRestoredCommand.SetParameterValue("@TargetFileId", targetfileid) + .SetParameterValue("@Metadata", includeMetadata ? 1 : 0) + .ExecuteNonQuery(); if (r <= 0) throw new Exception("Unexpected reset result"); } @@ -1129,8 +1238,8 @@ namespace Duplicati.Library.Main.Database public void SetFileDataVerified(long targetfileid) { m_hasUpdates = true; - m_updateFileAsDataVerifiedCommand.SetParameterValue(0, targetfileid); - var r = m_updateFileAsDataVerifiedCommand.ExecuteNonQuery(); + var r = m_updateFileAsDataVerifiedCommand.SetParameterValue("@TargetFileId", targetfileid) + .ExecuteNonQuery(); if (r != 1) throw new Exception("Unexpected result when marking file as verified."); } @@ -1138,53 +1247,34 @@ namespace Duplicati.Library.Main.Database public void SetBlockRestored(long targetfileid, long index, string hash, long size, bool metadata) { m_hasUpdates = true; - m_insertblockCommand.SetParameterValue(0, targetfileid); - m_insertblockCommand.SetParameterValue(1, index); - m_insertblockCommand.SetParameterValue(2, hash); - m_insertblockCommand.SetParameterValue(3, size); - m_insertblockCommand.SetParameterValue(4, metadata); - var r = m_insertblockCommand.ExecuteNonQuery(); + var r = m_insertblockCommand.SetParameterValue("@TargetFileId", targetfileid) + .SetParameterValue("@Index", index) + .SetParameterValue("@Hash", hash) + .SetParameterValue("@Size", size) + .SetParameterValue("@Metadata", metadata) + .ExecuteNonQuery(); if (r != 1) throw new Exception("Unexpected result when marking block."); - } public void Commit() { - var tr = m_insertblockCommand.Transaction; m_insertblockCommand.Dispose(); m_insertblockCommand = null; using (new Logging.Timer(LOGTAG, "CommitBlockMarker", "CommitBlockMarker")) - tr.Commit(); - tr.Dispose(); + m_transaction.Commit(); + m_transaction.Dispose(); + m_transaction = null; } public void Dispose() { - if (m_insertblockCommand != null) - try { m_insertblockCommand.Dispose(); } - catch { } - finally { m_insertblockCommand = null; } - - if (m_resetfileCommand != null) - try { m_resetfileCommand.Dispose(); } - catch { } - finally { m_resetfileCommand = null; } - - if (m_updateAsRestoredCommand != null) - try { m_updateAsRestoredCommand.Dispose(); } - catch { } - finally { m_updateAsRestoredCommand = null; } - - if (m_updateFileAsDataVerifiedCommand != null) - try { m_updateFileAsDataVerifiedCommand.Dispose(); } - catch { } - finally { m_updateFileAsDataVerifiedCommand = null; } - - if (m_statUpdateCommand != null) - try { m_statUpdateCommand.Dispose(); } - catch { } - finally { m_statUpdateCommand = null; } + m_insertblockCommand?.Dispose(); + m_resetfileCommand?.Dispose(); + m_updateAsRestoredCommand?.Dispose(); + m_updateFileAsDataVerifiedCommand?.Dispose(); + m_statUpdateCommand?.Dispose(); + m_transaction?.Dispose(); } } @@ -1195,16 +1285,23 @@ namespace Duplicati.Library.Main.Database public override void Dispose() { + foreach (var connection in m_connection_pool) + { + connection.Close(); + connection.Dispose(); + } + m_connection_pool.Clear(); DropRestoreTable(); base.Dispose(); } public IEnumerable GetTargetFolders() { - using (var cmd = m_connection.CreateCommand()) - using (var rd = cmd.ExecuteReader(string.Format(@"SELECT ""TargetPath"" FROM ""{0}"" WHERE ""BlocksetID"" == ?", m_tempfiletable), FOLDER_BLOCKSET_ID)) - while(rd.Read()) - yield return rd.GetValue(0).ToString(); + using var cmd = m_connection.CreateCommand(FormatInvariant($@"SELECT ""TargetPath"" FROM ""{m_tempfiletable}"" WHERE ""BlocksetID"" == @BlocksetID")) + .SetParameterValue("@BlocksetID", FOLDER_BLOCKSET_ID); + using (var rd = cmd.ExecuteReader()) + while (rd.Read()) + yield return rd.ConvertValueToString(0); } public interface IFastSource @@ -1214,7 +1311,7 @@ namespace Duplicati.Library.Main.Database string SourcePath { get; } IEnumerable Blocks { get; } } - + public interface IBlockEntry { long Offset { get; } @@ -1227,71 +1324,145 @@ namespace Duplicati.Library.Main.Database { private class BlockEntry : IBlockEntry { - private readonly System.Data.IDataReader m_rd; + private readonly IDataReader m_rd; private readonly long m_blocksize; - public BlockEntry(System.Data.IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; } - public long Offset { get { return m_rd.GetInt64(3) * m_blocksize; } } - public long Index { get { return m_rd.GetInt64(3); } } - public long Size { get { return m_rd.GetInt64(5); } } - public string Hash { get { return m_rd.GetString(4); } } + public BlockEntry(IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; } + public long Offset { get { return m_rd.ConvertValueToInt64(3) * m_blocksize; } } + public long Index { get { return m_rd.ConvertValueToInt64(3); } } + public long Size { get { return m_rd.ConvertValueToInt64(5); } } + public string Hash { get { return m_rd.ConvertValueToString(4); } } } - private readonly System.Data.IDataReader m_rd; + private readonly IDataReader m_rd; private readonly long m_blocksize; - public FastSource(System.Data.IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; MoreData = true; } + public FastSource(IDataReader rd, long blocksize) { m_rd = rd; m_blocksize = blocksize; MoreData = true; } public bool MoreData { get; private set; } - public string TargetPath { get { return m_rd.GetValue(0).ToString(); } } - public long TargetFileID { get { return m_rd.GetInt64(2); } } - public string SourcePath { get { return m_rd.GetValue(1).ToString(); } } - + public string TargetPath { get { return m_rd.ConvertValueToString(0); } } + public long TargetFileID { get { return m_rd.ConvertValueToInt64(2); } } + public string SourcePath { get { return m_rd.ConvertValueToString(1); } } + public IEnumerable Blocks { get { - var tid = this.TargetFileID; - + var tid = TargetFileID; + do { yield return new BlockEntry(m_rd, m_blocksize); - } while((MoreData = m_rd.Read()) && tid == this.TargetFileID); - + } while ((MoreData = m_rd.Read()) && tid == TargetFileID); + } } } public IEnumerable GetFilesAndSourceBlocksFast(long blocksize) { - var latestBlockTable = "LatestBlocksetIds-" + m_temptabsetguid; - var whereclause = string.Format(@" ""{0}"".""ID"" = ""{1}"".""FileID"" AND ""{1}"".""Restored"" = 0 AND ""{1}"".""Metadata"" = 0 AND ""{0}"".""TargetPath"" != ""{0}"".""Path"" ", m_tempfiletable, m_tempblocktable); - var sourcePaths = string.Format(@"SELECT DISTINCT ""{0}"".""Path"" FROM ""{0}"", ""{1}"" WHERE " + whereclause, m_tempfiletable, m_tempblocktable); - var sources = string.Format(@"SELECT DISTINCT ""{0}"".""TargetPath"", ""{0}"".""Path"", ""{0}"".""ID"", ""{1}"".""Index"", ""{1}"".""Hash"", ""{1}"".""Size"" FROM ""{0}"", ""{1}"", ""{2}"" S, ""Block"", ""BlocksetEntry"" WHERE ""BlocksetEntry"".""BlocksetID"" = ""S"".""BlocksetID"" AND ""BlocksetEntry"".""BlockID"" = ""Block"".""ID"" AND ""{1}"".""Hash"" = ""Block"".""Hash"" AND ""{1}"".""Size"" = ""Block"".""Size"" AND ""S"".""Path"" = ""{0}"".""Path"" AND ""{1}"".""Index"" = ""BlocksetEntry"".""Index"" AND " + whereclause + @" ORDER BY ""{0}"".""ID"", ""{1}"".""Index"" ", m_tempfiletable, m_tempblocktable, latestBlockTable); - var latestBlocksetIds = @"SELECT ""File"".""Path"" AS ""PATH"", ""File"".""BlocksetID"" AS ""BlocksetID"", MAX(""Fileset"".""Timestamp"") AS ""Timestamp"" FROM ""Fileset"", ""FilesetEntry"", ""File"" WHERE ""FilesetEntry"".""FileID"" = ""File"".""ID"" AND ""FilesetEntry"".""FilesetID"" = ""Fileset"".""ID"" AND ""File"".""Path"" IN (" + sourcePaths + @") GROUP BY ""File"".""Path"" "; - - using (var cmd = m_connection.CreateCommand()) + using (var transaction = m_connection.BeginTransaction()) + using (var cmdReader = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(transaction)) { - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}"" ", latestBlockTable)); - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" AS {1}", latestBlockTable, latestBlocksetIds)); + cmd.SetCommandAndParameters(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET ""LocalSourceExists"" = 1 WHERE Path = @Path")); + using (var rd = cmdReader.ExecuteReader(FormatInvariant($@"SELECT DISTINCT ""{m_tempfiletable}"".""Path"" FROM ""{m_tempfiletable}"""))) + { + while (rd.Read()) + { + var sourcepath = rd.GetValue(0).ToString(); + if (SystemIO.IO_OS.FileExists(sourcepath)) + { + cmd.SetParameterValue("@Path", sourcepath); + cmd.ExecuteNonQuery(); + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "LocalSourceMissing", "Local source file not found: {0}", sourcepath); + } + } + } + + //This localSourceExists index will make the query engine to start by searching FileSet table. As the result is ordered by FileSet.ID, we will get the cursor "instantly" + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_tempfiletable}_LocalSourceExists"" ON ""{m_tempfiletable}"" (""LocalSourceExists"")")); + transaction.Commit(); } + m_latestblocktable = "LatestBlocksetIds-" + m_temptabsetguid; + + var whereclause = FormatInvariant($@" + ""{m_tempfiletable}"".""LocalSourceExists"" = 1 AND + ""{m_tempblocktable}"".""Restored"" = 0 AND ""{m_tempblocktable}"".""Metadata"" = 0 AND + ""{m_tempfiletable}"".""TargetPath"" != ""{m_tempfiletable}"".""Path"""); + + var latestBlocksetIds = FormatInvariant($@" + SELECT + ""File"".""Path"" AS ""PATH"", + ""File"".""BlocksetID"" AS ""BlocksetID"", + MAX(""Fileset"".""Timestamp"") AS ""Timestamp"" + FROM + ""File"", + ""FilesetEntry"", + ""Fileset"" + WHERE + ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND + ""FilesetEntry"".""FilesetID"" = ""Fileset"".""ID"" AND + ""File"".""Path"" IN + (SELECT DISTINCT + ""{m_tempfiletable}"".""Path"" + FROM + ""{m_tempfiletable}"", + ""{m_tempblocktable}"" + WHERE + ""{m_tempfiletable}"".""ID"" = ""{m_tempblocktable}"".""FileID"" AND + {whereclause}) + GROUP BY ""File"".""Path"""); + using (var cmd = m_connection.CreateCommand()) - using(var rd = cmd.ExecuteReader(sources)) + { + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_latestblocktable}"" ")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{m_latestblocktable}"" AS {latestBlocksetIds}")); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE INDEX ""{m_latestblocktable}_path"" ON ""{m_latestblocktable}"" (""Path"")")); + + cmd.ExecuteNonQuery(FormatInvariant($@"UPDATE ""{m_tempfiletable}"" SET LatestBlocksetId = (SELECT BlocksetId FROM ""{m_latestblocktable}"" WHERE Path = ""{m_tempfiletable}"".Path)")); + } + + var sources = FormatInvariant($@" +SELECT DISTINCT + ""{m_tempfiletable}"".""TargetPath"", + ""{m_tempfiletable}"".""Path"", + ""{m_tempfiletable}"".""ID"", + ""{m_tempblocktable}"".""Index"", + ""{m_tempblocktable}"".""Hash"", + ""{m_tempblocktable}"".""Size"" +FROM + ""{m_tempfiletable}"", + ""{m_tempblocktable}"", + ""BlocksetEntry"" +WHERE + ""{m_tempfiletable}"".""ID"" = ""{m_tempblocktable}"".""FileID"" AND + ""BlocksetEntry"".""BlocksetID"" = ""{m_tempfiletable}"".""LatestBlocksetID"" AND + ""BlocksetEntry"".""BlockID"" = ""{m_tempblocktable}"".""BlockID"" AND + ""BlocksetEntry"".""Index"" = ""{m_tempblocktable}"".""Index"" AND + {whereclause} +ORDER BY ""{m_tempfiletable}"".""ID"", ""{m_tempblocktable}"".""Index"" "); + + using (var cmd = m_connection.CreateCommand()) + using (var rd = cmd.ExecuteReader(sources)) { if (rd.Read()) { - var more = false; + bool more; do { var n = new FastSource(rd, blocksize); var tid = n.TargetFileID; yield return n; - + more = n.MoreData; - while(more && n.TargetFileID == tid) + while (more && n.TargetFileID == tid) more = rd.Read(); } while (more); } - } + } } } diff --git a/Duplicati/Library/Main/Database/LocalTestDatabase.cs b/Duplicati/Library/Main/Database/LocalTestDatabase.cs index 783aad2b4..412f6662d 100644 --- a/Duplicati/Library/Main/Database/LocalTestDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalTestDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,6 +20,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Data; using System.Linq; using System.Collections.Generic; @@ -29,20 +30,20 @@ namespace Duplicati.Library.Main.Database { public LocalTestDatabase(string path) : base(path, "Test", true) - { + { } - + public LocalTestDatabase(LocalDatabase parent) : base(parent) - { + { } - + public void UpdateVerificationCount(string name) { - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) cmd.ExecuteNonQuery(@"UPDATE ""RemoteVolume"" SET ""VerificationCount"" = MAX(1, CASE WHEN ""VerificationCount"" <= 0 THEN (SELECT MAX(""VerificationCount"") FROM ""RemoteVolume"") ELSE ""VerificationCount"" + 1 END) WHERE ""Name"" = ?", name); } - + private class RemoteVolume : IRemoteVolume { public long ID { get; private set; } @@ -50,17 +51,17 @@ namespace Duplicati.Library.Main.Database public long Size { get; private set; } public string Hash { get; private set; } public long VerificationCount { get; private set; } - - public RemoteVolume(System.Data.IDataReader rd) + + public RemoteVolume(IDataReader rd) { - this.ID = rd.GetInt64(0); - this.Name = rd.GetString(1); - this.Size = rd.ConvertValueToInt64(2); - this.Hash = rd.ConvertValueToString(3); - this.VerificationCount = rd.ConvertValueToInt64(4); + ID = rd.GetInt64(0); + Name = rd.GetString(1); + Size = rd.ConvertValueToInt64(2); + Hash = rd.ConvertValueToString(3); + VerificationCount = rd.ConvertValueToInt64(4); } } - + private IEnumerable FilterByVerificationCount(IEnumerable volumes, long samples, long maxverification) { var rnd = new Random(); @@ -69,7 +70,7 @@ namespace Duplicati.Library.Main.Database var res = (from n in volumes where n.VerificationCount == 0 select n).ToList(); while (res.Count > samples) res.RemoveAt(rnd.Next(0, res.Count)); - + // Quick exit if we are done if (res.Count == samples) return res; @@ -81,8 +82,8 @@ namespace Duplicati.Library.Main.Database { var max = starved.Select(x => x.VerificationCount).Max(); var min = starved.Select(x => x.VerificationCount).Min(); - - for(var i = min; i <= max; i++) + + for (var i = min; i <= max; i++) { var p = starved.Where(x => x.VerificationCount == i).ToList(); while (res.Count < samples && p.Count > 0) @@ -92,12 +93,12 @@ namespace Duplicati.Library.Main.Database p.RemoveAt(n); } } - + // Quick exit if we are done if (res.Count == samples) return res; } - + if (maxverification > 0) { // Last is the items that are verified mostly @@ -109,16 +110,16 @@ namespace Duplicati.Library.Main.Database remainder.RemoveAt(n); } } - + return res; } - + public IEnumerable SelectTestTargets(long samples, Options options) { var tp = GetFilelistWhereClause(options.Time, options.Version); - + samples = Math.Max(1, samples); - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand()) { // Select any broken items using (var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE (""State"" = ? OR ""State"" = ?) AND (""Hash"" = '' OR ""Hash"" IS NULL OR ""Size"" <= 0) ", RemoteVolumeState.Verified.ToString(), RemoteVolumeState.Uploaded.ToString())) @@ -127,179 +128,164 @@ namespace Duplicati.Library.Main.Database //Grab the max value var max = cmd.ExecuteScalarInt64(@"SELECT MAX(""VerificationCount"") FROM ""RemoteVolume""", 0); - + //First we select some filesets var files = new List(); var whereClause = string.IsNullOrEmpty(tp.Item1) ? " WHERE " : (" " + tp.Item1 + " AND "); - using(var rd = cmd.ExecuteReader(@"SELECT ""A"".""VolumeID"", ""A"".""Name"", ""A"".""Size"", ""A"".""Hash"", ""A"".""VerificationCount"" FROM (SELECT ""ID"" AS ""VolumeID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""State"" IN (?, ?)) A, ""Fileset"" " + whereClause + @" ""A"".""VolumeID"" = ""Fileset"".""VolumeID"" ORDER BY ""Fileset"".""Timestamp"" ", RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString(), tp.Item2)) + using (var rd = cmd.SetCommandAndParameters(@"SELECT ""A"".""VolumeID"", ""A"".""Name"", ""A"".""Size"", ""A"".""Hash"", ""A"".""VerificationCount"" FROM (SELECT ""ID"" AS ""VolumeID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""State"" IN (@State1, @State2)) A, ""Fileset"" " + whereClause + @" ""A"".""VolumeID"" = ""Fileset"".""VolumeID"" ORDER BY ""Fileset"".""Timestamp"" ") + .SetParameterValue("@State1", RemoteVolumeState.Uploaded.ToString()) + .SetParameterValue("@State2", RemoteVolumeState.Verified.ToString()) + .SetParameterValues(tp.Item2) + .ExecuteReader()) while (rd.Read()) files.Add(new RemoteVolume(rd)); - + if (files.Count == 0) yield break; if (string.IsNullOrEmpty(tp.Item1)) files = FilterByVerificationCount(files, samples, max).ToList(); - - foreach(var f in files) + + foreach (var f in files) yield return f; - + //Then we select some index files files.Clear(); - - using(var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""Type"" = ? AND ""State"" IN (?, ?)", RemoteVolumeType.Index.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) + + using (var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""Type"" = ? AND ""State"" IN (?, ?)", RemoteVolumeType.Index.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) while (rd.Read()) files.Add(new RemoteVolume(rd)); - - foreach(var f in FilterByVerificationCount(files, samples, max)) + + foreach (var f in FilterByVerificationCount(files, samples, max)) yield return f; if (options.FullRemoteVerification == Options.RemoteTestStrategy.ListAndIndexes) - yield break; + yield break; //And finally some block files files.Clear(); - - using(var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""Type"" = ? AND ""State"" IN (?, ?)", RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) + + using (var rd = cmd.ExecuteReader(@"SELECT ""ID"", ""Name"", ""Size"", ""Hash"", ""VerificationCount"" FROM ""Remotevolume"" WHERE ""Type"" = ? AND ""State"" IN (?, ?)", RemoteVolumeType.Blocks.ToString(), RemoteVolumeState.Uploaded.ToString(), RemoteVolumeState.Verified.ToString())) while (rd.Read()) files.Add(new RemoteVolume(rd)); - - foreach(var f in FilterByVerificationCount(files, samples, max)) + + foreach (var f in FilterByVerificationCount(files, samples, max)) yield return f; } } - + private abstract class Basiclist : IDisposable { - protected readonly System.Data.IDbConnection m_connection; - protected readonly string m_volumename; - protected string m_tablename; - protected System.Data.IDbTransaction m_transaction; - protected System.Data.IDbCommand m_insertCommand; + protected readonly IDbConnection m_connection; + protected readonly string m_volumename; + protected string m_tablename; + protected IDbTransaction m_transaction; + protected IDbCommand m_insertCommand; - protected Basiclist(System.Data.IDbConnection connection, string volumename, string tablePrefix, string tableFormat, string insertCommand, int insertArguments) - { + protected Basiclist(IDbConnection connection, string volumename, string tablePrefix, string tableFormat, string insertCommand) + { m_connection = connection; m_volumename = volumename; m_transaction = m_connection.BeginTransaction(); var tablename = tablePrefix + "-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - using(var cmd = m_connection.CreateCommand()) + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - cmd.ExecuteNonQuery(string.Format(@"CREATE TEMPORARY TABLE ""{0}"" {1}", tablename, tableFormat)); + cmd.ExecuteNonQuery(FormatInvariant($@"CREATE TEMPORARY TABLE ""{tablename}"" {tableFormat}")); m_tablename = tablename; } - - m_insertCommand = m_connection.CreateCommand(); - m_insertCommand.Transaction = m_transaction; - m_insertCommand.CommandText = string.Format(@"INSERT INTO ""{0}"" {1}", m_tablename, insertCommand); - m_insertCommand.AddParameters(insertArguments); + + m_insertCommand = m_connection.CreateCommand(m_transaction, FormatInvariant($@"INSERT INTO ""{m_tablename}"" {insertCommand}")); } public virtual void Dispose() { if (m_tablename != null) - try - { - using(var cmd = m_connection.CreateCommand()) - { - cmd.Transaction = m_transaction; - cmd.ExecuteNonQuery(string.Format(@"DROP TABLE IF EXISTS ""{0}""", m_tablename)); - } + try + { + using (var cmd = m_connection.CreateCommand(m_transaction)) + cmd.ExecuteNonQuery(FormatInvariant($@"DROP TABLE IF EXISTS ""{m_tablename}""")); } - catch {} + catch { } finally { m_tablename = null; } - - if (m_insertCommand != null) - try { m_insertCommand.Dispose(); } - catch {} - finally { m_insertCommand = null; } - - if (m_transaction != null) - try { m_transaction.Rollback(); } - catch {} - finally { m_transaction = null; } + + m_insertCommand?.Dispose(); + m_transaction?.Rollback(); } } - + public interface IFilelist : IDisposable { void Add(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time); - IEnumerable> Compare(); + IEnumerable> Compare(); } - + private class Filelist : Basiclist, IFilelist { private const string TABLE_PREFIX = "Filelist"; private const string TABLE_FORMAT = @"(""Path"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL, ""Hash"" TEXT NULL, ""Metasize"" INTEGER NOT NULL, ""Metahash"" TEXT NOT NULL)"; private const string INSERT_COMMAND = @"(""Path"", ""Size"", ""Hash"", ""Metasize"", ""Metahash"") VALUES (?,?,?,?,?)"; - private const int INSERT_ARGUMENTS = 5; - - public Filelist(System.Data.IDbConnection connection, string volumename) - : base(connection, volumename, Filelist.TABLE_PREFIX, Filelist.TABLE_FORMAT, Filelist.INSERT_COMMAND, Filelist.INSERT_ARGUMENTS) - { + public Filelist(IDbConnection connection, string volumename) + : base(connection, volumename, TABLE_PREFIX, TABLE_FORMAT, INSERT_COMMAND) + { } - + public void Add(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time) { m_insertCommand.SetParameterValue(0, path); - m_insertCommand.SetParameterValue(1, hash == null ? - 1: size); + m_insertCommand.SetParameterValue(1, hash == null ? -1 : size); m_insertCommand.SetParameterValue(2, hash); m_insertCommand.SetParameterValue(3, metasize); m_insertCommand.SetParameterValue(4, metahash); m_insertCommand.ExecuteNonQuery(); } - + public IEnumerable> Compare() { var cmpName = "CmpTable-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - - var create = @"CREATE TEMPORARY TABLE ""{1}"" AS SELECT ""A"".""Path"" AS ""Path"", CASE WHEN ""B"".""Fullhash"" IS NULL THEN -1 ELSE ""B"".""Length"" END AS ""Size"", ""B"".""Fullhash"" AS ""Hash"", ""C"".""Length"" AS ""Metasize"", ""C"".""Fullhash"" AS ""Metahash"" FROM (SELECT ""File"".""Path"", ""File"".""BlocksetID"" AS ""FileBlocksetID"", ""Metadataset"".""BlocksetID"" AS ""MetadataBlocksetID"" from ""Remotevolume"", ""Fileset"", ""FilesetEntry"", ""File"", ""Metadataset"" WHERE ""Remotevolume"".""Name"" = ? AND ""Fileset"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Fileset"".""ID"" = ""FilesetEntry"".""FilesetID"" AND ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""MetadataID"" = ""Metadataset"".""ID"") A LEFT OUTER JOIN ""Blockset"" B ON ""B"".""ID"" = ""A"".""FileBlocksetID"" LEFT OUTER JOIN ""Blockset"" C ON ""C"".""ID""=""A"".""MetadataBlocksetID"" "; - var extra = @"SELECT ? AS ""Type"", ""{0}"".""Path"" AS ""Path"" FROM ""{0}"" WHERE ""{0}"".""Path"" NOT IN ( SELECT ""Path"" FROM ""{1}"" )"; - var missing = @"SELECT ? AS ""Type"", ""Path"" AS ""Path"" FROM ""{1}"" WHERE ""Path"" NOT IN (SELECT ""Path"" FROM ""{0}"")"; - var modified = @"SELECT ? AS ""Type"", ""E"".""Path"" AS ""Path"" FROM ""{0}"" E, ""{1}"" D WHERE ""D"".""Path"" = ""E"".""Path"" AND (""D"".""Size"" != ""E"".""Size"" OR ""D"".""Hash"" != ""E"".""Hash"" OR ""D"".""Metasize"" != ""E"".""Metasize"" OR ""D"".""Metahash"" != ""E"".""Metahash"") "; - var drop = @"DROP TABLE IF EXISTS ""{1}"" "; - - using(var cmd = m_connection.CreateCommand()) + + var create = FormatInvariant($@"CREATE TEMPORARY TABLE ""{cmpName}"" AS SELECT ""A"".""Path"" AS ""Path"", CASE WHEN ""B"".""Fullhash"" IS NULL THEN -1 ELSE ""B"".""Length"" END AS ""Size"", ""B"".""Fullhash"" AS ""Hash"", ""C"".""Length"" AS ""Metasize"", ""C"".""Fullhash"" AS ""Metahash"" FROM (SELECT ""File"".""Path"", ""File"".""BlocksetID"" AS ""FileBlocksetID"", ""Metadataset"".""BlocksetID"" AS ""MetadataBlocksetID"" from ""Remotevolume"", ""Fileset"", ""FilesetEntry"", ""File"", ""Metadataset"" WHERE ""Remotevolume"".""Name"" = ? AND ""Fileset"".""VolumeID"" = ""Remotevolume"".""ID"" AND ""Fileset"".""ID"" = ""FilesetEntry"".""FilesetID"" AND ""File"".""ID"" = ""FilesetEntry"".""FileID"" AND ""File"".""MetadataID"" = ""Metadataset"".""ID"") A LEFT OUTER JOIN ""Blockset"" B ON ""B"".""ID"" = ""A"".""FileBlocksetID"" LEFT OUTER JOIN ""Blockset"" C ON ""C"".""ID""=""A"".""MetadataBlocksetID"" "); + var extra = FormatInvariant($@"SELECT ? AS ""Type"", ""{m_tablename}"".""Path"" AS ""Path"" FROM ""{m_tablename}"" WHERE ""{m_tablename}"".""Path"" NOT IN ( SELECT ""Path"" FROM ""{cmpName}"" )"); + var missing = FormatInvariant($@"SELECT ? AS ""Type"", ""Path"" AS ""Path"" FROM ""{cmpName}"" WHERE ""Path"" NOT IN (SELECT ""Path"" FROM ""{m_tablename}"")"); + var modified = FormatInvariant($@"SELECT ? AS ""Type"", ""E"".""Path"" AS ""Path"" FROM ""{m_tablename}"" E, ""{cmpName}"" D WHERE ""D"".""Path"" = ""E"".""Path"" AND (""D"".""Size"" != ""E"".""Size"" OR ""D"".""Hash"" != ""E"".""Hash"" OR ""D"".""Metasize"" != ""E"".""Metasize"" OR ""D"".""Metahash"" != ""E"".""Metahash"") "); + var drop = FormatInvariant($@"DROP TABLE IF EXISTS ""{cmpName}"" "); + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - try { - cmd.ExecuteNonQuery(string.Format(create, m_tablename, cmpName), m_volumename); - using(var rd = cmd.ExecuteReader(string.Format(extra + " UNION " + missing + " UNION " + modified, m_tablename, cmpName), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) - while(rd.Read()) + cmd.ExecuteNonQuery(create, m_volumename); + using (var rd = cmd.ExecuteReader(FormatInvariant($"{extra} UNION {missing} UNION {modified}"), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) + while (rd.Read()) yield return new KeyValuePair((Duplicati.Library.Interface.TestEntryStatus)rd.GetInt64(0), rd.GetString(1)); - + } finally { - try { cmd.ExecuteNonQuery(string.Format(drop, m_tablename, cmpName)); } - catch {} + try { cmd.ExecuteNonQuery(drop); } + catch { } } } } } - - public interface IIndexlist: IDisposable + + public interface IIndexlist : IDisposable { void AddBlockLink(string filename, string hash, long length); IEnumerable> Compare(); } - + private class Indexlist : Basiclist, IIndexlist { private const string TABLE_PREFIX = "Indexlist"; private const string TABLE_FORMAT = @"(""Name"" TEXT NOT NULL, ""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL)"; private const string INSERT_COMMAND = @"(""Name"", ""Hash"", ""Size"") VALUES (?,?,?)"; - private const int INSERT_ARGUMENTS = 3; - - public Indexlist(System.Data.IDbConnection connection, string volumename) - : base(connection, volumename, Indexlist.TABLE_PREFIX, Indexlist.TABLE_FORMAT, Indexlist.INSERT_COMMAND, Indexlist.INSERT_ARGUMENTS) + + public Indexlist(IDbConnection connection, string volumename) + : base(connection, volumename, TABLE_PREFIX, TABLE_FORMAT, INSERT_COMMAND) { } - + public void AddBlockLink(string filename, string hash, long length) { m_insertCommand.SetParameterValue(0, filename); @@ -307,93 +293,89 @@ namespace Duplicati.Library.Main.Database m_insertCommand.SetParameterValue(2, length); m_insertCommand.ExecuteNonQuery(); } - + public IEnumerable> Compare() { var cmpName = "CmpTable-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); - var create = @"CREATE TEMPORARY TABLE ""{1}"" AS SELECT ""A"".""Name"", ""A"".""Hash"", ""A"".""Size"" FROM ""Remotevolume"" A, ""Remotevolume"" B, ""IndexBlockLink"" WHERE ""B"".""Name"" = ? AND ""A"".""ID"" = ""IndexBlockLink"".""BlockVolumeID"" AND ""B"".""ID"" = ""IndexBlockLink"".""IndexVolumeID"" "; - var extra = @"SELECT ? AS ""Type"", ""{0}"".""Name"" AS ""Name"" FROM ""{0}"" WHERE ""{0}"".""Name"" NOT IN ( SELECT ""Name"" FROM ""{1}"" )"; - var missing = @"SELECT ? AS ""Type"", ""Name"" AS ""Name"" FROM ""{1}"" WHERE ""Name"" NOT IN (SELECT ""Name"" FROM ""{0}"")"; - var modified = @"SELECT ? AS ""Type"", ""E"".""Name"" AS ""Name"" FROM ""{0}"" E, ""{1}"" D WHERE ""D"".""Name"" = ""E"".""Name"" AND (""D"".""Hash"" != ""E"".""Hash"" OR ""D"".""Size"" != ""E"".""Size"") "; - var drop = @"DROP TABLE IF EXISTS ""{1}"" "; - - using(var cmd = m_connection.CreateCommand()) + var create = FormatInvariant($@"CREATE TEMPORARY TABLE ""{cmpName}"" AS SELECT ""A"".""Name"", ""A"".""Hash"", ""A"".""Size"" FROM ""Remotevolume"" A, ""Remotevolume"" B, ""IndexBlockLink"" WHERE ""B"".""Name"" = ? AND ""A"".""ID"" = ""IndexBlockLink"".""BlockVolumeID"" AND ""B"".""ID"" = ""IndexBlockLink"".""IndexVolumeID"" "); + var extra = FormatInvariant($@"SELECT ? AS ""Type"", ""{m_tablename}"".""Name"" AS ""Name"" FROM ""{m_tablename}"" WHERE ""{m_tablename}"".""Name"" NOT IN ( SELECT ""Name"" FROM ""{cmpName}"" )"); + var missing = FormatInvariant($@"SELECT ? AS ""Type"", ""Name"" AS ""Name"" FROM ""{cmpName}"" WHERE ""Name"" NOT IN (SELECT ""Name"" FROM ""{m_tablename}"")"); + var modified = FormatInvariant($@"SELECT ? AS ""Type"", ""E"".""Name"" AS ""Name"" FROM ""{m_tablename}"" E, ""{cmpName}"" D WHERE ""D"".""Name"" = ""E"".""Name"" AND (""D"".""Hash"" != ""E"".""Hash"" OR ""D"".""Size"" != ""E"".""Size"") "); + var drop = FormatInvariant($@"DROP TABLE IF EXISTS ""{cmpName}"" "); + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - try { - cmd.ExecuteNonQuery(string.Format(create, m_tablename, cmpName), m_volumename); - using(var rd = cmd.ExecuteReader(string.Format(extra + " UNION " + missing + " UNION " + modified, m_tablename, cmpName), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) - while(rd.Read()) - yield return new KeyValuePair((Duplicati.Library.Interface.TestEntryStatus)rd.GetInt64(0), rd.GetString(1) ); - + cmd.ExecuteNonQuery(create, m_volumename); + using (var rd = cmd.ExecuteReader(FormatInvariant($"{extra} UNION {missing} UNION {modified}"), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) + while (rd.Read()) + yield return new KeyValuePair((Duplicati.Library.Interface.TestEntryStatus)rd.GetInt64(0), rd.GetString(1)); + } finally { - try { cmd.ExecuteNonQuery(string.Format(drop, m_tablename, cmpName)); } - catch {} + try { cmd.ExecuteNonQuery(drop); } + catch { } } } } } - - public interface IBlocklist: IDisposable + + public interface IBlocklist : IDisposable { void AddBlock(string key, long value); IEnumerable> Compare(); } - private class Blocklist : Basiclist, IBlocklist - { + private class Blocklist : Basiclist, IBlocklist + { private const string TABLE_PREFIX = "Blocklist"; private const string TABLE_FORMAT = @"(""Hash"" TEXT NOT NULL, ""Size"" INTEGER NOT NULL)"; private const string INSERT_COMMAND = @"(""Hash"", ""Size"") VALUES (?,?)"; - private const int INSERT_ARGUMENTS = 2; - - public Blocklist(System.Data.IDbConnection connection, string volumename) - : base(connection, volumename, Blocklist.TABLE_PREFIX, Blocklist.TABLE_FORMAT, Blocklist.INSERT_COMMAND, Blocklist.INSERT_ARGUMENTS) - { } - + + public Blocklist(IDbConnection connection, string volumename) + : base(connection, volumename, TABLE_PREFIX, TABLE_FORMAT, INSERT_COMMAND) + { } + public void AddBlock(string hash, long size) { m_insertCommand.SetParameterValue(0, hash); m_insertCommand.SetParameterValue(1, size); m_insertCommand.ExecuteNonQuery(); } - - public IEnumerable> Compare() + + public IEnumerable> Compare() { var cmpName = "CmpTable-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); var curBlocks = @"SELECT ""Block"".""Hash"" AS ""Hash"", ""Block"".""Size"" AS ""Size"" FROM ""Remotevolume"", ""Block"" WHERE ""Remotevolume"".""Name"" = ? AND ""Remotevolume"".""ID"" = ""Block"".""VolumeID"""; + var duplBlocks = @"SELECT ""Block"".""Hash"" AS ""Hash"", ""Block"".""Size"" AS ""Size"" FROM ""DuplicateBlock"", ""Block"" WHERE ""DuplicateBlock"".""VolumeID"" = (SELECT ""ID"" FROM ""RemoteVolume"" WHERE ""Name"" = ?) AND ""Block"".""ID"" = ""DuplicateBlock"".""BlockID"""; var delBlocks = @"SELECT ""DeletedBlock"".""Hash"" AS ""Hash"", ""DeletedBlock"".""Size"" AS ""Size"" FROM ""DeletedBlock"", ""RemoteVolume"" WHERE ""RemoteVolume"".""Name"" = ? AND ""RemoteVolume"".""ID"" = ""DeletedBlock"".""VolumeID"""; - var create = @"CREATE TEMPORARY TABLE ""{0}"" AS SELECT DISTINCT ""Hash"" AS ""Hash"", ""Size"" AS ""Size"" FROM ({1} UNION {2})"; - var extra = @"SELECT ? AS ""Type"", ""{0}"".""Hash"" AS ""Hash"" FROM ""{0}"" WHERE ""{0}"".""Hash"" NOT IN ( SELECT ""Hash"" FROM ""{1}"" )"; - var missing = @"SELECT ? AS ""Type"", ""Hash"" AS ""Hash"" FROM ""{1}"" WHERE ""Hash"" NOT IN (SELECT ""Hash"" FROM ""{0}"")"; - var modified = @"SELECT ? AS ""Type"", ""E"".""Hash"" AS ""Hash"" FROM ""{0}"" E, ""{1}"" D WHERE ""D"".""Hash"" = ""E"".""Hash"" AND ""D"".""Size"" != ""E"".""Size"" "; - var drop = @"DROP TABLE IF EXISTS ""{1}"" "; - - using(var cmd = m_connection.CreateCommand()) + var create = FormatInvariant($@"CREATE TEMPORARY TABLE ""{cmpName}"" AS SELECT DISTINCT ""Hash"" AS ""Hash"", ""Size"" AS ""Size"" FROM ({curBlocks} UNION {delBlocks} UNION {duplBlocks})"); + var extra = FormatInvariant($@"SELECT ? AS ""Type"", ""{m_tablename}"".""Hash"" AS ""Hash"" FROM ""{m_tablename}"" WHERE ""{m_tablename}"".""Hash"" NOT IN ( SELECT ""Hash"" FROM ""{cmpName}"" )"); + var missing = FormatInvariant($@"SELECT ? AS ""Type"", ""Hash"" AS ""Hash"" FROM ""{cmpName}"" WHERE ""Hash"" NOT IN (SELECT ""Hash"" FROM ""{m_tablename}"")"); + var modified = FormatInvariant($@"SELECT ? AS ""Type"", ""E"".""Hash"" AS ""Hash"" FROM ""{m_tablename}"" E, ""{cmpName}"" D WHERE ""D"".""Hash"" = ""E"".""Hash"" AND ""D"".""Size"" != ""E"".""Size"" "); + var drop = FormatInvariant($@"DROP TABLE IF EXISTS ""{cmpName}"" "); + + using (var cmd = m_connection.CreateCommand(m_transaction)) { - cmd.Transaction = m_transaction; - try { - cmd.ExecuteNonQuery(string.Format(create, cmpName, curBlocks, delBlocks), m_volumename, m_volumename); - using(var rd = cmd.ExecuteReader(string.Format(extra + " UNION " + missing + " UNION " + modified, m_tablename, cmpName), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) - while(rd.Read()) - yield return new KeyValuePair((Duplicati.Library.Interface.TestEntryStatus)rd.GetInt64(0), rd.GetString(1) ); - + cmd.ExecuteNonQuery(create, m_volumename, m_volumename, m_volumename); + using (var rd = cmd.ExecuteReader(FormatInvariant($"{extra} UNION {missing} UNION {modified}"), (int)Library.Interface.TestEntryStatus.Extra, (int)Library.Interface.TestEntryStatus.Missing, (int)Library.Interface.TestEntryStatus.Modified)) + while (rd.Read()) + yield return new KeyValuePair((Duplicati.Library.Interface.TestEntryStatus)rd.GetInt64(0), rd.GetString(1)); + } finally { - try { cmd.ExecuteNonQuery(string.Format(drop, m_tablename, cmpName)); } - catch {} + try { cmd.ExecuteNonQuery(drop); } + catch { } } } } } - + public IFilelist CreateFilelist(string name) { return new Filelist(m_connection, name); diff --git a/Duplicati/Library/Main/Database/RemoteVolume.cs b/Duplicati/Library/Main/Database/RemoteVolume.cs index a16cb7a39..92f3bc77b 100644 --- a/Duplicati/Library/Main/Database/RemoteVolume.cs +++ b/Duplicati/Library/Main/Database/RemoteVolume.cs @@ -1,51 +1,47 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.Main.Database { public class RemoteVolume : IRemoteVolume { - public RemoteVolume(Library.Interface.IFileEntry file, string hash = null) + public RemoteVolume(Interface.IFileEntry file, string hash = null) { - this.Name = file.Name; - this.Size = file.Size; - this.Hash = hash; - this.File = file; + Name = file.Name; + Size = file.Size; + Hash = hash; + File = file; } public RemoteVolume(string name, string hash, long size) { - this.Name = name; - this.Hash = hash; - this.Size = size; - this.File = null; + Name = name; + Hash = hash; + Size = size; + File = null; } public string Name { get; private set; } public string Hash { get; private set; } public long Size { get; private set; } - public Library.Interface.IFileEntry File { get; private set; } + public Interface.IFileEntry File { get; private set; } } } diff --git a/Duplicati/Library/Main/Database/RemoteVolumeEntry.cs b/Duplicati/Library/Main/Database/RemoteVolumeEntry.cs index 13e55bc64..fb17607e9 100644 --- a/Duplicati/Library/Main/Database/RemoteVolumeEntry.cs +++ b/Duplicati/Library/Main/Database/RemoteVolumeEntry.cs @@ -1,27 +1,24 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace Duplicati.Library.Main.Database { diff --git a/Duplicati/Library/Main/Database/ReusableTransaction.cs b/Duplicati/Library/Main/Database/ReusableTransaction.cs new file mode 100644 index 000000000..d83e6c9b2 --- /dev/null +++ b/Duplicati/Library/Main/Database/ReusableTransaction.cs @@ -0,0 +1,70 @@ +using System; +using System.Data; + +#nullable enable + +namespace Duplicati.Library.Main.Database; + +/// +/// Wraps a transaction so it can be comitted and restarted +/// +internal class ReusableTransaction : IDisposable +{ + /// + /// The tag used for logging + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(LocalDatabase)); + + /// + /// The database to use + /// + private readonly LocalDatabase m_db; + /// + /// The current transaction + /// + private IDbTransaction? m_transaction; + + /// + /// Creates a new reusable transaction + /// + /// The database to use + public ReusableTransaction(LocalDatabase db, IDbTransaction? transaction = null) + { + m_db = db; + m_transaction = transaction ?? db.BeginTransaction(); + } + + /// + /// The current transaction + /// + public IDbTransaction Transaction => m_transaction ?? throw new InvalidOperationException("Transaction is disposed"); + + /// + /// Commits the current transaction and optionally restarts it + /// + /// The log message to use + /// True if the transaction should be restarted + public void Commit(string? message = null, bool restart = true) + { + if (m_transaction == null) + throw new InvalidOperationException("Transaction is already disposed"); + + if (m_transaction != null) + { + using (var timer = string.IsNullOrWhiteSpace(message) ? null : new Logging.Timer(LOGTAG, message, "CommitTransaction")) + m_transaction.Commit(); + m_transaction.Dispose(); + m_transaction = null; + } + + if (restart) + m_transaction = m_db.BeginTransaction(); + } + + /// + public void Dispose() + { + m_transaction?.Dispose(); + m_transaction = null; + } +} diff --git a/Duplicati/Library/Main/Duplicati.Library.Main.csproj b/Duplicati/Library/Main/Duplicati.Library.Main.csproj index 53480660c..031c46fd1 100644 --- a/Duplicati/Library/Main/Duplicati.Library.Main.csproj +++ b/Duplicati/Library/Main/Duplicati.Library.Main.csproj @@ -3,12 +3,13 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + Duplicati.Library.Main - - - - + + + + @@ -18,8 +19,9 @@ + - + @@ -34,8 +36,9 @@ + - + PreserveNewest @@ -43,8 +46,9 @@ - + + diff --git a/Duplicati/Library/Main/Enums.cs b/Duplicati/Library/Main/Enums.cs index 2ad5e2e58..3486c098e 100644 --- a/Duplicati/Library/Main/Enums.cs +++ b/Duplicati/Library/Main/Enums.cs @@ -1,27 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.Main { @@ -32,9 +28,11 @@ namespace Duplicati.Library.Main Get, Put, Delete, - CreateFolder + CreateFolder, + QuotaInfo, + WaitForEmpty } - + public enum BackendEventType { Started, @@ -150,5 +148,5 @@ namespace Duplicati.Library.Main /// The entry is a symbolic link /// Symlink - } + } } diff --git a/Duplicati/Library/Main/IBackendManager.cs b/Duplicati/Library/Main/IBackendManager.cs new file mode 100644 index 000000000..c9cd0372c --- /dev/null +++ b/Duplicati/Library/Main/IBackendManager.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; +using IFileEntry = Duplicati.Library.Interface.IFileEntry; + +#nullable enable + +namespace Duplicati.Library.Main; + +/// +/// Interface for the backend manager +/// +internal interface IBackendManager : IDisposable +{ + /// + /// Uploads a block volume to the backend, including an optional index volume + /// + /// The block volume to upload + /// The index volume to upload, if any + /// The action to call when the index volume is finished + /// Whether to wait for the upload to complete + /// The cancellation token + /// An awaitable task + Task PutAsync(VolumeWriterBase blockVolume, IndexVolumeWriter? indexVolume, Action? indexVolumeFinished, bool waitForComplete, CancellationToken cancelToken); + + /// + /// Uploads a file to the backend without encryption + /// + /// The name of the file to upload to + /// The file to upload + /// The cancellation token + /// An awaitable task + Task PutVerificationFileAsync(string remotename, TempFile tempFile, CancellationToken cancelToken); + + /// + /// Waits for the backend queue to be empty and flushes any pending messages to the database + /// + /// The database to write pending messages to + /// The transaction to use, if any + /// The cancellation token + /// An awaitable task + Task WaitForEmptyAsync(LocalDatabase database, IDbTransaction? transaction, CancellationToken cancellationToken); + + /// + /// Lists the files on the backend + /// + /// The cancellation token + /// An enumerable of file entries + Task> ListAsync(CancellationToken cancelToken); + + /// + /// Decrypts the given file and returns the decrypted file + /// + /// The file to decrypt + /// The name of the file. Used for detecting encryption algorithm if not specified in options or if it differs from the options + /// The Duplicati options + /// The decrypted file + TempFile DecryptFile(TempFile volume, string volume_name, Options options); + + /// + /// Deletes a file on the backend + /// + /// The name of the file to delete + /// The size of the file to delete, or -1 if not known + /// Whether to wait for the delete to complete + /// The cancellation token + /// An awaitable task + Task DeleteAsync(string remotename, long size, bool waitForComplete, CancellationToken cancelToken); + + /// + /// Gets the quota information for the backend + /// + /// The cancellation token + /// The quota information, or null if not available or disabled + Task GetQuotaInfoAsync(CancellationToken cancelToken); + + /// + /// Gets a file with the hash and size + /// + /// The name of the file to get + /// The hash of the file to get, or null if not known + /// The size of the file to get, or -1 if not known + /// The cancellation token + /// The file, hash, and size + Task<(TempFile File, string Hash, long Size)> GetWithInfoAsync(string remotename, string hash, long size, CancellationToken cancelToken); + + /// + /// Gets a file from the backend + /// + /// The name of the file to get + /// The hash of the file to get, or null if not known + /// The size of the file to get, or -1 if not known + /// The cancellation token + /// The downloaded file + Task GetAsync(string remotename, string hash, long size, CancellationToken cancelToken); + + /// + /// Gets a file from the backend without decrypting it + /// + /// The name of the remote volume + /// The hash of the volume + /// The size of the volume + /// The cancellation token + /// The downloaded file + Task GetDirectAsync(string remotename, string hash, long size, CancellationToken cancelToken); + + /// + /// Performs a download of the files specified, with pre-fetch to overlap the download and processing + /// + /// The volumes to download + /// The cancellation token + /// The downloaded files, hash, size, and name + IAsyncEnumerable<(TempFile File, string Hash, long Size, string Name)> GetFilesOverlappedAsync(IEnumerable volumes, CancellationToken cancelToken); + + /// + /// Gets the size of the last read operation + /// + long LastReadSize { get; } + + /// + /// Gets the size of the last write operation + /// + long LastWriteSize { get; } + +} diff --git a/Duplicati/Library/Main/IMetahash.cs b/Duplicati/Library/Main/IMetahash.cs index 1ea8eb675..108d055f3 100644 --- a/Duplicati/Library/Main/IMetahash.cs +++ b/Duplicati/Library/Main/IMetahash.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/LocaleChange.cs b/Duplicati/Library/Main/LocaleChange.cs index 402d240e1..253fd6605 100644 --- a/Duplicati/Library/Main/LocaleChange.cs +++ b/Duplicati/Library/Main/LocaleChange.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs b/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs deleted file mode 100644 index 73aadb9a3..000000000 --- a/Duplicati/Library/Main/Operation/Backup/BackendUploader.cs +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using CoCoL; -using Duplicati.Library.Interface; -using Duplicati.Library.Main.Operation.Common; -using Duplicati.Library.Main.Volumes; -using Duplicati.Library.Utility; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using static Duplicati.Library.Main.Operation.Common.BackendHandler; - -namespace Duplicati.Library.Main.Operation.Backup -{ - internal interface IUploadRequest - { - } - - internal class FlushRequest : IUploadRequest - { - public Task LastWriteSizeAsync { get { return m_tcs.Task; } } - private readonly TaskCompletionSource m_tcs = new TaskCompletionSource(); - - public void SetFlushed(long size) - { - m_tcs.TrySetResult(size); - } - - public void TrySetCanceled() - { - m_tcs.TrySetCanceled(); - } - } - - internal class IndexVolumeUploadRequest : IUploadRequest - { - public IndexVolumeWriter IndexVolume { get; private set; } - - public IndexVolumeUploadRequest(IndexVolumeWriter indexVolume) - { - IndexVolume = indexVolume; - } - } - - internal class FilesetUploadRequest : IUploadRequest - { - public FilesetVolumeWriter Fileset { get; private set; } - - public FilesetUploadRequest(FilesetVolumeWriter fileset) - { - Fileset = fileset; - } - } - - internal class VolumeUploadRequest : IUploadRequest - { - public FileEntryItem BlockEntry { get; } - public BlockVolumeWriter BlockVolume { get; } - public TemporaryIndexVolume IndexVolume { get; } - public Options Options { get; } - public BackupDatabase Database { get; } - - public VolumeUploadRequest(BlockVolumeWriter blockVolume, FileEntryItem blockEntry, TemporaryIndexVolume indexVolume, Options options, BackupDatabase database) - { - BlockVolume = blockVolume; - BlockEntry = blockEntry; - IndexVolume = indexVolume; - Options = options; - Database = database; - } - } - - /// - /// This class encapsulates all requests to the backend - /// and ensures that the AsynchronousUploadLimit is honored - /// - internal class BackendUploader - { - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private readonly Func m_backendFactory; - private CancellationTokenSource m_cancelTokenSource; - private readonly DatabaseCommon m_database; - private long m_initialUploadThrottleSpeed; - private int m_lastThrottleCheckTime; - private long m_lastUploadThrottleSpeed; - private int m_maxConcurrentUploads; - private readonly Options m_options; - private readonly FileProgressThrottler m_progressUpdater; - private readonly StatsCollector m_stats; - private readonly ITaskReader m_taskReader; - - public BackendUploader(Func backendFactory, Options options, DatabaseCommon database, ITaskReader taskReader, StatsCollector stats) - { - m_backendFactory = backendFactory; - m_options = options; - m_taskReader = taskReader; - m_stats = stats; - m_database = database; - m_progressUpdater = new FileProgressThrottler(stats, options.MaxUploadPrSecond); - } - - public Task Run() - { - return AutomationExtensions.RunTask(new - { - Input = Channels.BackendRequest.ForRead, - }, - - async self => - { - var workers = new List(); - m_maxConcurrentUploads = m_options.AsynchronousConcurrentUploadLimit <= 0 ? int.MaxValue : m_options.AsynchronousConcurrentUploadLimit; - m_initialUploadThrottleSpeed = m_options.AsynchronousConcurrentUploadLimit <= 0 ? int.MaxValue : m_options.MaxUploadPrSecond / m_maxConcurrentUploads; - var lastSize = -1L; - var uploadsInProgress = 0; - m_cancelTokenSource = new CancellationTokenSource(); - m_progressUpdater.Run(m_cancelTokenSource.Token); - var filesetsToUpload = new List(); - - try - { - while (!await self.Input.IsRetiredAsync && await m_taskReader.ProgressAsync) - { - var req = await self.Input.ReadAsync(); - - if (!await m_taskReader.ProgressAsync) - break; - - Worker worker = GetWorker(workers); - - if (req is VolumeUploadRequest volumeUpload) - { - if (volumeUpload.IndexVolume == null) - worker.Task = Task.Run(() => UploadFileAsync(volumeUpload.BlockEntry, worker, m_cancelTokenSource.Token)); - else - worker.Task = Task.Run(() => UploadBlockAndIndexAsync(volumeUpload, worker, m_cancelTokenSource.Token)); - - lastSize = volumeUpload.BlockVolume.SourceSize; - uploadsInProgress++; - } - else if (req is FilesetUploadRequest filesetUpload) - { - filesetsToUpload.Add(filesetUpload); - } - else if (req is IndexVolumeUploadRequest indexUpload) - { - worker.Task = Task.Run(() => UploadVolumeWriter(indexUpload.IndexVolume, worker, m_cancelTokenSource.Token)); - uploadsInProgress++; - } - else if (req is FlushRequest flush) - { - await WaitForWorkers(workers, flush).ConfigureAwait(false); - UploadFilesets(workers, filesetsToUpload); - await WaitForWorkers(workers, flush).ConfigureAwait(false); - - flush.SetFlushed(lastSize); - uploadsInProgress = 0; - break; - } - - if (uploadsInProgress >= m_maxConcurrentUploads) - { - await Task.WhenAny(workers.Select(w => w.Task)).ConfigureAwait(false); - uploadsInProgress--; - - var failedUploads = workers.Where(w => w.Task.IsFaulted).Select(w => GetInnerMostException(w.Task.Exception)).ToList(); - if (failedUploads.Any()) - { - if (failedUploads.Count == 1) - ExceptionDispatchInfo.Capture(failedUploads.First()).Throw(); - else - throw new AggregateException(failedUploads); - } - } - } - } - catch (Exception ex) when (!ex.IsRetiredException()) - { - m_cancelTokenSource.Cancel(); - try - { - await Task.WhenAll(workers.Select(w => w.Task)); - } - catch - { - // As we are cancelling all threads we do not need to alert the user to any of these exceptions. - } - finally - { - workers.ForEach(w => w.Dispose()); - } - throw; - } - - try - { - m_stats.SetBlocking(true); - await Task.WhenAll(workers.Select(w => w.Task)); - } - finally - { - m_stats.SetBlocking(false); - workers.ForEach(w => w.Dispose()); - } - }); - } - - private Worker GetWorker(List workers) - { - var worker = workers.FirstOrDefault(w => w.Task.IsCompleted && !w.Task.IsFaulted); - if (worker == null) - { - worker = new Worker(m_backendFactory()); - workers.Add(worker); - } - - return worker; - } - - private async Task WaitForWorkers(List workers, FlushRequest flush) - { - while (workers.Any()) - { - Task finishedTask = await Task.WhenAny(workers.Select(w => w.Task)).ConfigureAwait(false); - if (finishedTask.IsFaulted) - { - flush.TrySetCanceled(); - ExceptionDispatchInfo.Capture(finishedTask.Exception).Throw(); - } - - Worker finishedWorker = workers.Single(w => w.Task == finishedTask); - workers.Remove(finishedWorker); - finishedWorker.Dispose(); - } - } - - private void UploadFilesets(List workers, List filesets) - { - foreach (var fileset in filesets) - { - Worker worker = GetWorker(workers); - worker.Task = Task.Run(() => UploadVolumeWriter(fileset.Fileset, worker, m_cancelTokenSource.Token)); - } - - filesets.Clear(); - } - - private static Exception GetInnerMostException(Exception ex) - { - while (ex.InnerException != null) - ex = ex.InnerException; - return ex; - } - - private async Task UploadBlockAndIndexAsync(VolumeUploadRequest upload, Worker worker, CancellationToken cancelToken) - { - if (await UploadFileAsync(upload.BlockEntry, worker, cancelToken).ConfigureAwait(false)) - { - // We must use the BlockEntry's RemoteFilename and not the BlockVolume's since the - // BlockEntry's' RemoteFilename reflects renamed files due to retries after errors. - IndexVolumeWriter indexVolumeWriter = await upload.IndexVolume.CreateVolume(upload.BlockEntry.RemoteFilename, upload.BlockEntry.Hash, upload.BlockEntry.Size, upload.Options, upload.Database); - FileEntryItem indexEntry = indexVolumeWriter.CreateFileEntryForUpload(upload.Options); - if (await UploadFileAsync(indexEntry, worker, cancelToken).ConfigureAwait(false)) - { - await m_database.AddIndexBlockLinkAsync(indexVolumeWriter.VolumeID, upload.BlockVolume.VolumeID).ConfigureAwait(false); - } - } - } - - private async Task UploadVolumeWriter(VolumeWriterBase volumeWriter, Worker worker, CancellationToken cancelToken) - { - var fileEntry = new FileEntryItem(BackendActionType.Put, volumeWriter.RemoteFilename); - fileEntry.SetLocalfilename(volumeWriter.LocalFilename); - fileEntry.Encrypt(m_options); - fileEntry.UpdateHashAndSize(m_options); - - await UploadFileAsync(fileEntry, worker, cancelToken).ConfigureAwait(false); - } - - private async Task UploadFileAsync(FileEntryItem item, Worker worker, CancellationToken cancelToken) - { - if (cancelToken.IsCancellationRequested) - return false; - - return await DoWithRetry(async () => - { - if (item.IsRetry) - await RenameFileAfterErrorAsync(item).ConfigureAwait(false); - await DoPut(item, worker.Backend, cancelToken).ConfigureAwait(false); - }, - item, worker, cancelToken).ConfigureAwait(false); - } - - private async Task DoWithRetry(Func method, FileEntryItem item, Worker worker, CancellationToken cancelToken) - { - item.IsRetry = false; - var retryCount = 0; - - for (retryCount = 0; retryCount <= m_options.NumberOfRetries; retryCount++) - { - if (m_options.RetryDelay.Ticks != 0 && retryCount != 0) - { - var delay = Library.Utility.Utility.GetRetryDelay(m_options.RetryDelay, retryCount, m_options.RetryWithExponentialBackoff); - await Task.Delay(delay).ConfigureAwait(false); - } - - if (cancelToken.IsCancellationRequested) - return false; - - try - { - if (worker.Backend == null) - worker.Backend = m_backendFactory(); - await method().ConfigureAwait(false); - return true; - } - catch (Exception ex) - { - item.IsRetry = true; - Logging.Log.WriteRetryMessage(LOGTAG, $"Retry{item.Operation}", ex, "Operation {0} with file {1} attempt {2} of {3} failed with message: {4}", item.Operation, item.RemoteFilename, retryCount + 1, m_options.NumberOfRetries + 1, ex.Message); - if (ex is ThreadAbortException || ex is OperationCanceledException) - break; - - await m_stats.SendEventAsync(item.Operation, retryCount < m_options.NumberOfRetries ? BackendEventType.Retrying : BackendEventType.Failed, item.RemoteFilename, item.Size); - - bool recovered = false; - if (m_options.AutocreateFolders && ex is FolderMissingException) - { - try - { - // If we successfully create the folder, we can re-use the connection - await worker.Backend.CreateFolderAsync(cancelToken).ConfigureAwait(false); - recovered = true; - } - catch (Exception dex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateError", dex, "Failed to create folder: {0}", ex.Message); - } - } - - if (!recovered) - ResetBackend(ex, worker); - - if (retryCount == m_options.NumberOfRetries) - throw; - } - finally - { - if (m_options.NoConnectionReuse) - ResetBackend(null, worker); - } - } - - return false; - } - - private void ResetBackend(Exception ex, Worker worker) - { - try - { - worker.Backend?.Dispose(); - } - catch (Exception dex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "BackendDisposeError", dex, "Failed to dispose backend instance: {0}", ex?.Message); - } - finally - { - worker.Backend = null; - } - } - - private async Task RenameFileAfterErrorAsync(FileEntryItem item) - { - var p = VolumeBase.ParseFilename(item.RemoteFilename); - var guid = VolumeWriterBase.GenerateGuid(); - var time = p.Time.Ticks == 0 ? p.Time : p.Time.AddSeconds(1); - var newname = VolumeBase.GenerateFilename(p.FileType, p.Prefix, guid, time, p.CompressionModule, p.EncryptionModule); - var oldname = item.RemoteFilename; - - await m_stats.SendEventAsync(item.Operation, BackendEventType.Rename, oldname, item.Size); - await m_stats.SendEventAsync(item.Operation, BackendEventType.Rename, newname, item.Size); - Logging.Log.WriteInformationMessage(LOGTAG, "RenameRemoteTargetFile", "Renaming \"{0}\" to \"{1}\"", oldname, newname); - await m_database.RenameRemoteFileAsync(oldname, newname); - item.RemoteFilename = newname; - } - - private async Task DoPut(FileEntryItem item, IBackend backend, CancellationToken cancelToken) - { - if (cancelToken.IsCancellationRequested) - return; - - if (item.TrackedInDb) - await m_database.UpdateRemoteVolumeAsync(item.RemoteFilename, RemoteVolumeState.Uploading, item.Size, item.Hash); - - if (m_options.Dryrun) - { - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadVolume", "Would upload volume: {0}, size: {1}", item.RemoteFilename, Library.Utility.Utility.FormatSizeString(new FileInfo(item.LocalFilename).Length)); - item.DeleteLocalFile(); - return; - } - - await m_database.LogRemoteOperationAsync("put", item.RemoteFilename, JsonConvert.SerializeObject(new { Size = item.Size, Hash = item.Hash })); - await m_stats.SendEventAsync(BackendActionType.Put, BackendEventType.Started, item.RemoteFilename, item.Size, updateProgress: false); - m_progressUpdater.StartFileProgress(item.RemoteFilename, item.Size); - - var begin = DateTime.Now; - - if (!m_options.DisableStreamingTransfers && backend is IStreamingBackend streamingBackend) - { - // A download throttle speed is not given to the ThrottledStream as we are only uploading data here - using (var fs = File.OpenRead(item.LocalFilename)) - using (var ts = new ThrottledStream(fs, m_initialUploadThrottleSpeed, 0)) - using (var pgs = new ProgressReportingStream(ts, pg => HandleProgress(ts, pg, item.RemoteFilename))) - await streamingBackend.PutAsync(item.RemoteFilename, pgs, cancelToken).ConfigureAwait(false); - } - else - await backend.PutAsync(item.RemoteFilename, item.LocalFilename, cancelToken).ConfigureAwait(false); - - var duration = DateTime.Now - begin; - m_progressUpdater.EndFileProgress(item.RemoteFilename); - Logging.Log.WriteProfilingMessage(LOGTAG, "UploadSpeed", "Uploaded {0} in {1}, {2}/s", Library.Utility.Utility.FormatSizeString(item.Size), duration, Library.Utility.Utility.FormatSizeString((long)(item.Size / duration.TotalSeconds))); - - if (item.TrackedInDb) - await m_database.UpdateRemoteVolumeAsync(item.RemoteFilename, RemoteVolumeState.Uploaded, item.Size, item.Hash); - - await m_stats.SendEventAsync(BackendActionType.Put, BackendEventType.Completed, item.RemoteFilename, item.Size); - - if (m_options.ListVerifyUploads) - { - var f = backend.List().FirstOrDefault(n => n.Name.Equals(item.RemoteFilename, StringComparison.OrdinalIgnoreCase)); - if (f == null) - throw new Exception(string.Format("List verify failed, file was not found after upload: {0}", item.RemoteFilename)); - else if (f.Size != item.Size && f.Size >= 0) - throw new Exception(string.Format("List verify failed for file: {0}, size was {1} but expected to be {2}", f.Name, f.Size, item.Size)); - } - - item.DeleteLocalFile(); - await m_database.CommitTransactionAsync("CommitAfterUpload"); - } - - private void HandleProgress(ThrottledStream stream, long progress, string path) - { - UpdateThrottleSpeed(); - m_progressUpdater.UpdateFileProgress(path, progress, stream); - } - - private void UpdateThrottleSpeed() - { - var updateThrottleSpeed = false; - - lock (m_progressUpdater) - { - var currentTick = Environment.TickCount; - if (currentTick - m_lastThrottleCheckTime > 2000) - { - m_lastThrottleCheckTime = currentTick; - updateThrottleSpeed = true; - } - } - - if (!updateThrottleSpeed) - return; - - var maxUploadPerSecond = m_options.MaxUploadPrSecond; - if (maxUploadPerSecond != m_lastUploadThrottleSpeed) - { - m_lastUploadThrottleSpeed = maxUploadPerSecond; - m_initialUploadThrottleSpeed = maxUploadPerSecond / m_maxConcurrentUploads; - m_progressUpdater.UpdateThrottleSpeeds(maxUploadPerSecond); - } - } - - private class Worker : IDisposable - { - public Task Task; - public IBackend Backend; - - public Worker(IBackend backend) - { - Backend = backend; - Task = Task.FromResult(true); - } - - public void Dispose() - { - this.Task?.Dispose(); - this.Backend?.Dispose(); - } - } - } -} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs index 41bb22b20..cc7eb1b39 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -57,17 +57,17 @@ namespace Duplicati.Library.Main.Operation.Backup public Task FindBlockIDAsync(string hash, long size) { - return RunOnMain(() => m_database.FindBlockID(hash, size, m_transaction)); + return RunOnMain(() => m_database.FindBlockID(hash, size, GetTransaction())); } public Task AddBlockAsync(string hash, long size, long volumeid) { - return RunOnMain(() => m_database.AddBlock(hash, size, volumeid, m_transaction)); + return RunOnMain(() => m_database.AddBlock(hash, size, volumeid, GetTransaction())); } public Task GetFileHashAsync(long fileid) { - return RunOnMain(() => m_database.GetFileHash(fileid, m_transaction)); + return RunOnMain(() => m_database.GetFileHash(fileid, GetTransaction())); } public Task> AddMetadatasetAsync(string hash, long size, long blocksetid) @@ -75,7 +75,7 @@ namespace Duplicati.Library.Main.Operation.Backup return RunOnMain(() => { long metadataid; - var n = m_database.AddMetadataset(hash, size, blocksetid, out metadataid, m_transaction); + var n = m_database.AddMetadataset(hash, size, blocksetid, out metadataid, GetTransaction()); return new Tuple(n, metadataid); }); } @@ -85,44 +85,45 @@ namespace Duplicati.Library.Main.Operation.Backup return RunOnMain(() => { long metadataid; - var r = m_database.GetMetadatasetID(hash, size, out metadataid, m_transaction); + var r = m_database.GetMetadatasetID(hash, size, out metadataid, GetTransaction()); return new Tuple(r, metadataid); }); } public Task AddDirectoryEntryAsync(string filename, long metadataid, DateTime lastModified) { - return RunOnMain(() => m_database.AddDirectoryEntry(filename, metadataid, lastModified, m_transaction)); + return RunOnMain(() => m_database.AddDirectoryEntry(filename, metadataid, lastModified, GetTransaction())); } public Task AddSymlinkEntryAsync(string filename, long metadataid, DateTime lastModified) { - return RunOnMain(() => m_database.AddSymlinkEntry(filename, metadataid, lastModified, m_transaction)); + return RunOnMain(() => m_database.AddSymlinkEntry(filename, metadataid, lastModified, GetTransaction())); } public Task> GetMetadataHashAndSizeForFileAsync(long fileid) { - return RunOnMain(() => m_database.GetMetadataHashAndSizeForFile(fileid, m_transaction)); + return RunOnMain(() => m_database.GetMetadataHashAndSizeForFile(fileid, GetTransaction())); } public Task> GetFileLastModifiedAsync(long prefixid, string path, long lastfilesetid, bool includeLength) { return RunOnMain(() => { - var id = m_database.GetFileLastModified(prefixid, path, lastfilesetid, includeLength, out var lastModified, out var length, m_transaction); + var id = m_database.GetFileLastModified(prefixid, path, lastfilesetid, includeLength, out var lastModified, out var length, GetTransaction()); return new Tuple(id, lastModified, length); }); - } + } - public Task GetFileEntryAsync(long prefixid, string path, long lastfilesetid) + public Task GetFileEntryAsync(long prefixid, string path, long lastfilesetid) { - return RunOnMain(() => { + return RunOnMain(() => + { DateTime oldModified; long lastFileSize; string oldMetahash; long oldMetasize; - var id = m_database.GetFileEntry(prefixid, path, lastfilesetid, out oldModified, out lastFileSize, out oldMetahash, out oldMetasize, m_transaction); + var id = m_database.GetFileEntry(prefixid, path, lastfilesetid, out oldModified, out lastFileSize, out oldMetahash, out oldMetasize, GetTransaction()); return id < 0 ? null : @@ -132,43 +133,44 @@ namespace Duplicati.Library.Main.Operation.Backup public Task AddBlocksetAsync(string filehash, long size, int blocksize, IEnumerable hashlist, IEnumerable blocklisthashes) { - return RunOnMain(() => { + return RunOnMain(() => + { long blocksetid; - m_database.AddBlockset(filehash, size, blocksize, hashlist, blocklisthashes, out blocksetid, m_transaction); + m_database.AddBlockset(filehash, size, blocksize, hashlist, blocklisthashes, out blocksetid, GetTransaction()); return blocksetid; }); } public Task GetOrCreatePathPrefix(string prefix) { - return RunOnMain(() => m_database.GetOrCreatePathPrefix(prefix, m_transaction)); + return RunOnMain(() => m_database.GetOrCreatePathPrefix(prefix, GetTransaction())); } public Task AddFileAsync(long prefixid, string filename, DateTime lastmodified, long blocksetid, long metadataid) { - return RunOnMain(() => m_database.AddFile(prefixid, filename, lastmodified, blocksetid, metadataid, m_transaction)); + return RunOnMain(() => m_database.AddFile(prefixid, filename, lastmodified, blocksetid, metadataid, GetTransaction())); } public Task AddUnmodifiedAsync(long fileid, DateTime lastModified) { - return RunOnMain(() => m_database.AddUnmodifiedFile(fileid, lastModified, m_transaction)); + return RunOnMain(() => m_database.AddUnmodifiedFile(fileid, lastModified, GetTransaction())); } - + public Task MoveBlockToVolumeAsync(string blockkey, long size, long sourcevolumeid, long targetvolumeid) { - return RunOnMain(() => m_database.MoveBlockToVolume(blockkey, size, sourcevolumeid, targetvolumeid, m_transaction)); + return RunOnMain(() => m_database.MoveBlockToVolume(blockkey, size, sourcevolumeid, targetvolumeid, GetTransaction())); } public Task SafeDeleteRemoteVolumeAsync(string remotename) { - return RunOnMain(() => m_database.SafeDeleteRemoteVolume(remotename, m_transaction)); + return RunOnMain(() => m_database.SafeDeleteRemoteVolume(remotename, GetTransaction())); } public Task GetBlocklistHashesAsync(string remotename) { - return RunOnMain(() => Task.FromResult(m_database.GetBlocklistHashes(remotename, m_transaction))); + return RunOnMain(() => Task.FromResult(m_database.GetBlocklistHashes(remotename, GetTransaction()))); } public Task UpdateIndexVolumeAsync(IndexVolumeWriter indexvolume, BlockVolumeWriter blockvolume) @@ -178,29 +180,29 @@ namespace Duplicati.Library.Main.Operation.Backup return RunOnMain(() => { - m_database.AddIndexBlockLink(indexvolume.VolumeID, blockvolume.VolumeID, m_transaction); + m_database.AddIndexBlockLink(indexvolume.VolumeID, blockvolume.VolumeID, GetTransaction()); indexvolume.StartVolume(blockvolume.RemoteFilename); - foreach(var b in m_database.GetBlocks(blockvolume.VolumeID, m_transaction)) + foreach (var b in m_database.GetBlocks(blockvolume.VolumeID, GetTransaction())) indexvolume.AddBlock(b.Hash, b.Size); - m_database.UpdateRemoteVolume(indexvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_transaction); + m_database.UpdateRemoteVolume(indexvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, GetTransaction()); }); } public Task AppendFilesFromPreviousSetAsync() { - return RunOnMain(() => m_database.AppendFilesFromPreviousSet(m_transaction)); + return RunOnMain(() => m_database.AppendFilesFromPreviousSet(GetTransaction())); } public Task AppendFilesFromPreviousSetAsync(string[] deletedFilelist) { - return RunOnMain(() => m_database.AppendFilesFromPreviousSet(m_transaction, deletedFilelist)); + return RunOnMain(() => m_database.AppendFilesFromPreviousSet(GetTransaction(), deletedFilelist)); } public Task AppendFilesFromPreviousSetAsync(string[] deletedFilelist, long filesetid, long prevId, DateTime timestamp) { - return RunOnMain(() => m_database.AppendFilesFromPreviousSet(m_transaction, deletedFilelist, filesetid, prevId, timestamp)); + return RunOnMain(() => m_database.AppendFilesFromPreviousSet(GetTransaction(), deletedFilelist, filesetid, prevId, timestamp)); } /// @@ -211,7 +213,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// Optional exclusion predicate (true = exclude file) public Task AppendFilesFromPreviousSetWithPredicateAsync(Func exclusionPredicate) { - return RunOnMain(() => m_database.AppendFilesFromPreviousSetWithPredicate(m_transaction, exclusionPredicate)); + return RunOnMain(() => m_database.AppendFilesFromPreviousSetWithPredicate(GetTransaction(), exclusionPredicate)); } /// @@ -225,12 +227,12 @@ namespace Duplicati.Library.Main.Operation.Backup /// If prevFileSetId == -1, used to locate previous fileset public Task AppendFilesFromPreviousSetWithPredicateAsync(Func exclusionPredicate, long fileSetId, long prevFileSetId, DateTime timestamp) { - return RunOnMain(() => m_database.AppendFilesFromPreviousSetWithPredicate(m_transaction, exclusionPredicate, fileSetId, prevFileSetId, timestamp)); + return RunOnMain(() => m_database.AppendFilesFromPreviousSetWithPredicate(GetTransaction(), exclusionPredicate, fileSetId, prevFileSetId, timestamp)); } public Task[]> GetIncompleteFilesetsAsync() { - return RunOnMain(() => m_database.GetIncompleteFilesets(m_transaction).OrderBy(x => x.Value).ToArray()); + return RunOnMain(() => m_database.GetIncompleteFilesets(GetTransaction()).OrderBy(x => x.Value).ToArray()); } public Task[]> GetFilesetTimesAsync() @@ -240,57 +242,68 @@ namespace Duplicati.Library.Main.Operation.Backup public Task CreateFilesetAsync(long volumeID, DateTime fileTime) { - return RunOnMain(() => m_database.CreateFileset(volumeID, fileTime, m_transaction)); + return RunOnMain(() => m_database.CreateFileset(volumeID, fileTime, GetTransaction())); } public Task LinkFilesetToVolumeAsync(long filesetid, long volumeid) { - return RunOnMain(() => m_database.LinkFilesetToVolume(filesetid, volumeid, m_transaction)); + return RunOnMain(() => m_database.LinkFilesetToVolume(filesetid, volumeid, GetTransaction())); } public Task WriteFilesetAsync(FilesetVolumeWriter fsw, long filesetid) { - return RunOnMain(() => m_database.WriteFileset(fsw, filesetid, m_transaction)); + return RunOnMain(() => m_database.WriteFileset(fsw, filesetid, GetTransaction())); + } + + public Task PushTimestampChangesToPreviousVersionAsync(long filesetid) + { + return RunOnMain(() => m_database.PushTimestampChangesToPreviousVersion(filesetid, GetTransaction())); } public Task UpdateFilesetAndMarkAsFullBackupAsync(long filesetid) { - return RunOnMain(() => m_database.UpdateFullBackupStateInFileset(filesetid, true, m_transaction)); + return RunOnMain(() => m_database.UpdateFullBackupStateInFileset(filesetid, true, GetTransaction())); } + // TODO: Make IAsyncEnumerable or get rid of the SingleRunner setup public Task GetMissingIndexFilesAsync() { - return RunOnMain(() => m_database.GetMissingIndexFiles(m_transaction).ToArray()); + return RunOnMain(() => m_database.GetMissingIndexFiles(GetTransaction()).ToArray()); } public Task UpdateChangeStatisticsAsync(BackupResults result) { - return RunOnMain(() => m_database.UpdateChangeStatistics(result, m_transaction)); + return RunOnMain(() => m_database.UpdateChangeStatistics(result, GetTransaction())); } public Task VerifyConsistencyAsync(int blocksize, int blockhashSize, bool verifyfilelists) { - return RunOnMain(() => m_database.VerifyConsistency(blocksize, blockhashSize, verifyfilelists, m_transaction)); + return RunOnMain(() => m_database.VerifyConsistency(blocksize, blockhashSize, verifyfilelists, GetTransaction())); } public Task RemoveRemoteVolumeAsync(string remoteFilename) { - return RunOnMain(() => m_database.RemoveRemoteVolume(remoteFilename, m_transaction)); + return RunOnMain(() => m_database.RemoveRemoteVolume(remoteFilename, GetTransaction())); } public Task GetRemoteVolumeFromFilesetIDAsync(long filesetID) { - return RunOnMain(() => m_database.GetRemoteVolumeFromFilesetID(filesetID, m_transaction)); + return RunOnMain(() => m_database.GetRemoteVolumeFromFilesetID(filesetID, GetTransaction())); } public Task CreateChangeJournalDataAsync(IEnumerable journalData) { - return RunOnMain(() => m_database.CreateChangeJournalData(journalData, m_transaction)); + return RunOnMain(() => m_database.CreateChangeJournalData(journalData, GetTransaction())); } public Task UpdateChangeJournalDataAsync(IEnumerable journalData, long lastfilesetid) { - return RunOnMain(() => m_database.UpdateChangeJournalData(journalData, lastfilesetid, m_transaction)); + return RunOnMain(() => m_database.UpdateChangeJournalData(journalData, lastfilesetid, GetTransaction())); + } + + public Task IsBlocklistHashKnownAsync(string hash) + { + return RunOnMain(() => m_database.IsBlocklistHashKnown(hash, GetTransaction())); } } diff --git a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs index 27a706faa..03572ab17 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -41,15 +41,25 @@ namespace Duplicati.Library.Main.Operation.Backup public Task AddOpenedFile(long size) { - return RunOnMain(() => { + return RunOnMain(() => + { m_res.SizeOfOpenedFiles += size; m_res.OpenedFiles++; }); } + public Task AddTimestampChangedFile() + { + return RunOnMain(() => + { + m_res.TimestampChangedFiles++; + }); + } + public Task AddAddedFile(long size) { - return RunOnMain(() => { + return RunOnMain(() => + { m_res.SizeOfAddedFiles += size; m_res.AddedFiles++; }); @@ -57,7 +67,8 @@ namespace Duplicati.Library.Main.Operation.Backup public Task AddModifiedFile(long size) { - return RunOnMain(() => { + return RunOnMain(() => + { m_res.SizeOfModifiedFiles += size; m_res.ModifiedFiles++; }); @@ -65,7 +76,8 @@ namespace Duplicati.Library.Main.Operation.Backup public Task AddExaminedFile(long size) { - return RunOnMain(() => { + return RunOnMain(() => + { m_res.SizeOfExaminedFiles += size; m_res.ExaminedFiles++; }); diff --git a/Duplicati/Library/Main/Operation/Backup/Channels.cs b/Duplicati/Library/Main/Operation/Backup/Channels.cs index d7c5ff88e..f65c545d7 100644 --- a/Duplicati/Library/Main/Operation/Backup/Channels.cs +++ b/Duplicati/Library/Main/Operation/Backup/Channels.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,48 +19,44 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using CoCoL; +using Duplicati.Library.Interface; namespace Duplicati.Library.Main.Operation.Backup { /// /// Static typed definitions of channels used for the backup process /// - internal static class Channels + internal class Channels { - /// - /// Requests to the backend are send over this channel and picked up by the class - /// - public static readonly ChannelMarkerWrapper BackendRequest = new ChannelMarkerWrapper(new ChannelNameAttribute("BackendRequests")); /// /// When the backup completes, all in-progress archives are sent from the to the /// - public static readonly ChannelMarkerWrapper SpillPickup = new ChannelMarkerWrapper(new ChannelNameAttribute("SpillPickup")); + public readonly IChannel SpillPickup = ChannelManager.CreateChannel(); /// /// All data blocks are sent during the scanning to the who bundles them in compressed archives /// - public static readonly ChannelMarkerWrapper OutputBlocks = new ChannelMarkerWrapper(new ChannelNameAttribute("OutputBlocks")); + public readonly IChannel OutputBlocks = ChannelManager.CreateChannel(); /// /// If a file has changes in the metadata, it is sent to the where it is read /// - public static readonly ChannelMarkerWrapper AcceptedChangedFile = new ChannelMarkerWrapper(new ChannelNameAttribute("AcceptedChangedFile")); + public readonly IChannel AcceptedChangedFile = ChannelManager.CreateChannel(); /// /// If a file has changes in the metadata, it is sent to the where it is read /// - public static readonly ChannelMarkerWrapper StreamBlock = new ChannelMarkerWrapper(new ChannelNameAttribute("StreamBlockSplitter")); + public readonly IChannel StreamBlock = ChannelManager.CreateChannel(); /// /// After metadata has been processed and collected, the sends the file data to the /// - public static readonly ChannelMarkerWrapper ProcessedFiles = new ChannelMarkerWrapper(new ChannelNameAttribute("ProcessedFiles")); + public readonly IChannel ProcessedFiles = ChannelManager.CreateChannel(); /// - /// When enumerating the source folders, all discovered paths are sent by the to the + /// When enumerating the source folders, all discovered paths are sent by the to the /// - public static readonly ChannelMarkerWrapper SourcePaths = new ChannelMarkerWrapper(new ChannelNameAttribute("SourcePaths")); + public readonly IChannel SourcePaths = ChannelManager.CreateChannel(); /// /// All progress events are communicated over this channel, to ensure that parallel progress is reported as a if it was sequential /// - public static readonly ChannelMarkerWrapper ProgressEvents = new ChannelMarkerWrapper(new ChannelNameAttribute("ProgressEvents")); + public readonly IChannel ProgressEvents = ChannelManager.CreateChannel(); } } diff --git a/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs b/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs index 490f49a07..1d16f29d9 100644 --- a/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,24 +22,38 @@ using System.Collections.Generic; using System.Threading.Tasks; using Duplicati.Library.Utility; -using Duplicati.Library.Snapshots; using CoCoL; +using Duplicati.Library.Interface; +using Duplicati.Library.Snapshots.USN; namespace Duplicati.Library.Main.Operation.Backup { internal static class CountFilesHandler { - public static async Task Run(IEnumerable sources, Snapshots.ISnapshotService snapshot, UsnJournalService journalService, BackupResults result, Options options, IFilter sourcefilter, IFilter filter, HashSet blacklistPaths, Common.ITaskReader taskreader, System.Threading.CancellationToken token) + public static async Task Run( + ISourceProvider sources, + UsnJournalService journalService, + BackupResults result, + Options options, + IFilter filter, + HashSet blacklistPaths, + Common.ITaskReader taskreader, + System.Threading.CancellationToken token + ) { - // Make sure we create the enumeration process in a separate scope, - // but keep the log channel from the parent scope + // Keep the log channel from the parent scope using (Logging.Log.StartIsolatingScope(true)) - using (new IsolatedChannelScope()) { - var enumeratorTask = Backup.FileEnumerationProcess.Run(sources, snapshot, journalService, options.FileAttributeFilter, sourcefilter, filter, options.SymlinkPolicy, options.HardlinkPolicy, options.ExcludeEmptyFolders, options.IgnoreFilenames, blacklistPaths, options.ChangedFilelist, taskreader, token); + Channels channels = new(); + var enumeratorTask = FileEnumerationProcess.Run( + channels, sources, journalService, options.FileAttributeFilter, filter, + options.SymlinkPolicy, options.HardlinkPolicy, + options.ExcludeEmptyFolders, options.IgnoreFilenames, + blacklistPaths, options.ChangedFilelist, taskreader, null, token); + var counterTask = AutomationExtensions.RunTask(new { - Input = Backup.Channels.SourcePaths.ForRead + Input = channels.SourcePaths.AsRead() }, async self => @@ -49,15 +63,17 @@ namespace Duplicati.Library.Main.Operation.Backup try { - while (await taskreader.ProgressAsync && !token.IsCancellationRequested) + while (await taskreader.ProgressRendevouz() && !token.IsCancellationRequested) { - var path = await self.Input.ReadAsync(); + var entry = await self.Input.ReadAsync(); + if (entry.IsFolder) + continue; count++; try { - size += snapshot.GetFileSize(path); + size += entry.Size; } catch { diff --git a/Duplicati/Library/Main/Operation/Backup/DataBlock.cs b/Duplicati/Library/Main/Operation/Backup/DataBlock.cs index 27b21a68a..3c40c8b86 100644 --- a/Duplicati/Library/Main/Operation/Backup/DataBlock.cs +++ b/Duplicati/Library/Main/Operation/Backup/DataBlock.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs b/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs index b3cef5a37..670f3a809 100644 --- a/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs +++ b/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,7 +24,6 @@ using Duplicati.Library.Main.Operation.Common; using Duplicati.Library.Main.Volumes; using System; using System.Threading.Tasks; -using static Duplicati.Library.Main.Operation.Common.BackendHandler; namespace Duplicati.Library.Main.Operation.Backup { @@ -35,30 +34,46 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal static class DataBlockProcessor { - public static Task Run(BackupDatabase database, Options options, ITaskReader taskreader) + public static Task Run(Channels channels, BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) { return AutomationExtensions.RunTask( new { - Input = Channels.OutputBlocks.ForRead, - Output = Channels.BackendRequest.ForWrite, - SpillPickup = Channels.SpillPickup.ForWrite, + Input = channels.OutputBlocks.AsRead(), + SpillPickup = channels.SpillPickup.AsWrite(), }, async self => { var noIndexFiles = options.IndexfilePolicy == Options.IndexFileStrategy.None; var fullIndexFiles = options.IndexfilePolicy == Options.IndexFileStrategy.Full; + var blocklistHashesAdded = 0L; BlockVolumeWriter blockvolume = null; TemporaryIndexVolume indexvolume = null; + System.Diagnostics.Stopwatch sw_workload = new(); + long allowed_workload_ms = options.CPUIntensity * 100; + try { while (true) { var b = await self.Input.ReadAsync(); + // Check if the process has spent more than allowed workload time + if (options.CPUIntensity < 10 && sw_workload.ElapsedMilliseconds > allowed_workload_ms) + { + // Sleep the remaining time + int time_to_sleep = 1000 - (int)allowed_workload_ms; + await Task.Delay(time_to_sleep); + sw_workload.Reset(); + } + sw_workload.Start(); + + // We need these blocks to be stored in the full index files + var isMandatoryBlocklistHash = b.IsBlocklistHashes && fullIndexFiles; + // Lazy-start a new block volume if (blockvolume == null) { @@ -67,9 +82,10 @@ namespace Duplicati.Library.Main.Operation.Backup // There can be a race, such that two workers determine that // the block is missing, but this will be solved by the AddBlock call // which runs atomically - if (await database.FindBlockIDAsync(b.HashKey, b.Size) >= 0) + if (!isMandatoryBlocklistHash && await database.FindBlockIDAsync(b.HashKey, b.Size) >= 0) { b.TaskCompletion.TrySetResult(false); + sw_workload.Stop(); continue; } @@ -77,23 +93,33 @@ namespace Duplicati.Library.Main.Operation.Backup blockvolume.VolumeID = await database.RegisterRemoteVolumeAsync(blockvolume.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary); indexvolume = noIndexFiles ? null : new TemporaryIndexVolume(options); + blocklistHashesAdded = 0; } var newBlock = await database.AddBlockAsync(b.HashKey, b.Size, blockvolume.VolumeID); b.TaskCompletion.TrySetResult(newBlock); + // If we are recording blocklist hashes, add them to the index file, + // even if they are already added as non-blocklist blocks, but filter out duplicates + if (indexvolume != null && isMandatoryBlocklistHash) + { + // This can cause a race between workers, + // but the side-effect is that the index files are slightly larger + if (newBlock || !await database.IsBlocklistHashKnownAsync(b.HashKey)) + { + blocklistHashesAdded++; + indexvolume.AddBlockListHash(b.HashKey, b.Size, b.Data); + } + } + if (newBlock) { blockvolume.AddBlock(b.HashKey, b.Data, b.Offset, (int)b.Size, b.Hint); if (indexvolume != null) - { indexvolume.AddBlock(b.HashKey, b.Size); - if (b.IsBlocklistHashes && fullIndexFiles) - indexvolume.AddBlockListHash(b.HashKey, b.Size, b.Data); - } // If the volume is full, send to upload - if (blockvolume.Filesize > options.VolumeSize - options.Blocksize) + if (blockvolume.Filesize > (options.VolumeSize - options.Blocksize)) { //When uploading a new volume, we register the volumes and then flush the transaction // this ensures that the local database and remote storage are as closely related as possible @@ -103,28 +129,30 @@ namespace Duplicati.Library.Main.Operation.Backup await database.CommitTransactionAsync("CommitAddBlockToOutputFlush"); - FileEntryItem blockEntry = blockvolume.CreateFileEntryForUpload(options); - - TemporaryIndexVolume indexVolumeCopy = null; + IndexVolumeWriter indexVolumeCopy = null; if (indexvolume != null) { - indexVolumeCopy = new TemporaryIndexVolume(options); - indexvolume.CopyTo(indexVolumeCopy, false); + // TODO: It is much easier to let the BackendManager deal with index files, + // but it adds a bit of strain to the database + indexVolumeCopy = await indexvolume.CreateVolume(blockvolume.RemoteFilename, options, database); + // Create link before upload is started, it will be removed later if upload fails + await database.AddIndexBlockLinkAsync(indexVolumeCopy.VolumeID, blockvolume.VolumeID).ConfigureAwait(false); } - var uploadRequest = new VolumeUploadRequest(blockvolume, blockEntry, indexVolumeCopy, options, database); - + var blockVolumeCopy = blockvolume; blockvolume = null; indexvolume = null; - // Write to output at the end here to prevent sending a full volume to the SpillCollector - await self.Output.WriteAsync(uploadRequest); + await backendManager.PutAsync(blockVolumeCopy, indexVolumeCopy, null, false, taskreader.ProgressToken); + } } // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressAsync; + await taskreader.ProgressRendevouz().ConfigureAwait(false); + + sw_workload.Stop(); } } catch (Exception ex) @@ -132,9 +160,16 @@ namespace Duplicati.Library.Main.Operation.Backup if (ex.IsRetiredException()) { // If we have collected data, merge all pending volumes into a single volume - if (blockvolume != null && blockvolume.SourceSize > 0) + if (blockvolume != null) { - await self.SpillPickup.WriteAsync(new SpillVolumeRequest(blockvolume, indexvolume)); + if (blockvolume.SourceSize > 0 || blocklistHashesAdded > 0) + { + await self.SpillPickup.WriteAsync(new SpillVolumeRequest(blockvolume, indexvolume)); + } + else + { + await database.RemoveRemoteVolumeAsync(blockvolume.RemoteFilename); + } } } diff --git a/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs b/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs index 42096e655..02a983aac 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs @@ -1,29 +1,28 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using CoCoL; using Duplicati.Library.Main.Operation.Common; using System.Threading.Tasks; -using System.Threading; namespace Duplicati.Library.Main.Operation.Backup { @@ -37,29 +36,27 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILELOGTAG = Logging.Log.LogTagFromType(typeof(FileBlockProcessor)) + ".FileEntry"; - public static Task Run(Snapshots.ISnapshotService snapshot, Options options, BackupDatabase database, BackupStatsCollector stats, ITaskReader taskreader, CancellationToken token) + public static Task Run(Channels channels, Options options, BackupDatabase database, BackupStatsCollector stats, ITaskReader taskreader) { return AutomationExtensions.RunTask( - new + new { - Input = Channels.AcceptedChangedFile.ForRead, - StreamBlockChannel = Channels.StreamBlock.ForWrite, + Input = channels.AcceptedChangedFile.AsRead(), + StreamBlockChannel = channels.StreamBlock.AsWrite(), }, async self => { - while (await taskreader.ProgressAsync) + while (true) { var e = await self.Input.ReadAsync(); + // We ignore the stop signal, but not the pause and terminate + await taskreader.ProgressRendevouz().ConfigureAwait(false); + try { - if (token.IsCancellationRequested) - { - break; - } - - var hint = options.GetCompressionHintFromFilename(e.Path); + var hint = options.GetCompressionHintFromFilename(e.Entry.Path); var oldHash = e.OldId < 0 ? null : await database.GetFileHashAsync(e.OldId); StreamProcessResult filestreamdata; @@ -75,15 +72,15 @@ namespace Duplicati.Library.Main.Operation.Backup if (res.Item1) return res.Item2; - Logging.Log.WriteWarningMessage(FILELOGTAG, "UnexpectedMetadataLookup", null, "Metadata was reported as not changed, but still requires being added?\nHash: {0}, Length: {1}, ID: {2}, Path: {3}", e.MetaHashAndSize.FileHash, e.MetaHashAndSize.Blob.Length, res.Item2, e.Path); + Logging.Log.WriteWarningMessage(FILELOGTAG, "UnexpectedMetadataLookup", null, "Metadata was reported as not changed, but still requires being added?\nHash: {0}, Length: {1}, ID: {2}, Path: {3}", e.MetaHashAndSize.FileHash, e.MetaHashAndSize.Blob.Length, res.Item2, e.Entry.Path); e.MetadataChanged = true; } - return (await MetadataPreProcess.AddMetadataToOutputAsync(e.Path, e.MetaHashAndSize, database, self.StreamBlockChannel)).Item2; + return (await MetadataPreProcess.AddMetadataToOutputAsync(e.Entry.Path, e.MetaHashAndSize, database, self.StreamBlockChannel)).Item2; }); - using (var fs = snapshot.OpenRead(e.Path)) - filestreamdata = await StreamBlock.ProcessStream(self.StreamBlockChannel, e.Path, fs, false, hint); + using (var fs = await e.Entry.OpenRead(taskreader.ProgressToken)) + filestreamdata = await StreamBlock.ProcessStream(self.StreamBlockChannel, e.Entry.Path, fs, false, hint); await stats.AddOpenedFile(filestreamdata.Streamlength); @@ -94,36 +91,42 @@ namespace Duplicati.Library.Main.Operation.Backup if (oldHash != filekey) { if (oldHash == null) - Logging.Log.WriteVerboseMessage(FILELOGTAG, "NewFile", "New file {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "NewFile", "New file {0}", e.Entry.Path); else - Logging.Log.WriteVerboseMessage(FILELOGTAG, "ChangedFile", "File has changed {0}", e.Path); - + Logging.Log.WriteVerboseMessage(FILELOGTAG, "ChangedFile", "File has changed {0}", e.Entry.Path); + if (e.OldId < 0) { await stats.AddAddedFile(filesize); if (options.Dryrun) - Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddNewFile", "Would add new file {0}, size {1}", e.Path, Library.Utility.Utility.FormatSizeString(filesize)); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddNewFile", "Would add new file {0}, size {1}", e.Entry.Path, Library.Utility.Utility.FormatSizeString(filesize)); } else { await stats.AddModifiedFile(filesize); if (options.Dryrun) - Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddChangedFile", "Would add changed file {0}, size {1}", e.Path, Library.Utility.Utility.FormatSizeString(filesize)); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddChangedFile", "Would add changed file {0}, size {1}", e.Entry.Path, Library.Utility.Utility.FormatSizeString(filesize)); } await database.AddFileAsync(e.PathPrefixID, e.Filename, e.LastWrite, filestreamdata.Blocksetid, metadataid); } else if (e.MetadataChanged) { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "FileMetadataChanged", "File has only metadata changes {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "FileMetadataChanged", "File has only metadata changes {0}", e.Entry.Path); await database.AddFileAsync(e.PathPrefixID, e.Filename, e.LastWrite, filestreamdata.Blocksetid, metadataid); } + else if (e.TimestampChanged) + { + await stats.AddTimestampChangedFile(); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "FileTimestampChanged", "File has only timestamp changes {0}", e.Entry.Path); + await database.AddUnmodifiedAsync(e.OldId, e.LastWrite); + } else /*if (e.OldId >= 0)*/ { // When we write the file to output, update the last modified time - Logging.Log.WriteVerboseMessage(FILELOGTAG, "NoFileChanges", "File has not changed {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "NoFileChanges", "File has not changed {0}", e.Entry.Path); try { @@ -131,16 +134,16 @@ namespace Duplicati.Library.Main.Operation.Backup } catch (Exception ex) { - Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Path); + Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Entry.Path); } } } - catch(Exception ex) + catch (Exception ex) { if (ex.IsRetiredException()) return; else - Logging.Log.WriteWarningMessage(FILELOGTAG, "PathProcessingFailed", ex, "Failed to process path: {0}", e.Path); + Logging.Log.WriteWarningMessage(FILELOGTAG, "PathProcessingFailed", ex, "Failed to process path: {0}", e.Entry.Path); } } }); diff --git a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs index a96ae5576..848610186 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,6 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using CoCoL; using System.Threading.Tasks; @@ -27,8 +29,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using Duplicati.Library.Main.Operation.Common; -using Duplicati.Library.Snapshots; -using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using System.Runtime.CompilerServices; +using Duplicati.Library.SourceProvider; +using Duplicati.Library.Snapshots.USN; namespace Duplicati.Library.Main.Operation.Backup { @@ -44,21 +48,45 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILTER_LOGTAG = Logging.Log.LogTagFromType(typeof(FileEnumerationProcess)); - public static Task Run(IEnumerable sources, Snapshots.ISnapshotService snapshot, UsnJournalService journalService, FileAttributes fileAttributes, Duplicati.Library.Utility.IFilter sourcefilter, Duplicati.Library.Utility.IFilter emitfilter, Options.SymlinkStrategy symlinkPolicy, Options.HardlinkStrategy hardlinkPolicy, bool excludeemptyfolders, string[] ignorenames, HashSet blacklistPaths, string[] changedfilelist, ITaskReader taskreader, CancellationToken token) + public static Task Run( + Channels channels, + ISourceProvider sourceProvider, + UsnJournalService? journalService, + FileAttributes fileAttributeFilter, + Library.Utility.IFilter emitfilter, + Options.SymlinkStrategy symlinkPolicy, + Options.HardlinkStrategy hardlinkPolicy, + bool excludeemptyfolders, + string[]? ignorenames, + HashSet blacklistPaths, + IEnumerable? changedfilelist, + ITaskReader taskreader, + Action? onStopRequested, + CancellationToken token) { return AutomationExtensions.RunTask( new { - Output = Backup.Channels.SourcePaths.ForWrite + Output = channels.SourcePaths.AsWrite() }, async self => { if (!token.IsCancellationRequested) { + // The hardlink map tracks the hardlink targets we have seen + // and avoid multiple processing of the same contents var hardlinkmap = new Dictionary(); - var mixinqueue = new Queue(); - Duplicati.Library.Utility.IFilter enumeratefilter = emitfilter; + + // The mixin queue is used to store symlinks that should be processed + // The symlinks are emitted during the enumeration process when they are found + var mixinqueue = new Queue(); + + // The enumeration filter is used to determine what paths to + // recurse into. If the emit filter only has includes, + // the enumeration filter will also include all folders, + // as nothing will match otherwise + var enumeratefilter = emitfilter; Library.Utility.FilterExpression.AnalyzeFilters(emitfilter, out var includes, out var excludes); if (includes && !excludes) @@ -68,65 +96,96 @@ namespace Duplicati.Library.Main.Operation.Backup if (ignorenames != null && ignorenames.Length == 0) ignorenames = null; + + // Shared filter function with bound variables + ValueTask FilterEntry(ISourceProviderEntry entry) + => SourceFileEntryFilter(entry, blacklistPaths, hardlinkPolicy, symlinkPolicy, hardlinkmap, fileAttributeFilter, enumeratefilter, ignorenames, mixinqueue, token); + + // Prepare the work list + IAsyncEnumerable worklist; + // If we have a specific list, use that instead of enumerating the filesystem - IEnumerable worklist; - if (changedfilelist != null && changedfilelist.Length > 0) + if (changedfilelist != null && changedfilelist.Any()) { - worklist = changedfilelist.Where(x => + async IAsyncEnumerable ExpandSources(IEnumerable list) { - var fa = FileAttributes.Normal; - try - { - fa = snapshot.GetAttributes(x); - } - catch + foreach (var s in list) { + var r = await sourceProvider.GetEntry(s, s.EndsWith(Path.DirectorySeparatorChar), token).ConfigureAwait(false); + if (r != null) + { + //TODO: Set r.IsRoot = true for source elements + yield return r; + } } + } + worklist = ExpandSources(changedfilelist).WhereAwait(FilterEntry); + } + else if (journalService != null) + { + if (!OperatingSystem.IsWindows()) + throw new NotSupportedException("USN is only supported on Windows"); - if (token.IsCancellationRequested) - { - return false; - } + var fileProviders = (sourceProvider is Combiner c ? c.Providers.AsEnumerable() : [sourceProvider]) + .OfType() + .ToList(); - return AttributeFilter(x, fa, snapshot, sourcefilter, blacklistPaths, hardlinkPolicy, symlinkPolicy, hardlinkmap, fileAttributes, enumeratefilter, ignorenames, mixinqueue); - }); + if (fileProviders.Count <= 0) + throw new InvalidOperationException("No file providers found, but USN was enabled?"); + if (fileProviders.Count > 1) + throw new InvalidOperationException("Multiple file providers found, but USN only supports one"); + + // TODO: This is not as effecient as possible. + // If the root folder is marked changed by USN, the expansion with RecurseEntries + // will cause a full regular scan. It should be possible to *only* process the + // changed elements as returned from the USN journal. + // It should be possible to remove RecurseEntries from the GetModifiedSources() + // enumeration result. + // Such a change requires significant testing as there are many pitfalls with USN. + worklist = RecurseEntries(journalService.GetModifiedSources(FilterEntry, token), + FilterEntry, + token + ) + .Concat( + RecurseEntries(journalService.GetFullScanSources(token), + FilterEntry, + token) + ); } else { - Library.Utility.Utility.EnumerationFilterDelegate attributeFilter = (root, path, attr) => - AttributeFilter(path, attr, snapshot, sourcefilter, blacklistPaths, hardlinkPolicy, symlinkPolicy, hardlinkmap, fileAttributes, enumeratefilter, ignorenames, mixinqueue); - - if (journalService != null) - { - // filter sources using USN journal, to obtain a sub-set of files / folders that may have been modified - sources = journalService.GetModifiedSources(attributeFilter); - } - - worklist = snapshot.EnumerateFilesAndFolders(sources, attributeFilter, (rootpath, errorpath, ex) => - { - Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FileAccessError", ex, "Error reported while accessing file: {0}", errorpath); - }).Distinct(Library.Utility.Utility.IsFSCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + worklist = RecurseEntries(sourceProvider.Enumerate(token), + FilterEntry, + token + ); } if (token.IsCancellationRequested) - { return; - } - var source = ExpandWorkList(worklist, mixinqueue, emitfilter, enumeratefilter); + var source = ExpandWorkList(worklist, mixinqueue, emitfilter, enumeratefilter, token); + // TODO: There was a call to DistinctBy here, but this would cause all paths to be stored in memory + //.DistinctBy(x => x.Path, Library.Utility.Utility.IsFSCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + if (excludeemptyfolders) - source = ExcludeEmptyFolders(source); + source = ExcludeEmptyFolders(source, token); // Process each path, and dequeue the mixins with symlinks as we go - foreach (var s in source) + await foreach (var s in source.WithCancellation(token).ConfigureAwait(false)) { - if (token.IsCancellationRequested) - { - break; - } - - if (!await taskreader.ProgressAsync) +#if DEBUG + // For testing purposes, we need exact control + // when requesting a process stop. + // The "onStopRequested" callback is used to detect + // if the process is the real file enumeration process + // because the counter processe does not have a callback + if (onStopRequested != null) + taskreader.TestMethodCallback?.Invoke(s.Path); +#endif + // Stop if requested + if (token.IsCancellationRequested || !await taskreader.ProgressRendevouz().ConfigureAwait(false)) { + onStopRequested?.Invoke(); return; } @@ -142,13 +201,13 @@ namespace Duplicati.Library.Main.Operation.Backup private class DirectoryStackEntry { /// - /// The path for the folder + /// The item being tracked /// - public string Path; + public required ISourceProviderEntry Item; /// /// A flag indicating if any items are found in this folder /// - public bool AnyEntries; + public required bool AnyEntries; } @@ -157,17 +216,17 @@ namespace Duplicati.Library.Main.Operation.Backup /// /// The list without empty folders. /// The list with potential empty folders. - private static IEnumerable ExcludeEmptyFolders(IEnumerable source) + private static async IAsyncEnumerable ExcludeEmptyFolders(IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken) { var pathstack = new Stack(); - foreach (var s in source) + await foreach (var s in source.WithCancellation(cancellationToken).ConfigureAwait(false)) { // Keep track of directories - var isDirectory = s[s.Length - 1] == System.IO.Path.DirectorySeparatorChar; + var isDirectory = s.Path[s.Path.Length - 1] == System.IO.Path.DirectorySeparatorChar; if (isDirectory) { - while (pathstack.Count > 0 && !s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparison)) + while (pathstack.Count > 0 && !s.Path.StartsWith(pathstack.Peek().Item.Path, Library.Utility.Utility.ClientFilenameStringComparison)) { var e = pathstack.Pop(); if (e.AnyEntries || pathstack.Count == 0) @@ -176,15 +235,15 @@ namespace Duplicati.Library.Main.Operation.Backup if (pathstack.Count > 0) pathstack.Peek().AnyEntries = true; - yield return e.Path; + yield return e.Item; } else - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingEmptyFolder", "Excluding empty folder {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingEmptyFolder", "Excluding empty folder {0}", e.Item); } - if (pathstack.Count == 0 || s.StartsWith(pathstack.Peek().Path, Library.Utility.Utility.ClientFilenameStringComparison)) + if (pathstack.Count == 0 || s.Path.StartsWith(pathstack.Peek().Item.Path, Library.Utility.Utility.ClientFilenameStringComparison)) { - pathstack.Push(new DirectoryStackEntry() { Path = s }); + pathstack.Push(new DirectoryStackEntry() { Item = s, AnyEntries = false }); continue; } } @@ -206,7 +265,46 @@ namespace Duplicati.Library.Main.Operation.Backup if (pathstack.Count > 0) pathstack.Peek().AnyEntries = true; - yield return e.Path; + yield return e.Item; + } + } + } + + /// + /// Performs recursive traversal of the sources + /// + /// The entries to recurse + /// The filter to apply + /// + private static async IAsyncEnumerable RecurseEntries(IAsyncEnumerable entries, Func> filter, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var work = new Stack(); + + await foreach (var e in entries.WithCancellation(cancellationToken).ConfigureAwait(false)) + if (await filter(e).ConfigureAwait(false)) + work.Push(e); + + while (work.Count > 0) + { + var e = work.Pop(); + + // Process meta entry contents, but don't emit them for processing + if (!e.IsMetaEntry) + yield return e; + + if (e.IsFolder) + { + try + { + // We only filter new items, as we assume the input is already filtered + await foreach (var r in e.Enumerate(cancellationToken).ConfigureAwait(false)) + if (await filter(r).ConfigureAwait(false)) + work.Push(r); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorEnumerate", ex, "Failed to enumerate path: {0}", e.Path); + } } } } @@ -219,16 +317,18 @@ namespace Duplicati.Library.Main.Operation.Backup /// The mix in queue. /// The emitfilter. /// The enumeratefilter. - private static IEnumerable ExpandWorkList(IEnumerable worklist, Queue mixinqueue, Library.Utility.IFilter emitfilter, Library.Utility.IFilter enumeratefilter) + private static async IAsyncEnumerable ExpandWorkList(IAsyncEnumerable worklist, Queue mixinqueue, Library.Utility.IFilter emitfilter, Library.Utility.IFilter enumeratefilter, [EnumeratorCancellation] CancellationToken cancellationToken) { // Process each path, and dequeue the mixins with symlinks as we go - foreach (var s in worklist) + await foreach (var s in worklist.WithCancellation(cancellationToken).ConfigureAwait(false)) { while (mixinqueue.Count > 0) yield return mixinqueue.Dequeue(); - Library.Utility.IFilter m; - if (emitfilter != enumeratefilter && !Library.Utility.FilterExpression.Matches(emitfilter, s, out m)) + // If there are only includes in the filter, check if the item is in the original filter + // Since the enumerate filter also includes all folders, we need to ensure we do not emit + // any entries that are filtered explicitly by the user + if (emitfilter != enumeratefilter && !Library.Utility.FilterExpression.Matches(emitfilter, s.Path, out var _)) continue; yield return s; @@ -240,165 +340,208 @@ namespace Duplicati.Library.Main.Operation.Backup } /// - /// Plugin filter for enumerating a list of files. + /// Performs a pre-filter on the source entry to see if it should be included in the backup /// - /// True if the path should be returned, false otherwise. - /// The current path. - /// The file or folder attributes. - /// The snapshot service. - /// The source filter. + /// The entry to evaluate. /// The blacklist paths. - /// The hardlink policy. - /// The symlink policy. - /// The hardlink map. - /// The file attributes to exclude. - /// The enumerate filter. - /// The ignore names. - /// The mixin queue. - private static bool AttributeFilter(string path, FileAttributes attributes, Snapshots.ISnapshotService snapshot, Library.Utility.IFilter sourcefilter, HashSet blacklistPaths, Options.HardlinkStrategy hardlinkPolicy, Options.SymlinkStrategy symlinkPolicy, Dictionary hardlinkmap, FileAttributes fileAttributes, Duplicati.Library.Utility.IFilter enumeratefilter, string[] ignorenames, Queue mixinqueue) + /// True if the path should be returned, false otherwise. + private static bool PreFilterSourceEntry(ISourceProviderEntry entry, HashSet blacklistPaths) { + // Don't filter meta stuff + if (entry.IsMetaEntry) + return true; + // Exclude any blacklisted paths - if (blacklistPaths.Contains(path)) + if (blacklistPaths.Contains(entry.Path)) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingBlacklistedPath", "Excluding blacklisted path: {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingBlacklistedPath", "Excluding blacklisted path: {0}", entry.Path); return false; } // Exclude block devices try { - if (snapshot.IsBlockDevice(path)) + if (entry.IsBlockDevice) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingBlockDevice", "Excluding block device: {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingBlockDevice", "Excluding block device: {0}", entry.Path); return false; } } catch (Exception ex) { - Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorBlockDevice", ex, "Failed to process path: {0}", path); + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorBlockDevice", ex, "Failed to process path: {0}", entry.Path); return false; } - // Check if we explicitly include this entry - Duplicati.Library.Utility.IFilter sourcematch; - bool sourcematches; - if (sourcefilter.Matches(path, out sourcematches, out sourcematch) && sourcematches) + // Exclude character devices + try { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingSourcePath", "Including source path: {0}", path); - return true; + if (entry.IsCharacterDevice) + { + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingCharacterDevice", "Excluding character device: {0}", entry.Path); + return false; + } } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorCharacterDevice", ex, "Failed to process path: {0}", entry.Path); + return false; + } + + return true; + } + + /// + /// Evaluates a single entry for inclusion in the backup + /// + /// The current entry. + /// The snapshot service. + /// The blacklist paths. + /// The hardlink policy. + /// The symlink policy. + /// The hardlink map. + /// The file attributes to exclude. + /// The enumerate filter. + /// The ignore names. + /// The mixin queue. + /// True if the path should be returned, false otherwise. + private static async ValueTask SourceFileEntryFilter(ISourceProviderEntry entry, HashSet blacklistPaths, Options.HardlinkStrategy hardlinkPolicy, Options.SymlinkStrategy symlinkPolicy, Dictionary hardlinkmap, FileAttributes fileAttributeFilter, Duplicati.Library.Utility.IFilter enumeratefilter, string[]? ignorenames, Queue mixinqueue, CancellationToken cancellationToken) + { + // Do the course pre-filtering first + if (!PreFilterSourceEntry(entry, blacklistPaths)) + return false; + + // Never exclude the root entries + if (entry.IsRootEntry) + return true; // If we have a hardlink strategy, obey it if (hardlinkPolicy != Options.HardlinkStrategy.All) { try { - var id = snapshot.HardlinkTargetID(path); + var id = entry.HardlinkTargetId; if (id != null) { if (hardlinkPolicy == Options.HardlinkStrategy.None) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingHardlinkByPolicy", "Excluding hardlink: {0} ({1})", path, id); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingHardlinkByPolicy", "Excluding hardlink: {0} ({1})", entry.Path, id); return false; } else if (hardlinkPolicy == Options.HardlinkStrategy.First) { - string prevPath; - if (hardlinkmap.TryGetValue(id, out prevPath)) + if (hardlinkmap.TryGetValue(id, out var prevPath)) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingDuplicateHardlink", "Excluding hardlink ({1}) for: {0}, previous hardlink: {2}", path, id, prevPath); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingDuplicateHardlink", "Excluding hardlink ({1}) for: {0}, previous hardlink: {2}", entry.Path, id, prevPath); return false; } else { - hardlinkmap.Add(id, path); + hardlinkmap.Add(id, entry.Path); } } } } catch (Exception ex) { - Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorHardLink", ex, "Failed to process path: {0}", path); + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorHardLink", ex, "Failed to process path: {0}", entry.Path); return false; } } - if (ignorenames != null && (attributes & FileAttributes.Directory) != 0) + // Check if there is an ignore marker file + if (ignorenames != null && entry.IsFolder) { try { foreach (var n in ignorenames) { - var ignorepath = SystemIO.IO_OS.PathCombine(path, n); - if (snapshot.FileExists(ignorepath)) + if (await entry.FileExists(n, cancellationToken).ConfigureAwait(false)) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathDueToIgnoreFile", "Excluding path because ignore file was found: {0}", ignorepath); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathDueToIgnoreFile", "Excluding path because ignore file {0} was found in: {1}", n, entry.Path); return false; } } } catch (Exception ex) { - Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorIgnoreFile", ex, "Failed to process path: {0}", path); + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorIgnoreFile", ex, "Failed to process path: {0}", entry.Path); } } - // If we exclude files based on attributes, filter that - if ((fileAttributes & attributes) != 0) + // Setup some basic processing attributes + var attributes = entry.IsFolder + ? FileAttributes.Directory + : FileAttributes.Normal; + + try { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromAttributes", "Excluding path due to attribute filter: {0}", path); + attributes = entry.Attributes; + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "PathProcessingErrorAttributes", ex, "Failed to process path, using default attributes: {0}", entry.Path); + } + + // If we exclude files based on attributes, filter that + if ((fileAttributeFilter & attributes) != 0) + { + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromAttributes", "Excluding path due to attribute filter: {0}", entry.Path); return false; } // Then check if the filename is not explicitly excluded by a filter - Library.Utility.IFilter match; var filtermatch = false; - if (!Library.Utility.FilterExpression.Matches(enumeratefilter, path, out match)) + if (!Library.Utility.FilterExpression.Matches(enumeratefilter, entry.Path, out var match)) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromFilter", "Excluding path due to filter: {0} => {1}", path, match == null ? "null" : match.ToString()); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromFilter", "Excluding path due to filter: {0} => {1}", entry.Path, match == null ? "null" : match.ToString()); return false; } else if (match != null) { filtermatch = true; - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingPathFromFilter", "Including path due to filter: {0} => {1}", path, match.ToString()); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingPathFromFilter", "Including path due to filter: {0} => {1}", entry.Path, match.ToString()); } // If the file is a symlink, apply special handling - var isSymlink = snapshot.IsSymlink(path, attributes); - string symlinkTarget = null; - if (isSymlink) - try { symlinkTarget = snapshot.GetSymlinkTarget(path); } - catch (Exception ex) { Logging.Log.WriteExplicitMessage(FILTER_LOGTAG, "SymlinkTargetReadError", ex, "Failed to read symlink target for path: {0}", path); } + string? symlinkTarget = null; + try + { + symlinkTarget = entry.SymlinkTarget; + } + catch (Exception ex) + { + Logging.Log.WriteExplicitMessage(FILTER_LOGTAG, "SymlinkTargetReadError", ex, "Failed to read symlink target for path: {0}", entry.Path); + } - if (isSymlink) + if (symlinkTarget != null) { if (!string.IsNullOrWhiteSpace(symlinkTarget)) { if (symlinkPolicy == Options.SymlinkStrategy.Ignore) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludeSymlink", "Excluding symlink: {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludeSymlink", "Excluding symlink: {0}", entry.Path); return false; } if (symlinkPolicy == Options.SymlinkStrategy.Store) { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "StoreSymlink", "Storing symlink: {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "StoreSymlink", "Storing symlink: {0}", entry.Path); // We return false because we do not want to recurse into the path, // but we add the symlink to the mixin so we process the symlink itself - mixinqueue.Enqueue(path); + mixinqueue.Enqueue(entry); return false; } } else { - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "FollowingEmptySymlink", "Treating empty symlink as regular path {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "FollowingEmptySymlink", "Treating empty symlink as regular path {0}", entry.Path); } } if (!filtermatch) - Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingPath", "Including path as no filters matched: {0}", path); + Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "IncludingPath", "Including path as no filters matched: {0}", entry.Path); // All the way through, yes! return true; diff --git a/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs b/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs index 8e84c651a..03e651c17 100644 --- a/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -29,7 +29,7 @@ using Duplicati.Library.Main.Operation.Common; namespace Duplicati.Library.Main.Operation.Backup { /// - /// This process takes files that are processed for metadata, + /// This process takes files that are processed for metadata, /// and checks if anything indicates that the file has changed /// and submits potentially changed files for scanning /// @@ -40,14 +40,14 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILELOGTAG = Logging.Log.LogTagFromType(typeof(FilePreFilterProcess)) + ".FileEntry"; - public static Task Run(Snapshots.ISnapshotService snapshot, Options options, BackupStatsCollector stats, BackupDatabase database) + public static Task Run(Channels channels, Options options, BackupStatsCollector stats, BackupDatabase database) { return AutomationExtensions.RunTask( new { - Input = Channels.ProcessedFiles.ForRead, - ProgressChannel = Channels.ProgressEvents.ForWrite, - Output = Channels.AcceptedChangedFile.ForWrite + Input = channels.ProcessedFiles.AsRead(), + ProgressChannel = channels.ProgressEvents.AsWrite(), + Output = channels.AcceptedChangedFile.AsWrite() }, async self => @@ -73,11 +73,11 @@ namespace Duplicati.Library.Main.Operation.Backup long filestatsize = -1; try { - filestatsize = snapshot.GetFileSize(e.Path); + filestatsize = e.Entry.Size; } - catch(Exception ex) + catch (Exception ex) { - Logging.Log.WriteExplicitMessage(FILELOGTAG, "FailedToReadSize", ex, "Failed to read size of file: {0}", e.Path); + Logging.Log.WriteExplicitMessage(FILELOGTAG, "FailedToReadSize", ex, "Failed to read size of file: {0}", e.Entry.Path); } await stats.AddExaminedFile(filestatsize); @@ -86,8 +86,8 @@ namespace Duplicati.Library.Main.Operation.Backup var tooLargeFile = SKIPFILESLARGERTHAN != 0 && filestatsize >= 0 && filestatsize > SKIPFILESLARGERTHAN; if (tooLargeFile) { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckTooLarge", "Skipped checking file, because the size exceeds limit {0}", e.Path); - await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = filestatsize, Type = EventType.FileSkipped }); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckTooLarge", "Skipped checking file, because the size exceeds limit {0}", e.Entry.Path); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Entry.Path, Length = filestatsize, Type = EventType.FileSkipped }); continue; } @@ -96,13 +96,13 @@ namespace Duplicati.Library.Main.Operation.Backup // If we disable the filetime check, we always assume that the file has changed // Otherwise we check that the timestamps are different or if any of them are empty - var timestampChanged = DISABLEFILETIMECHECK || e.LastWrite != e.OldModified || e.LastWrite.Ticks == 0 || e.OldModified.Ticks == 0; + e.TimestampChanged = DISABLEFILETIMECHECK || e.LastWrite != e.OldModified || e.LastWrite.Ticks == 0 || e.OldModified.Ticks == 0; // Avoid generating a new metadata blob if timestamp has not changed // and we only check for timestamp changes - if (CHECKFILETIMEONLY && !timestampChanged && !isNewFile) + if (CHECKFILETIMEONLY && !e.TimestampChanged && !isNewFile) { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckNoTimestampChange", "Skipped checking file, because timestamp was not updated {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckNoTimestampChange", "Skipped checking file, because timestamp was not updated {0}", e.Entry.Path); try { await database.AddUnmodifiedAsync(e.OldId, e.LastWrite); @@ -111,9 +111,9 @@ namespace Duplicati.Library.Main.Operation.Backup { if (ex.IsRetiredException()) throw; - Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Path); + Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Entry.Path); } - await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = filestatsize, Type = EventType.FileSkipped }); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Entry.Path, Length = filestatsize, Type = EventType.FileSkipped }); continue; } @@ -130,28 +130,28 @@ namespace Duplicati.Library.Main.Operation.Backup } // Compute current metadata - e.MetaHashAndSize = SKIPMETADATA ? EMPTY_METADATA : Utility.WrapMetadata(MetadataGenerator.GenerateMetadata(e.Path, e.Attributes, options, snapshot), options); + e.MetaHashAndSize = SKIPMETADATA ? EMPTY_METADATA : Utility.WrapMetadata(MetadataGenerator.GenerateMetadata(e.Entry, e.Attributes, options), options); e.MetadataChanged = !SKIPMETADATA && (e.MetaHashAndSize.Blob.Length != e.OldMetaSize || e.MetaHashAndSize.FileHash != e.OldMetaHash); // Check if the file is new, or something indicates a change var filesizeChanged = filestatsize < 0 || e.LastFileSize < 0 || filestatsize != e.LastFileSize; - if (isNewFile || timestampChanged || filesizeChanged || e.MetadataChanged) + if (isNewFile || e.TimestampChanged || filesizeChanged || e.MetadataChanged) { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "CheckFileForChanges", "Checking file for changes {0}, new: {1}, timestamp changed: {2}, size changed: {3}, metadatachanged: {4}, {5} vs {6}", e.Path, isNewFile, timestampChanged, filesizeChanged, e.MetadataChanged, e.LastWrite, e.OldModified); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "CheckFileForChanges", "Checking file for changes {0}, new: {1}, timestamp changed: {2}, size changed: {3}, metadatachanged: {4}, {5} vs {6}", e.Entry.Path, isNewFile, e.TimestampChanged, filesizeChanged, e.MetadataChanged, e.LastWrite, e.OldModified); await self.Output.WriteAsync(e); } else { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckNoMetadataChange", "Skipped checking file, because no metadata was updated {0}", e.Path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "SkipCheckNoMetadataChange", "Skipped checking file, because no metadata was updated {0}", e.Entry.Path); try { await database.AddUnmodifiedAsync(e.OldId, e.LastWrite); } catch (Exception ex) { - Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Path); + Logging.Log.WriteWarningMessage(FILELOGTAG, "FailedToAddFile", ex, "Failed while attempting to add unmodified file to database: {0}", e.Entry.Path); } - await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = filestatsize, Type = EventType.FileSkipped }); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Entry.Path, Length = filestatsize, Type = EventType.FileSkipped }); } } }); diff --git a/Duplicati/Library/Main/Operation/Backup/FileProgressThrottler.cs b/Duplicati/Library/Main/Operation/Backup/FileProgressThrottler.cs index 0858890ee..e30eb775c 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileProgressThrottler.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileProgressThrottler.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Main.Operation.Common; diff --git a/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs b/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs index 7836f2158..6be52d47c 100644 --- a/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs +++ b/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,11 +20,8 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Threading.Tasks; using System.Collections.Generic; -using CoCoL; -using Duplicati.Library.Main.Operation.Common; -using Duplicati.Library.Snapshots; +using Duplicati.Library.Interface; namespace Duplicati.Library.Main.Operation.Backup { @@ -35,7 +32,7 @@ namespace Duplicati.Library.Main.Operation.Backup { private static readonly string METALOGTAG = Logging.Log.LogTagFromType(typeof(MetadataGenerator)) + ".Metadata"; - public static Dictionary GenerateMetadata(string path, System.IO.FileAttributes attributes, Options options, Snapshots.ISnapshotService snapshot) + public static Dictionary GenerateMetadata(ISourceProviderEntry entry, System.IO.FileAttributes attributes, Options options) { try { @@ -43,7 +40,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (!options.SkipMetadata) { - metadata = snapshot.GetMetadata(path, snapshot.IsSymlink(path, attributes), options.SymlinkPolicy == Options.SymlinkStrategy.Follow); + metadata = entry.MinorMetadata; if (metadata == null) metadata = new Dictionary(); @@ -54,11 +51,11 @@ namespace Duplicati.Library.Main.Operation.Backup { try { - metadata["CoreLastWritetime"] = snapshot.GetLastWriteTimeUtc(path).Ticks.ToString(); + metadata["CoreLastWritetime"] = entry.LastModificationUtc.Ticks.ToString(); } catch (Exception ex) { - Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path); + Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", entry.Path); } } @@ -66,11 +63,11 @@ namespace Duplicati.Library.Main.Operation.Backup { try { - metadata["CoreCreatetime"] = snapshot.GetCreationTimeUtc(path).Ticks.ToString(); + metadata["CoreCreatetime"] = entry.CreatedUtc.Ticks.ToString(); } catch (Exception ex) { - Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path); + Logging.Log.WriteWarningMessage(METALOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", entry.Path); } } } @@ -81,9 +78,9 @@ namespace Duplicati.Library.Main.Operation.Backup return metadata; } - catch(Exception ex) + catch (Exception ex) { - Logging.Log.WriteWarningMessage(METALOGTAG, "MetadataProcessFailed", ex, "Failed to process metadata for \"{0}\", storing empty metadata", path); + Logging.Log.WriteWarningMessage(METALOGTAG, "MetadataProcessFailed", ex, "Failed to process metadata for \"{0}\", storing empty metadata", entry.Path); return new Dictionary(); } } diff --git a/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs b/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs index a449b03c9..2ae10faa1 100644 --- a/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,11 +24,8 @@ using CoCoL; using System.Threading.Tasks; using System.IO; using System.Collections.Generic; -using System.Security.Principal; -using System.Threading; using Duplicati.Library.Interface; using Duplicati.Library.Main.Operation.Common; -using Duplicati.Library.Snapshots; namespace Duplicati.Library.Main.Operation.Backup { @@ -43,7 +40,7 @@ namespace Duplicati.Library.Main.Operation.Backup public class FileEntry { // From input - public string Path; + public ISourceProviderEntry Entry; // Split public long PathPrefixID; @@ -63,17 +60,18 @@ namespace Duplicati.Library.Main.Operation.Backup // After processing metadata public IMetahash MetaHashAndSize; public bool MetadataChanged; + public bool TimestampChanged; } - - public static Task Run(Snapshots.ISnapshotService snapshot, Options options, BackupDatabase database, long lastfilesetid, CancellationToken token) + + public static Task Run(Channels channels, Options options, BackupDatabase database, long lastfilesetid, ITaskReader taskReader) { return AutomationExtensions.RunTask(new { - Input = Backup.Channels.SourcePaths.ForRead, - StreamBlockChannel = Channels.StreamBlock.ForWrite, - Output = Backup.Channels.ProcessedFiles.ForWrite, + Input = channels.SourcePaths.AsRead(), + StreamBlockChannel = channels.StreamBlock.AsWrite(), + Output = channels.ProcessedFiles.AsWrite(), }, - + async self => { var emptymetadata = Utility.WrapMetadata(new Dictionary(), options); @@ -84,34 +82,40 @@ namespace Duplicati.Library.Main.Operation.Backup while (true) { - var path = await self.Input.ReadAsync(); + var entry = await self.Input.ReadAsync(); + + // We ignore the stop signal, but not the pause and terminate + await taskReader.ProgressRendevouz().ConfigureAwait(false); var lastwrite = new DateTime(0, DateTimeKind.Utc); - var attributes = default(FileAttributes); - try - { - lastwrite = snapshot.GetLastWriteTimeUtc(path); - } - catch (Exception ex) + var attributes = entry.IsFolder + ? FileAttributes.Directory + : FileAttributes.Normal; + + try { - Logging.Log.WriteWarningMessage(FILELOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", path); + lastwrite = entry.LastModificationUtc; + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(FILELOGTAG, "TimestampReadFailed", ex, "Failed to read timestamp on \"{0}\"", entry.Path); } - try - { - attributes = snapshot.GetAttributes(path); - } - catch (Exception ex) + try { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); + attributes = entry.Attributes; + } + catch (Exception ex) + { + Logging.Log.WriteVerboseMessage(FILELOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", entry.Path, ex.Message); } // If we only have metadata, stop here - if (await ProcessMetadata(path, attributes, lastwrite, options, snapshot, emptymetadata, database, self.StreamBlockChannel).ConfigureAwait(false)) + if (await ProcessMetadata(entry, attributes, lastwrite, options, emptymetadata, database, self.StreamBlockChannel).ConfigureAwait(false)) { try { - var split = Database.LocalDatabase.SplitIntoPrefixAndName(path); + var split = Database.LocalDatabase.SplitIntoPrefixAndName(entry.Path); long prefixid; if (string.Equals(prevprefix.Key, split.Key, StringComparison.Ordinal)) @@ -128,7 +132,7 @@ namespace Duplicati.Library.Main.Operation.Backup await self.Output.WriteAsync(new FileEntry { OldId = tmp.Item1, - Path = path, + Entry = entry, PathPrefixID = prefixid, Filename = split.Value, Attributes = attributes, @@ -145,7 +149,7 @@ namespace Duplicati.Library.Main.Operation.Backup await self.Output.WriteAsync(new FileEntry { OldId = res == null ? -1 : res.id, - Path = path, + Entry = entry, PathPrefixID = prefixid, Filename = split.Value, Attributes = attributes, @@ -159,12 +163,11 @@ namespace Duplicati.Library.Main.Operation.Backup } catch (Exception ex) { - if (ex.IsRetiredException() || token.IsCancellationRequested) - { + if (ex.IsRetiredException()) continue; - } + Logging.Log.WriteWarningMessage(FILELOGTAG, "ProcessingMetadataFailed", ex, - "Failed to process entry, path: {0}", path); + "Failed to process entry, path: {0}", entry.Path); } } } @@ -177,9 +180,9 @@ namespace Duplicati.Library.Main.Operation.Backup /// Processes the metadata for the given path. /// /// True if the path should be submitted to more analysis, false if there is nothing else to do - private static async Task ProcessMetadata(string path, FileAttributes attributes, DateTime lastwrite, Options options, Snapshots.ISnapshotService snapshot, IMetahash emptymetadata, BackupDatabase database, IWriteChannel streamblockchannel) + private static async Task ProcessMetadata(ISourceProviderEntry entry, FileAttributes attributes, DateTime lastwrite, Options options, IMetahash emptymetadata, BackupDatabase database, IWriteChannel streamblockchannel) { - if (snapshot.IsSymlink(path, attributes)) + if (entry.IsSymlink) { // Not all reparse points are symlinks. // For example, on Windows 10 Fall Creator's Update, the OneDrive folder (and all subfolders) @@ -188,57 +191,58 @@ namespace Duplicati.Library.Main.Operation.Backup string symlinkTarget = null; try { - symlinkTarget = snapshot.GetSymlinkTarget(path); + symlinkTarget = entry.SymlinkTarget; } catch (Exception ex) { - Logging.Log.WriteExplicitMessage(FILELOGTAG, "SymlinkTargetReadFailure", ex, "Failed to read symlink target for path: {0}", path); + Logging.Log.WriteExplicitMessage(FILELOGTAG, "SymlinkTargetReadFailure", ex, "Failed to read symlink target for path: {0}", entry.Path); } if (!string.IsNullOrWhiteSpace(symlinkTarget)) { if (options.SymlinkPolicy == Options.SymlinkStrategy.Ignore) { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "IgnoreSymlink", "Ignoring symlink {0}", path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "IgnoreSymlink", "Ignoring symlink {0}", entry.Path); return false; } if (options.SymlinkPolicy == Options.SymlinkStrategy.Store) { - var metadata = MetadataGenerator.GenerateMetadata(path, attributes, options, snapshot); + var metadata = MetadataGenerator.GenerateMetadata(entry, attributes, options); if (!metadata.ContainsKey("CoreSymlinkTarget")) metadata["CoreSymlinkTarget"] = symlinkTarget; var metahash = Utility.WrapMetadata(metadata, options); - await AddSymlinkToOutputAsync(path, DateTime.UtcNow, metahash, database, streamblockchannel).ConfigureAwait(false); + await AddSymlinkToOutputAsync(entry.Path, DateTime.UtcNow, metahash, database, streamblockchannel).ConfigureAwait(false); - Logging.Log.WriteVerboseMessage(FILELOGTAG, "StoreSymlink", "Stored symlink {0}", path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "StoreSymlink", "Stored symlink {0}", entry.Path); // Don't process further return false; } } else { - Logging.Log.WriteVerboseMessage(FILELOGTAG, "FollowingEmptySymlink", "Treating empty symlink as regular path {0}", path); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "FollowingEmptySymlink", "Treating empty symlink as regular path {0}", entry.Path); } } + if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) { IMetahash metahash; if (!options.SkipMetadata) { - metahash = Utility.WrapMetadata(MetadataGenerator.GenerateMetadata(path, attributes, options, snapshot), options); + metahash = Utility.WrapMetadata(MetadataGenerator.GenerateMetadata(entry, attributes, options), options); } else { metahash = emptymetadata; } - Logging.Log.WriteVerboseMessage(FILELOGTAG, "AddDirectory", "Adding directory {0}", path); - await AddFolderToOutputAsync(path, lastwrite, metahash, database, streamblockchannel).ConfigureAwait(false); + Logging.Log.WriteVerboseMessage(FILELOGTAG, "AddDirectory", "Adding directory {0}", entry.Path); + await AddFolderToOutputAsync(entry.Path, lastwrite, metahash, database, streamblockchannel).ConfigureAwait(false); return false; } @@ -258,13 +262,13 @@ namespace Duplicati.Library.Main.Operation.Backup { StreamProcessResult res; using (var ms = new MemoryStream(meta.Blob)) - res = await StreamBlock.ProcessStream(streamblockchannel, path, ms, true, CompressionHint.Default); + res = await StreamBlock.ProcessStream(streamblockchannel, path, ms, true, CompressionHint.Default); return await database.AddMetadatasetAsync(res.Streamhash, res.Streamlength, res.Blocksetid); } /// - /// Adds a file to the output, + /// Adds a file to the output, /// /// The name of the file to record /// The value of the lastModified timestamp @@ -275,7 +279,7 @@ namespace Duplicati.Library.Main.Operation.Backup } /// - /// Adds a file to the output, + /// Adds a file to the output, /// /// The name of the file to record /// The value of the lastModified timestamp diff --git a/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs b/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs index 46f3f354b..aa240834b 100644 --- a/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs +++ b/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -33,13 +33,13 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal static class ProgressHandler { - public static Task Run(BackupResults stat) + public static Task Run(Channels channels, BackupResults stat) { return AutomationExtensions.RunTask(new { - Input = Channels.ProgressEvents.ForRead + Input = channels.ProgressEvents.AsRead() }, - + async self => { var filesStarted = new Dictionary(); @@ -64,7 +64,7 @@ namespace Duplicati.Library.Main.Operation.Backup case EventType.FileClosed: if (fileProgress.ContainsKey(t.Filepath)) fileProgress[t.Filepath] = t.Length; - + if (t.Filepath == current) { stat.OperationProgressUpdater.UpdateFileProgress(t.Length); @@ -94,7 +94,7 @@ namespace Duplicati.Library.Main.Operation.Backup { stat.OperationProgressUpdater.StartFile(current, filesStarted[current]); if (fileProgress.ContainsKey(current) && fileProgress[current] > 0) - stat.OperationProgressUpdater.UpdateFileProgress(fileProgress[current]); + stat.OperationProgressUpdater.UpdateFileProgress(fileProgress[current]); } } } diff --git a/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs b/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs index a1242260a..46e80a80a 100644 --- a/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs +++ b/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,49 +19,38 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Threading.Tasks; -using CoCoL; using Duplicati.Library.Main.Operation.Common; -namespace Duplicati.Library.Main.Operation.Backup +namespace Duplicati.Library.Main.Operation.Backup; + +internal static class RecreateMissingIndexFiles { - internal static class RecreateMissingIndexFiles - { - /// - /// The tag used for log messages - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(RecreateMissingIndexFiles)); + /// + /// The tag used for log messages + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(RecreateMissingIndexFiles)); - public static Task Run(BackupDatabase database, Options options, ITaskReader taskreader) - { - return AutomationExtensions.RunTask(new - { - UploadChannel = Channels.BackendRequest.ForWrite - }, - - async self => + public static async Task Run(BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) + { + if (options.IndexfilePolicy != Options.IndexFileStrategy.None) + { + foreach (var blockfile in await database.GetMissingIndexFilesAsync().ConfigureAwait(false)) { - if (options.IndexfilePolicy != Options.IndexFileStrategy.None) - { - foreach (var blockfile in await database.GetMissingIndexFilesAsync()) - { - if (!await taskreader.ProgressAsync) - return; + if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + return; - Logging.Log.WriteInformationMessage(LOGTAG, "RecreateMissingIndexFile", "Re-creating missing index file for {0}", blockfile); - var w = await Common.IndexVolumeCreator.CreateIndexVolume(blockfile, options, database); + Logging.Log.WriteInformationMessage(LOGTAG, "RecreateMissingIndexFile", "Re-creating missing index file for {0}", blockfile); + var w = await Common.IndexVolumeCreator.CreateIndexVolume(blockfile, options, database).ConfigureAwait(false); - if (!await taskreader.ProgressAsync) - return; - - await database.UpdateRemoteVolumeAsync(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null); - await self.UploadChannel.WriteAsync(new IndexVolumeUploadRequest(w)); - } - } - }); + if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + return; + await database.UpdateRemoteVolumeAsync(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null).ConfigureAwait(false); + await backendManager.PutAsync(w, null, null, false, taskreader.ProgressToken).ConfigureAwait(false); + } } - } + } + diff --git a/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs b/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs index 10bab81a8..c391f7b21 100644 --- a/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,7 +25,6 @@ using Duplicati.Library.Main.Volumes; using System; using System.Collections.Generic; using System.Threading.Tasks; -using static Duplicati.Library.Main.Operation.Common.BackendHandler; namespace Duplicati.Library.Main.Operation.Backup { @@ -49,13 +48,12 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal static class SpillCollectorProcess { - public static Task Run(Options options, BackupDatabase database, ITaskReader taskreader) + public static Task Run(Channels channels, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) { return AutomationExtensions.RunTask( new { - Input = Channels.SpillPickup.ForRead, - Output = Channels.BackendRequest.ForWrite, + Input = channels.SpillPickup.AsRead() }, async self => @@ -74,11 +72,34 @@ namespace Duplicati.Library.Main.Operation.Backup throw; } + async Task GetNextTarget(SpillVolumeRequest source) + { + SpillVolumeRequest target = null; + if (lst.Count == 0) + { + // No more targets, make one + target = new SpillVolumeRequest(new BlockVolumeWriter(options), source.IndexVolume == null ? null : new TemporaryIndexVolume(options)); + target.BlockVolume.VolumeID = await database.RegisterRemoteVolumeAsync(target.BlockVolume.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary).ConfigureAwait(false); + } + else + { + // Grab the next target + target = lst[0]; + lst.RemoveAt(0); + } + + // We copy all blocklisthashes as they are pre-filtered to be unique + if (source.IndexVolume != null) + source.IndexVolume.CopyTo(target.IndexVolume, true); + + return target; + } + while (lst.Count > 1) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressAsync.ConfigureAwait(false); + await taskreader.ProgressRendevouz().ConfigureAwait(false); SpillVolumeRequest target = null; var source = lst[0]; @@ -93,29 +114,14 @@ namespace Duplicati.Library.Main.Operation.Backup using (var rd = new BlockVolumeReader(options.CompressionModule, source.BlockVolume.LocalFilename, options)) { + // Make sure we process the blocklisthashes, even if the blockvolume is empty + target = await GetNextTarget(source).ConfigureAwait(false); + foreach (var file in rd.Blocks) { // Grab a target if (target == null) - { - if (lst.Count == 0) - { - // No more targets, make one - target = new SpillVolumeRequest(new BlockVolumeWriter(options), source.IndexVolume == null ? null : new TemporaryIndexVolume(options)); - target.BlockVolume.VolumeID = await database.RegisterRemoteVolumeAsync(target.BlockVolume.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary).ConfigureAwait(false); - } - else - { - // Grab the next target - target = lst[0]; - lst.RemoveAt(0); - } - - // We copy all the blocklisthashes, which may create duplicates - // but otherwise we need to query all hashes to see if they are blocklisthashes - if (source.IndexVolume != null) - source.IndexVolume.CopyTo(target.IndexVolume, true); - } + target = await GetNextTarget(source).ConfigureAwait(false); var len = rd.ReadBlock(file.Key, buffer); target.BlockVolume.AddBlock(file.Key, buffer, 0, len, Duplicati.Library.Interface.CompressionHint.Default); @@ -127,7 +133,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (target.BlockVolume.Filesize > options.VolumeSize - options.Blocksize) { target.BlockVolume.Close(); - await UploadVolumeAndIndex(target, self.Output, options, database).ConfigureAwait(false); + await UploadVolumeAndIndex(target, options, database, backendManager, taskreader).ConfigureAwait(false); target = null; } } @@ -145,28 +151,28 @@ namespace Duplicati.Library.Main.Operation.Backup foreach (var n in lst) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressAsync.ConfigureAwait(false); + await taskreader.ProgressRendevouz().ConfigureAwait(false); n.BlockVolume.Close(); - await UploadVolumeAndIndex(n, self.Output, options, database).ConfigureAwait(false); + await UploadVolumeAndIndex(n, options, database, backendManager, taskreader).ConfigureAwait(false); } }); } - private static async Task UploadVolumeAndIndex(SpillVolumeRequest target, IWriteChannel outputChannel, Options options, BackupDatabase database) + private static async Task UploadVolumeAndIndex(SpillVolumeRequest target, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) { - var blockEntry = target.BlockVolume.CreateFileEntryForUpload(options); - - TemporaryIndexVolume indexVolumeCopy = null; + IndexVolumeWriter indexVolumeCopy = null; if (target.IndexVolume != null) { - indexVolumeCopy = new TemporaryIndexVolume(options); - target.IndexVolume.CopyTo(indexVolumeCopy, false); + // TODO: It is much easier to let the BackendManager deal with index files, + // but it adds a bit of strain to the database + indexVolumeCopy = await target.IndexVolume.CreateVolume(target.BlockVolume.RemoteFilename, options, database).ConfigureAwait(false); + // Create link before upload is started, it will be removed later if upload fails + await database.AddIndexBlockLinkAsync(indexVolumeCopy.VolumeID, target.BlockVolume.VolumeID).ConfigureAwait(false); } - var uploadRequest = new VolumeUploadRequest(target.BlockVolume, blockEntry, indexVolumeCopy, options, database); - await outputChannel.WriteAsync(uploadRequest).ConfigureAwait(false); + await backendManager.PutAsync(target.BlockVolume, indexVolumeCopy, null, false, taskreader.ProgressToken).ConfigureAwait(false); } } } diff --git a/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs b/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs index 1de702054..a20fabe0d 100644 --- a/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs +++ b/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using CoCoL; diff --git a/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs b/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs index 1dd4f6666..449ee9cfb 100644 --- a/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs +++ b/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -37,31 +37,33 @@ namespace Duplicati.Library.Main.Operation.Backup /// /// The tag used for log messages /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(StreamBlockSplitter)) ; + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(StreamBlockSplitter)); private static readonly string FILELOGTAG = LOGTAG + ".FileEntry"; - public static Task Run(Options options, BackupDatabase database, ITaskReader taskreader) + public static Task Run(Channels channels, Options options, BackupDatabase database, ITaskReader taskreader) { return AutomationExtensions.RunTask( new { - Input = Channels.StreamBlock.ForRead, - ProgressChannel = Channels.ProgressEvents.ForWrite, - BlockOutput = Channels.OutputBlocks.ForWrite + Input = channels.StreamBlock.AsRead(), + ProgressChannel = channels.ProgressEvents.AsWrite(), + BlockOutput = channels.OutputBlocks.AsWrite() }, async self => { var blocksize = options.Blocksize; var emptymetadata = Utility.WrapMetadata(new Dictionary(), options); - var maxmetadatasize = (options.Blocksize / (long) options.BlockhashSize) * options.Blocksize; + var maxmetadatasize = (options.Blocksize / (long)options.BlockhashSize) * options.Blocksize; - using(var filehasher = HashFactory.CreateHasher(options.FileHashAlgorithm)) - using(var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) + using (var filehasher = HashFactory.CreateHasher(options.FileHashAlgorithm)) + using (var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) using (var empty_metadata_stream = new MemoryStream(emptymetadata.Blob)) { - while (await taskreader.ProgressAsync) + while (true) { + // We ignore the stop signal, but not the pause and terminate + await taskreader.ProgressRendevouz().ConfigureAwait(false); var send_close = false; var filesize = 0L; @@ -105,7 +107,7 @@ namespace Duplicati.Library.Main.Operation.Backup // Don't send progress reports for metadata if (!e.IsMetadata) { - await self.ProgressChannel.WriteAsync(new ProgressEvent() {Filepath = e.Path, Length = fslen, Type = EventType.FileStarted}); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = fslen, Type = EventType.FileStarted }); send_close = true; } @@ -126,7 +128,7 @@ namespace Duplicati.Library.Main.Operation.Backup // If we have too many hashes, flush the blocklist if (blocklistbuffer.Length - blocklistoffset < hashdata.Length) { - var blkey = Convert.ToBase64String(blockhasher.ComputeHash(blocklistbuffer, 0, (int) blocklistoffset)); + var blkey = Convert.ToBase64String(blockhasher.ComputeHash(blocklistbuffer, 0, (int)blocklistoffset)); blocklisthashes.Add(blkey); await DataBlock.AddBlockToOutputAsync(self.BlockOutput, blkey, blocklistbuffer, 0, blocklistoffset, CompressionHint.Noncompressible, true); blocklistoffset = 0; @@ -142,7 +144,7 @@ namespace Duplicati.Library.Main.Operation.Backup // Don't spam updates if (send_close && (DateTime.Now - lastupdate).TotalSeconds > 5) { - await self.ProgressChannel.WriteAsync(new ProgressEvent() {Filepath = e.Path, Length = filesize, Type = EventType.FileProgressUpdate}); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = filesize, Type = EventType.FileProgressUpdate }); lastupdate = DateTime.Now; } @@ -155,7 +157,7 @@ namespace Duplicati.Library.Main.Operation.Backup // If we have more than a single block of data, output the (trailing) blocklist if (hashcollector.Count > 1) { - var blkey = Convert.ToBase64String(blockhasher.ComputeHash(blocklistbuffer, 0, (int) blocklistoffset)); + var blkey = Convert.ToBase64String(blockhasher.ComputeHash(blocklistbuffer, 0, (int)blocklistoffset)); blocklisthashes.Add(blkey); await DataBlock.AddBlockToOutputAsync(self.BlockOutput, blkey, blocklistbuffer, 0, blocklistoffset, CompressionHint.Noncompressible, true); } @@ -163,7 +165,7 @@ namespace Duplicati.Library.Main.Operation.Backup filehasher.TransformFinalBlock(new byte[0], 0, 0); var filehash = Convert.ToBase64String(filehasher.Hash); var blocksetid = await database.AddBlocksetAsync(filehash, filesize, blocksize, hashcollector, blocklisthashes); - cur.SetResult(new StreamProcessResult() {Streamlength = filesize, Streamhash = filehash, Blocksetid = blocksetid}); + cur.SetResult(new StreamProcessResult() { Streamlength = filesize, Streamhash = filehash, Blocksetid = blocksetid }); cur = null; } } @@ -198,7 +200,7 @@ namespace Duplicati.Library.Main.Operation.Backup } if (send_close) - await self.ProgressChannel.WriteAsync(new ProgressEvent() {Filepath = e.Path, Length = filesize, Type = EventType.FileClosed}); + await self.ProgressChannel.WriteAsync(new ProgressEvent() { Filepath = e.Path, Length = filesize, Type = EventType.FileClosed }); send_close = false; } } diff --git a/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs b/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs index 292eac1a5..3d8431f62 100644 --- a/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs +++ b/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,71 +21,69 @@ using System; using System.Threading.Tasks; -using CoCoL; using Duplicati.Library.Main.Volumes; -namespace Duplicati.Library.Main.Operation.Backup +namespace Duplicati.Library.Main.Operation.Backup; + +internal static class UploadRealFilelist { - internal static class UploadRealFilelist + /// + /// The tag used for log messages + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(UploadRealFilelist)); + + public static async Task Run(BackupResults result, BackupDatabase db, IBackendManager backendManager, Options options, FilesetVolumeWriter filesetvolume, long filesetid, Common.ITaskReader taskreader, bool lastWasPartial) { - /// - /// The tag used for log messages - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(UploadRealFilelist)); + // We ignore the stop signal, but not the pause and terminate + await taskreader.ProgressRendevouz().ConfigureAwait(false); - public static Task Run(BackupResults result, BackupDatabase db, Options options, FilesetVolumeWriter filesetvolume, long filesetid, Common.ITaskReader taskreader) + // Update the reported source and backend changes + using (new Logging.Timer(LOGTAG, "UpdateChangeStatistics", "UpdateChangeStatistics")) + await db.UpdateChangeStatisticsAsync(result); + + var changeCount = + result.AddedFiles + result.ModifiedFiles + result.DeletedFiles + + result.AddedFolders + result.ModifiedFolders + result.DeletedFolders + + result.AddedSymlinks + result.ModifiedSymlinks + result.DeletedSymlinks; + + //Changes in the filelist triggers a filelist upload + if (options.UploadUnchangedBackups || changeCount > 0 || lastWasPartial) { - return AutomationExtensions.RunTask(new + using (new Logging.Timer(LOGTAG, "UploadNewFileset", "Uploading a new fileset")) { - Output = Channels.BackendRequest.ForWrite, - }, + if (!string.IsNullOrEmpty(options.ControlFiles)) + foreach (var p in options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + filesetvolume.AddControlFile(p, options.GetCompressionHintFromFilename(p)); - async self => - { - if (!await taskreader.ProgressAsync) - return; - - // Update the reported source and backend changes - using(new Logging.Timer(LOGTAG, "UpdateChangeStatistics", "UpdateChangeStatistics")) - await db.UpdateChangeStatisticsAsync(result); + // We ignore the stop signal, but not the pause and terminate + await taskreader.ProgressRendevouz().ConfigureAwait(false); - var changeCount = - result.AddedFiles + result.ModifiedFiles + result.DeletedFiles + - result.AddedFolders + result.ModifiedFolders + result.DeletedFolders + - result.AddedSymlinks + result.ModifiedSymlinks + result.DeletedSymlinks; + await db.WriteFilesetAsync(filesetvolume, filesetid).ConfigureAwait(false); + filesetvolume.Close(); - //Changes in the filelist triggers a filelist upload - if (options.UploadUnchangedBackups || changeCount > 0) - { - using(new Logging.Timer(LOGTAG, "UploadNewFileset", "Uploading a new fileset")) - { - if (!string.IsNullOrEmpty(options.ControlFiles)) - foreach(var p in options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) - filesetvolume.AddControlFile(p, options.GetCompressionHintFromFilename(p)); + // We ignore the stop signal, but not the pause and terminate + await taskreader.ProgressRendevouz().ConfigureAwait(false); - if (!await taskreader.ProgressAsync) - return; + await db.UpdateRemoteVolumeAsync(filesetvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null).ConfigureAwait(false); + await db.CommitTransactionAsync("CommitUpdateRemoteVolume").ConfigureAwait(false); - await db.WriteFilesetAsync(filesetvolume, filesetid); - filesetvolume.Close(); - - if (!await taskreader.ProgressAsync) - return; - - await db.UpdateRemoteVolumeAsync(filesetvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null); - await db.CommitTransactionAsync("CommitUpdateRemoteVolume"); - await self.Output.WriteAsync(new FilesetUploadRequest(filesetvolume)); - } - } - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingLeftoverTempFile", "removing temp files, as no data needs to be uploaded"); - await db.RemoveRemoteVolumeAsync(filesetvolume.RemoteFilename); - } - - await db.CommitTransactionAsync("CommitUpdateRemoteVolume"); - }); + await backendManager.PutAsync(filesetvolume, null, null, false, taskreader.ProgressToken).ConfigureAwait(false); + } } + else + { + if (result.TimestampChangedFiles != 0) + { + Logging.Log.WriteInformationMessage(LOGTAG, "DetectedTimestampChanges", "Detected timestamp changes, but no data needs to be uploaded, pushing timestamp changes to the latest fileset"); + await db.PushTimestampChangesToPreviousVersionAsync(filesetid).ConfigureAwait(false); + } + + Logging.Log.WriteVerboseMessage(LOGTAG, "RemovingLeftoverTempFile", "removing temp files, as no data needs to be uploaded"); + await db.RemoveRemoteVolumeAsync(filesetvolume.RemoteFilename); + } + + await db.CommitTransactionAsync("CommitUpdateRemoteVolume"); } } + diff --git a/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs b/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs index 1da10d502..7efa1b13e 100644 --- a/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs +++ b/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,7 +19,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using CoCoL; +using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Operation.Common; using Duplicati.Library.Main.Volumes; using System; @@ -38,114 +38,92 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(UploadSyntheticFilelist)); - public static Task Run(BackupDatabase database, Options options, BackupResults result, ITaskReader taskreader, string lastTempFilelist, long lastTempFilesetId) + /// + /// This method is used to generate and upload a synthetic file list, if needed + /// + /// The database to use + /// The options to use + /// The backup results + /// The task reader to use + /// The backend manager to use + /// The last temporary file list volume + /// + public static async Task Run(BackupDatabase database, Options options, BasicResults result, ITaskReader taskreader, IBackendManager backendManager, RemoteVolumeEntry lastTempFilelist) { - return AutomationExtensions.RunTask(new + // Check if we should upload a synthetic filelist + if (options.DisableSyntheticFilelist || string.IsNullOrWhiteSpace(lastTempFilelist.Name) || lastTempFilelist.ID <= 0) + return; + + // Files is missing or repaired + if (lastTempFilelist.State != RemoteVolumeState.Uploading && lastTempFilelist.State != RemoteVolumeState.Temporary) { - UploadChannel = Channels.BackendRequest.ForWrite - }, - - async self => - { - // Check if we should upload a synthetic filelist - if (options.DisableSyntheticFilelist || string.IsNullOrWhiteSpace(lastTempFilelist) || lastTempFilesetId <= 0) - return; - - // Check that we still need to process this after the cleanup has performed its duties - var syntbase = await database.GetRemoteVolumeFromFilesetIDAsync(lastTempFilesetId); - - // If we do not have a valid entry, warn and quit - if (syntbase.Name == null) - { - // TODO: If the repair succeeds, this could give a false warning? - Logging.Log.WriteWarningMessage(LOGTAG, "MissingTemporaryFilelist", null, "Expected there to be a temporary fileset for synthetic filelist ({0}, {1}), but none was found?", lastTempFilesetId, lastTempFilelist); - return; - } - - // Files is missing or repaired - if (syntbase.State != RemoteVolumeState.Uploading && syntbase.State != RemoteVolumeState.Temporary) - { - Logging.Log.WriteInformationMessage(LOGTAG, "SkippingSyntheticListUpload", "Skipping synthetic upload because temporary fileset appers to be complete: ({0}, {1}, {2})", lastTempFilesetId, lastTempFilelist, syntbase.State); - return; - } - - // Ready to build and upload the synthetic list - await database.CommitTransactionAsync("PreSyntheticFilelist"); - var incompleteFilesets = (await database.GetIncompleteFilesetsAsync()).OrderBy(x => x.Value).ToList(); - - if (!incompleteFilesets.Any()) - { - return; - } - - if (!await taskreader.ProgressAsync) - return; - - result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreviousBackupFinalize); - Logging.Log.WriteInformationMessage(LOGTAG, "PreviousBackupFilelistUpload", "Uploading filelist from previous interrupted backup"); - - var incompleteSet = incompleteFilesets.Last(); - var badIds = from n in incompleteFilesets select n.Key; - - var prevs = (from n in await database.GetFilesetTimesAsync() - where - n.Key < incompleteSet.Key - && - !badIds.Contains(n.Key) - orderby n.Key - select n.Key).ToArray(); - - var prevId = prevs.Length == 0 ? -1 : prevs.Last(); - - FilesetVolumeWriter fsw = null; - try - { - var s = 1; - var fileTime = incompleteSet.Value + TimeSpan.FromSeconds(s); - - // Probe for an unused filename - while (s < 60) - { - var id = await database.GetRemoteVolumeIDAsync(VolumeBase.GenerateFilename(RemoteVolumeType.Files, options, null, fileTime)); - if (id < 0) - break; - - fileTime = incompleteSet.Value + TimeSpan.FromSeconds(++s); - } - - fsw = new FilesetVolumeWriter(options, fileTime); - fsw.VolumeID = await database.RegisterRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary); - - if (!string.IsNullOrEmpty(options.ControlFiles)) - foreach (var p in options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) - fsw.AddControlFile(p, options.GetCompressionHintFromFilename(p)); - - // We declare this to be a partial backup since the synthetic filelist is only created - // when a backup is interrupted. - fsw.CreateFilesetFile(false); - var newFilesetID = await database.CreateFilesetAsync(fsw.VolumeID, fileTime); - await database.LinkFilesetToVolumeAsync(newFilesetID, fsw.VolumeID); - await database.AppendFilesFromPreviousSetAsync(null, newFilesetID, prevId, fileTime); - - await database.WriteFilesetAsync(fsw, newFilesetID); - fsw.Close(); - - if (!await taskreader.ProgressAsync) - return; - - await database.UpdateRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeState.Uploading, -1, null); - await database.CommitTransactionAsync("CommitUpdateFilelistVolume"); - - await self.UploadChannel.WriteAsync(new FilesetUploadRequest(fsw)); - } - catch - { - await database.RollbackTransactionAsync(); - fsw?.Dispose(); - throw; - } + Logging.Log.WriteInformationMessage(LOGTAG, "SkippingSyntheticListUpload", "Skipping synthetic upload because temporary fileset appers to be complete: ({0}, {1}, {2})", lastTempFilelist.ID, lastTempFilelist.Name, lastTempFilelist.State); + return; + } + + // Ready to build and upload the synthetic list + await database.CommitTransactionAsync("PreSyntheticFilelist"); + var incompleteFilesets = (await database.GetIncompleteFilesetsAsync()).OrderBy(x => x.Value).ToList(); + + if (!incompleteFilesets.Any()) + return; + + if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + return; + + result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreviousBackupFinalize); + Logging.Log.WriteInformationMessage(LOGTAG, "PreviousBackupFilelistUpload", "Uploading filelist from previous interrupted backup"); + + var incompleteSet = incompleteFilesets.Last(); + var badIds = incompleteFilesets.Select(n => n.Key); + + var prevs = (await database.GetFilesetTimesAsync()) + .Where(n => n.Key < incompleteSet.Key && !badIds.Contains(n.Key)) + .OrderBy(n => n.Key) + .Select(n => n.Key) + .ToArray(); + + var prevId = prevs.Length == 0 ? -1 : prevs.Last(); + + FilesetVolumeWriter fsw = null; + try + { + var fileTime = await FilesetVolumeWriter.ProbeUnusedFilenameName(database, options, incompleteSet.Value).ConfigureAwait(false); + fsw = new FilesetVolumeWriter(options, fileTime); + fsw.VolumeID = await database.RegisterRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary); + + if (!string.IsNullOrEmpty(options.ControlFiles)) + foreach (var p in options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + fsw.AddControlFile(p, options.GetCompressionHintFromFilename(p)); + + // We declare this to be a partial backup since the synthetic filelist is only created + // when a backup is interrupted. + fsw.CreateFilesetFile(false); + var newFilesetID = await database.CreateFilesetAsync(fsw.VolumeID, fileTime); + await database.LinkFilesetToVolumeAsync(newFilesetID, fsw.VolumeID); + await database.AppendFilesFromPreviousSetAsync(null, newFilesetID, prevId, fileTime); + + await database.WriteFilesetAsync(fsw, newFilesetID); + fsw.Close(); + + if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + return; + + await database.UpdateRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeState.Uploading, -1, null); + // If the previous filelist was not uploaded, we register it for deletion, as we have created a new synthetic one + // Because it is registered as "Deleting", it will be removed from remote storage by the cleanup process if it exists + if (!string.IsNullOrWhiteSpace(lastTempFilelist.Name) && (lastTempFilelist.State == RemoteVolumeState.Uploading || lastTempFilelist.State == RemoteVolumeState.Temporary)) + await database.UpdateRemoteVolumeAsync(lastTempFilelist.Name, RemoteVolumeState.Deleting, -1, null); + await database.CommitTransactionAsync("CommitUpdateFilelistVolume"); + + await backendManager.PutAsync(fsw, null, null, false, taskreader.ProgressToken); + } + catch + { + await database.RollbackTransactionAsync(); + fsw?.Dispose(); + throw; } - ); } } } diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index 7db790590..650ca5f7b 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -32,6 +32,12 @@ using Duplicati.Library.Snapshots; using Duplicati.Library.Utility; using Duplicati.Library.Common.IO; using Duplicati.Library.Logging; +using Duplicati.Library.Main.Operation.Common; +using System.Data; +using Duplicati.Library.DynamicLoader; +using Duplicati.Library.SourceProvider; +using Duplicati.Library.Snapshots.USN; +using System.Text.RegularExpressions; using System.IO; namespace Duplicati.Library.Main.Operation @@ -46,76 +52,164 @@ namespace Duplicati.Library.Main.Operation /// /// The tag used for logging /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + private static readonly string LOGTAG = Log.LogTagFromType(); private readonly Options m_options; - private readonly string m_backendurl; private LocalBackupDatabase m_database; - private System.Data.IDbTransaction m_transaction; - - private Library.Utility.IFilter m_filter; - private Library.Utility.IFilter m_sourceFilter; + private IFilter m_filter; private readonly BackupResults m_result; + private readonly ITaskReader m_taskReader; public readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); - public BackupHandler(string backendurl, Options options, BackupResults results) + public BackupHandler(Options options, BackupResults results) { m_options = options; m_result = results; - m_backendurl = backendurl; + m_taskReader = results.TaskControl; if (options.AllowPassphraseChange) throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported, "PassphraseChangeUnsupported"); } - public static Snapshots.ISnapshotService GetSnapshot(string[] sources, Options options) + /// + /// Gets a single source provider for the given sources + /// + /// The sources to get providers for + /// The options to use + /// The source providers + public static async Task GetSourceProvider(IEnumerable sources, Options options, CancellationToken cancellationToken) + => Combiner.Combine(await GetSourceProviders(sources, options, cancellationToken)); + + /// + /// Gets a snapshot service for the given sources + /// + /// The sources to get the snapshot for + /// The options to use + /// The source provider + private static ISnapshotService GetFileSnapshotService(IEnumerable sources, Options options) { try { if (options.SnapShotStrategy != Options.OptimizationStrategy.Off) - return Duplicati.Library.Snapshots.SnapshotUtility.CreateSnapshot(sources, options.RawOptions); + return SnapshotUtility.CreateSnapshot(sources, options.RawOptions, options.SymlinkPolicy == Options.SymlinkStrategy.Follow); } catch (Exception ex) { if (options.SnapShotStrategy == Options.OptimizationStrategy.Required) throw new UserInformationException(Strings.Common.SnapshotFailedError(ex.Message), "SnapshotFailed", ex); else if (options.SnapShotStrategy == Options.OptimizationStrategy.On) - Logging.Log.WriteWarningMessage(LOGTAG, "SnapshotFailed", ex, Strings.Common.SnapshotFailedError(ex.Message)); + Log.WriteWarningMessage(LOGTAG, "SnapshotFailed", ex, Strings.Common.SnapshotFailedError(ex.Message)); else if (options.SnapShotStrategy == Options.OptimizationStrategy.Auto) - Logging.Log.WriteInformationMessage(LOGTAG, "SnapshotFailed", Strings.Common.SnapshotFailedError(ex.Message)); + Log.WriteInformationMessage(LOGTAG, "SnapshotFailed", Strings.Common.SnapshotFailedError(ex.Message)); } - if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) - { - return new NoSnapshotLinux(options.IgnoreAdvisoryLocking); - } - else if (OperatingSystem.IsWindows()) - { - return new NoSnapshotWindows(); - } - else - { - throw new NotSupportedException("Unsupported Operating System"); - } + return SnapshotUtility.CreateNoSnapshot(sources, options.IgnoreAdvisoryLocking, options.SymlinkPolicy == Options.SymlinkStrategy.Follow); } /// - /// Create instance of USN journal service + /// Gets all source providers for the given sources /// - /// - /// - /// - /// - /// - private UsnJournalService GetJournalService(IEnumerable sources, ISnapshotService snapshot, IFilter filter, long lastfilesetid) + /// The sources to get providers for + /// The options to use + /// The filter to use + /// The last fileset id + /// The source providers + private static async Task> GetSourceProviders(IEnumerable sources, Options options, CancellationToken cancellationToken) + { + // Group the sources by their type, so we can combine all snapshot paths into a single snapshot + var sourceTypes = sources.GroupBy(x => x.StartsWith("@") ? "@" : Library.Utility.Utility.GuessScheme(x) ?? "file", StringComparer.OrdinalIgnoreCase); + + // To avoid leaking snapshot instances, we create all instances first and then dispose them if an exception occurs + // The number of instances is expected to be low, so the memory overhead is acceptable + var results = new List(); + try + { + foreach (var entry in sourceTypes) + { + if ("file".Equals(entry.Key, StringComparison.OrdinalIgnoreCase)) + results.Add(new LocalFileSource(GetFileSnapshotService(entry, options))); + else if ("vss".Equals(entry.Key, StringComparison.OrdinalIgnoreCase) || "lvm".Equals(entry.Key, StringComparison.OrdinalIgnoreCase)) + results.Add(new LocalFileSource(SnapshotUtility.CreateSnapshot(entry, options.RawOptions, options.SymlinkPolicy == Options.SymlinkStrategy.Follow))); + else if ("@".Equals(entry.Key, StringComparison.OrdinalIgnoreCase)) + { + foreach (var url in entry) + { + var sanitizedUrl = Library.Utility.Utility.GetUrlWithoutCredentials(url); + var m = Regex.Match(url, @"^@(?[^|]+)\|(?.+)$", RegexOptions.IgnoreCase); + if (m.Success) + { + var mountpoint = m.Groups["mountpoint"].Value; + + if (mountpoint.Any(x => Path.GetInvalidPathChars().Contains(x))) + throw new UserInformationException(string.Format("The mountpoint \"{0}\" contains invalid characters", mountpoint), "InvalidMountpoint"); + if (!Path.IsPathRooted(mountpoint)) + throw new UserInformationException(string.Format("The mountpoint \"{0}\" is not a valid rooted mountpoint", mountpoint), "InvalidMountpoint"); + + var backendurl = m.Groups["url"].Value; + + ISourceProvider provider; + try + { + provider = await SourceProviderLoader.GetSourceProvider(backendurl, Path.GetFullPath(mountpoint), options.RawOptions, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + if (options.AllowMissingSource) + { + Log.WriteWarningMessage(LOGTAG, "SourceProviderFailed", ex, "Failed to load source provider for \"{0}\"", sanitizedUrl); + continue; + } + + throw new UserInformationException($"Failed to load source provider for \"{sanitizedUrl}\": {ex.Message}", "SourceProviderFailed", ex); + } + + // Don't accept missing providers + results.Add(provider ?? throw new UserInformationException($"The source \"{sanitizedUrl}\" is not supported", "SourceNotSupported")); + } + else + throw new UserInformationException($"The source \"{sanitizedUrl}\" is not a supported format", "SourceFormatNotSupported"); + } + } + else + throw new UserInformationException($"The source type \"{entry.Key}\" is not supported", "SourceTypeNotSupported"); + } + } + catch + { + foreach (var provider in results) + (provider as IDisposable)?.Dispose(); + + throw; + } + + if (results.Count == 0) + throw new UserInformationException("No sources were available for the backup", "NoSourcesAvailable"); + + return results; + } + + public UsnJournalService GetJournalService(ISourceProvider provider, IFilter filter, long lastfilesetid) { if (m_options.UsnStrategy == Options.OptimizationStrategy.Off) return null; + if (!OperatingSystem.IsWindows()) + throw new UserInformationException("USN journal is only supported on Windows", "UsnJournalNotSupported"); + + var providers = (provider is Combiner c ? c.Providers : [provider]) + .Select((x, i) => new { Provider = x, Index = i }) + .Where(x => x.Provider is LocalFileSource) + .ToList(); + + if (providers.Count == 0) + return null; + if (providers.Count > 1) + throw new UserInformationException("Multiple USN journal services are not supported", "MultipleUSNJournalServices"); + var fileProvider = providers.First().Provider as LocalFileSource; var journalData = m_database.GetChangeJournalData(lastfilesetid); - var service = new UsnJournalService(sources, snapshot, filter, m_options.FileAttributeFilter, m_options.SkipFilesLargerThan, + var service = new UsnJournalService(fileProvider.SnapshotService, filter, m_options.FileAttributeFilter, m_options.SkipFilesLargerThan, journalData, cancellationTokenSource.Token); foreach (var volumeData in service.VolumeDataList) @@ -169,9 +263,7 @@ namespace Duplicati.Library.Main.Operation private sealed record PreBackupVerifyResult( LocalBackupDatabase Database, - BackendManager BackendManager, - string LastTempFilelist, - long LastTempFilesetId + RemoteVolumeEntry LastTempFilelist ); /// @@ -179,35 +271,30 @@ namespace Duplicati.Library.Main.Operation /// The logic here is needed to check that the database is in a state /// where it can be used for the backup, and that the backend is also /// in the same state as the database. - /// + /// /// If the auto-repair option is enabled, this method will attempt to /// call the repair method, which requires that the database is closed /// and re-opened. - /// + /// /// For efficiency, the database is only closed if the repair is needed, /// and returned to the caller in an open state in either case. /// /// Results from the pre-backup verification - private static async Task PreBackupVerify(string backendurl, Options options, BackupResults result) + private static async Task PreBackupVerify(Options options, BackupResults result, IBackendManager backendManager) { result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreBackupVerify); // Setup variables LocalBackupDatabase database = null; - BackendManager backendManager = null; // If we have an interrupted backup, grab the fileset - string lastTempFilelist = null; - long lastTempFilesetId = -1; + RemoteVolumeEntry lastTempFilelist = default; using (new Logging.Timer(LOGTAG, "PreBackupVerify", "PreBackupVerify")) { try { database = new LocalBackupDatabase(options.Dbpath, options); - backendManager = new BackendManager(backendurl, options, result.BackendWriter, database); - - result.SetDatabase(database); result.Dryrun = options.Dryrun; // Check the database integrity @@ -229,56 +316,47 @@ namespace Duplicati.Library.Main.Operation // Make sure the database is sane await db.VerifyConsistencyAsync(options.Blocksize, options.BlockhashSize, !options.DisableFilelistConsistencyChecks); + // Guard the last filelist if (!options.DisableSyntheticFilelist) - { - var candidates = (await db.GetIncompleteFilesetsAsync()).OrderBy(x => x.Value).ToArray(); - if (candidates.Any()) - { - lastTempFilesetId = candidates.Last().Key; - lastTempFilelist = database.GetRemoteVolumeFromFilesetID(lastTempFilesetId).Name; - } - } + lastTempFilelist = database.GetLastIncompleteFilesetVolume(null); } try { if (options.NoBackendverification) { - FilelistProcessor.VerifyLocalList(backendManager, database); - UpdateStorageStatsFromDatabase(result, database, options, backendManager); + await FilelistProcessor.VerifyLocalList(backendManager, database, result.TaskControl.ProgressToken).ConfigureAwait(false); + await UpdateStorageStatsFromDatabase(result, database, options, backendManager, result.TaskControl.ProgressToken).ConfigureAwait(false); } else - FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, new string[] { lastTempFilelist }, logErrors: false); + { + await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: false, verifyMode: FilelistProcessor.VerifyMode.VerifyAndClean).ConfigureAwait(false); + } } catch (RemoteListVerificationException ex) { if (options.AutoCleanup) { - Logging.Log.WriteWarningMessage(LOGTAG, "BackendVerifyFailedAttemptingCleanup", ex, "Backend verification failed, attempting automatic cleanup"); + Log.WriteWarningMessage(LOGTAG, "BackendVerifyFailedAttemptingCleanup", ex, "Backend verification failed, attempting automatic cleanup"); result.RepairResults = new RepairResults(result); - - // Close the database to allow the repair to run, it may create a new database - backendManager.Dispose(); database.Dispose(); database = null; - backendManager = null; - result.SetDatabase(null); - new RepairHandler(backendurl, options, (RepairResults)result.RepairResults).Run(); + await new RepairHandler(options, (RepairResults)result.RepairResults) + .RunAsync(backendManager, null) + .ConfigureAwait(false); // Re-open the database and backend manager database = new LocalBackupDatabase(options.Dbpath, options); - backendManager = new BackendManager(backendurl, options, result.BackendWriter, database); - result.SetDatabase(database); - Logging.Log.WriteInformationMessage(LOGTAG, "BackendCleanupFinished", "Backend cleanup finished, retrying verification"); - FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, new string[] { lastTempFilelist }); + Log.WriteInformationMessage(LOGTAG, "BackendCleanupFinished", "Backend cleanup finished, retrying verification"); + await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); } else throw; } - return new PreBackupVerifyResult(database, backendManager, lastTempFilelist, lastTempFilesetId); + return new PreBackupVerifyResult(database, lastTempFilelist); } catch { @@ -292,43 +370,46 @@ namespace Duplicati.Library.Main.Operation /// /// Performs the bulk of work by starting all relevant processes /// - private static async Task RunMainOperation(IEnumerable sources, Snapshots.ISnapshotService snapshot, UsnJournalService journalService, Backup.BackupDatabase database, Backup.BackupStatsCollector stats, Options options, IFilter sourcefilter, IFilter filter, BackupResults result, Common.ITaskReader taskreader, long filesetid, long lastfilesetid, CancellationToken token) + private static async Task RunMainOperation(Backup.Channels channels, ISourceProvider source, UsnJournalService journalService, Backup.BackupDatabase database, IBackendManager backendManager, Backup.BackupStatsCollector stats, Options options, IFilter filter, BackupResults result, ITaskReader taskreader, long filesetid, long lastfilesetid) { using (new Logging.Timer(LOGTAG, "BackupMainOperation", "BackupMainOperation")) { // Make sure the CompressionHints table is initialized, otherwise all workers will initialize it var unused = options.CompressionHints.Count; - Task all; - using (new ChannelScope()) - { - all = Task.WhenAll( - new[] - { - Backup.DataBlockProcessor.Run(database, options, taskreader), - Backup.FileBlockProcessor.Run(snapshot, options, database, stats, taskreader, token), - Backup.StreamBlockSplitter.Run(options, database, taskreader), - Backup.FileEnumerationProcess.Run(sources, snapshot, journalService, - options.FileAttributeFilter, sourcefilter, filter, options.SymlinkPolicy, - options.HardlinkPolicy, options.ExcludeEmptyFolders, options.IgnoreFilenames, - GetBlacklistedPaths(options), options.ChangedFilelist, taskreader, token), - Backup.FilePreFilterProcess.Run(snapshot, options, stats, database), - Backup.MetadataPreProcess.Run(snapshot, options, database, lastfilesetid, token), - Backup.SpillCollectorProcess.Run(options, database, taskreader), - Backup.ProgressHandler.Run(result) - } - // Spawn additional block hashers - .Union( - Enumerable.Range(0, options.ConcurrencyBlockHashers - 1).Select(x => - Backup.StreamBlockSplitter.Run(options, database, taskreader)) - ) - // Spawn additional compressors - .Union( - Enumerable.Range(0, options.ConcurrencyCompressors - 1).Select(x => - Backup.DataBlockProcessor.Run(database, options, taskreader)) - ) - ); - } + Task all = Task.WhenAll( + new[] + { + Backup.DataBlockProcessor.Run(channels, database, backendManager, options, taskreader), + Backup.FileBlockProcessor.Run(channels, options, database, stats, taskreader), + Backup.StreamBlockSplitter.Run(channels, options, database, taskreader), + Backup.FileEnumerationProcess.Run(channels, source, journalService, + options.FileAttributeFilter, filter, options.SymlinkPolicy, + options.HardlinkPolicy, options.ExcludeEmptyFolders, options.IgnoreFilenames, + GetBlacklistedPaths(options), options.ChangedFilelist, taskreader, + () => result.PartialBackup = true, CancellationToken.None), + Backup.FilePreFilterProcess.Run(channels, options, stats, database), + Backup.MetadataPreProcess.Run(channels, options, database, lastfilesetid, taskreader), + Backup.SpillCollectorProcess.Run(channels, options, database, backendManager, taskreader), + Backup.ProgressHandler.Run(channels, result) + } + // Spawn additional block hashers + .Concat( + Enumerable.Range(0, options.ConcurrencyBlockHashers - 1).Select(x => + Backup.StreamBlockSplitter.Run(channels, options, database, taskreader)) + ) + // Spawn additional compressors + .Concat( + Enumerable.Range(0, options.ConcurrencyCompressors - 1).Select(x => + Backup.DataBlockProcessor.Run(channels, database, backendManager, options, taskreader)) + ) + // Spawn additional file processors + .Concat( + Enumerable.Range(0, options.ConcurrencyFileprocessors - 1).Select(x => + Backup.FileBlockProcessor.Run(channels, options, database, stats, taskreader) + ) + ) + ); await all.ConfigureAwait(false); @@ -338,22 +419,27 @@ namespace Duplicati.Library.Main.Operation } else if (journalService != null) { + if (!OperatingSystem.IsWindows()) + throw new UserInformationException("USN journal is only supported on Windows", "USNJournalNotSupported"); + // append files from previous fileset, unless part of modifiedSources, which we've just scanned await database.AppendFilesFromPreviousSetWithPredicateAsync((path, fileSize) => { + if (!OperatingSystem.IsWindows()) + throw new UserInformationException("USN journal is only supported on Windows", "USNJournalNotSupported"); + // TODO: This is technically unsupported, but the method itself works cross-platform if (journalService.IsPathEnumerated(path)) return true; if (fileSize >= 0) - { stats.AddExaminedFile(fileSize); - } + return false; }); // store journal data in database, unless job is being canceled - if (!token.IsCancellationRequested) + if (!result.PartialBackup) { var data = journalService.VolumeDataList.Where(p => p.JournalData != null).Select(p => p.JournalData).ToList(); if (data.Any()) @@ -367,14 +453,12 @@ namespace Duplicati.Library.Main.Operation } } - if (token.IsCancellationRequested) + if (result.PartialBackup) { - result.PartialBackup = true; Log.WriteWarningMessage(LOGTAG, "CancellationRequested", null, "Cancellation was requested by user."); } else { - result.PartialBackup = false; await database.UpdateFilesetAndMarkAsFullBackupAsync(filesetid); } @@ -382,7 +466,7 @@ namespace Duplicati.Library.Main.Operation } } - private void CompactIfRequired(BackendManager backend, long lastVolumeSize) + private async Task CompactIfRequired(IBackendManager backendManager, ReusableTransaction transaction, long lastVolumeSize) { var currentIsSmall = lastVolumeSize != -1 && lastVolumeSize <= m_options.SmallFileSize; @@ -391,7 +475,9 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Delete); m_result.DeleteResults = new DeleteResults(m_result); using (var db = new LocalDeleteDatabase(m_database)) - new DeleteHandler(backend.BackendUrl, m_options, (DeleteResults)m_result.DeleteResults).DoRun(db, ref m_transaction, true, currentIsSmall, backend); + await new DeleteHandler(m_options, (DeleteResults)m_result.DeleteResults) + .DoRunAsync(db, transaction, true, currentIsSmall, backendManager) + .ConfigureAwait(false); } else if (currentIsSmall && !m_options.NoAutoCompact) @@ -399,21 +485,26 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Compact); m_result.CompactResults = new CompactResults(m_result); using (var db = new LocalDeleteDatabase(m_database)) - new CompactHandler(backend.BackendUrl, m_options, (CompactResults)m_result.CompactResults).DoCompact(db, true, ref m_transaction, backend); + await new CompactHandler(m_options, (CompactResults)m_result.CompactResults) + .DoCompactAsync(db, true, transaction, backendManager) + .ConfigureAwait(false); } } - private void PostBackupVerification(string currentFilelistVolume) + private async Task PostBackupVerification(string currentFilelistVolume, string previousTemporaryFilelist, IBackendManager backendManager, ReusableTransaction rtr) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PostBackupVerify); - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, m_database)) - { - using (new Logging.Timer(LOGTAG, "AfterBackupVerify", "AfterBackupVerify")) - FilelistProcessor.VerifyRemoteList(backend, m_options, m_database, m_result.BackendWriter, new string[] { currentFilelistVolume }); - backend.WaitForComplete(m_database, null); - } + using (new Logging.Timer(LOGTAG, "AfterBackupVerify", "AfterBackupVerify")) + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, m_database, m_result.BackendWriter, [currentFilelistVolume], [previousTemporaryFilelist], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + await backendManager.WaitForEmptyAsync(m_database, rtr.Transaction, m_taskReader.ProgressToken); + + // Calculate the number of samples to test, using the largest number of file of a given type + long remoteVolumeCount = m_database.GetRemoteVolumes() + .Where(x => x.State == RemoteVolumeState.Verified) + .GroupBy(x => x.Type) + .Select(x => x.LongCount()) + .Max(); - long remoteVolumeCount = m_database.GetRemoteVolumes().LongCount(x => x.State == RemoteVolumeState.Verified); long samplesToTest = Math.Max(m_options.BackupTestSampleCount, (long)Math.Round(remoteVolumeCount * (m_options.BackupTestPercentage / 100m), MidpointRounding.AwayFromZero)); if (samplesToTest > 0 && remoteVolumeCount > 0) { @@ -421,20 +512,21 @@ namespace Duplicati.Library.Main.Operation m_result.TestResults = new TestResults(m_result); using (var testdb = new LocalTestDatabase(m_database)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, testdb)) - new TestHandler(m_backendurl, m_options, (TestResults)m_result.TestResults) - .DoRun(samplesToTest, testdb, backend); + await new TestHandler(m_options, (TestResults)m_result.TestResults) + .DoRunAsync(samplesToTest, testdb, backendManager) + .ConfigureAwait(false); } } /// /// Handler for computing backend statistics, without relying on a remote folder listing /// - private static void UpdateStorageStatsFromDatabase(BackupResults result, LocalBackupDatabase database, Options options, BackendManager backendManager) + private static async Task UpdateStorageStatsFromDatabase(BackupResults result, LocalBackupDatabase database, Options options, IBackendManager backendManager, CancellationToken cancelToken) { if (result.BackendWriter != null) { result.BackendWriter.KnownFileCount = database.GetRemoteVolumes().Count(); + result.BackendWriter.KnownFilesets = database.GetRemoteVolumes().Count(x => x.Type == RemoteVolumeType.Files); result.BackendWriter.KnownFileSize = database.GetRemoteVolumes().Select(x => Math.Max(0, x.Size)).Sum(); result.BackendWriter.UnknownFileCount = 0; @@ -445,7 +537,7 @@ namespace Duplicati.Library.Main.Operation if (!options.QuotaDisable) { - var quota = backendManager.GetQuotaInfoAsync(CancellationToken.None).Await(); + var quota = await backendManager.GetQuotaInfoAsync(cancelToken).ConfigureAwait(false); if (quota != null) { result.BackendWriter.TotalQuotaSpace = quota.TotalQuotaSpace; @@ -457,18 +549,15 @@ namespace Duplicati.Library.Main.Operation } } - public void Run(string[] sources, Library.Utility.IFilter filter, CancellationToken token) - { - RunAsync(sources, filter, token).WaitForTaskOrThrow(); - } - private static Exception BuildException(Exception source, params Task[] tasks) { if (tasks == null || tasks.Length == 0) return source; - var ex = new List(); - ex.Add(source); + var ex = new List + { + source + }; foreach (var t in tasks) if (t != null) @@ -486,220 +575,177 @@ namespace Duplicati.Library.Main.Operation return new AggregateException(ex.First().Message, ex); } - private static async Task FlushBackend(BackupResults result, IWriteChannel uploadtarget, Task uploader) + private static async Task FlushBackend(LocalDatabase database, IDbTransaction transaction, BackupResults result, IBackendManager backendManager) { // Wait for upload completion result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload); - if (!await uploadtarget.IsRetiredAsync) + try { - try - { - var flushReq = new Backup.FlushRequest(); - await uploadtarget.WriteAsync(flushReq).ConfigureAwait(false); - await uploader.ConfigureAwait(false); - // Grab the size of the last uploaded volume - return await flushReq.LastWriteSizeAsync; - } - catch (RetiredException) - { - // Retired check is not atomic, so this exception can still happen - } + await backendManager.WaitForEmptyAsync(database, transaction, result.TaskControl.ProgressToken).ConfigureAwait(false); + // Grab the size of the last uploaded volume + return backendManager.LastWriteSize; } - await uploader.ConfigureAwait(false); + catch (RetiredException) + { + } + return -1; } - private async Task RunAsync(string[] sources, Library.Utility.IFilter filter, CancellationToken token) + public async Task RunAsync(string[] sources, IBackendManager backendManager, IFilter filter) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Begin); // Do a remote verification, unless disabled - var (database, backendManager, lastTempFilelist, lastTempFilesetId) = await PreBackupVerify(m_backendurl, m_options, m_result); + var (database, lastTempFilelist) = await PreBackupVerify(m_options, m_result, backendManager); + var lastTempVolumeIncomplete = false; - // New isolated scope for each operation - using (new IsolatedChannelScope()) + if (!string.IsNullOrWhiteSpace(lastTempFilelist.Name)) { - // If there is no filter, we set an empty filter to simplify the code - // If there is a filter, we make sure that the sources are included - m_filter = filter ?? new Library.Utility.FilterExpression(); - m_sourceFilter = new Library.Utility.FilterExpression(sources, true); + if (lastTempFilelist.State == RemoteVolumeState.Temporary || lastTempFilelist.State == RemoteVolumeState.Uploading) + lastTempVolumeIncomplete = true; + } - Task parallelScanner = null; - Task uploaderTask = null; - try + Backup.Channels channels = new(); + + // If there is no filter, we set an empty filter to simplify the code + m_filter = filter ?? new Library.Utility.FilterExpression(); + + Task parallelScanner = null; + try + { + // If we crash now, there is a chance we leave behind partial remote files + if (!m_options.Dryrun) + database.TerminatedWithActiveUploads = true; + + using (m_database = database) + using (var db = new Backup.BackupDatabase(m_database, m_options)) + // Setup runners and instances here + using (var filesetvolume = new FilesetVolumeWriter(m_options, m_database.OperationTimestamp)) + using (var stats = new Backup.BackupStatsCollector(m_result)) { - using (m_database = database) - using (backendManager) - using (var db = new Backup.BackupDatabase(m_database, m_options)) - // Setup runners and instances here - using (var filesetvolume = new FilesetVolumeWriter(m_options, m_database.OperationTimestamp)) - using (var stats = new Backup.BackupStatsCollector(m_result)) - // Keep a reference to these channels to avoid shutdown - using (var uploadtarget = ChannelManager.GetChannel(Backup.Channels.BackendRequest.ForWrite)) + long filesetid; + using var counterToken = CancellationTokenSource.CreateLinkedTokenSource(m_taskReader.ProgressToken); + using (var source = await GetSourceProvider(sources, m_options, m_taskReader.ProgressToken).ConfigureAwait(false)) { - long filesetid; - var counterToken = new CancellationTokenSource(); - var uploader = new Backup.BackendUploader(() => DynamicLoader.BackendLoader.GetBackend(m_backendurl, m_options.RawOptions), m_options, db, m_result.TaskReader, stats); - using (var snapshot = GetSnapshot(sources, m_options)) - { - try - { - // Start the uploader process - uploaderTask = uploader.Run(); - - // If the previous backup was interrupted, send a synthetic list - await Backup.UploadSyntheticFilelist.Run(db, m_options, m_result, m_result.TaskReader, lastTempFilelist, lastTempFilesetId); - - // Grab the previous backup ID, if any - var prevfileset = m_database.FilesetTimes.FirstOrDefault(); - if (prevfileset.Value.ToUniversalTime() > m_database.OperationTimestamp.ToUniversalTime()) - throw new Exception(string.Format("The previous backup has time {0}, but this backup has time {1}. Something is wrong with the clock.", prevfileset.Value.ToLocalTime(), m_database.OperationTimestamp.ToLocalTime())); - - var lastfilesetid = prevfileset.Value.Ticks == 0 ? -1 : prevfileset.Key; - - // Rebuild any index files that are missing - await Backup.RecreateMissingIndexFiles.Run(db, m_options, m_result.TaskReader); - - // Prepare the operation by registering the filelist - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_ProcessingFiles); - - var repcnt = 0; - while (repcnt < 100 && await db.GetRemoteVolumeIDAsync(filesetvolume.RemoteFilename) >= 0) - filesetvolume.ResetRemoteFilename(m_options, m_database.OperationTimestamp.AddSeconds(repcnt++)); - - if (await db.GetRemoteVolumeIDAsync(filesetvolume.RemoteFilename) >= 0) - throw new Exception("Unable to generate a unique fileset name"); - - var filesetvolumeid = await db.RegisterRemoteVolumeAsync(filesetvolume.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary); - filesetid = await db.CreateFilesetAsync(filesetvolumeid, VolumeBase.ParseFilename(filesetvolume.RemoteFilename).Time); - - // create USN-based scanner if enabled - var journalService = GetJournalService(sources, snapshot, filter, lastfilesetid); - - // Start parallel scan, or use the database - if (m_options.DisableFileScanner) - { - var d = m_database.GetLastBackupFileCountAndSize(); - m_result.OperationProgressUpdater.UpdatefileCount(d.Item1, d.Item2, true); - } - else - { - parallelScanner = Backup.CountFilesHandler.Run(sources, snapshot, journalService, m_result, m_options, m_sourceFilter, m_filter, GetBlacklistedPaths(), m_result.TaskReader, counterToken.Token); - } - - // Run the backup operation - if (await m_result.TaskReader.ProgressAsync) - { - await RunMainOperation(sources, snapshot, journalService, db, stats, m_options, m_sourceFilter, m_filter, m_result, m_result.TaskReader, filesetid, lastfilesetid, token).ConfigureAwait(false); - } - } - finally - { - //If the scanner is still running for some reason, make sure we kill it now - counterToken.Cancel(); - } - } - - // Add the fileset file to the dlist file - filesetvolume.CreateFilesetFile(!token.IsCancellationRequested); - - // Ensure the database is in a sane state after adding data - using (new Logging.Timer(LOGTAG, "VerifyConsistency", "VerifyConsistency")) - await db.VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, false); - - // Send the actual filelist - if (await m_result.TaskReader.ProgressAsync) - await Backup.UploadRealFilelist.Run(m_result, db, m_options, filesetvolume, filesetid, m_result.TaskReader); - - // Wait for upload completion - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload); - var lastVolumeSize = await FlushBackend(m_result, uploadtarget, uploaderTask).ConfigureAwait(false); - - // Make sure we have the database up-to-date - await db.CommitTransactionAsync("CommitAfterUpload", false); - - // TODO: Remove this later - m_transaction = m_database.BeginTransaction(); - try { - // If this throws, we should roll back the transaction - if (await m_result.TaskReader.ProgressAsync) - CompactIfRequired(backendManager, lastVolumeSize); + // If the previous backup was interrupted, send a synthetic list + await Backup.UploadSyntheticFilelist.Run(db, m_options, m_result, m_result.TaskControl, backendManager, lastTempFilelist); - if (m_options.UploadVerificationFile && await m_result.TaskReader.ProgressAsync) + // Grab the previous backup ID, if any + var prevfileset = m_database.FilesetTimes.FirstOrDefault(); + if (prevfileset.Value.ToUniversalTime() > m_database.OperationTimestamp.ToUniversalTime()) + throw new Exception(string.Format("The previous backup has time {0}, but this backup has time {1}. Something is wrong with the clock.", prevfileset.Value.ToLocalTime(), m_database.OperationTimestamp.ToLocalTime())); + + var lastfilesetid = prevfileset.Value.Ticks == 0 ? -1 : prevfileset.Key; + + // Rebuild any index files that are missing + await Backup.RecreateMissingIndexFiles.Run(db, backendManager, m_options, m_result.TaskControl); + + // Prepare the operation by registering the filelist + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_ProcessingFiles); + + var repcnt = 0; + while (repcnt < 100 && await db.GetRemoteVolumeIDAsync(filesetvolume.RemoteFilename) >= 0) + filesetvolume.ResetRemoteFilename(m_options, m_database.OperationTimestamp.AddSeconds(repcnt++)); + + if (await db.GetRemoteVolumeIDAsync(filesetvolume.RemoteFilename) >= 0) + throw new Exception("Unable to generate a unique fileset name"); + + var filesetvolumeid = await db.RegisterRemoteVolumeAsync(filesetvolume.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary); + filesetid = await db.CreateFilesetAsync(filesetvolumeid, VolumeBase.ParseFilename(filesetvolume.RemoteFilename).Time); + + var journalService = GetJournalService(source, filter, lastfilesetid); + + // Start parallel scan, or use the database + if (m_options.DisableFileScanner) { - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_VerificationUpload); - FilelistProcessor.UploadVerificationFile(backendManager.BackendUrl, m_options, m_result.BackendWriter, m_database, m_transaction); + var d = m_database.GetLastBackupFileCountAndSize(); + m_result.OperationProgressUpdater.UpdatefileCount(d.Item1, d.Item2, true); } - } - catch - { - try + else { - m_transaction.Rollback(); - m_transaction.Dispose(); - m_transaction = null; - } - catch (Exception ex) - { - Logging.Log.WriteErrorMessage(LOGTAG, "RollbackError", ex, "Rollback error: {0}", ex.Message); + parallelScanner = Backup.CountFilesHandler.Run(source, journalService, m_result, m_options, m_filter, GetBlacklistedPaths(), m_result.TaskControl, counterToken.Token); } - // Re-throw the original exception - throw; + // Run the backup operation + if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await RunMainOperation(channels, source, journalService, db, backendManager, stats, m_options, m_filter, m_result, m_result.TaskControl, filesetid, lastfilesetid).ConfigureAwait(false); + } + } + finally + { + //If the scanner is still running for some reason, make sure we kill it now + counterToken.Cancel(); + } + } + + // Add the fileset file to the dlist file + filesetvolume.CreateFilesetFile(!m_result.PartialBackup); + + // Ensure the database is in a sane state after adding data + using (new Logging.Timer(LOGTAG, "VerifyConsistency", "VerifyConsistency")) + await db.VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, false); + + // Send the actual filelist + await Backup.UploadRealFilelist.Run(m_result, db, backendManager, m_options, filesetvolume, filesetid, m_result.TaskControl, lastTempVolumeIncomplete); + + // Wait for upload completion + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload); + var lastVolumeSize = await FlushBackend(m_database, null, m_result, backendManager).ConfigureAwait(false); + + if (!m_options.Dryrun) + database.TerminatedWithActiveUploads = false; + // Make sure we have the database up-to-date + await db.CommitTransactionAsync("CommitAfterUpload", false); + + using (var rtr = new ReusableTransaction(m_database)) + { + // If this throws, we should roll back the transaction + if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + await CompactIfRequired(backendManager, rtr, lastVolumeSize); + + if (m_options.UploadVerificationFile && await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_VerificationUpload); + await FilelistProcessor.UploadVerificationFile(backendManager, m_options, m_database, rtr.Transaction); } - if (m_options.Dryrun) + if (!m_options.Dryrun) { - m_transaction.Rollback(); - m_transaction = null; - } - else - { - using (new Logging.Timer(LOGTAG, "CommitFinalizingBackup", "CommitFinalizingBackup")) - m_transaction.Commit(); - - m_transaction = null; - - if (m_result.TaskControlRendevouz() != TaskControlState.Abort) + rtr.Commit("CommitFinalizingBackup"); + if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { if (m_options.NoBackendverification) - UpdateStorageStatsFromDatabase(m_result, m_database, m_options, backendManager); + await UpdateStorageStatsFromDatabase(m_result, m_database, m_options, backendManager, m_taskReader.ProgressToken).ConfigureAwait(false); else - PostBackupVerification(filesetvolume.RemoteFilename); + await PostBackupVerification(filesetvolume.RemoteFilename, lastTempFilelist.Name, backendManager, rtr).ConfigureAwait(false); } + rtr.Commit("CommitAfterVerification", restart: false); } - - m_database.WriteResults(); - m_database.PurgeLogData(m_options.LogRetention); - m_database.PurgeDeletedVolumes(DateTime.UtcNow); - - if (m_options.AutoVacuum) - { - m_result.VacuumResults = new VacuumResults(m_result); - new VacuumHandler(m_options, (VacuumResults)m_result.VacuumResults).Run(); - } - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Complete); - return; } - } - catch (Exception ex) - { - var aex = BuildException(ex, uploaderTask, parallelScanner); - Logging.Log.WriteErrorMessage(LOGTAG, "FatalError", ex, "Fatal error"); - if (aex == ex) - throw; - throw aex; - } - finally - { - if (parallelScanner != null && !parallelScanner.IsCompleted) - parallelScanner.Wait(500); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Complete); + return; } } + catch (Exception ex) + { + var aex = BuildException(ex, parallelScanner); + Logging.Log.WriteErrorMessage(LOGTAG, "FatalError", ex, "Fatal error"); + if (aex == ex) + throw; + + throw aex; + } + finally + { + if (parallelScanner != null && !parallelScanner.IsCompleted) + parallelScanner.Wait(500); + } } public void Dispose() diff --git a/Duplicati/Library/Main/Operation/Common/BackendHandler.cs b/Duplicati/Library/Main/Operation/Common/BackendHandler.cs deleted file mode 100644 index af8b1d144..000000000 --- a/Duplicati/Library/Main/Operation/Common/BackendHandler.cs +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using Duplicati.Library.Interface; -using Duplicati.Library.Main.Volumes; -using Duplicati.Library.Utility; -using System.IO; -using System.Security.Cryptography; - -namespace Duplicati.Library.Main.Operation.Common -{ - /// - /// This class encapsulates access to the backend and ensures at most one connection is active at a time - /// - internal class BackendHandler : SingleRunner - { - /// - /// The tag used for logging - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - /// - /// Data storage for an ongoing backend operation - /// - public class FileEntryItem - { - /// - /// The current operation this entry represents - /// - public readonly BackendActionType Operation; - /// - /// The name of the remote file - /// - public string RemoteFilename; - /// - /// The name of the local file - /// - public string LocalFilename { get { return LocalTempfile; } } - /// - /// A reference to a temporary file that is disposed upon - /// failure or completion of the item - /// - public TempFile LocalTempfile; - /// - /// True if the item has been encrypted - /// - public bool Encrypted; - /// - /// The expected hash value of the file - /// - public string Hash; - /// - /// The expected size of the file - /// - public long Size; - /// - /// A flag indicating if the file is a extra metadata file - /// that has no entry in the database - /// - public bool TrackedInDb = true; - /// - /// A flag indicating if the operation is a retry run - /// - public bool IsRetry; - - public FileEntryItem(BackendActionType operation, string remotefilename) - { - Operation = operation; - RemoteFilename = remotefilename; - Size = -1; - } - - public void SetLocalfilename(string name) - { - this.LocalTempfile = Library.Utility.TempFile.WrapExistingFile(name); - this.LocalTempfile.Protected = true; - } - - public void Encrypt(Options options) - { - if (!this.Encrypted && !options.NoEncryption) - { - var tempfile = new Library.Utility.TempFile(); - using (var enc = DynamicLoader.EncryptionLoader.GetModule(options.EncryptionModule, options.Passphrase, options.RawOptions)) - enc.Encrypt(this.LocalFilename, tempfile); - - this.DeleteLocalFile(); - - this.LocalTempfile = tempfile; - this.Hash = null; - this.Size = 0; - this.Encrypted = true; - } - } - - public static string CalculateFileHash(string filename, Options options) - { - using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) - using (var hasher = HashFactory.CreateHasher(options.FileHashAlgorithm)) - return Convert.ToBase64String(hasher.ComputeHash(fs)); - } - - - public bool UpdateHashAndSize(Options options) - { - if (Hash == null || Size < 0) - { - Hash = CalculateFileHash(this.LocalFilename, options); - Size = new System.IO.FileInfo(this.LocalFilename).Length; - return true; - } - - return false; - } - - public void DeleteLocalFile() - { - if (this.LocalTempfile != null) - { - try - { - this.LocalTempfile.Protected = false; - this.LocalTempfile.Dispose(); - } - catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "DeleteTemporaryFileError", ex, "Failed to dispose temporary file: {0}", this.LocalTempfile); } - finally { this.LocalTempfile = null; } - } - } - } - } -} - diff --git a/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs b/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs index 20851adf1..d7acefa5a 100644 --- a/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs +++ b/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,9 +22,7 @@ using System; using System.Linq; using System.Threading.Tasks; -using CoCoL; using Duplicati.Library.Main.Database; -using System.Collections.Generic; namespace Duplicati.Library.Main.Operation.Common { @@ -40,7 +38,7 @@ namespace Duplicati.Library.Main.Operation.Common private static readonly string LOGTAG = Logging.Log.LogTagFromType(); protected readonly LocalDatabase m_db; - protected System.Data.IDbTransaction m_transaction; + private System.Data.IDbTransaction m_transaction; protected readonly Options m_options; public DatabaseCommon(LocalDatabase db, Options options) @@ -51,19 +49,26 @@ namespace Duplicati.Library.Main.Operation.Common m_transaction = db.BeginTransaction(); } + protected System.Data.IDbTransaction GetTransaction() + { + if (m_transaction == null) + m_transaction = m_db.BeginTransaction(); + return m_transaction; + } + public Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, RemoteVolumeState state) { - return RunOnMain(() => m_db.RegisterRemoteVolume(name, type, state, m_transaction)); + return RunOnMain(() => m_db.RegisterRemoteVolume(name, type, state, GetTransaction())); } public Task UpdateRemoteVolumeAsync(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup = false, TimeSpan deleteGraceTime = default(TimeSpan)) { - return RunOnMain(() => m_db.UpdateRemoteVolume(name, state, size, hash, suppressCleanup, deleteGraceTime, m_transaction)); + return RunOnMain(() => m_db.UpdateRemoteVolume(name, state, size, hash, suppressCleanup, deleteGraceTime, GetTransaction())); } public Task CommitTransactionAsync(string message, bool restart = true) { - return RunOnMain(() => + return RunOnMain(() => { if (m_options.Dryrun) { @@ -73,27 +78,27 @@ namespace Duplicati.Library.Main.Operation.Common m_transaction.Rollback(); m_transaction = null; } - return; + return; } - using(new Logging.Timer(LOGTAG, "CommitTransactionAsync", message)) - m_transaction.Commit(); - if (restart) - m_transaction = m_db.BeginTransaction(); - else + if (m_transaction != null) + { + using (new Logging.Timer(LOGTAG, "CommitTransactionAsync", message)) + m_transaction.Commit(); m_transaction = null; + } }); } - public Task RollbackTransactionAsync(bool restart = true) + public Task RollbackTransactionAsync() { return RunOnMain(() => { - m_transaction.Rollback(); - if (restart) - m_transaction = m_db.BeginTransaction(); - else + if (m_transaction != null) + { + m_transaction.Rollback(); m_transaction = null; + } }); } @@ -101,52 +106,50 @@ namespace Duplicati.Library.Main.Operation.Common public Task RenameRemoteFileAsync(string oldname, string newname) { - return RunOnMain(() => m_db.RenameRemoteFile(oldname, newname, m_transaction)); + return RunOnMain(() => m_db.RenameRemoteFile(oldname, newname, GetTransaction())); } public Task LogRemoteOperationAsync(string operation, string path, string data) { - return RunOnMain(() => m_db.LogRemoteOperation(operation, path, data, m_transaction)); + return RunOnMain(() => m_db.LogRemoteOperation(operation, path, data, GetTransaction())); } public Task GetBlocksAsync(long volumeid) { // TODO: Figure out how to return the enumerable, while keeping the lock // and not creating the entire result in memory - return RunOnMain(() => m_db.GetBlocks(volumeid, m_transaction).ToArray()); + return RunOnMain(() => m_db.GetBlocks(volumeid, GetTransaction()).ToArray()); } public Task GetVolumeInfoAsync(string remotename) { - return RunOnMain(() => m_db.GetRemoteVolume(remotename, m_transaction)); + return RunOnMain(() => m_db.GetRemoteVolume(remotename, GetTransaction())); } public Task[]> GetBlocklistsAsync(long volumeid, int blocksize, int hashsize) { // TODO: Figure out how to return the enumerable, while keeping the lock // and not creating the entire result in memory - return RunOnMain(() => m_db.GetBlocklists(volumeid, blocksize, hashsize, m_transaction).ToArray()); + return RunOnMain(() => m_db.GetBlocklists(volumeid, blocksize, hashsize, GetTransaction()).ToArray()); } public Task GetRemoteVolumeIDAsync(string remotename) { - return RunOnMain(() => m_db.GetRemoteVolumeID(remotename, m_transaction)); + return RunOnMain(() => m_db.GetRemoteVolumeID(remotename, GetTransaction())); } public Task AddIndexBlockLinkAsync(long indexVolumeID, long blockVolumeID) { - return RunOnMain(() => m_db.AddIndexBlockLink(indexVolumeID, blockVolumeID, m_transaction)); + return RunOnMain(() => m_db.AddIndexBlockLink(indexVolumeID, blockVolumeID, GetTransaction())); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (m_transaction != null) - { - m_transaction.Commit(); - m_transaction = null; - } + var tr = System.Threading.Interlocked.Exchange(ref m_transaction, null); + tr?.Commit(); + tr?.Dispose(); } } diff --git a/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs b/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs index 4a9cc8b79..caa6349e1 100644 --- a/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs +++ b/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,13 +20,15 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.IO; +using System.Collections.Generic; using System.Threading.Tasks; using Duplicati.Library.Main.Volumes; using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation.Common { + // TODO: Remove this class and simply rely on the database to create the index files + /// /// A collection class for keeping the temporary data required to build an index file /// @@ -35,12 +37,17 @@ namespace Duplicati.Library.Main.Operation.Common /// /// The block hashes /// - private readonly Library.Utility.FileBackedStringList blockHashes = new Library.Utility.FileBackedStringList(); + private readonly FileBackedStringList blockHashes = new(); /// /// The blocklist hashes /// - private readonly Library.Utility.FileBackedStringList blockListHashes = new Library.Utility.FileBackedStringList(); + private readonly FileBackedStringList blockListHashes = new(); + + /// + /// The known block list hashes + /// + private readonly HashSet knownBlockListHashes = new(); /// /// Cached copy of the blocklist hash size @@ -53,8 +60,8 @@ namespace Duplicati.Library.Main.Operation.Common /// /// The options used in this run. public TemporaryIndexVolume(Options options) - { - m_blockhashsize = options.BlockhashSize; + { + m_blockhashsize = options.BlockhashSize; } /// @@ -62,24 +69,15 @@ namespace Duplicati.Library.Main.Operation.Common /// /// The index volume. /// The name of the block file. - /// Hash of the volume - /// Size of the volume - public async Task CreateVolume(string blockfilename, string blockHash, long blockSize, Options options, DatabaseCommon database) + /// The options used in this run. + /// The database to use. + public async Task CreateVolume(string blockfilename, Options options, DatabaseCommon database) { var w = new IndexVolumeWriter(options); w.VolumeID = await database.RegisterRemoteVolumeAsync(w.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary); - w.StartVolume(blockfilename); - foreach (var n in blockHashes) - { - var args = n.Split(new char[] { ':' }, 2); - w.AddBlock(args[1], long.Parse(args[0])); - } - - w.FinishVolume(blockHash, blockSize); - var enumerator = blockListHashes.GetEnumerator(); - while(enumerator.MoveNext()) + while (enumerator.MoveNext()) { var hash = enumerator.Current; enumerator.MoveNext(); @@ -88,7 +86,12 @@ namespace Duplicati.Library.Main.Operation.Common w.WriteBlocklist(hash, data, 0, data.Length); } - w.Close(); + w.StartVolume(blockfilename); + foreach (var n in blockHashes) + { + var args = n.Split(new char[] { ':' }, 2); + w.AddBlock(args[1], long.Parse(args[0])); + } return w; } @@ -103,9 +106,21 @@ namespace Duplicati.Library.Main.Operation.Common if (!onlyBlocklistHashes) foreach (var n in blockHashes) target.blockHashes.Add(n); - - foreach (var n in blockListHashes) - target.blockListHashes.Add(n); + + var enumerator = blockListHashes.GetEnumerator(); + while (enumerator.MoveNext()) + { + var hash = enumerator.Current; + enumerator.MoveNext(); + + // Filter duplicates + if (target.knownBlockListHashes.Contains(hash)) + continue; + + target.knownBlockListHashes.Add(hash); + target.blockListHashes.Add(hash); + target.blockListHashes.Add(enumerator.Current); + } } /// @@ -126,8 +141,13 @@ namespace Duplicati.Library.Main.Operation.Common /// The block contents. public void AddBlockListHash(string hash, long size, byte[] data) { - if (size % m_blockhashsize != 0) - throw new ArgumentException($"The {nameof(size)} value is {size}, but it must be evenly divisible by the blockhash size ({m_blockhashsize})", nameof(size)); + // Filter out duplicates to reduce size + if (knownBlockListHashes.Contains(hash)) + return; + knownBlockListHashes.Add(hash); + + if (size % m_blockhashsize != 0) + throw new ArgumentException($"The {nameof(size)} value is {size}, but it must be evenly divisible by the blockhash size ({m_blockhashsize})", nameof(size)); blockListHashes.Add(hash); blockListHashes.Add(Convert.ToBase64String(data, 0, (int)size)); } @@ -140,7 +160,7 @@ namespace Duplicati.Library.Main.Operation.Common { public static async Task CreateIndexVolume(string blockname, Options options, Common.DatabaseCommon database) { - using(var h = HashFactory.CreateHasher(options.BlockHashAlgorithm)) + using (var h = HashFactory.CreateHasher(options.BlockHashAlgorithm)) { var w = new IndexVolumeWriter(options); w.VolumeID = await database.RegisterRemoteVolumeAsync(w.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary); @@ -148,13 +168,13 @@ namespace Duplicati.Library.Main.Operation.Common var blockvolume = await database.GetVolumeInfoAsync(blockname); w.StartVolume(blockname); - foreach(var b in await database.GetBlocksAsync(blockvolume.ID)) + foreach (var b in await database.GetBlocksAsync(blockvolume.ID)) w.AddBlock(b.Hash, b.Size); w.FinishVolume(blockvolume.Hash, blockvolume.Size); if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) - foreach(var b in await database.GetBlocklistsAsync(blockvolume.ID, options.Blocksize, options.BlockhashSize)) + foreach (var b in await database.GetBlocklistsAsync(blockvolume.ID, options.Blocksize, options.BlockhashSize)) { var bh = Convert.ToBase64String(h.ComputeHash(b.Item2, 0, b.Item3)); if (bh != b.Item1) @@ -170,39 +190,6 @@ namespace Duplicati.Library.Main.Operation.Common return w; } } - - /*public static async Task ReCreateIndexVolume(string selfname, Options options, Repair.RepairDatabase database) - { - using(var h = HashFactory.CreateHasher(options.BlockHashAlgorithm)) - { - var w = new IndexVolumeWriter(options); - w.SetRemoteFilename(selfname); - - foreach(var blockvolume in await database.GetBlockVolumesFromIndexNameAsync(selfname)) - { - w.StartVolume(blockvolume.Name); - var volumeid = await database.GetRemoteVolumeIDAsync(blockvolume.Name); - - foreach(var b in await database.GetBlocksAsync(volumeid)) - w.AddBlock(b.Hash, b.Size); - - w.FinishVolume(blockvolume.Hash, blockvolume.Size); - - if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) - foreach(var b in await database.GetBlocklistsAsync(volumeid, options.Blocksize, options.BlockhashSize)) - { - var bh = Convert.ToBase64String(h.ComputeHash(b.Item2, 0, b.Item3)); - if (bh != b.Item1) - throw new Exception(string.Format("Internal consistency check failed, generated index block has wrong hash, {0} vs {1}", bh, b.Item1)); - w.WriteBlocklist(b.Item1, b.Item2, 0, b.Item3); - } - } - - w.Close(); - - return w; - } - }*/ } } diff --git a/Duplicati/Library/Main/Operation/Common/ProgressEvent.cs b/Duplicati/Library/Main/Operation/Common/ProgressEvent.cs index 68623880b..c09d42d81 100644 --- a/Duplicati/Library/Main/Operation/Common/ProgressEvent.cs +++ b/Duplicati/Library/Main/Operation/Common/ProgressEvent.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Operation/Common/SingleRunner.cs b/Duplicati/Library/Main/Operation/Common/SingleRunner.cs index 895bbaf75..c819017ce 100644 --- a/Duplicati/Library/Main/Operation/Common/SingleRunner.cs +++ b/Duplicati/Library/Main/Operation/Common/SingleRunner.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Operation/Common/StatsCollector.cs b/Duplicati/Library/Main/Operation/Common/StatsCollector.cs index b4240a5bc..80203f4be 100644 --- a/Duplicati/Library/Main/Operation/Common/StatsCollector.cs +++ b/Duplicati/Library/Main/Operation/Common/StatsCollector.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Operation/Common/TaskControl.cs b/Duplicati/Library/Main/Operation/Common/TaskControl.cs index e834779b8..1c02eb455 100644 --- a/Duplicati/Library/Main/Operation/Common/TaskControl.cs +++ b/Duplicati/Library/Main/Operation/Common/TaskControl.cs @@ -1,25 +1,26 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; +using System.Threading; using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation.Common @@ -30,16 +31,35 @@ namespace Duplicati.Library.Main.Operation.Common public interface ITaskReader { /// - /// Processing tasks can regularly await this, - /// which will pause the task or stop it if required. - /// The return value is true if the program should continue and false if a stop is requested + /// A cancellation token that can be used to monitor progress abort /// - Task ProgressAsync { get; } + CancellationToken ProgressToken { get; } + /// - /// Gets the transfer progress async control. - /// The transfer handler should await this instead of the event. + /// Gets the progress state, waiting if the state is paused, throws if terminated /// - Task TransferProgressAsync { get; } + /// true if the progress should continue, false if it should stop + Task ProgressRendevouz(); + + /// + /// A cancellation token that can be used to monitor transfer abort + /// + CancellationToken TransferToken { get; } + + /// + /// Gets the transfer state, waiting if the state is paused, throws if terminated + /// + /// true if the transfer should continue, false if it should stop + Task TransferRendevouz(); + +#if DEBUG + /// + /// Callback for testing the task control + /// + /// The path being processed + Action TestMethodCallback { get; set; } +#endif + } /// @@ -50,31 +70,37 @@ namespace Duplicati.Library.Main.Operation.Common /// /// Requests that progress should be paused /// - /// If set to true transfer are also suspended. - void Pause(bool alsoTransfers = false); + /// If true, the transfer should also be paused + void Pause(bool alsoTransfer); /// /// Resumes running a paused process /// void Resume(); /// - /// Requests that the progress should be stopped in an orderly manner, - /// which allows current transfers to be completed. + /// Requests that the progress should be stopped in a controlled way. + /// This will finalize the current file and transfer. /// - /// If set to true active transfer are also stopped. - void Stop(bool alsoTransfers = false); + void Stop(); /// /// Terminates the progress without allowing a flush /// void Terminate(); } + /// + /// Interface for the task control + /// + public interface ITaskControl : ITaskReader, ITaskCommander + { + } + /// /// Implementation of the task control /// - public class TaskControl : ITaskReader, ITaskCommander, IDisposable + public class TaskControl : ITaskControl, IDisposable { /// - /// Internal state tracking to avoid invalid requests + /// State tracking to avoid invalid requests /// private enum State { @@ -99,11 +125,22 @@ namespace Duplicati.Library.Main.Operation.Common /// /// Internal control state for progress event /// - private TaskCompletionSource m_progress; + private TaskCompletionSource m_progress = new(); + /// - /// Internal control state for the transfer event + /// Internal control state for transfer event /// - private TaskCompletionSource m_transfer; + private TaskCompletionSource m_transfer = new(); + + /// + /// The progress task completion source, cancelled if the operation is terminated + /// + private readonly CancellationTokenSource m_progressTcs = new(); + + /// + /// The transfer task completion source, cancelled if the operation is terminated + /// + private readonly CancellationTokenSource m_transferTcs = new(); /// /// The control lock instance @@ -114,45 +151,67 @@ namespace Duplicati.Library.Main.Operation.Common /// The current progress state /// private State m_progressstate = State.Paused; + /// /// The current transfer state /// private State m_transferstate = State.Paused; /// - /// Processing tasks can regularly await this, - /// which will pause the task or stop it if required. - /// The return value is true if the program should continue and false if a stop is requested + /// A cancellation token that is cancelled if the operation is aborted /// - public Task ProgressAsync { get { return m_progress.Task; } } + public CancellationToken ProgressToken => m_progressTcs.Token; + /// - /// Gets the transfer progress async control. - /// The transfer handler should await this instead of the event. + /// A cancellation token that is cancelled if the transfers are aborted /// - public Task TransferProgressAsync { get { return m_transfer.Task; } } + public CancellationToken TransferToken => m_transferTcs.Token; /// /// Initializes a new instance of the class. /// public TaskControl() { - m_progress = new TaskCompletionSource(); - m_transfer = new TaskCompletionSource(); Resume(); } + /// + /// Gets the progress state, waiting if the state is paused + /// + /// true if the progress should continue, false if it should stop + public async Task ProgressRendevouz() + { + var res = await m_progress.Task.ConfigureAwait(false); + lock (m_lock) + m_progressTcs.Token.ThrowIfCancellationRequested(); + return res; + } + + /// + /// Gets the transfer state, waiting if the state is paused + /// + /// true if the transfer should continue, false if it should stop + public async Task TransferRendevouz() + { + var res = await m_transfer.Task.ConfigureAwait(false); + lock (m_lock) + m_transferTcs.Token.ThrowIfCancellationRequested(); + return res; + } + /// /// Resumes running a paused process /// public void Resume() { - lock(m_lock) + lock (m_lock) { if (m_progressstate == State.Paused) { m_progress.SetResult(true); m_progressstate = State.Active; } + if (m_transferstate == State.Paused) { m_transfer.SetResult(true); @@ -164,10 +223,9 @@ namespace Duplicati.Library.Main.Operation.Common /// /// Requests that progress should be paused /// - /// If set to true also transfers. - public void Pause(bool alsoTransfers = false) + public void Pause(bool alsoTransfer) { - lock(m_lock) + lock (m_lock) { if (m_progressstate == State.Active) { @@ -175,7 +233,7 @@ namespace Duplicati.Library.Main.Operation.Common m_progressstate = State.Paused; } - if (alsoTransfers && m_transferstate == State.Active) + if (alsoTransfer && m_transferstate == State.Active) { m_transfer = new TaskCompletionSource(); m_transferstate = State.Paused; @@ -187,25 +245,27 @@ namespace Duplicati.Library.Main.Operation.Common /// Requests that the progress should be stopped in an orderly manner, /// which allows current transfers to be completed. /// - /// If set to true also transfers. - public void Stop(bool alsoTransfers = false) + public void Stop() { - lock(m_lock) + lock (m_lock) { if (m_progressstate == State.Active || m_progressstate == State.Paused) { if (m_progressstate != State.Paused) m_progress = new TaskCompletionSource(); - + m_progress.SetResult(false); m_progressstate = State.Stopped; } - if (alsoTransfers && (m_transferstate == State.Active || m_transferstate == State.Paused)) + // For symmetry, we also mark the transfer as stopped + // but the transfer logic does not stop transfers + // unless they are aborted + if (m_transferstate == State.Active || m_transferstate == State.Paused) { if (m_transferstate != State.Paused) m_transfer = new TaskCompletionSource(); - + m_transfer.SetResult(false); m_transferstate = State.Stopped; } @@ -217,40 +277,40 @@ namespace Duplicati.Library.Main.Operation.Common /// public void Terminate() { - lock(m_lock) + lock (m_lock) { if (m_progressstate != State.Terminated) { if (m_progressstate != State.Paused) m_progress = new TaskCompletionSource(); - + m_progress.SetCanceled(); m_progressstate = State.Terminated; + m_progressTcs.Cancel(); } + if (m_transferstate != State.Terminated) { if (m_transferstate != State.Paused) m_transfer = new TaskCompletionSource(); - + m_transfer.SetCanceled(); m_transferstate = State.Terminated; + m_transferTcs.Cancel(); } } } - /// - /// Releases all resource used by the object. - /// - /// Call when you are finished using the - /// . The method leaves - /// the in an unusable state. After calling - /// , you must release all references to the - /// so the garbage collector can reclaim the - /// memory that the was occupying. + /// public void Dispose() { Terminate(); } + +#if DEBUG + /// + public Action TestMethodCallback { get; set; } +#endif } } diff --git a/Duplicati/Library/Main/Operation/CompactHandler.cs b/Duplicati/Library/Main/Operation/CompactHandler.cs index f38a81c94..070d443be 100644 --- a/Duplicati/Library/Main/Operation/CompactHandler.cs +++ b/Duplicati/Library/Main/Operation/CompactHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,9 +21,14 @@ using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { @@ -33,261 +38,252 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - protected readonly string m_backendurl; protected readonly Options m_options; protected readonly CompactResults m_result; - public CompactHandler(string backend, Options options, CompactResults result) + public CompactHandler(Options options, CompactResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public virtual void Run() + public async Task RunAsync(IBackendManager backendManager) { if (!System.IO.File.Exists(m_options.Dbpath)) throw new Exception(string.Format("Database file does not exist: {0}", m_options.Dbpath)); using (var db = new LocalDeleteDatabase(m_options.Dbpath, "Compact")) + using (var tr = new ReusableTransaction(db)) { - var tr = db.BeginTransaction(); - try + Utility.UpdateOptionsFromDb(db, m_options); + Utility.VerifyOptionsAndUpdateDatabase(db, m_options); + + var changed = await DoCompactAsync(db, false, tr, backendManager).ConfigureAwait(false); + + if (changed && m_options.UploadVerificationFile) + await FilelistProcessor.UploadVerificationFile(backendManager, m_options, db, null); + + if (!m_options.Dryrun) { - m_result.SetDatabase(db); - Utility.UpdateOptionsFromDb(db, m_options); - Utility.VerifyOptionsAndUpdateDatabase(db, m_options); - - var changed = DoCompact(db, false, ref tr, null); - - if (changed && m_options.UploadVerificationFile) - FilelistProcessor.UploadVerificationFile(m_backendurl, m_options, m_result.BackendWriter, db, null); - - if (!m_options.Dryrun) + tr.Commit("CommitCompact", restart: false); + if (changed) { - using (new Logging.Timer(LOGTAG, "CommitCompact", "CommitCompact")) - tr.Commit(); - if (changed) + db.WriteResults(m_result); + if (m_options.AutoVacuum) { - db.WriteResults(); - if (m_options.AutoVacuum) - { - m_result.VacuumResults = new VacuumResults(m_result); - new VacuumHandler(m_options, (VacuumResults)m_result.VacuumResults).Run(); - } + m_result.VacuumResults = new VacuumResults(m_result); + await new VacuumHandler(m_options, (VacuumResults)m_result.VacuumResults).RunAsync().ConfigureAwait(false); } } - else - tr.Rollback(); - - tr = null; - } - finally - { - if (tr != null) - try { tr.Rollback(); } - catch { } } } } - internal bool DoCompact(LocalDeleteDatabase db, bool hasVerifiedBackend, ref System.Data.IDbTransaction transaction, BackendManager sharedBackend) + internal async Task DoCompactAsync(LocalDeleteDatabase db, bool hasVerifiedBackend, ReusableTransaction rtr, IBackendManager backendManager) { - var report = db.GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, transaction); + var report = db.GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, rtr.Transaction); report.ReportCompactData(); if (report.ShouldReclaim || report.ShouldCompact) { // Workaround where we allow a running backendmanager to be used - using (var bk = sharedBackend == null ? new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db) : null) + if (!hasVerifiedBackend) + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, true, FilelistProcessor.VerifyMode.VerifyStrict, rtr.Transaction).ConfigureAwait(false); + + var newvol = new BlockVolumeWriter(m_options); + newvol.VolumeID = db.RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, rtr.Transaction); + + IndexVolumeWriter newvolindex = null; + if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) { - var backend = bk ?? sharedBackend; - if (!hasVerifiedBackend) + newvolindex = new IndexVolumeWriter(m_options); + newvolindex.VolumeID = db.RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, rtr.Transaction); + db.AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, rtr.Transaction); + } + + var blocksInVolume = 0L; + var buffer = new byte[m_options.Blocksize]; + var remoteList = db.GetRemoteVolumes().Where(n => n.State == RemoteVolumeState.Uploaded || n.State == RemoteVolumeState.Verified).ToArray(); + + //These are for bookkeeping + var uploadedVolumes = new List>(); + var deletedVolumes = new List>(); + var downloadedVolumes = new List>(); + + //We start by deleting unused volumes to save space before uploading new stuff + List fullyDeleteable = []; + if (report.DeleteableVolumes.Any()) + { + var deleteableVolumesAsHashSet = new HashSet(report.DeleteableVolumes); + fullyDeleteable = + remoteList + .Where(n => deleteableVolumesAsHashSet.Contains(n.Name)) + .Cast() + .ToList(); + } + await foreach (var d in DoDelete(db, backendManager, fullyDeleteable, rtr, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + deletedVolumes.Add(d); + + // This list is used to pick up unused volumes, + // so they can be deleted once the upload of the + // required fragments is complete + var deleteableVolumes = new List(); + + if (report.ShouldCompact) + { + // If we crash now, we may leave partial files + if (!m_options.Dryrun) + db.TerminatedWithActiveUploads = true; + + newvolindex?.StartVolume(newvol.RemoteFilename); + List volumesToDownload = []; + if (report.CompactableVolumes.Any()) { - FilelistProcessor.VerifyRemoteList(backend, m_options, db, m_result.BackendWriter, true, transaction); - } - - BlockVolumeWriter newvol = new BlockVolumeWriter(m_options); - newvol.VolumeID = db.RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, transaction); - - IndexVolumeWriter newvolindex = null; - if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) - { - newvolindex = new IndexVolumeWriter(m_options); - newvolindex.VolumeID = db.RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, transaction); - db.AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, transaction); - } - - long blocksInVolume = 0; - byte[] buffer = new byte[m_options.Blocksize]; - var remoteList = db.GetRemoteVolumes().Where(n => n.State == RemoteVolumeState.Uploaded || n.State == RemoteVolumeState.Verified).ToArray(); - - //These are for bookkeeping - var uploadedVolumes = new List>(); - var deletedVolumes = new List>(); - var downloadedVolumes = new List>(); - - //We start by deleting unused volumes to save space before uploading new stuff - List fullyDeleteable = []; - if (report.DeleteableVolumes.Any()) - { - var deleteableVolumesAsHashSet = new HashSet(report.DeleteableVolumes); - fullyDeleteable = + var compactableVolumesAsHashSet = new HashSet(report.CompactableVolumes); + volumesToDownload = remoteList - .Where(n => deleteableVolumesAsHashSet.Contains(n.Name)) + .Where(n => compactableVolumesAsHashSet.Contains(n.Name)) .Cast() .ToList(); } - deletedVolumes.AddRange(DoDelete(db, backend, fullyDeleteable, ref transaction)); - // This list is used to pick up unused volumes, - // so they can be deleted once the upload of the - // required fragments is complete - var deleteableVolumes = new List(); - - if (report.ShouldCompact) + using (var q = db.CreateBlockQueryHelper(rtr.Transaction)) { - newvolindex?.StartVolume(newvol.RemoteFilename); - List volumesToDownload = []; - if (report.CompactableVolumes.Any()) + await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(volumesToDownload, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - var compactableVolumesAsHashSet = new HashSet(report.CompactableVolumes); - volumesToDownload = - remoteList - .Where(n => compactableVolumesAsHashSet.Contains(n.Name)) - .Cast() - .ToList(); - } - - using (var q = db.CreateBlockQueryHelper(transaction)) - { - foreach (var entry in new AsyncDownloader(volumesToDownload, backend)) + using (tmpfile) { - using (var tmpfile = entry.TempFile) + var entry = new RemoteVolume(name, hash, size); + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(db, transaction); - return false; - } + await backendManager.WaitForEmptyAsync(db, rtr.Transaction, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + return false; + } - downloadedVolumes.Add(new KeyValuePair(entry.Name, entry.Size)); - var volumeid = db.GetRemoteVolumeID(entry.Name, transaction); - var inst = VolumeBase.ParseFilename(entry.Name); - using (var f = new BlockVolumeReader(inst.CompressionModule, tmpfile, m_options)) + downloadedVolumes.Add(new KeyValuePair(entry.Name, entry.Size)); + var volumeid = db.GetRemoteVolumeID(entry.Name, rtr.Transaction); + var inst = VolumeBase.ParseFilename(entry.Name); + using (var f = new BlockVolumeReader(inst.CompressionModule, tmpfile, m_options)) + { + foreach (var e in f.Blocks) { - foreach (var e in f.Blocks) + if (q.UseBlock(e.Key, e.Value, volumeid, rtr.Transaction)) { - if (q.UseBlock(e.Key, e.Value, volumeid, transaction)) + //TODO: How do we get the compression hint? Reverse query for filename in db? + var s = f.ReadBlock(e.Key, buffer); + if (s != e.Value) + throw new Exception(string.Format("Size mismatch problem for block {0}, {1} vs {2}", e.Key, s, e.Value)); + + newvol.AddBlock(e.Key, buffer, 0, s, Interface.CompressionHint.Compressible); + if (newvolindex != null) + newvolindex.AddBlock(e.Key, e.Value); + + db.RegisterDuplicatedBlock(e.Key, e.Value, newvol.VolumeID, rtr.Transaction); + blocksInVolume++; + + if (newvol.Filesize > (m_options.VolumeSize - m_options.Blocksize)) { - //TODO: How do we get the compression hint? Reverse query for filename in db? - var s = f.ReadBlock(e.Key, buffer); - if (s != e.Value) - throw new Exception(string.Format("Size mismatch problem for block {0}, {1} vs {2}", e.Key, s, e.Value)); + await FinishVolumeAndUpload(db, backendManager, newvol, newvolindex, uploadedVolumes); - newvol.AddBlock(e.Key, buffer, 0, s, Duplicati.Library.Interface.CompressionHint.Compressible); - if (newvolindex != null) - newvolindex.AddBlock(e.Key, e.Value); + newvol = new BlockVolumeWriter(m_options); + newvol.VolumeID = db.RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, rtr.Transaction); - db.MoveBlockToNewVolume(e.Key, e.Value, newvol.VolumeID, transaction); - blocksInVolume++; - - if (newvol.Filesize > m_options.VolumeSize) + if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) { - FinishVolumeAndUpload(db, backend, newvol, newvolindex, uploadedVolumes); - - newvol = new BlockVolumeWriter(m_options); - newvol.VolumeID = db.RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, transaction); - - if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) - { - newvolindex = new IndexVolumeWriter(m_options); - newvolindex.VolumeID = db.RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, transaction); - db.AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, transaction); - newvolindex.StartVolume(newvol.RemoteFilename); - } - - blocksInVolume = 0; - - //After we upload this volume, we can delete all previous encountered volumes - deletedVolumes.AddRange(DoDelete(db, backend, deleteableVolumes, ref transaction)); - deleteableVolumes = new List(); + newvolindex = new IndexVolumeWriter(m_options); + newvolindex.VolumeID = db.RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, rtr.Transaction); + db.AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, rtr.Transaction); + newvolindex.StartVolume(newvol.RemoteFilename); } + + blocksInVolume = 0; + + // Wait for the backend to catch up + await backendManager.WaitForEmptyAsync(db, rtr.Transaction, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + + // Commit as we have uploaded a volume + if (!m_options.Dryrun) + rtr.Commit(); } } } - - deleteableVolumes.Add(entry); } - } - if (blocksInVolume > 0) - { - FinishVolumeAndUpload(db, backend, newvol, newvolindex, uploadedVolumes); + deleteableVolumes.Add(entry); } - else + } + + if (blocksInVolume > 0) + { + await FinishVolumeAndUpload(db, backendManager, newvol, newvolindex, uploadedVolumes).ConfigureAwait(false); + } + else + { + db.RemoveRemoteVolume(newvol.RemoteFilename, rtr.Transaction); + if (newvolindex != null) { - db.RemoveRemoteVolume(newvol.RemoteFilename, transaction); - if (newvolindex != null) - { - db.RemoveRemoteVolume(newvolindex.RemoteFilename, transaction); - newvolindex.FinishVolume(null, 0); - } + db.RemoveRemoteVolume(newvolindex.RemoteFilename, rtr.Transaction); + newvolindex.FinishVolume(null, 0); } } } - else - { - newvolindex?.Dispose(); - newvol.Dispose(); - } - deletedVolumes.AddRange(DoDelete(db, backend, deleteableVolumes, ref transaction)); - - var downloadSize = downloadedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); - var deletedSize = deletedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); - var uploadSize = uploadedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); - - m_result.DeletedFileCount = deletedVolumes.Count; - m_result.DownloadedFileCount = downloadedVolumes.Count; - m_result.UploadedFileCount = uploadedVolumes.Count; - m_result.DeletedFileSize = deletedSize; - m_result.DownloadedFileSize = downloadSize; - m_result.UploadedFileSize = uploadSize; - m_result.Dryrun = m_options.Dryrun; - - if (m_result.Dryrun) - { - if (downloadedVolumes.Count == 0) - Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would delete {0} files, which would reduce storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)); - else - Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would download {0} file(s) with a total size of {1}, delete {2} file(s) with a total size of {3}, and compact to {4} file(s) with a size of {5}, which would reduce storage by {6} file(s) and {7}", - m_result.DownloadedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.DownloadedFileSize), - m_result.DeletedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize), m_result.UploadedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize), - m_result.DeletedFileCount - m_result.UploadedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)); - } - else - { - if (m_result.DownloadedFileCount == 0) - Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Deleted {0} files, which reduced storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)); - else - Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Downloaded {0} file(s) with a total size of {1}, deleted {2} file(s) with a total size of {3}, and compacted to {4} file(s) with a size of {5}, which reduced storage by {6} file(s) and {7}", - m_result.DownloadedFileCount, - Library.Utility.Utility.FormatSizeString(downloadSize), - m_result.DeletedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize), - m_result.UploadedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize), - m_result.DeletedFileCount - m_result.UploadedFileCount, - Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)); - } - - backend.WaitForComplete(db, transaction); + // The remainder of the operation cannot leave partial files + if (!m_options.Dryrun) + db.TerminatedWithActiveUploads = false; } + else + { + newvolindex?.Dispose(); + newvol.Dispose(); + } + + await foreach (var d in DoDelete(db, backendManager, deleteableVolumes, rtr, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + deletedVolumes.Add(d); + + var downloadSize = downloadedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); + var deletedSize = deletedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); + var uploadSize = uploadedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); + + m_result.DeletedFileCount = deletedVolumes.Count; + m_result.DownloadedFileCount = downloadedVolumes.Count; + m_result.UploadedFileCount = uploadedVolumes.Count; + m_result.DeletedFileSize = deletedSize; + m_result.DownloadedFileSize = downloadSize; + m_result.UploadedFileSize = uploadSize; + m_result.Dryrun = m_options.Dryrun; + + if (m_result.Dryrun) + { + if (downloadedVolumes.Count == 0) + Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would delete {0} files, which would reduce storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)); + else + Logging.Log.WriteDryrunMessage(LOGTAG, "CompactResults", "Would download {0} file(s) with a total size of {1}, delete {2} file(s) with a total size of {3}, and compact to {4} file(s) with a size of {5}, which would reduce storage by {6} file(s) and {7}", + m_result.DownloadedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.DownloadedFileSize), + m_result.DeletedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize), m_result.UploadedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize), + m_result.DeletedFileCount - m_result.UploadedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)); + } + else + { + if (m_result.DownloadedFileCount == 0) + Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Deleted {0} files, which reduced storage by {1}", m_result.DeletedFileCount, Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize)); + else + Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Downloaded {0} file(s) with a total size of {1}, deleted {2} file(s) with a total size of {3}, and compacted to {4} file(s) with a size of {5}, which reduced storage by {6} file(s) and {7}", + m_result.DownloadedFileCount, + Library.Utility.Utility.FormatSizeString(downloadSize), + m_result.DeletedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize), + m_result.UploadedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.UploadedFileSize), + m_result.DeletedFileCount - m_result.UploadedFileCount, + Library.Utility.Utility.FormatSizeString(m_result.DeletedFileSize - m_result.UploadedFileSize)); + } + + await backendManager.WaitForEmptyAsync(db, rtr.Transaction, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; return (m_result.DeletedFileCount + m_result.UploadedFileCount) > 0; @@ -299,26 +295,35 @@ namespace Duplicati.Library.Main.Operation } } - private IEnumerable> DoDelete(LocalDeleteDatabase db, BackendManager backend, IEnumerable deleteableVolumes, ref System.Data.IDbTransaction transaction) + private async IAsyncEnumerable> DoDelete(LocalDeleteDatabase db, IBackendManager backend, IEnumerable deleteableVolumes, ReusableTransaction rtr, [EnumeratorCancellation] CancellationToken cancellationToken) { + // Find volumes that can be deleted + var remoteFilesToRemove = db.ReOrderDeleteableVolumes(deleteableVolumes, rtr.Transaction).ToList(); + + // Make sure we do not re-assign blocks to any of the volumes we are about to delete + var toRemoveVolumeIds = db.GetRemoteVolumeIDs(remoteFilesToRemove.Select(x => x.Name), rtr.Transaction) + .Select(x => x.Value) + .Distinct() + .ToList(); + // Mark all volumes and relevant index files as disposable - List remoteFilesToRemove = db.GetDeletableVolumes(deleteableVolumes, transaction).ToList(); foreach (var f in remoteFilesToRemove) - db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, transaction); - - // Before we commit the current state, make sure the backend has caught up - backend.WaitForEmpty(db, transaction); - - if (!m_options.Dryrun) { - transaction.Commit(); - transaction = db.BeginTransaction(); + db.PrepareForDelete(f.Name, toRemoveVolumeIds, rtr.Transaction); + db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, rtr.Transaction); } - return PerformDelete(backend, remoteFilesToRemove); + // Before we commit the current state, make sure the backend has caught up + await backend.WaitForEmptyAsync(db, rtr.Transaction, cancellationToken).ConfigureAwait(false); + + if (!m_options.Dryrun) + rtr.Commit(); + + await foreach (var d in PerformDelete(backend, remoteFilesToRemove, cancellationToken).ConfigureAwait(false)) + yield return d; } - private void FinishVolumeAndUpload(LocalDeleteDatabase db, BackendManager backend, BlockVolumeWriter newvol, IndexVolumeWriter newvolindex, List> uploadedVolumes) + private async Task FinishVolumeAndUpload(LocalDeleteDatabase db, IBackendManager backendManager, BlockVolumeWriter newvol, IndexVolumeWriter newvolindex, List> uploadedVolumes) { Action indexVolumeFinished = () => { @@ -335,17 +340,17 @@ namespace Duplicati.Library.Main.Operation if (newvolindex != null) uploadedVolumes.Add(new KeyValuePair(newvolindex.RemoteFilename, newvolindex.Filesize)); if (!m_options.Dryrun) - backend.Put(newvol, newvolindex, indexVolumeFinished); + await backendManager.PutAsync(newvol, newvolindex, indexVolumeFinished, false, m_result.TaskControl.ProgressToken).ConfigureAwait(false); else Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadGeneratedBlockset", "Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize)); } - private IEnumerable> PerformDelete(BackendManager backend, IEnumerable list) + private async IAsyncEnumerable> PerformDelete(IBackendManager backendManager, IEnumerable list, [EnumeratorCancellation] CancellationToken cancelToken) { foreach (var f in list) { if (!m_options.Dryrun) - backend.Delete(f.Name, f.Size); + await backendManager.DeleteAsync(f.Name, f.Size, false, cancelToken).ConfigureAwait(false); else Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFile", "Would delete remote file: {0}, size: {1}", f.Name, Library.Utility.Utility.FormatSizeString(f.Size)); diff --git a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs index 76357b8d3..a295e641b 100644 --- a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs +++ b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,9 +20,12 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation { @@ -43,18 +46,18 @@ namespace Duplicati.Library.Main.Operation m_result = result; } - public void Run() + public async Task RunAsync() { - var ext = System.IO.Path.GetExtension(m_targetpath); + var ext = Path.GetExtension(m_targetpath); var module = m_options.CompressionModule; if (ext == "" || string.Compare(ext, 1, module, 0, module.Length, StringComparison.OrdinalIgnoreCase) != 0) m_targetpath = m_targetpath + "." + module; - if (System.IO.File.Exists(m_targetpath)) + if (File.Exists(m_targetpath)) throw new UserInformationException(string.Format("Output file already exists, not overwriting: {0}", m_targetpath), "BugReportTargetAlreadyExists"); - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "BugReportSourceDatabaseNotFound"); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.BugReport_Running); @@ -62,27 +65,32 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "ScrubbingFilenames", "Scrubbing filenames from database, this may take a while, please wait"); - using (var tmp = new Library.Utility.TempFile()) + using (var tmp = new TempFile()) { - System.IO.File.Copy(m_options.Dbpath, tmp, true); + File.Copy(m_options.Dbpath, tmp, true); using (var db = new LocalBugReportDatabase(tmp)) { - m_result.SetDatabase(db); db.Fix(); if (m_options.AutoVacuum) - { db.Vacuum(); - } } - using (var stream = new System.IO.FileStream(m_targetpath, FileMode.Create, FileAccess.Write, FileShare.Read)) - using (ICompression cm = DynamicLoader.CompressionLoader.GetModule(module, stream, Interface.ArchiveMode.Write, m_options.RawOptions)) - { - using (var cs = cm.CreateFile("log-database.sqlite", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow)) - using (var fs = System.IO.File.Open(tmp, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite)) - Library.Utility.Utility.CopyStream(fs, cs); + // Apply zip64 option if not already set + var options = new Dictionary(m_options.RawOptions); - using (var cs = new System.IO.StreamWriter(cm.CreateFile("system-info.txt", Duplicati.Library.Interface.CompressionHint.Compressible, DateTime.UtcNow))) + // If the file is larger than 1GiB, switch to zip64 if possible + if (new FileInfo(tmp).Length > 1L * 1024 * 1024 * 1024) + if (string.Equals(module, "zip", StringComparison.OrdinalIgnoreCase) && !options.ContainsKey("zip-compression-zip64")) + options["zip-compression-zip64"] = "true"; + + using (var stream = new FileStream(m_targetpath, FileMode.Create, FileAccess.Write, FileShare.Read)) + using (ICompression cm = DynamicLoader.CompressionLoader.GetModule(module, stream, ArchiveMode.Write, options)) + { + using (var cs = cm.CreateFile("log-database.sqlite", CompressionHint.Compressible, DateTime.UtcNow)) + using (var fs = File.Open(tmp, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + await Library.Utility.Utility.CopyStreamAsync(fs, cs, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + + using (var cs = new StreamWriter(cm.CreateFile("system-info.txt", CompressionHint.Compressible, DateTime.UtcNow))) foreach (var line in SystemInfoHandler.GetSystemInfo()) cs.WriteLine(line); } diff --git a/Duplicati/Library/Main/Operation/DeleteHandler.cs b/Duplicati/Library/Main/Operation/DeleteHandler.cs index 85a8af056..d39b14190 100644 --- a/Duplicati/Library/Main/Operation/DeleteHandler.cs +++ b/Duplicati/Library/Main/Operation/DeleteHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,6 +24,9 @@ using System.Linq; using System.Collections.Generic; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; +using System.Threading; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { @@ -35,140 +38,108 @@ namespace Duplicati.Library.Main.Operation internal static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly DeleteResults m_result; - protected readonly string m_backendurl; protected readonly Options m_options; - public DeleteHandler(string backend, Options options, DeleteResults result) + public DeleteHandler(Options options, DeleteResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public void Run() + public async Task RunAsync(IBackendManager backendManager) { if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseFileMissing"); - using (var db = new Database.LocalDeleteDatabase(m_options.Dbpath, "Delete")) + using (var db = new LocalDeleteDatabase(m_options.Dbpath, "Delete")) + using (var tr = new ReusableTransaction(db)) { - var tr = db.BeginTransaction(); - try - { - m_result.SetDatabase(db); - Utility.UpdateOptionsFromDb(db, m_options); - Utility.VerifyOptionsAndUpdateDatabase(db, m_options); + Utility.UpdateOptionsFromDb(db, m_options); + Utility.VerifyOptionsAndUpdateDatabase(db, m_options); - DoRun(db, ref tr, false, false, null); + await DoRunAsync(db, tr, false, false, backendManager).ConfigureAwait(false); - if (!m_options.Dryrun) - { - using (new Logging.Timer(LOGTAG, "CommitDelete", "CommitDelete")) - tr.Commit(); - - db.WriteResults(); - } - else - tr.Rollback(); - - tr = null; - } - finally - { - if (tr != null) - try { tr.Rollback(); } - catch { } - } + if (!m_options.Dryrun) + tr.Commit("ComitDelete", restart: false); } } - public void DoRun(Database.LocalDeleteDatabase db, ref System.Data.IDbTransaction transaction, bool hasVerifiedBackend, bool forceCompact, BackendManager sharedManager) + public async Task DoRunAsync(LocalDeleteDatabase db, ReusableTransaction rtr, bool hasVerifiedBackend, bool forceCompact, IBackendManager backendManager) { - // Workaround where we allow a running backendmanager to be used - using (var bk = sharedManager == null ? new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db) : null) + if (!hasVerifiedBackend) + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, rtr.Transaction).ConfigureAwait(false); + + IListResultFileset[] filesets = db.FilesetsWithBackupVersion.ToArray(); + List versionsToDelete = + [ + .. new SpecificVersionsRemover(m_options).GetFilesetsToDelete(filesets), + .. new KeepTimeRemover(m_options).GetFilesetsToDelete(filesets), + .. new RetentionPolicyRemover(m_options).GetFilesetsToDelete(filesets), + ]; + + // When determining the number of full versions to keep, we need to ignore the versions already marked for removal. + versionsToDelete.AddRange(new KeepVersionsRemover(m_options).GetFilesetsToDelete(filesets.Except(versionsToDelete))); + + if (!m_options.AllowFullRemoval && filesets.Length == versionsToDelete.Count) { - var backend = bk ?? sharedManager; - - if (!hasVerifiedBackend) - { - FilelistProcessor.VerifyRemoteList(backend, m_options, db, m_result.BackendWriter, true, transaction); - } - - IListResultFileset[] filesets = db.FilesetsWithBackupVersion.ToArray(); - List versionsToDelete = new List(); - versionsToDelete.AddRange(new SpecificVersionsRemover(this.m_options).GetFilesetsToDelete(filesets)); - versionsToDelete.AddRange(new KeepTimeRemover(this.m_options).GetFilesetsToDelete(filesets)); - versionsToDelete.AddRange(new RetentionPolicyRemover(this.m_options).GetFilesetsToDelete(filesets)); - - // When determining the number of full versions to keep, we need to ignore the versions already marked for removal. - versionsToDelete.AddRange(new KeepVersionsRemover(this.m_options).GetFilesetsToDelete(filesets.Except(versionsToDelete))); - - if (!m_options.AllowFullRemoval && filesets.Length == versionsToDelete.Count) - { - Logging.Log.WriteInformationMessage(LOGTAG, "PreventingLastFilesetRemoval", "Preventing removal of last fileset, use --{0} to allow removal ...", "allow-full-removal"); - versionsToDelete = versionsToDelete.OrderBy(x => x.Version).Skip(1).ToList(); - } - - if (versionsToDelete.Count > 0) - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileset", "Deleting {0} remote fileset(s) ...", versionsToDelete.Count); - - var lst = db.DropFilesetsFromTable(versionsToDelete.Select(x => x.Time).ToArray(), transaction).ToArray(); - foreach (var f in lst) - db.UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, transaction); - - if (!m_options.Dryrun) - { - transaction.Commit(); - transaction = db.BeginTransaction(); - } - - foreach (var f in lst) - { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(db, transaction); - return; - } - - if (!m_options.Dryrun) - backend.Delete(f.Key, f.Value); - else - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFileset", "Would delete remote fileset: {0}", f.Key); - } - - if (sharedManager == null) - backend.WaitForComplete(db, transaction); - else - backend.WaitForEmpty(db, transaction); - - var count = lst.Length; - if (!m_options.Dryrun) - { - if (count == 0) - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "No remote filesets were deleted"); - else - Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "Deleted {0} remote fileset(s)", count); - } - else - { - - if (count == 0) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "No remote filesets would be deleted"); - else - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "{0} remote fileset(s) would be deleted", count); - - if (count > 0 && m_options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteHelp", "Remove --dry-run to actually delete files"); - } - - if (!m_options.NoAutoCompact && (forceCompact || versionsToDelete.Count > 0)) - { - m_result.CompactResults = new CompactResults(m_result); - new CompactHandler(m_backendurl, m_options, (CompactResults)m_result.CompactResults).DoCompact(db, true, ref transaction, sharedManager); - } - - m_result.SetResults(versionsToDelete.Select(v => new Tuple(v.Version, v.Time)), m_options.Dryrun); + Logging.Log.WriteInformationMessage(LOGTAG, "PreventingLastFilesetRemoval", "Preventing removal of last fileset, use --{0} to allow removal ...", "allow-full-removal"); + versionsToDelete = versionsToDelete.OrderBy(x => x.Version).Skip(1).ToList(); } + + if (versionsToDelete.Count > 0) + Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileset", "Deleting {0} remote fileset(s) ...", versionsToDelete.Count); + + var lst = db.DropFilesetsFromTable(versionsToDelete.Select(x => x.Time).ToArray(), rtr.Transaction).ToArray(); + foreach (var f in lst) + db.UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, rtr.Transaction); + + if (!m_options.Dryrun) + rtr.Commit(); + + foreach (var f in lst) + { + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(db, rtr.Transaction, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + return; + } + + if (!m_options.Dryrun) + await backendManager.DeleteAsync(f.Key, f.Value, false, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + else + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFileset", "Would delete remote fileset: {0}", f.Key); + } + + await backendManager.WaitForEmptyAsync(db, rtr.Transaction, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + + var count = lst.Length; + if (!m_options.Dryrun) + { + if (count == 0) + Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "No remote filesets were deleted"); + else + Logging.Log.WriteInformationMessage(LOGTAG, "DeleteResults", "Deleted {0} remote fileset(s)", count); + } + else + { + + if (count == 0) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "No remote filesets would be deleted"); + else + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteResults", "{0} remote fileset(s) would be deleted", count); + + if (count > 0 && m_options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteHelp", "Remove --dry-run to actually delete files"); + } + + if (!m_options.NoAutoCompact && (forceCompact || versionsToDelete.Count > 0)) + { + m_result.CompactResults = new CompactResults(m_result); + await new CompactHandler(m_options, (CompactResults)m_result.CompactResults) + .DoCompactAsync(db, true, rtr, backendManager).ConfigureAwait(false); + } + + m_result.SetResults(versionsToDelete.Select(v => new Tuple(v.Version, v.Time)), m_options.Dryrun); } } diff --git a/Duplicati/Library/Main/Operation/FilelistProcessor.cs b/Duplicati/Library/Main/Operation/FilelistProcessor.cs index f7903569c..c47bfedab 100644 --- a/Duplicati/Library/Main/Operation/FilelistProcessor.cs +++ b/Duplicati/Library/Main/Operation/FilelistProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,12 +26,36 @@ using System.Data; using System.Linq; using Duplicati.Library.Interface; using System.Threading; -using Duplicati.Library.Utility; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { internal static class FilelistProcessor { + /// + /// The approach to take when verifying the filelist + /// + public enum VerifyMode + { + /// + /// Just verify the list, do not repair partials + /// + VerifyOnly, + /// + /// Verify the list and fail if there are partials + /// + VerifyStrict, + /// + /// Verify the list and repair partials + /// + VerifyAndClean, + /// + /// Force a verification and repair, even if the last operation was successful + /// + VerifyAndCleanForced + } + + /// /// The tag used for logging /// @@ -42,7 +66,7 @@ namespace Duplicati.Library.Main.Operation /// Throws an error if there are issues with the remote storage /// /// The database to compare with - public static void VerifyLocalList(BackendManager backend, LocalDatabase database) + public static async Task VerifyLocalList(IBackendManager backendManager, LocalDatabase database, CancellationToken cancellationToken) { var locallist = database.GetRemoteVolumes(); foreach (var i in locallist) @@ -60,7 +84,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "RemovingStaleFile", "Removing remote file listed as {0}: {1}", i.State, i.Name); try { - backend.Delete(i.Name, i.Size, true); + await backendManager.DeleteAsync(i.Name, i.Size, true, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -74,17 +98,29 @@ namespace Duplicati.Library.Main.Operation break; } - backend.FlushDbMessages(); + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); } } - public static void VerifyRemoteList(BackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, IDbTransaction transaction) + /// + /// Helper method that verifies uploaded volumes and updates their state in the database. + /// Throws an error if there are issues with the remote storage + /// + /// The backend instance to use + /// The options used + /// The database to compare with + /// The backend writer to use + /// True if only the latest volumes should be verified + /// The mode to use for verification + /// An optional transaction object + /// An awaitable task + public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, VerifyMode verifyMode, IDbTransaction transaction) { if (!options.NoBackendverification) { LocalBackupDatabase backupDatabase = new LocalBackupDatabase(database, options); IEnumerable protectedFiles = backupDatabase.GetTemporaryFilelistVolumeNames(latestVolumesOnly, transaction); - FilelistProcessor.VerifyRemoteList(backend, options, database, backendWriter, protectedFiles); + await VerifyRemoteList(backend, options, database, backendWriter, protectedFiles, [], logErrors: true, verifyMode: verifyMode).ConfigureAwait(false); } } @@ -97,10 +133,13 @@ namespace Duplicati.Library.Main.Operation /// The database to compare with /// The log instance to use /// Filenames that should be exempted from deletion + /// Filenames that should be exempted from strict verification + /// The mode to use for verification /// Disable the logging of errors to prevent spamming the log; exceptions will be thrown regardless - public static void VerifyRemoteList(BackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles = null, bool logErrors = true) + /// An awaitable task + public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, bool logErrors, VerifyMode verifyMode) { - var tp = RemoteListAnalysis(backend, options, database, log, protectedFiles); + var tp = await RemoteListAnalysis(backend, options, database, log, protectedFiles, strictExcemptFiles, verifyMode).ConfigureAwait(false); long extraCount = 0; long missingCount = 0; @@ -177,17 +216,15 @@ namespace Duplicati.Library.Main.Operation /// /// The backend url /// The options to use - /// The result writer /// The attached database /// An optional transaction object - public static void UploadVerificationFile(string backendurl, Options options, IBackendWriter result, LocalDatabase db, System.Data.IDbTransaction transaction) + public static async Task UploadVerificationFile(IBackendManager backendManager, Options options, LocalDatabase db, IDbTransaction transaction) { - using (var backend = new BackendManager(backendurl, options, result, db)) using (var tempfile = new Library.Utility.TempFile()) { var remotename = options.Prefix + "-verification.json"; - using (var stream = new System.IO.StreamWriter(tempfile, false, System.Text.Encoding.UTF8)) - FilelistProcessor.CreateVerificationFile(db, stream); + await using (var stream = new System.IO.StreamWriter(tempfile, false, System.Text.Encoding.UTF8)) + CreateVerificationFile(db, stream); if (options.Dryrun) { @@ -195,8 +232,8 @@ namespace Duplicati.Library.Main.Operation } else { - backend.PutUnencrypted(remotename, tempfile); - backend.WaitForComplete(db, transaction); + await backendManager.PutVerificationFileAsync(remotename, tempfile, CancellationToken.None).ConfigureAwait(false); + await backendManager.WaitForEmptyAsync(db, transaction, CancellationToken.None).ConfigureAwait(false); } } } @@ -209,11 +246,23 @@ namespace Duplicati.Library.Main.Operation /// The options used /// The database to compare with /// Filenames that should be exempted from deletion - public static RemoteAnalysisResult RemoteListAnalysis(BackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles) + /// Filenames that should be exempted from strict verification + /// The mode to use for verification + /// The result of the analysis + public static async Task RemoteListAnalysis(IBackendManager backendManager, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, VerifyMode verifyMode) { - var rawlist = backend.List(); + // If the last operation completed, no cleanup should be required + if (verifyMode == VerifyMode.VerifyAndClean && !database.TerminatedWithActiveUploads) + verifyMode = VerifyMode.VerifyStrict; + + // Force cleanup should set the mode to cleanup + if (verifyMode == VerifyMode.VerifyAndCleanForced) + verifyMode = VerifyMode.VerifyAndClean; + + var rawlist = await backendManager.ListAsync(CancellationToken.None).ConfigureAwait(false); var lookup = new Dictionary(); - protectedFiles = protectedFiles ?? Enumerable.Empty(); + protectedFiles = protectedFiles ?? []; + strictExcemptFiles = strictExcemptFiles ?? []; var remotelist = (from n in rawlist let p = Volumes.VolumeBase.ParseFilename(n) @@ -236,6 +285,7 @@ namespace Duplicati.Library.Main.Operation select n).ToList(); log.KnownFileCount = remotelist.Count; + log.KnownFilesets = filesets.Count; long knownFileSize = remotelist.Select(x => Math.Max(0, x.File.Size)).Sum(); log.KnownFileSize = knownFileSize; log.UnknownFileCount = unknownlist.Count; @@ -243,14 +293,15 @@ namespace Duplicati.Library.Main.Operation log.BackupListCount = database.FilesetTimes.Count(); log.LastBackupDate = filesets.Count == 0 ? new DateTime(0) : filesets[0].Time.ToLocalTime(); - CheckQuota(backend, options, log, knownFileSize); + await CheckQuota(backendManager, options, log, knownFileSize).ConfigureAwait(false); foreach (var s in remotelist) lookup[s.File.Name] = s; var missing = new List(); var missingHash = new List>(); - var cleanupRemovedRemoteVolumes = new HashSet(); + var temporaryAndDeletedVolumes = new HashSet(); + var missingUploadingVolumes = new HashSet(); foreach (var e in database.DuplicateRemoteVolumes()) { @@ -263,8 +314,7 @@ namespace Duplicati.Library.Main.Operation var locallist = database.GetRemoteVolumes(); foreach (var i in locallist) { - Volumes.IParsedVolume r; - var remoteFound = lookup.TryGetValue(i.Name, out r); + var remoteFound = lookup.TryGetValue(i.Name, out var r); var correctSize = remoteFound && i.Size >= 0 && (i.Size == r.File.Size || r.File.Size < 0); lookup.Remove(i.Name); @@ -273,7 +323,7 @@ namespace Duplicati.Library.Main.Operation { case RemoteVolumeState.Deleted: if (remoteFound) - Logging.Log.WriteInformationMessage(LOGTAG, "IgnoreRemoteDeletedFile", "ignoring remote file listed as {0}: {1}", i.State, i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "IgnoreRemoteDeletedFile", "Ignoring remote file listed as {0}: {1}", i.State, i.Name); break; @@ -281,25 +331,36 @@ namespace Duplicati.Library.Main.Operation case RemoteVolumeState.Deleting: if (remoteFound) { - Logging.Log.WriteInformationMessage(LOGTAG, "RemoveUnwantedRemoteFile", "removing remote file listed as {0}: {1}", i.State, i.Name); - backend.Delete(i.Name, i.Size, true); + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFile", "Would delete remote file: {0}", i.Name); + } + else if (verifyMode == VerifyMode.VerifyOnly) + { + Logging.Log.WriteInformationMessage(LOGTAG, "WouldRemoveUnwantedRemoteFile", "Would remove remote file listed as {0}: {1}, but defering as {2} is requested", i.State, i.Name, verifyMode); + } + else + { + Logging.Log.WriteInformationMessage(LOGTAG, "RemoveUnwantedRemoteFile", "Removing remote file listed as {0}: {1}", i.State, i.Name); + await backendManager.DeleteAsync(i.Name, i.Size, true, CancellationToken.None).ConfigureAwait(false); + } } else { if (i.DeleteGracePeriod > DateTime.UtcNow) { - Logging.Log.WriteInformationMessage(LOGTAG, "KeepDeleteRequest", "keeping delete request for {0} until {1}", i.Name, i.DeleteGracePeriod.ToLocalTime()); + Logging.Log.WriteInformationMessage(LOGTAG, "KeepDeleteRequest", "Keeping delete request for {0} until {1}", i.Name, i.DeleteGracePeriod.ToLocalTime()); } else { if (i.State == RemoteVolumeState.Temporary && protectedFiles.Any(pf => pf == i.Name)) { - Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "Keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); } else { - Logging.Log.WriteInformationMessage(LOGTAG, "RemoteUnwantedMissingFile", "removing file listed as {0}: {1}", i.State, i.Name); - cleanupRemovedRemoteVolumes.Add(i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "RemoteUnwantedMissingFile", "Removing file listed as {0}: {1}", i.State, i.Name); + temporaryAndDeletedVolumes.Add(i.Name); } } } @@ -307,20 +368,25 @@ namespace Duplicati.Library.Main.Operation case RemoteVolumeState.Uploading: if (remoteFound && correctSize && r.File.Size >= 0) { - Logging.Log.WriteInformationMessage(LOGTAG, "PromotingCompleteFile", "promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded); + Logging.Log.WriteInformationMessage(LOGTAG, "PromotingCompleteFile", "Promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded); database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash); } else if (!remoteFound) { if (protectedFiles.Any(pf => pf == i.Name)) { - Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "Keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null); } + else if (verifyMode == VerifyMode.VerifyStrict && !strictExcemptFiles.Any(pf => pf == i.Name)) + { + Logging.Log.WriteErrorMessage(LOGTAG, "UnexpectedUploadingFile", null, "Unexpected file in uploading state: {0}", i.Name); + throw new RemoteListVerificationException($"The missing remote volume {i.Name} is in uploading state and strict mode is on", "UnexpectedUploadingFile"); + } else { - Logging.Log.WriteInformationMessage(LOGTAG, "SchedulingMissingFileForDelete", "scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name); - cleanupRemovedRemoteVolumes.Add(i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "SchedulingMissingFileForDelete", "Scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name); + missingUploadingVolumes.Add(i.Name); database.UpdateRemoteVolume(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null); } } @@ -328,12 +394,28 @@ namespace Duplicati.Library.Main.Operation { if (protectedFiles.Any(pf => pf == i.Name)) { - Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); + Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "Keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); + } + else if (verifyMode == VerifyMode.VerifyStrict && !strictExcemptFiles.Any(pf => pf == i.Name)) + { + Logging.Log.WriteErrorMessage(LOGTAG, "UnexpectedUploadingFile", null, "Unexpected file in uploading state: {0}", i.Name); + throw new RemoteListVerificationException($"The remote volume {i.Name} is in uploading state, partially uploaded, and strict mode is on", "UnexpectedUploadingFile"); } else { - Logging.Log.WriteInformationMessage(LOGTAG, "Remove incomplete file", "removing incomplete remote file listed as {0}: {1}", i.State, i.Name); - backend.Delete(i.Name, i.Size, true); + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteRemoteFile", "Would delete remote file: {0}", i.Name); + } + else if (verifyMode == VerifyMode.VerifyOnly) + { + Logging.Log.WriteInformationMessage(LOGTAG, "WouldDeleteIncompleteFile", "Would remove incomplete remote file listed as {0}: {1}, but defering as {2} is requested", i.State, i.Name, verifyMode); + } + else + { + Logging.Log.WriteInformationMessage(LOGTAG, "RemovingIncompleteFile", "Removing incomplete remote file listed as {0}: {1}", i.State, i.Name); + await backendManager.DeleteAsync(i.Name, i.Size, true, CancellationToken.None).ConfigureAwait(false); + } } } break; @@ -357,18 +439,30 @@ namespace Duplicati.Library.Main.Operation break; default: - Logging.Log.WriteWarningMessage(LOGTAG, "UnknownFileState", null, "unknown state for remote file listed as {0}: {1}", i.State, i.Name); + Logging.Log.WriteWarningMessage(LOGTAG, "UnknownFileState", null, "Unknown state for remote file listed as {0}: {1}", i.State, i.Name); break; } - - backend.FlushDbMessages(); } - // cleanup deleted volumes in DB en block - database.RemoveRemoteVolumes(cleanupRemovedRemoteVolumes, null); + await backendManager.WaitForEmptyAsync(database, null, CancellationToken.None).ConfigureAwait(false); + + // Batch cleanup deleted volumes in DB + if (verifyMode == VerifyMode.VerifyStrict && missingUploadingVolumes.Count > 0) + throw new RemoteListVerificationException($"The remote volumes {string.Join(", ", missingUploadingVolumes)} are supposed to be deleted, but strict mode is on. Try running the \"repair\" command", "DeleteDuringStrictMode"); + else if (verifyMode == VerifyMode.VerifyAndClean || verifyMode == VerifyMode.VerifyStrict) + { + database.RemoveRemoteVolumes(missingUploadingVolumes.Concat(temporaryAndDeletedVolumes), null); + // Clear the flag after we have cleaned up + if (!options.Dryrun) + database.TerminatedWithActiveUploads = false; + } + else if (verifyMode == VerifyMode.VerifyOnly && database.TerminatedWithActiveUploads) + { + Logging.Log.WriteWarningMessage(LOGTAG, "ActiveUploadsDetected", null, "Active uploads detected, but no cleanup was performed. Run the \"repair\" command to clean up the incomplete files"); + } foreach (var i in missingHash) - Logging.Log.WriteWarningMessage(LOGTAG, "MissingRemoteHash", null, "remote file {1} is listed as {0} with size {2} but should be {3}, please verify the sha256 hash \"{4}\"", i.Item2.State, i.Item2.Name, i.Item1, i.Item2.Size, i.Item2.Hash); + Logging.Log.WriteWarningMessage(LOGTAG, "MissingRemoteHash", null, "Remote file {1} is listed as {0} with size {2} but should be {3}, please verify the sha256 hash \"{4}\"", i.Item2.State, i.Item2.Name, i.Item1, i.Item2.Size, i.Item2.Hash); return new RemoteAnalysisResult() { @@ -380,9 +474,12 @@ namespace Duplicati.Library.Main.Operation }; } - private static void CheckQuota(BackendManager backend, Options options, IBackendWriter log, long knownFileSize) + private static async Task CheckQuota(IBackendManager backendManager, Options options, IBackendWriter log, long knownFileSize) { - var quota = backend.GetQuotaInfoAsync(CancellationToken.None).Await(); + if (options.QuotaDisable) + return; + + var quota = await backendManager.GetQuotaInfoAsync(CancellationToken.None).ConfigureAwait(false); if (quota != null) { log.TotalQuotaSpace = quota.TotalQuotaSpace; diff --git a/Duplicati/Library/Main/Operation/ListAffected.cs b/Duplicati/Library/Main/Operation/ListAffected.cs index da10253ac..ca27dd034 100644 --- a/Duplicati/Library/Main/Operation/ListAffected.cs +++ b/Duplicati/Library/Main/Operation/ListAffected.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,6 +23,8 @@ using System; using System.Linq; using System.Collections.Generic; using Duplicati.Library.Interface; +using System.Threading.Tasks; +using System.IO; namespace Duplicati.Library.Main.Operation { @@ -36,15 +38,14 @@ namespace Duplicati.Library.Main.Operation m_options = options; m_result = result; } - - public void Run(List args, Action callback = null) + + public Task RunAsync(List args, Action callback = null) { - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); - using(var db = new Database.LocalListAffectedDatabase(m_options.Dbpath)) + using (var db = new Database.LocalListAffectedDatabase(m_options.Dbpath)) { - m_result.SetDatabase(db); if (callback == null) { m_result.SetResult( @@ -66,6 +67,8 @@ namespace Duplicati.Library.Main.Operation callback(m_result); } } + + return Task.CompletedTask; } } } diff --git a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs index a28e2cc1a..7e82c7754 100644 --- a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,8 +22,11 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Interface; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation { @@ -33,30 +36,28 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ListBrokenFilesHandler)); - protected readonly string m_backendurl; protected readonly Options m_options; protected readonly ListBrokenFilesResults m_result; - public ListBrokenFilesHandler(string backend, Options options, ListBrokenFilesResults result) + public ListBrokenFilesHandler(Options options, ListBrokenFilesResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public void Run(Library.Utility.IFilter filter, Func callbackhandler = null) + public async Task RunAsync(IBackendManager backendManager, IFilter filter, Func callbackhandler = null) { - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); using (var db = new Database.LocalListBrokenFilesDatabase(m_options.Dbpath)) using (var tr = db.BeginTransaction()) - DoRun(db, tr, filter, callbackhandler); + await DoRunAsync(backendManager, db, tr, filter, callbackhandler).ConfigureAwait(false); } - public static Tuple[] GetBrokenFilesetsFromRemote(string backendurl, BasicResults result, Database.LocalListBrokenFilesDatabase db, System.Data.IDbTransaction transaction, Options options, out List missing) + public static async Task<(Tuple[], List Missing)> GetBrokenFilesetsFromRemote(IBackendManager backendManager, BasicResults result, Database.LocalListBrokenFilesDatabase db, System.Data.IDbTransaction transaction, Options options) { - missing = null; + List missing = null; var brokensets = db.GetBrokenFilesets(options.Time, options.Version, transaction).ToArray(); if (brokensets.Length == 0) @@ -66,35 +67,32 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesetsInDatabase", "No broken filesets found in database, checking for missing remote files"); - using (var backend = new BackendManager(backendurl, options, result.BackendWriter, db)) + var remotestate = await FilelistProcessor.RemoteListAnalysis(backendManager, options, db, result.BackendWriter, null, null, FilelistProcessor.VerifyMode.VerifyOnly).ConfigureAwait(false); + if (!remotestate.ParsedVolumes.Any()) + throw new UserInformationException("No remote volumes were found, refusing purge", "CannotPurgeWithNoRemoteVolumes"); + + missing = remotestate.MissingVolumes.ToList(); + if (missing.Count == 0) { - var remotestate = FilelistProcessor.RemoteListAnalysis(backend, options, db, result.BackendWriter, null); - if (!remotestate.ParsedVolumes.Any()) - throw new UserInformationException("No remote volumes were found, refusing purge", "CannotPurgeWithNoRemoteVolumes"); - - missing = remotestate.MissingVolumes.ToList(); - if (missing.Count == 0) - { - Logging.Log.WriteInformationMessage(LOGTAG, "NoMissingFilesFound", "Skipping operation because no files were found to be missing, and no filesets were recorded as broken."); - return null; - } - - // Mark all volumes as disposable - foreach (var f in missing) - db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, transaction); - - Logging.Log.WriteInformationMessage(LOGTAG, "MarkedRemoteFilesForDeletion", "Marked {0} remote files for deletion", missing.Count); - - // Drop all content from tables - db.RemoveMissingBlocks(missing.Select(x => x.Name), transaction); + Logging.Log.WriteInformationMessage(LOGTAG, "NoMissingFilesFound", "Skipping operation because no files were found to be missing, and no filesets were recorded as broken."); + return (null, missing); } + + // Mark all volumes as disposable + foreach (var f in missing) + db.UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, transaction); + + Logging.Log.WriteInformationMessage(LOGTAG, "MarkedRemoteFilesForDeletion", "Marked {0} remote files for deletion", missing.Count); + + // Drop all content from tables + db.RemoveMissingBlocks(missing.Select(x => x.Name), transaction); brokensets = db.GetBrokenFilesets(options.Time, options.Version, transaction).ToArray(); } - return brokensets; + return (brokensets, missing); } - private void DoRun(Database.LocalListBrokenFilesDatabase db, System.Data.IDbTransaction transaction, Library.Utility.IFilter filter, Func callbackhandler) + private async Task DoRunAsync(IBackendManager backendManager, Database.LocalListBrokenFilesDatabase db, System.Data.IDbTransaction transaction, IFilter filter, Func callbackhandler) { if (filter != null && !filter.Empty) throw new UserInformationException("Filters are not supported for this operation", "FiltersAreNotSupportedForListBrokenFiles"); @@ -102,14 +100,13 @@ namespace Duplicati.Library.Main.Operation if (db.PartiallyRecreated) throw new UserInformationException("The command does not work on partially recreated databases", "ListBrokenFilesDoesNotWorkOnPartialDatabase"); - List missing; - var brokensets = GetBrokenFilesetsFromRemote(m_backendurl, m_result, db, transaction, m_options, out missing); + (var brokensets, var missing) = await GetBrokenFilesetsFromRemote(backendManager, m_result, db, transaction, m_options).ConfigureAwait(false); if (brokensets == null) return; if (brokensets.Length == 0) { - m_result.BrokenFiles = new Tuple>>[0]; + m_result.BrokenFiles = []; if (missing == null) Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesets", "Found no broken filesets"); @@ -123,8 +120,8 @@ namespace Duplicati.Library.Main.Operation var fstimes = db.FilesetTimes.ToList(); - var brokenfilesets = - brokensets.Select(x => new + var brokenfilesets = + brokensets.Select(x => new { Version = fstimes.FindIndex(y => y.Key == x.Item2), Timestamp = x.Item1, @@ -138,8 +135,8 @@ namespace Duplicati.Library.Main.Operation m_result.BrokenFiles = brokenfilesets.Select( x => new Tuple>>( - x.Version, - x.Timestamp, + x.Version, + x.Timestamp, callbackhandler == null && !m_options.ListSetsOnly ? db.GetBrokenFilenames(x.FilesetID, transaction).ToArray().AsEnumerable() : new MockList>((int)x.BrokenCount) @@ -169,7 +166,7 @@ namespace Duplicati.Library.Main.Operation public int Count { get; private set; } - public bool IsReadOnly { get { return true; }} + public bool IsReadOnly => true; public void Add(T item) { diff --git a/Duplicati/Library/Main/Operation/ListChangesHandler.cs b/Duplicati/Library/Main/Operation/ListChangesHandler.cs index 184f6eb28..a73b92c6a 100644 --- a/Duplicati/Library/Main/Operation/ListChangesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListChangesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,7 +22,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation { @@ -33,41 +36,39 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(ListChangesHandler)); - private readonly string m_backendurl; private readonly Options m_options; private readonly ListChangesResults m_result; - public ListChangesHandler(string backend, Options options, ListChangesResults result) + public ListChangesHandler(Options options, ListChangesResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - + private static Tuple SelectTime(string value, IEnumerable> list, out long index, out DateTime time, out T el) { long indexValue; Tuple res; if (!long.TryParse(value, out indexValue)) { - var t = Library.Utility.Timeparser.ParseTimeInterval(value, DateTime.Now, true); + var t = Timeparser.ParseTimeInterval(value, DateTime.Now, true); res = list.OrderBy(x => Math.Abs((x.Item2 - t).Ticks)).First(); } else { res = list.OrderBy(x => Math.Abs(x.Item1 - indexValue)).First(); } - + index = res.Item1; time = res.Item2; el = res.Item3; return res; } - public void Run(string baseVersion, string compareVersion, IEnumerable filterstrings = null, Library.Utility.IFilter compositefilter = null, Action>> callback = null) + public async Task RunAsync(string baseVersion, string compareVersion, IBackendManager backendManager, IEnumerable filterstrings, IFilter compositefilter, Action>> callback) { - var filter = Library.Utility.JoinedFilterExpression.Join(new Library.Utility.FilterExpression(filterstrings), compositefilter); - + var filter = JoinedFilterExpression.Join(new FilterExpression(filterstrings), compositefilter); + var useLocalDb = !m_options.NoLocalDb && System.IO.File.Exists(m_options.Dbpath); baseVersion = string.IsNullOrEmpty(baseVersion) ? "1" : baseVersion; compareVersion = string.IsNullOrEmpty(compareVersion) ? "0" : compareVersion; @@ -77,85 +78,83 @@ namespace Duplicati.Library.Main.Operation DateTime baseVersionTime; DateTime compareVersionTime; - - using(var tmpdb = useLocalDb ? null : new Library.Utility.TempFile()) - using(var db = new Database.LocalListChangesDatabase(useLocalDb ? m_options.Dbpath : (string)tmpdb)) - using(var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) - using(var storageKeeper = db.CreateStorageHelper()) + + using (var tmpdb = useLocalDb ? null : new TempFile()) + using (var db = new Database.LocalListChangesDatabase(useLocalDb ? m_options.Dbpath : (string)tmpdb)) + using (var storageKeeper = db.CreateStorageHelper()) { - m_result.SetDatabase(db); - if (useLocalDb) { var dbtimes = db.FilesetTimes.ToList(); if (dbtimes.Count < 2) throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", dbtimes.Count), "NeedTwoBackupsToStartDiff"); - + long baseVersionId; long compareVersionId; - + var times = dbtimes.Zip(Enumerable.Range(0, dbtimes.Count), (a, b) => new Tuple(b, a.Value, a.Key)).ToList(); var bt = SelectTime(baseVersion, times, out baseVersionIndex, out baseVersionTime, out baseVersionId); times.Remove(bt); SelectTime(compareVersion, times, out compareVersionIndex, out compareVersionTime, out compareVersionId); - + storageKeeper.AddFromDb(baseVersionId, false, filter); storageKeeper.AddFromDb(compareVersionId, true, filter); } else { Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, accessing remote store"); - - var parsedlist = (from n in backend.List() - let p = Volumes.VolumeBase.ParseFilename(n) - where p != null && p.FileType == RemoteVolumeType.Files - orderby p.Time descending - select p).ToArray(); - + + var parsedlist = (await backendManager.ListAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + .Select(n => Volumes.VolumeBase.ParseFilename(n)) + .Where(p => p != null && p.FileType == RemoteVolumeType.Files) + .OrderByDescending(p => p.Time) + .ToArray(); + var numberedList = parsedlist.Zip(Enumerable.Range(0, parsedlist.Length), (a, b) => new Tuple(b, a.Time, a)).ToList(); if (numberedList.Count < 2) throw new UserInformationException(string.Format("Need at least two backups to show differences, database contains {0} backups", numberedList.Count), "NeedTwoBackupsToStartDiff"); Volumes.IParsedVolume baseFile; Volumes.IParsedVolume compareFile; - + var bt = SelectTime(baseVersion, numberedList, out baseVersionIndex, out baseVersionTime, out baseFile); numberedList.Remove(bt); SelectTime(compareVersion, numberedList, out compareVersionIndex, out compareVersionTime, out compareFile); - - Func conv = (x) => { + + Func conv = (x) => + { switch (x) { case FilelistEntryType.File: - return Library.Interface.ListChangesElementType.File; + return ListChangesElementType.File; case FilelistEntryType.Folder: - return Library.Interface.ListChangesElementType.Folder; + return ListChangesElementType.Folder; case FilelistEntryType.Symlink: - return Library.Interface.ListChangesElementType.Symlink; + return ListChangesElementType.Symlink; default: - return (Library.Interface.ListChangesElementType)(-1); + return (ListChangesElementType)(-1); } }; - - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; - - using(var tmpfile = backend.Get(baseFile.File.Name, baseFile.File.Size, null)) - using(var rd = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(baseFile.File.Name), tmpfile, m_options)) - foreach(var f in rd.Files) - if (Library.Utility.FilterExpression.Matches(filter, f.Path)) + + using (var tmpfile = await backendManager.GetAsync(baseFile.File.Name, null, baseFile.File.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + using (var rd = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(baseFile.File.Name), tmpfile, m_options)) + foreach (var f in rd.Files) + if (FilterExpression.Matches(filter, f.Path)) storageKeeper.AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), false); - - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; - - using(var tmpfile = backend.Get(compareFile.File.Name, compareFile.File.Size, null)) - using(var rd = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(compareFile.File.Name), tmpfile, m_options)) - foreach(var f in rd.Files) - if (Library.Utility.FilterExpression.Matches(filter, f.Path)) + + using (var tmpfile = await backendManager.GetAsync(compareFile.File.Name, null, compareFile.File.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + using (var rd = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(compareFile.File.Name), tmpfile, m_options)) + foreach (var f in rd.Files) + if (FilterExpression.Matches(filter, f.Path)) storageKeeper.AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), true); } - + var changes = storageKeeper.CreateChangeCountReport(); var sizes = storageKeeper.CreateChangeSizeReport(); @@ -175,8 +174,8 @@ namespace Duplicati.Library.Main.Operation if (callback != null) callback(m_result, lst); - return; - } + return; + } } } } diff --git a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs index 8902ee70f..988bade52 100644 --- a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,78 +20,75 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Linq; using System.Collections.Generic; using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; +using System.Threading; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { internal class ListControlFilesHandler { private readonly Options m_options; - private readonly string m_backendurl; private readonly ListResults m_result; - - public ListControlFilesHandler(string backendurl, Options options, ListResults result) + + public ListControlFilesHandler(Options options, ListResults result) { m_options = options; - m_backendurl = backendurl; m_result = result; } - public void Run(IEnumerable filterstrings = null, Library.Utility.IFilter compositefilter = null) + public async Task RunAsync(IBackendManager backendManager, IEnumerable filterstrings, IFilter compositefilter) { - using (var tmpdb = new Library.Utility.TempFile()) - using (var db = new Database.LocalDatabase(System.IO.File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "ListControlFiles", true)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) + var cancellationToken = CancellationToken.None; + using (var tmpdb = new TempFile()) + using (var db = new LocalDatabase(System.IO.File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "ListControlFiles", true)) { - m_result.SetDatabase(db); - - var filter = Library.Utility.JoinedFilterExpression.Join(new Library.Utility.FilterExpression(filterstrings), compositefilter); - + var filter = JoinedFilterExpression.Join(new Library.Utility.FilterExpression(filterstrings), compositefilter); + try { - var filteredList = ListFilesHandler.ParseAndFilterFilesets(backend.List(), m_options); + var filteredList = ListFilesHandler.ParseAndFilterFilesets(await backendManager.ListAsync(cancellationToken).ConfigureAwait(false), m_options); if (filteredList.Count == 0) throw new Exception("No filesets found on remote target"); - + Exception lastEx = new Exception("No suitable files found on remote target"); - - foreach(var fileversion in filteredList) + + foreach (var fileversion in filteredList) try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; - + var file = fileversion.Value.File; var entry = db.GetRemoteVolume(file.Name); - + var files = new List(); - using (var tmpfile = backend.Get(file.Name, entry.Size < 0 ? file.Size : entry.Size, entry.Hash)) + using (var tmpfile = await backendManager.GetAsync(file.Name, entry.Hash, entry.Size < 0 ? file.Size : entry.Size, cancellationToken).ConfigureAwait(false)) using (var tmp = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(file.Name), tmpfile, m_options)) foreach (var cf in tmp.ControlFiles) if (Library.Utility.FilterExpression.Matches(filter, cf.Key)) files.Add(new ListResultFile(cf.Key, null)); - + m_result.SetResult(new Library.Interface.IListResultFileset[] { new ListResultFileset(fileversion.Key, BackupType.PARTIAL_BACKUP, fileversion.Value.Time, -1, -1) }, files); lastEx = null; break; } - catch(Exception ex) + catch (Exception ex) when (!ex.IsAbortException()) { lastEx = ex; - if (ex is System.Threading.ThreadAbortException) - throw; } - + if (lastEx != null) throw lastEx; } finally { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); } } - } } + } + } } diff --git a/Duplicati/Library/Main/Operation/ListFilesHandler.cs b/Duplicati/Library/Main/Operation/ListFilesHandler.cs index 79369b0c4..09ca9a157 100644 --- a/Duplicati/Library/Main/Operation/ListFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesHandler.cs @@ -1,27 +1,27 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Volumes; @@ -36,28 +36,26 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private readonly string m_backendurl; private readonly Options m_options; private readonly ListResults m_result; - public ListFilesHandler(string backend, Options options, ListResults result) + public ListFilesHandler(Options options, ListResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public void Run(IEnumerable filterstrings = null, Library.Utility.IFilter compositefilter = null) + public async Task RunAsync(IBackendManager backendManager, IEnumerable filterstrings, IFilter compositefilter) { - var parsedfilter = new Library.Utility.FilterExpression(filterstrings); - var filter = Library.Utility.JoinedFilterExpression.Join(parsedfilter, compositefilter); - var simpleList = !((filter is FilterExpression expression && expression.Type == Library.Utility.FilterType.Simple) || m_options.AllVersions); + var cancellationToken = m_result.TaskControl.ProgressToken; + var parsedfilter = new FilterExpression(filterstrings); + var filter = JoinedFilterExpression.Join(parsedfilter, compositefilter); + var simpleList = !((filter is FilterExpression expression && expression.Type == FilterType.Simple) || m_options.AllVersions); //Use a speedy local query if (!m_options.NoLocalDb && System.IO.File.Exists(m_options.Dbpath)) using (var db = new Database.LocalListDatabase(m_options.Dbpath)) { - m_result.SetDatabase(db); using (var filesets = db.SelectFileSets(m_options.Time, m_options.Version)) { if (!filter.Empty) @@ -120,17 +118,14 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException("Listing prefixes is not supported without a local database, consider using the \"repair\" option to rebuild the database.", "PrefixListingRequiresLocalDatabase"); // Otherwise, grab info from remote location - using (var tmpdb = new Library.Utility.TempFile()) - using (var db = new Database.LocalDatabase(tmpdb, "List", true)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) + using (var tmpdb = new TempFile()) + using (var db = new LocalDatabase(tmpdb, "List", true)) { - m_result.SetDatabase(db); - - var filteredList = ParseAndFilterFilesets(backend.List(), m_options); + var filteredList = ParseAndFilterFilesets(await backendManager.ListAsync(cancellationToken).ConfigureAwait(false), m_options); if (filteredList.Count == 0) throw new UserInformationException("No filesets found on remote target", "EmptyRemoteFolder"); - var numberSeq = CreateResultSequence(filteredList, backend, m_options); + var numberSeq = await CreateResultSequence(filteredList, backendManager, m_options, cancellationToken); if (filter.Empty) { m_result.SetResult(numberSeq, null); @@ -142,11 +137,11 @@ namespace Duplicati.Library.Main.Operation filteredList.RemoveAt(0); Dictionary> res; - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; - using (var tmpfile = backend.Get(firstEntry.File.Name, firstEntry.File.Size, null)) - using (var rd = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(firstEntry.File.Name), tmpfile, m_options)) + using (var tmpfile = await backendManager.GetAsync(firstEntry.File.Name, null, firstEntry.File.Size, cancellationToken).ConfigureAwait(false)) + using (var rd = new FilesetVolumeReader(RestoreHandler.GetCompressionModule(firstEntry.File.Name), tmpfile, m_options)) if (simpleList) { m_result.SetResult( @@ -177,11 +172,14 @@ namespace Duplicati.Library.Main.Operation } long flindex = 1; - foreach (var flentry in filteredList) - using (var tmpfile = backend.Get(flentry.Value.File.Name, flentry.Value.File == null ? -1 : flentry.Value.File.Size, null)) - using (var rd = new Volumes.FilesetVolumeReader(flentry.Value.CompressionModule, tmpfile, m_options)) + var filteredListMap = filteredList.ToDictionary(x => x.Value.File.Name, x => x.Value); + await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(filteredList.Select(x => new RemoteVolumeMapper(x.Value)), cancellationToken).ConfigureAwait(false)) + { + var flentry = filteredListMap[name]; + using (tmpfile) + using (var rd = new FilesetVolumeReader(flentry.CompressionModule, tmpfile, m_options)) { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) return; foreach (var p in from n in rd.Files where Library.Utility.FilterExpression.Matches(filter, n.Path) select n) @@ -203,6 +201,7 @@ namespace Duplicati.Library.Main.Operation flindex++; } + } m_result.SetResult( numberSeq, @@ -224,22 +223,30 @@ namespace Duplicati.Library.Main.Operation return filelistFilter(parsedlist).ToList(); } - private static IEnumerable CreateResultSequence(IEnumerable> filteredList, BackendManager backendManager, Options options) + private class RemoteVolumeMapper(IParsedVolume Volume) : IRemoteVolume { - List list = new List(); - foreach (KeyValuePair entry in filteredList) + public string Name => Volume.File.Name; + public string Hash => null; + public long Size => Volume.File.Size; + } + + private static async Task> CreateResultSequence(IEnumerable> filteredList, IBackendManager backendManager, Options options, CancellationToken cancelToken) + { + var list = new List(); + var map = filteredList.ToDictionary(x => x.Value.File.Name, x => x); + await foreach (var (file, hash, size, name) in backendManager.GetFilesOverlappedAsync(filteredList.Select(x => new RemoteVolumeMapper(x.Value)), cancelToken).ConfigureAwait(false)) { - AsyncDownloader downloader = new AsyncDownloader(new IRemoteVolume[] {new RemoteVolume(entry.Value.File)}, backendManager); - foreach (IAsyncDownloadedFile file in downloader) + // We must obtain the partial/full status from the fileset file in the dlist files. + // Without this, the restore dialog will show all versions as full, or all versions + // as partial. While the dlist files are already downloaded elsewhere, doing so again + // here is the most direct way to obtain the partial/full status without a major + // refactoring. Since restoring directly from the backend files should be a relatively + // rare event, we can work on improving the performance later. + using (file) { - // We must obtain the partial/full status from the fileset file in the dlist files. - // Without this, the restore dialog will show all versions as full, or all versions - // as partial. While the dlist files are already downloaded elsewhere, doing so again - // here is the most direct way to obtain the partial/full status without a major - // refactoring. Since restoring directly from the backend files should be a relatively - // rare event, we can work on improving the performance later. - VolumeBase.FilesetData filesetData = VolumeReaderBase.GetFilesetData(entry.Value.CompressionModule, file.TempFile, options); - list.Add(new ListResultFileset(entry.Key, filesetData.IsFullBackup ? BackupType.FULL_BACKUP : BackupType.PARTIAL_BACKUP, entry.Value.Time.ToLocalTime(), -1, -1)); + var ent = map[name]; + var filesetData = VolumeReaderBase.GetFilesetData(ent.Value.CompressionModule, file, options); + list.Add(new ListResultFileset(ent.Key, filesetData.IsFullBackup ? BackupType.FULL_BACKUP : BackupType.PARTIAL_BACKUP, ent.Value.Time.ToLocalTime(), -1, -1)); } } diff --git a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs index a21153fdb..f895aad89 100644 --- a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,7 +22,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Interface; +using Duplicati.Library.Main.Database; namespace Duplicati.Library.Main.Operation { @@ -32,18 +34,16 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(PurgeBrokenFilesHandler)); - protected readonly string m_backendurl; protected readonly Options m_options; protected readonly PurgeBrokenFilesResults m_result; - public PurgeBrokenFilesHandler(string backend, Options options, PurgeBrokenFilesResults result) + public PurgeBrokenFilesHandler(Options options, PurgeBrokenFilesResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public void Run(Library.Utility.IFilter filter) + public async Task RunAsync(IBackendManager backendManager, Library.Utility.IFilter filter) { if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); @@ -51,18 +51,16 @@ namespace Duplicati.Library.Main.Operation if (filter != null && !filter.Empty) throw new UserInformationException("Filters are not supported for this operation", "FiltersNotAllowedOnPurgeBrokenFiles"); - List missing = null; - - using (var db = new Database.LocalListBrokenFilesDatabase(m_options.Dbpath)) + using (var db = new LocalListBrokenFilesDatabase(m_options.Dbpath)) using (var tr = db.BeginTransaction()) { if (db.PartiallyRecreated) throw new UserInformationException("The command does not work on partially recreated databases", "CannotPurgeOnPartialDatabase"); - var sets = ListBrokenFilesHandler.GetBrokenFilesetsFromRemote(m_backendurl, m_result, db, tr, m_options, out missing); + (var sets, var missing) = await ListBrokenFilesHandler.GetBrokenFilesetsFromRemote(backendManager, m_result, db, tr, m_options).ConfigureAwait(false); if (sets == null) return; - + if (sets.Length == 0) { if (missing == null) @@ -73,7 +71,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenSetsButMissingRemoteFiles", string.Format("Found no broken filesets, but {0} missing remote files. Purging from database.", missing.Count)); } else - { + { Logging.Log.WriteInformationMessage(LOGTAG, "FoundBrokenFilesets", "Found {0} broken filesets with {1} affected files, purging files", sets.Length, sets.Sum(x => x.Item3)); var pgoffset = 0.0f; @@ -107,35 +105,18 @@ namespace Duplicati.Library.Main.Operation m_result.DeleteResults = new DeleteResults(m_result); using (var rmdb = new Database.LocalDeleteDatabase(db)) + using (var deltr = new ReusableTransaction(rmdb)) { - var deltr = rmdb.BeginTransaction(); - try - { - var opts = new Options(new Dictionary(m_options.RawOptions)); - opts.RawOptions["version"] = string.Join(",", fully_emptied.Select(x => x.Version.ToString())); - opts.RawOptions.Remove("time"); - opts.RawOptions["no-auto-compact"] = "true"; + var opts = new Options(new Dictionary(m_options.RawOptions)); + opts.RawOptions["version"] = string.Join(",", fully_emptied.Select(x => x.Version.ToString())); + opts.RawOptions.Remove("time"); + opts.RawOptions["no-auto-compact"] = "true"; - new DeleteHandler(m_backendurl, opts, (DeleteResults)m_result.DeleteResults) - .DoRun(rmdb, ref deltr, true, false, null); - - if (!m_options.Dryrun) - { - using (new Logging.Timer(LOGTAG, "CommitDelete", "CommitDelete")) - deltr.Commit(); - - rmdb.WriteResults(); - } - else - deltr.Rollback(); - } - finally - { - if (deltr != null) - try { deltr.Rollback(); } - catch { } - } + await new DeleteHandler(opts, (DeleteResults)m_result.DeleteResults) + .DoRunAsync(rmdb, deltr, true, false, backendManager).ConfigureAwait(false); + if (!m_options.Dryrun) + deltr.Commit("CommitDelete", restart: false); } pgoffset += (pgspan * fully_emptied.Length); @@ -163,12 +144,12 @@ namespace Duplicati.Library.Main.Operation opts.RawOptions.Remove("time"); opts.RawOptions["no-auto-compact"] = "true"; - new PurgeFilesHandler(m_backendurl, opts, (PurgeFilesResults)m_result.PurgeResults).Run(pgdb, pgoffset, pgspan, (cmd, filesetid, tablename) => + await new PurgeFilesHandler(opts, (PurgeFilesResults)m_result.PurgeResults).RunAsync(backendManager, pgdb, pgoffset, pgspan, (cmd, filesetid, tablename) => { if (filesetid != bs.FilesetID) throw new Exception(string.Format("Unexpected filesetid: {0}, expected {1}", filesetid, bs.FilesetID)); db.InsertBrokenFileIDsIntoTable(filesetid, tablename, "FileID", cmd.Transaction); - }); + }).ConfigureAwait(false); } pgoffset += pgspan; @@ -180,12 +161,16 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0.95f); if (!m_options.Dryrun && db.RepairInProgress) - { + { Logging.Log.WriteInformationMessage(LOGTAG, "ValidatingDatabase", "Database was previously marked as in-progress, checking if it is valid after purging files"); db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, null); Logging.Log.WriteInformationMessage(LOGTAG, "UpdatingDatabase", "Purge completed, and consistency checks completed, marking database as complete"); db.RepairInProgress = false; } + else + { + db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, null); + } m_result.OperationProgressUpdater.UpdateProgress(1.0f); diff --git a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs index c6d0f3fc2..878568246 100644 --- a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,10 +20,11 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; -using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation { @@ -33,18 +34,16 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - protected readonly string m_backendurl; protected readonly Options m_options; protected readonly PurgeFilesResults m_result; - public PurgeFilesHandler(string backend, Options options, PurgeFilesResults result) + public PurgeFilesHandler(Options options, PurgeFilesResults result) { - m_backendurl = backend; m_options = options; m_result = result; } - public void Run(Library.Utility.IFilter filter) + public async Task RunAsync(IBackendManager backendManager, IFilter filter) { if (filter == null || filter.Empty) throw new UserInformationException("Cannot purge with an empty filter, as that would cause all files to be removed.\nTo remove an entire backup set, use the \"delete\" command.", "EmptyFilterPurgeNotAllowed"); @@ -53,214 +52,209 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); using (var db = new Database.LocalPurgeDatabase(m_options.Dbpath)) - DoRun(db, filter, null, 0, 1); + { + await DoRunAsync(backendManager, db, filter, null, 0, 1).ConfigureAwait(false); + db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, null); + } } - public void Run(Database.LocalPurgeDatabase db, float pgoffset, float pgspan, Action filtercommand) - { - DoRun(db, null, filtercommand, pgoffset, pgspan); - } + public Task RunAsync(IBackendManager backendManager, Database.LocalPurgeDatabase db, float pgoffset, float pgspan, Action filtercommand) + => DoRunAsync(backendManager, db, null, filtercommand, pgoffset, pgspan); - private void DoRun(Database.LocalPurgeDatabase db, Library.Utility.IFilter filter, Action filtercommand, float pgoffset, float pgspan) + private async Task DoRunAsync(IBackendManager backendManager, Database.LocalPurgeDatabase db, IFilter filter, Action filtercommand, float pgoffset, float pgspan) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Begin); Logging.Log.WriteInformationMessage(LOGTAG, "StartingPurge", "Starting purge operation"); var doCompactStep = !m_options.NoAutoCompact && filtercommand == null; - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) + if (db.PartiallyRecreated) + throw new UserInformationException("The purge command does not work on partially recreated databases", "PurgeNotAllowedOnPartialDatabase"); + + if (db.RepairInProgress && filtercommand == null) + throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"), "PurgeNotAllowedOnIncompleteDatabase"); + + var versions = db.GetFilesetIDs(m_options.Time, m_options.Version).OrderByDescending(x => x).ToArray(); + if (versions.Length <= 0) + throw new UserInformationException("No filesets matched the supplied time or versions", "NoFilesetFoundForTimeOrVersion"); + + var orphans = db.CountOrphanFiles(null); + if (orphans != 0) + throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans), "CannotPurgeWithOrphans"); + + Utility.UpdateOptionsFromDb(db, m_options); + Utility.VerifyOptionsAndUpdateDatabase(db, m_options); + + if (filtercommand == null) { - if (db.PartiallyRecreated) - throw new UserInformationException("The purge command does not work on partially recreated databases", "PurgeNotAllowedOnPartialDatabase"); + db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, false, null); - if (db.RepairInProgress && filtercommand == null) - throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"), "PurgeNotAllowedOnIncompleteDatabase"); + if (m_options.NoBackendverification) + await FilelistProcessor.VerifyLocalList(backendManager, db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + else + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, null, null, logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict).ConfigureAwait(false); + } - var versions = db.GetFilesetIDs(m_options.Time, m_options.Version).OrderByDescending(x => x).ToArray(); - if (versions.Length <= 0) - throw new UserInformationException("No filesets matched the supplied time or versions", "NoFilesetFoundForTimeOrVersion"); + var filesets = db.FilesetTimes.OrderByDescending(x => x.Value).ToArray(); - var orphans = db.CountOrphanFiles(null); - if (orphans != 0) - throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans), "CannotPurgeWithOrphans"); + var versionprogress = ((doCompactStep ? 0.75f : 1.0f) / versions.Length) * pgspan; + var currentprogress = pgoffset; + var progress = 0; - Utility.UpdateOptionsFromDb(db, m_options); - Utility.VerifyOptionsAndUpdateDatabase(db, m_options); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Process); + m_result.OperationProgressUpdater.UpdateProgress(currentprogress); - if (filtercommand == null) + // If we crash now, it is possible that the remote storage contains partial files + if (!m_options.Dryrun) + db.TerminatedWithActiveUploads = true; + + // Reverse makes sure we re-write the old versions first + foreach (var versionid in versions.Reverse()) + { + progress++; + Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingFilelistVolumes", "Processing filelist volume {0} of {1}", progress, versions.Length); + + using (var tr = db.BeginTransaction()) { - db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, false, null); - - if (m_options.NoBackendverification) - FilelistProcessor.VerifyLocalList(backend, db); - else - FilelistProcessor.VerifyRemoteList(backend, m_options, db, m_result.BackendWriter, null); - } - - var filesets = db.FilesetTimes.OrderByDescending(x => x.Value).ToArray(); - - var versionprogress = ((doCompactStep ? 0.75f : 1.0f) / versions.Length) * pgspan; - var currentprogress = pgoffset; - var progress = 0; - - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Process); - m_result.OperationProgressUpdater.UpdateProgress(currentprogress); - - // Reverse makes sure we re-write the old versions first - foreach (var versionid in versions.Reverse()) - { - progress++; - Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingFilelistVolumes", "Processing filelist volume {0} of {1}", progress, versions.Length); - - using (var tr = db.BeginTransaction()) - { - var ix = -1; - for (var i = 0; i < filesets.Length; i++) - if (filesets[i].Key == versionid) - { - ix = i; - break; - } - - if (ix < 0) - throw new InvalidProgramException(string.Format("Fileset was reported with id {0}, but could not be found?", versionid)); - - var secs = 0; - while (secs < 60) + var ix = -1; + for (var i = 0; i < filesets.Length; i++) + if (filesets[i].Key == versionid) { - secs++; - var tfn = Volumes.VolumeBase.GenerateFilename(RemoteVolumeType.Files, m_options, null, filesets[ix].Value.AddSeconds(secs)); - if (db.GetRemoteVolumeID(tfn, tr) < 0) - break; + ix = i; + break; } - var tsOriginal = filesets[ix].Value; - var ts = tsOriginal.AddSeconds(secs); + if (ix < 0) + throw new InvalidProgramException(string.Format("Fileset was reported with id {0}, but could not be found?", versionid)); - var prevfilename = db.GetRemoteVolumeNameForFileset(filesets[ix].Key, tr); + var secs = 0; + while (secs < 60) + { + secs++; + var tfn = Volumes.VolumeBase.GenerateFilename(RemoteVolumeType.Files, m_options, null, filesets[ix].Value.AddSeconds(secs)); + if (db.GetRemoteVolumeID(tfn, tr) < 0) + break; + } - if (secs >= 60) - throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is more than 60 seconds away", prevfilename, ts)); + var tsOriginal = filesets[ix].Value; + var ts = tsOriginal.AddSeconds(secs); - if (ix != 0 && filesets[ix - 1].Value <= ts) - throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is larger than the next timestamp {2}", prevfilename, ts, filesets[ix - 1].Value)); + var prevfilename = db.GetRemoteVolumeNameForFileset(filesets[ix].Key, tr); - using (var tempset = db.CreateTemporaryFileset(versionid, tr)) + if (secs >= 60) + throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is more than 60 seconds away", prevfilename, ts)); + + if (ix != 0 && filesets[ix - 1].Value <= ts) + throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is larger than the next timestamp {2}", prevfilename, ts, filesets[ix - 1].Value)); + + using (var tempset = db.CreateTemporaryFileset(versionid, tr)) + { + if (filtercommand == null) + tempset.ApplyFilter(filter); + else + tempset.ApplyFilter(filtercommand); + + if (tempset.RemovedFileCount == 0) { - if (filtercommand == null) - tempset.ApplyFilter(filter); - else - tempset.ApplyFilter(filtercommand); + Logging.Log.WriteInformationMessage(LOGTAG, "NotWritingNewFileset", "Not writing a new fileset for {0} as it was not changed", prevfilename); + currentprogress += versionprogress; + tr.Rollback(); + continue; + } + else + { + using (var tf = new Library.Utility.TempFile()) + using (var vol = new Volumes.FilesetVolumeWriter(m_options, ts)) + { + var isOriginalFilesetFullBackup = db.IsFilesetFullBackup(tsOriginal); + var newids = tempset.ConvertToPermanentFileset(vol.RemoteFilename, ts, isOriginalFilesetFullBackup); + vol.VolumeID = newids.Item1; + vol.CreateFilesetFile(isOriginalFilesetFullBackup); - if (tempset.RemovedFileCount == 0) - { - Logging.Log.WriteInformationMessage(LOGTAG, "NotWritingNewFileset", "Not writing a new fileset for {0} as it was not changed", prevfilename); - currentprogress += versionprogress; - tr.Rollback(); - continue; - } - else - { - using (var tf = new Library.Utility.TempFile()) - using (var vol = new Volumes.FilesetVolumeWriter(m_options, ts)) + Logging.Log.WriteInformationMessage(LOGTAG, "ReplacingFileset", "Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize)); + + db.WriteFileset(vol, newids.Item2, tr); + + m_result.RemovedFileSize += tempset.RemovedFileSize; + m_result.RemovedFileCount += tempset.RemovedFileCount; + m_result.RewrittenFileLists++; + + currentprogress += (versionprogress / 2); + m_result.OperationProgressUpdater.UpdateProgress(currentprogress); + + if (m_options.Dryrun || m_options.FullResult) { - var isOriginalFilesetFullBackup = db.IsFilesetFullBackup(tsOriginal); - var newids = tempset.ConvertToPermanentFileset(vol.RemoteFilename, ts, isOriginalFilesetFullBackup); - vol.VolumeID = newids.Item1; - vol.CreateFilesetFile(isOriginalFilesetFullBackup); - - Logging.Log.WriteInformationMessage(LOGTAG, "ReplacingFileset", "Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize)); - - db.WriteFileset(vol, newids.Item2, tr); - - m_result.RemovedFileSize += tempset.RemovedFileSize; - m_result.RemovedFileCount += tempset.RemovedFileCount; - m_result.RewrittenFileLists++; - - currentprogress += (versionprogress / 2); - m_result.OperationProgressUpdater.UpdateProgress(currentprogress); - - if (m_options.Dryrun || m_options.FullResult) + foreach (var fe in tempset.ListAllDeletedFiles()) { - foreach (var fe in tempset.ListAllDeletedFiles()) - { - var msg = string.Format(" Purging file {0} ({1})", fe.Key, Library.Utility.Utility.FormatSizeString(fe.Value)); + var msg = string.Format(" Purging file {0} ({1})", fe.Key, Library.Utility.Utility.FormatSizeString(fe.Value)); - Logging.Log.WriteProfilingMessage(LOGTAG, "PurgeFile", msg); - Logging.Log.WriteVerboseMessage(LOGTAG, "PurgeFile", msg); - - if (m_options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPurgeFile", msg); - } + Logging.Log.WriteProfilingMessage(LOGTAG, "PurgeFile", msg); + Logging.Log.WriteVerboseMessage(LOGTAG, "PurgeFile", msg); if (m_options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldWriteRemoteFiles", "Would write files to remote storage"); - - Logging.Log.WriteVerboseMessage(LOGTAG, "WritingRemoteFiles", "Writing files to remote storage"); + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPurgeFile", msg); } if (m_options.Dryrun) - { - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadAndDelete", "Would upload file {0} ({1}) and delete file {2}, removing {3} files", vol.RemoteFilename, Library.Utility.Utility.FormatSizeString(vol.Filesize), prevfilename, tempset.RemovedFileCount); - tr.Rollback(); - } - else - { - var lst = db.DropFilesetsFromTable(new[] { filesets[ix].Value }, tr).ToArray(); - foreach (var f in lst) - db.UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, tr); + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldWriteRemoteFiles", "Would write files to remote storage"); - tr.Commit(); - backend.Put(vol, synchronous: true); - backend.Delete(prevfilename, -1, true); - backend.FlushDbMessages(); - } + Logging.Log.WriteVerboseMessage(LOGTAG, "WritingRemoteFiles", "Writing files to remote storage"); + } + + if (m_options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadAndDelete", "Would upload file {0} ({1}) and delete file {2}, removing {3} files", vol.RemoteFilename, Library.Utility.Utility.FormatSizeString(vol.Filesize), prevfilename, tempset.RemovedFileCount); + tr.Rollback(); + } + else + { + var lst = db.DropFilesetsFromTable(new[] { filesets[ix].Value }, tr).ToArray(); + foreach (var f in lst) + db.UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, tr); + + tr.Commit(); + await backendManager.PutAsync(vol, null, null, true, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await backendManager.DeleteAsync(prevfilename, -1, true, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await backendManager.WaitForEmptyAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } } } } - - currentprogress += (versionprogress / 2); - m_result.OperationProgressUpdater.UpdateProgress(currentprogress); } + currentprogress += (versionprogress / 2); + m_result.OperationProgressUpdater.UpdateProgress(currentprogress); + } - if (doCompactStep) + if (!m_options.Dryrun) + db.TerminatedWithActiveUploads = false; + + if (doCompactStep) + { + if (m_result.RewrittenFileLists == 0) { - if (m_result.RewrittenFileLists == 0) + Logging.Log.WriteInformationMessage(LOGTAG, "SkippingCompacting", "Skipping compacting as no new volumes were written"); + } + else + { + m_result.OperationProgressUpdater.UpdateProgress(pgoffset + (0.75f * pgspan)); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Compact); + m_result.CompactResults = new CompactResults(m_result); + using (var cdb = new Database.LocalDeleteDatabase(db)) + using (var ctr = new Database.ReusableTransaction(cdb)) { - Logging.Log.WriteInformationMessage(LOGTAG, "SkippingCompacting", "Skipping compacting as no new volumes were written"); - } - else - { - m_result.OperationProgressUpdater.UpdateProgress(pgoffset + (0.75f * pgspan)); - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Compact); - m_result.CompactResults = new CompactResults(m_result); - using (var cdb = new Database.LocalDeleteDatabase(db)) - { - var tr = cdb.BeginTransaction(); - try - { - new CompactHandler(backend.BackendUrl, m_options, (CompactResults)m_result.CompactResults).DoCompact(cdb, true, ref tr, backend); - } - catch - { - try { tr.Rollback(); } - catch { } - } - finally - { - try { tr.Commit(); } - catch { } - } - } - } + await new CompactHandler(m_options, (CompactResults)m_result.CompactResults) + .DoCompactAsync(cdb, true, ctr, backendManager) + .ConfigureAwait(false); - m_result.OperationProgressUpdater.UpdateProgress(pgoffset + pgspan); - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Complete); + ctr.Commit(restart: false); + } } - backend.WaitForComplete(db, null); + m_result.OperationProgressUpdater.UpdateProgress(pgoffset + pgspan); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.PurgeFiles_Complete); + + await backendManager.WaitForEmptyAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } } } diff --git a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs index 1e1e72370..2a56b7a5b 100644 --- a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs +++ b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,7 +20,11 @@ // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.Data; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Volumes; @@ -35,17 +39,15 @@ namespace Duplicati.Library.Main.Operation /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private readonly string m_backendurl; private readonly Options m_options; private readonly RecreateDatabaseResults m_result; public delegate IEnumerable> NumberedFilterFilelistDelegate(IEnumerable filelist); public delegate void BlockVolumePostProcessor(string volumename, BlockVolumeReader reader); - public RecreateDatabaseHandler(string backendurl, Options options, RecreateDatabaseResults result) + public RecreateDatabaseHandler(Options options, RecreateDatabaseResults result) { m_options = options; - m_backendurl = backendurl; m_result = result; } @@ -53,19 +55,21 @@ namespace Duplicati.Library.Main.Operation /// Run the recreate procedure /// /// Path to the database that will be created + /// The backend manager to use for downloading files /// A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist /// Filters the files in a filelist to prevent downloading unwanted data /// A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists - public void Run(string path, Library.Utility.IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null) + public async Task RunAsync(string path, IBackendManager backendManager, IFilter filter, NumberedFilterFilelistDelegate filelistfilter, BlockVolumePostProcessor blockprocessor) { - if (System.IO.File.Exists(path)) + if (File.Exists(path)) throw new UserInformationException(string.Format("Cannot recreate database because file already exists: {0}", path), "RecreateTargetDatabaseExists"); using (var db = new LocalDatabase(path, "Recreate", true)) { - m_result.SetDatabase(db); - DoRun(db, false, filter, filelistfilter, blockprocessor); - db.WriteResults(); + await DoRunAsync(backendManager, db, false, filter, filelistfilter, blockprocessor).ConfigureAwait(false); + + // Ensure database is consistent after the recreate + db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, null); } } @@ -75,15 +79,13 @@ namespace Duplicati.Library.Main.Operation /// A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist /// Filters the files in a filelist to prevent downloading unwanted data /// A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists - public void RunUpdate(Library.Utility.IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null) + public async Task RunUpdateAsync(IBackendManager backendManager, Library.Utility.IFilter filter, NumberedFilterFilelistDelegate filelistfilter, BlockVolumePostProcessor blockprocessor) { if (!m_options.RepairOnlyPaths) throw new UserInformationException(string.Format("Can only update with paths, try setting --{0}", "repair-only-paths"), "RepairUpdateRequiresPathsOnly"); using (var db = new LocalDatabase(m_options.Dbpath, "Recreate", true)) { - m_result.SetDatabase(db); - if (db.FindMatchingFilesets(m_options.Time, m_options.Version).Any()) throw new UserInformationException("The version(s) being updated to, already exists", "UpdateVersionAlreadyExists"); @@ -97,8 +99,7 @@ namespace Duplicati.Library.Main.Operation if (preexistingOptionsInDatabase) Utility.VerifyOptionsAndUpdateDatabase(db, m_options, null); - DoRun(db, true, filter, filelistfilter, blockprocessor); - db.WriteResults(); + await DoRunAsync(backendManager, db, true, filter, filelistfilter, blockprocessor).ConfigureAwait(false); } } @@ -110,13 +111,12 @@ namespace Duplicati.Library.Main.Operation /// A filter that can be used to disregard certain remote files, intended to be used to select a certain filelist /// Filters the files in a filelist to prevent downloading unwanted data /// A callback hook that can be used to work with downloaded block volumes, intended to be use to recover data blocks while processing blocklists - internal void DoRun(LocalDatabase dbparent, bool updating, Library.Utility.IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null) + internal async Task DoRunAsync(IBackendManager backendManager, LocalDatabase dbparent, bool updating, IFilter filter = null, NumberedFilterFilelistDelegate filelistfilter = null, BlockVolumePostProcessor blockprocessor = null) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Recreate_Running); //We build a local database in steps. using (var restoredb = new LocalRecreateDatabase(dbparent, m_options)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, restoredb)) { restoredb.RepairInProgress = true; var expRecreateDb = false; // experimental recreate db code flag @@ -127,7 +127,7 @@ namespace Duplicati.Library.Main.Operation expRecreateDb = true; } - var rawlist = backend.List(); + var rawlist = await backendManager.ListAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); //First step is to examine the remote storage to see what // kind of data we can find @@ -142,7 +142,7 @@ namespace Duplicati.Library.Main.Operation if (remotefiles.Length == 0) { - if (rawlist.Count == 0) + if (rawlist.Count() == 0) throw new UserInformationException("No files were found at the remote location, perhaps the target url is incorrect?", "EmptyRemoteLocation"); else { @@ -155,11 +155,11 @@ namespace Duplicati.Library.Main.Operation var types = tmp.Distinct().ToArray(); if (tmp.Length == 0) - throw new UserInformationException(string.Format("Found {0} files at the remote storage, but none that could be parsed", rawlist.Count), "EmptyRemoteLocation"); + throw new UserInformationException(string.Format("Found {0} files at the remote storage, but none that could be parsed", rawlist.Count()), "EmptyRemoteLocation"); else if (types.Length == 1) throw new UserInformationException(string.Format("Found {0} parse-able files with the prefix {1}, did you forget to set the backup prefix?", tmp.Length, types[0]), "EmptyRemoteLocationWithPrefix"); else - throw new UserInformationException(string.Format("Found {0} parse-able files (of {1} files) with different prefixes: {2}, did you forget to set the backup prefix?", tmp.Length, rawlist.Count, string.Join(", ", types)), "EmptyRemoteLocationWithPrefix"); + throw new UserInformationException(string.Format("Found {0} parse-able files (of {1} files) with different prefixes: {2}, did you forget to set the backup prefix?", tmp.Length, rawlist.Count(), string.Join(", ", types)), "EmptyRemoteLocationWithPrefix"); } } @@ -203,15 +203,14 @@ namespace Duplicati.Library.Main.Operation } var isFirstFilelist = true; - var blocksize = m_options.Blocksize; - var hashes_pr_block = blocksize / m_options.BlockhashSize; - - foreach (var entry in new AsyncDownloader(filelistWork, backend)) + await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(filelistWork, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + { + var entry = new RemoteVolume(name, hash, size); try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(restoredb, null); + await backendManager.WaitForEmptyAsync(restoredb, tr, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; return; } @@ -227,102 +226,36 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingFilelistVolumes", "Processing filelist volume {0} of {1}", progress, filelistWork.Count); } - using (var tmpfile = entry.TempFile) + using (tmpfile) { isFirstFilelist = false; - if (entry.Hash != null && entry.Size > 0) - restoredb.UpdateRemoteVolume(entry.Name, RemoteVolumeState.Verified, entry.Size, entry.Hash, tr); + if (!string.IsNullOrWhiteSpace(hash) && size > 0) + restoredb.UpdateRemoteVolume(entry.Name, RemoteVolumeState.Verified, size, hash, tr); var parsed = VolumeBase.ParseFilename(entry.Name); + using var stream = new FileStream(tmpfile, FileMode.Open, FileAccess.Read, FileShare.Read); + using var compressor = DynamicLoader.CompressionLoader.GetModule(parsed.CompressionModule, stream, ArchiveMode.Read, m_options.RawOptions); + if (compressor == null) + throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); + if (!hasUpdatedOptions) { - VolumeReaderBase.UpdateOptionsFromManifest(parsed.CompressionModule, tmpfile, m_options); + VolumeReaderBase.UpdateOptionsFromManifest(compressor, m_options); hasUpdatedOptions = true; - // Recompute the cached sizes - blocksize = m_options.Blocksize; - hashes_pr_block = blocksize / m_options.BlockhashSize; } // Create timestamped operations based on the file timestamp var filesetid = restoredb.CreateFileset(volumeIds[entry.Name], parsed.Time, tr); - // retrieve fileset data from dlist - var filesetData = VolumeReaderBase.GetFilesetData(parsed.CompressionModule, tmpfile, m_options); - - // update fileset using filesetData - restoredb.UpdateFullBackupStateInFileset(filesetid, filesetData.IsFullBackup); - - using (var filelistreader = new FilesetVolumeReader(parsed.CompressionModule, tmpfile, m_options)) - foreach (var fe in filelistreader.Files.Where(x => Library.Utility.FilterExpression.Matches(filter, x.Path))) - { - try - { - var expectedmetablocks = (fe.Metasize + blocksize - 1) / blocksize; - var expectedmetablocklisthashes = (expectedmetablocks + hashes_pr_block - 1) / hashes_pr_block; - if (expectedmetablocks <= 1) expectedmetablocklisthashes = 0; - - var metadataid = long.MinValue; - var split = Database.LocalDatabase.SplitIntoPrefixAndName(fe.Path); - var prefixid = restoredb.GetOrCreatePathPrefix(split.Key, tr); - - switch (fe.Type) - { - case FilelistEntryType.Folder: - metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, tr); - restoredb.AddDirectoryEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, tr); - break; - case FilelistEntryType.File: - var expectedblocks = (fe.Size + blocksize - 1) / blocksize; - var expectedblocklisthashes = (expectedblocks + hashes_pr_block - 1) / hashes_pr_block; - if (expectedblocks <= 1) expectedblocklisthashes = 0; - - var blocksetid = restoredb.AddBlockset(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes, tr); - metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, tr); - restoredb.AddFileEntry(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid, tr); - - if (fe.Size <= blocksize) - { - if (!string.IsNullOrWhiteSpace(fe.Blockhash)) - restoredb.AddSmallBlocksetLink(fe.Hash, fe.Blockhash, fe.Blocksize, tr); - else if (m_options.BlockHashAlgorithm == m_options.FileHashAlgorithm) - restoredb.AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size, tr); - else - Logging.Log.WriteWarningMessage(LOGTAG, "MissingBlockHash", null, "No block hash found for file: {0}", fe.Path); - } - - break; - case FilelistEntryType.Symlink: - metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, tr); - restoredb.AddSymlinkEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, tr); - break; - default: - Logging.Log.WriteWarningMessage(LOGTAG, "SkippingUnknownFileEntry", null, "Skipping file-entry with unknown type {0}: {1} ", fe.Type, fe.Path); - break; - } - - if (fe.Metasize <= blocksize && (fe.Type == FilelistEntryType.Folder || fe.Type == FilelistEntryType.File || fe.Type == FilelistEntryType.Symlink)) - { - if (!string.IsNullOrWhiteSpace(fe.Metablockhash)) - restoredb.AddSmallBlocksetLink(fe.Metahash, fe.Metablockhash, fe.Metasize, tr); - else if (m_options.BlockHashAlgorithm == m_options.FileHashAlgorithm) - restoredb.AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize, tr); - else - Logging.Log.WriteWarningMessage(LOGTAG, "MissingMetadataBlockHash", null, "No block hash found for file metadata: {0}", fe.Path); - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "FileEntryProcessingFailed", ex, "Failed to process file-entry: {0}", fe.Path); - } - } + RecreateFilesetFromRemoteList(restoredb, tr, compressor, filesetid, m_options, filter); } } catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "FileProcessingFailed", ex, "Failed to process file: {0}", entry.Name); - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortException()) { m_result.EndTime = DateTime.UtcNow; throw; @@ -338,6 +271,7 @@ namespace Duplicati.Library.Main.Operation throw; } + } //Make sure we write the config if it has been read from a manifest if (hasUpdatedOptions) @@ -368,12 +302,13 @@ namespace Duplicati.Library.Main.Operation var progress = 0; - foreach (var sf in new AsyncDownloader(indexfiles, backend)) + await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(indexfiles, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + { try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(restoredb, null); + await backendManager.WaitForEmptyAsync(restoredb, tr, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; return; } @@ -382,12 +317,12 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress((((float)progress / indexfiles.Count) * 0.5f) + 0.2f); Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingIndexlistVolumes", "Processing indexlist volume {0} of {1}", progress, indexfiles.Count); - using (var tmpfile = sf.TempFile) + using (tmpfile) { - if (sf.Hash != null && sf.Size > 0) - restoredb.UpdateRemoteVolume(sf.Name, RemoteVolumeState.Verified, sf.Size, sf.Hash, tr); + if (!string.IsNullOrWhiteSpace(hash) && size > 0) + restoredb.UpdateRemoteVolume(name, RemoteVolumeState.Verified, size, hash, tr); - using (var svr = new IndexVolumeReader(RestoreHandler.GetCompressionModule(sf.Name), tmpfile, m_options, hashsize)) + using (var svr = new IndexVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options, hashsize)) { foreach (var a in svr.Volumes) { @@ -405,7 +340,7 @@ namespace Duplicati.Library.Main.Operation var p = VolumeBase.ParseFilename(filename); if (p == null) throw new Exception(string.Format("Unable to parse filename: {0}", filename)); - Logging.Log.WriteErrorMessage(LOGTAG, "MissingFileDetected", null, "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file", filename, sf.Name); + Logging.Log.WriteWarningMessage(LOGTAG, "MissingFileDetected", null, "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file", filename, name); missing = true; volumeID = restoredb.RegisterRemoteVolume(filename, p.FileType, RemoteVolumeState.Temporary, tr); } @@ -416,7 +351,7 @@ namespace Duplicati.Library.Main.Operation restoredb.UpdateBlock(b.Key, b.Value, volumeID, tr, ref anyChange); restoredb.UpdateRemoteVolume(filename, missing ? RemoteVolumeState.Temporary : RemoteVolumeState.Verified, a.Length, a.Hash, tr); - restoredb.AddIndexBlockLink(restoredb.GetRemoteVolumeID(sf.Name), volumeID, tr); + restoredb.AddIndexBlockLink(restoredb.GetRemoteVolumeID(name), volumeID, tr); } //If there are blocklists in the index file, add them to the temp blocklist hashes table @@ -434,13 +369,13 @@ namespace Duplicati.Library.Main.Operation } catch (System.IO.InvalidDataException e) { - Logging.Log.WriteVerboseMessage(LOGTAG, "InvalidDataBlocklist", e, "Exception while processing blocklists in {0}", sf.Name); + Logging.Log.WriteVerboseMessage(LOGTAG, "InvalidDataBlocklist", e, "Exception while processing blocklists in {0}", name); ++wrongHashes; } } if (wrongHashes != 0) { - Logging.Log.WriteWarningMessage(LOGTAG, "WrongBlocklistHashes", null, "{0} had invalid blocklists which could not be used. Consider deleting this index file and run repair to recreate it.", sf.Name); + Logging.Log.WriteWarningMessage(LOGTAG, "WrongBlocklistHashes", null, "{0} had invalid blocklists which could not be used. Consider deleting this index file and run repair to recreate it.", name); } } } @@ -448,8 +383,8 @@ namespace Duplicati.Library.Main.Operation catch (Exception ex) { //Not fatal - Logging.Log.WriteErrorMessage(LOGTAG, "IndexFileProcessingFailed", ex, "Failed to process index file: {0}", sf.Name); - if (ex is System.Threading.ThreadAbortException) + Logging.Log.WriteErrorMessage(LOGTAG, "IndexFileProcessingFailed", ex, "Failed to process index file: {0}", name); + if (ex.IsAbortException()) { m_result.EndTime = DateTime.UtcNow; throw; @@ -458,6 +393,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.UnittestMode) throw; } + } using (new Logging.Timer(LOGTAG, "CommitRecreateDb", "CommitRecreatedDb")) tr.Commit(); @@ -466,9 +402,7 @@ namespace Duplicati.Library.Main.Operation // if we are lucky and pick the right ones } - // In some cases we have a stale reference from an index file to a deleted block file - if (!m_options.UnittestMode) - restoredb.CleanupMissingVolumes(); + restoredb.CleanupMissingVolumes(); // Update the real tables from the temp tables if (expRecreateDb) @@ -506,17 +440,17 @@ namespace Duplicati.Library.Main.Operation } var progress = 0; - foreach (var sf in new AsyncDownloader(lst, backend)) + await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(lst, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { try { - using (var tmpfile = sf.TempFile) - using (var rd = new BlockVolumeReader(RestoreHandler.GetCompressionModule(sf.Name), tmpfile, m_options)) + using (tmpfile) + using (var rd = new BlockVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options)) using (var tr = restoredb.BeginTransaction()) { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(restoredb, null); + await backendManager.WaitForEmptyAsync(restoredb, tr, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; return; } @@ -525,9 +459,9 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress((((float)progress / lst.Count) * 0.1f) + 0.7f + (i * 0.1f)); Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingBlocklistVolumes", "Pass {0} of 3, processing blocklist volume {1} of {2}", (i + 1), progress, lst.Count); - var volumeid = restoredb.GetRemoteVolumeID(sf.Name); + var volumeid = restoredb.GetRemoteVolumeID(name); - restoredb.UpdateRemoteVolume(sf.Name, RemoteVolumeState.Uploaded, sf.Size, sf.Hash, tr); + restoredb.UpdateRemoteVolume(name, RemoteVolumeState.Uploaded, size, hash, tr); bool anyChange = false; // Update the block table so we know about the block/volume map @@ -554,7 +488,7 @@ namespace Duplicati.Library.Main.Operation { if (i == 2) { - Logging.Log.WriteWarningMessage(LOGTAG, "UpdatingTables", null, "Unexpected changes caused by block {0}", sf.Name); + Logging.Log.WriteWarningMessage(LOGTAG, "UpdatingTables", null, "Unexpected changes caused by block {0}", name); } if (expRecreateDb) restoredb.AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, tr, false); @@ -567,12 +501,12 @@ namespace Duplicati.Library.Main.Operation //At this point we can patch files with data from the block volume if (blockprocessor != null) - blockprocessor(sf.Name, rd); + blockprocessor(name, rd); } } catch (Exception ex) { - Logging.Log.WriteWarningMessage(LOGTAG, "FailedRebuildingWithFile", ex, "Failed to use information from {0} to rebuild database: {1}", sf.Name, ex.Message); + Logging.Log.WriteWarningMessage(LOGTAG, "FailedRebuildingWithFile", ex, "Failed to use information from {0} to rebuild database: {1}", name, ex.Message); if (m_options.UnittestMode) throw; } @@ -580,11 +514,17 @@ namespace Duplicati.Library.Main.Operation } } - backend.WaitForComplete(restoredb, null); + await backendManager.WaitForEmptyAsync(restoredb, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - // In some cases we have a stale reference from an index file to a deleted block file - if (!m_options.UnittestMode) - restoredb.CleanupMissingVolumes(); + if (!m_options.RepairOnlyPaths) + { + // All blocks are collected and added into the Block table + // Find out which blocks are deleted and move them into DeletedBlock, + // so that compact can calculate the unused space + restoredb.CleanupDeletedBlocks(null); + } + + restoredb.CleanupMissingVolumes(); if (m_options.RepairOnlyPaths) { @@ -617,6 +557,82 @@ namespace Duplicati.Library.Main.Operation } } + public static void RecreateFilesetFromRemoteList(LocalRecreateDatabase restoredb, IDbTransaction transaction, ICompression compressor, long filesetid, Options options, IFilter filter) + { + var blocksize = options.Blocksize; + var hashes_pr_block = blocksize / options.BlockhashSize; + + // retrieve fileset data from dlist + var filesetData = VolumeReaderBase.GetFilesetData(compressor, options); + + // update fileset using filesetData + restoredb.UpdateFullBackupStateInFileset(filesetid, filesetData.IsFullBackup, transaction); + + using (var filelistreader = new FilesetVolumeReader(compressor, options)) + foreach (var fe in filelistreader.Files.Where(x => Library.Utility.FilterExpression.Matches(filter, x.Path))) + { + try + { + var expectedmetablocks = (fe.Metasize + blocksize - 1) / blocksize; + var expectedmetablocklisthashes = (expectedmetablocks + hashes_pr_block - 1) / hashes_pr_block; + if (expectedmetablocks <= 1) expectedmetablocklisthashes = 0; + + var metadataid = long.MinValue; + var split = LocalDatabase.SplitIntoPrefixAndName(fe.Path); + var prefixid = restoredb.GetOrCreatePathPrefix(split.Key, transaction); + + switch (fe.Type) + { + case FilelistEntryType.Folder: + metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, transaction); + restoredb.AddDirectoryEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, transaction); + break; + case FilelistEntryType.File: + var expectedblocks = (fe.Size + blocksize - 1) / blocksize; + var expectedblocklisthashes = (expectedblocks + hashes_pr_block - 1) / hashes_pr_block; + if (expectedblocks <= 1) expectedblocklisthashes = 0; + + var blocksetid = restoredb.AddBlockset(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes, transaction); + metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, transaction); + restoredb.AddFileEntry(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid, transaction); + + if (fe.Size <= blocksize) + { + if (!string.IsNullOrWhiteSpace(fe.Blockhash)) + restoredb.AddSmallBlocksetLink(fe.Hash, fe.Blockhash, fe.Blocksize, transaction); + else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) + restoredb.AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size, transaction); + else if (fe.Size > 0) + Logging.Log.WriteWarningMessage(LOGTAG, "MissingBlockHash", null, "No block hash found for file: {0}", fe.Path); + } + + break; + case FilelistEntryType.Symlink: + metadataid = restoredb.AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, transaction); + restoredb.AddSymlinkEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, transaction); + break; + default: + Logging.Log.WriteWarningMessage(LOGTAG, "SkippingUnknownFileEntry", null, "Skipping file-entry with unknown type {0}: {1} ", fe.Type, fe.Path); + break; + } + + if (fe.Metasize <= blocksize && (fe.Type == FilelistEntryType.Folder || fe.Type == FilelistEntryType.File || fe.Type == FilelistEntryType.Symlink)) + { + if (!string.IsNullOrWhiteSpace(fe.Metablockhash)) + restoredb.AddSmallBlocksetLink(fe.Metahash, fe.Metablockhash, fe.Metasize, transaction); + else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) + restoredb.AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize, transaction); + else + Logging.Log.WriteWarningMessage(LOGTAG, "MissingMetadataBlockHash", null, "No block hash found for file metadata: {0}", fe.Path); + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FileEntryProcessingFailed", ex, "Failed to process file-entry: {0}", fe.Path); + } + } + } + /// /// Look in the database for filenames similar to the current filename, but with a different compression and encryption module /// diff --git a/Duplicati/Library/Main/Operation/RepairHandler.cs b/Duplicati/Library/Main/Operation/RepairHandler.cs index 0f5aa8ec1..bfd52a0fc 100644 --- a/Duplicati/Library/Main/Operation/RepairHandler.cs +++ b/Duplicati/Library/Main/Operation/RepairHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,9 +20,10 @@ // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Security.Cryptography; -using System.Text; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; using Duplicati.Library.Main.Volumes; @@ -36,13 +37,11 @@ namespace Duplicati.Library.Main.Operation /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - private readonly string m_backendurl; private readonly Options m_options; private readonly RepairResults m_result; - public RepairHandler(string backend, Options options, RepairResults result) + public RepairHandler(Options options, RepairResults result) { - m_backendurl = backend; m_options = options; m_result = result; @@ -50,11 +49,11 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(Strings.Common.PassphraseChangeUnsupported, "PassphraseChangeUnsupported"); } - public void Run(Library.Utility.IFilter filter = null) + public async Task RunAsync(IBackendManager backendManager, IFilter filter) { - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) { - RunRepairLocal(filter); + await RunRepairLocalAsync(backendManager, filter).ConfigureAwait(false); RunRepairCommon(); m_result.EndTime = DateTime.UtcNow; return; @@ -79,55 +78,55 @@ namespace Duplicati.Library.Main.Operation } else { - var baseName = System.IO.Path.ChangeExtension(m_options.Dbpath, "backup"); + var baseName = Path.ChangeExtension(m_options.Dbpath, "backup"); var i = 0; - while (System.IO.File.Exists(baseName) && i++ < 1000) - baseName = System.IO.Path.ChangeExtension(m_options.Dbpath, "backup-" + i.ToString()); + while (File.Exists(baseName) && i++ < 1000) + baseName = Path.ChangeExtension(m_options.Dbpath, "backup-" + i.ToString()); Logging.Log.WriteInformationMessage(LOGTAG, "RenamingDatabase", "Renaming existing db from {0} to {1}", m_options.Dbpath, baseName); - System.IO.File.Move(m_options.Dbpath, baseName); + File.Move(m_options.Dbpath, baseName); } - RunRepairLocal(filter); + await RunRepairLocalAsync(backendManager, filter).ConfigureAwait(false); RunRepairCommon(); } else { RunRepairCommon(); - RunRepairRemote(); + await RunRepairBrokenFilesets(backendManager).ConfigureAwait(false); + await RunRepairRemoteAsync(backendManager, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } m_result.EndTime = DateTime.UtcNow; } - public void RunRepairLocal(Library.Utility.IFilter filter = null) + public async Task RunRepairLocalAsync(IBackendManager backendManager, IFilter filter) { m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result); using (new Logging.Timer(LOGTAG, "RecreateDbForRepair", "Recreate database for repair")) - using (var f = m_options.Dryrun ? new Library.Utility.TempFile() : null) + using (var f = m_options.Dryrun ? new TempFile() : null) { - if (f != null && System.IO.File.Exists(f)) - System.IO.File.Delete(f); + if (f != null && File.Exists(f)) + File.Delete(f); var filelistfilter = RestoreHandler.FilterNumberedFilelist(m_options.Time, m_options.Version); - new RecreateDatabaseHandler(m_backendurl, m_options, (RecreateDatabaseResults)m_result.RecreateDatabaseResults) - .Run(m_options.Dryrun ? (string)f : m_options.Dbpath, filter, filelistfilter); + await new RecreateDatabaseHandler(m_options, (RecreateDatabaseResults)m_result.RecreateDatabaseResults) + .RunAsync(m_options.Dryrun ? (string)f : m_options.Dbpath, backendManager, filter, filelistfilter, null) + .ConfigureAwait(false); } } - public void RunRepairRemote() + private async Task RunRepairRemoteAsync(IBackendManager backendManager, CancellationToken cancellationToken) { - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "RepairDatabaseFileDoesNotExist"); m_result.OperationProgressUpdater.UpdateProgress(0); using (var db = new LocalRepairDatabase(m_options.Dbpath)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) { - m_result.SetDatabase(db); Utility.UpdateOptionsFromDb(db, m_options); Utility.VerifyOptionsAndUpdateDatabase(db, m_options); @@ -137,12 +136,34 @@ namespace Duplicati.Library.Main.Operation if (db.RepairInProgress) throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsInRepairState"); - var tp = FilelistProcessor.RemoteListAnalysis(backend, m_options, db, m_result.BackendWriter, null); + // Ensure the database is consistent before we start fixing the remote + db.VerifyConsistencyForRepair(m_options.Blocksize, m_options.BlockhashSize, true, null); + + // If the last backup failed, guard the incomplete fileset, so we can create a synthetic filelist + var lastTempFilelist = db.GetLastIncompleteFilesetVolume(null); + var tp = await FilelistProcessor.RemoteListAnalysis(backendManager, m_options, db, m_result.BackendWriter, [lastTempFilelist.Name], null, FilelistProcessor.VerifyMode.VerifyAndCleanForced).ConfigureAwait(false); + var buffer = new byte[m_options.Blocksize]; var hashsize = HashFactory.HashSizeBytes(m_options.BlockHashAlgorithm); + var missingRemoteFilesets = db.MissingRemoteFilesets().ToList(); + var missingLocalFilesets = db.MissingLocalFilesets().ToList(); + var emptyIndexFiles = db.EmptyIndexFiles().ToList(); + var progress = 0; - var targetProgess = tp.ExtraVolumes.Count() + tp.MissingVolumes.Count() + tp.VerificationRequiredVolumes.Count(); + var targetProgess = tp.ExtraVolumes.Count() + tp.MissingVolumes.Count() + tp.VerificationRequiredVolumes.Count() + missingRemoteFilesets.Count + missingLocalFilesets.Count + emptyIndexFiles.Count; + + var mostRecentLocal = db.FilesetTimes.Select(x => x.Value.ToLocalTime()).Append(DateTime.MinValue).Max(); + var mostRecentRemote = tp.ParsedVolumes.Select(x => x.Time.ToLocalTime()).Append(DateTime.MinValue).Max(); + if (mostRecentLocal < DateTime.UnixEpoch) + throw new UserInformationException("The local database has no fileset times. Consider deleting the local database and run the repair operation again.", "LocalDatabaseHasNoFilesetTimes"); + if (mostRecentRemote > mostRecentLocal) + { + if (m_options.RepairIgnoreOutdatedDatabase) + Logging.Log.WriteWarningMessage(LOGTAG, "RemoteFilesNewerThanLocalDatabase", null, "The remote files are newer ({0}) than the local database ({1}), this is likely because the database is outdated. Continuing as the options force ignoring this.", mostRecentRemote, mostRecentLocal); + else + throw new UserInformationException($"The remote files are newer ({mostRecentRemote}) than the local database ({mostRecentLocal}), this is likely because the database is outdated. Consider deleting the local database and run the repair operation again. If this is expected, set the option \"--repair-ignore-outdated-database\" ", "RemoteFilesNewerThanLocalDatabase"); + } if (m_options.Dryrun) { @@ -159,7 +180,7 @@ namespace Duplicati.Library.Main.Operation } } - if (tp.ExtraVolumes.Any() || tp.MissingVolumes.Any() || tp.VerificationRequiredVolumes.Any()) + if (tp.ExtraVolumes.Any() || tp.MissingVolumes.Any() || tp.VerificationRequiredVolumes.Any() || missingRemoteFilesets.Any() || missingLocalFilesets.Any() || emptyIndexFiles.Any()) { if (tp.VerificationRequiredVolumes.Any()) { @@ -168,20 +189,18 @@ namespace Duplicati.Library.Main.Operation foreach (var n in tp.VerificationRequiredVolumes) try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(testdb, null, cancellationToken).ConfigureAwait(false); return; } progress++; m_result.OperationProgressUpdater.UpdateProgress((float)progress / targetProgess); - long size; - string hash; KeyValuePair>> res; - - using (var tf = backend.GetWithInfo(n.Name, out size, out hash)) + (var tf, var hash, var size) = await backendManager.GetWithInfoAsync(n.Name, n.Hash, n.Size, cancellationToken).ConfigureAwait(false); + using (tf) res = TestHandler.TestVolumeInternals(testdb, n, tf, m_options, 1); if (res.Value.Any()) @@ -197,7 +216,7 @@ namespace Duplicati.Library.Main.Operation catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileVerificationError", ex, "Failed to perform verification for file: {0}, please run verify; message: {1}", n.Name, ex.Message); - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortException()) throw; } } @@ -207,9 +226,9 @@ namespace Duplicati.Library.Main.Operation foreach (var n in tp.ExtraVolumes) try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); return; } @@ -222,9 +241,8 @@ namespace Duplicati.Library.Main.Operation { try { - string hash; - long size; - using (var tf = backend.GetWithInfo(n.File.Name, out size, out hash)) + (var tf, var hash, var size) = await backendManager.GetWithInfoAsync(n.File.Name, null, n.File.Size, cancellationToken).ConfigureAwait(false); + using (tf) using (var ifr = new IndexVolumeReader(n.CompressionModule, tf, m_options, m_options.BlockhashSize)) { foreach (var rv in ifr.Volumes) @@ -246,14 +264,25 @@ namespace Duplicati.Library.Main.Operation foreach (var ixb in ifr.BlockLists) db.CheckBlocklistCorrect(ixb.Hash, ixb.Length, ixb.Blocklist, blocksize, hashsize); - var selfid = db.GetRemoteVolumeID(n.File.Name); - foreach (var rv in ifr.Volumes) - db.AddIndexBlockLink(selfid, db.GetRemoteVolumeID(rv.Filename), null); + // Register the new index file and link it to the block files + using (var tr = db.BeginTransaction()) + { + var selfid = db.RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, RemoteVolumeState.Uploading, size, new TimeSpan(0), tr); + foreach (var rv in ifr.Volumes) + { + // Guard against unknown block files + long id = db.GetRemoteVolumeID(rv.Filename, tr); + if (id == -1) + Logging.Log.WriteWarningMessage(LOGTAG, "UnknownBlockFile", null, "Index file {0} references unknown block file: {1}", n.File.Name, rv.Filename); + else + db.AddIndexBlockLink(selfid, id, tr); + } + tr.Commit(); + } } // All checks fine, we accept the new index file Logging.Log.WriteInformationMessage(LOGTAG, "AcceptNewIndexFile", "Accepting new index file {0}", n.File.Name); - db.RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, size, RemoteVolumeState.Uploading); db.UpdateRemoteVolume(n.File.Name, RemoteVolumeState.Verified, size, hash); continue; } @@ -266,7 +295,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) { db.RegisterRemoteVolume(n.File.Name, n.FileType, n.File.Size, RemoteVolumeState.Deleting); - backend.Delete(n.File.Name, n.File.Size); + await backendManager.DeleteAsync(n.File.Name, n.File.Size, false, cancellationToken).ConfigureAwait(false); } else Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteFile", "would delete file {0}", n.File.Name); @@ -274,7 +303,7 @@ namespace Duplicati.Library.Main.Operation catch (Exception ex) { Logging.Log.WriteErrorMessage(LOGTAG, "FailedExtraFileCleanup", ex, "Failed to perform cleanup for extra file: {0}, message: {1}", n.File.Name, ex.Message); - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortException()) throw; } @@ -285,15 +314,78 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException($"The backup storage destination is missing data files. You can either enable `--rebuild-missing-dblock-files` or run the purge command to remove these files. The following files are missing: {string.Join(", ", missingDblocks.Select(x => x.Name))}", "MissingDblockFiles"); } + var anyDlistUploads = false; + foreach (var (filesetId, timestamp, isfull) in missingRemoteFilesets) + { + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); + return; + } + + progress++; + m_result.OperationProgressUpdater.UpdateProgress((float)progress / targetProgess); + var fileTime = FilesetVolumeWriter.ProbeUnusedFilenameName(db, m_options, timestamp); + + var fsw = new FilesetVolumeWriter(m_options, fileTime); + Logging.Log.WriteInformationMessage(LOGTAG, "ReuploadingFileset", "Re-uploading fileset {0} from {1} as remote volume registration is missing, new filename: {2}", filesetId, timestamp, fsw.RemoteFilename); + + if (!string.IsNullOrEmpty(m_options.ControlFiles)) + foreach (var p in m_options.ControlFiles.Split(new char[] { System.IO.Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries)) + fsw.AddControlFile(p, m_options.GetCompressionHintFromFilename(p)); + + fsw.CreateFilesetFile(isfull); + db.WriteFileset(fsw, filesetId, null); + fsw.Close(); + + if (m_options.Dryrun) + { + fsw.Dispose(); + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldReUploadFileset", "would re-upload fileset {0}", fsw.RemoteFilename); + continue; + } + + fsw.VolumeID = db.RegisterRemoteVolume(fsw.RemoteFilename, RemoteVolumeType.Files, -1, RemoteVolumeState.Temporary); + db.LinkFilesetToVolume(filesetId, fsw.VolumeID, null); + await backendManager.PutAsync(fsw, null, null, false, cancellationToken).ConfigureAwait(false); + } + + if (anyDlistUploads) + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); + + foreach (var volumename in missingLocalFilesets) + { + var remoteVolume = db.GetRemoteVolume(volumename); + using (var tmpfile = await backendManager.GetAsync(remoteVolume.Name, remoteVolume.Hash, remoteVolume.Size, cancellationToken).ConfigureAwait(false)) + { + var parsed = VolumeBase.ParseFilename(remoteVolume.Name); + using (var stream = new FileStream(tmpfile, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var compressor = DynamicLoader.CompressionLoader.GetModule(parsed.CompressionModule, stream, ArchiveMode.Read, m_options.RawOptions)) + using (var transaction = db.BeginTransaction()) + using (var recreatedb = new LocalRecreateDatabase(db, m_options)) + { + if (compressor == null) + throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); + + var filesetid = db.CreateFileset(remoteVolume.ID, parsed.Time, transaction); + RecreateDatabaseHandler.RecreateFilesetFromRemoteList(recreatedb, transaction, compressor, filesetid, m_options, new FilterExpression()); + transaction.Commit(); + } + } + } + + if (!m_options.Dryrun && tp.MissingVolumes.Any()) + db.TerminatedWithActiveUploads = true; + foreach (var n in tp.MissingVolumes) { IDisposable newEntry = null; try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); return; } @@ -320,7 +412,7 @@ namespace Duplicati.Library.Main.Operation else { db.UpdateRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null); - backend.Put(volumeWriter); + await backendManager.PutAsync(volumeWriter, null, null, false, cancellationToken).ConfigureAwait(false); } } else if (n.Type == RemoteVolumeType.Index) @@ -361,7 +453,7 @@ namespace Duplicati.Library.Main.Operation else { db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null); - backend.Put(w); + await backendManager.PutAsync(w, null, null, false, cancellationToken).ConfigureAwait(false); } } else if (n.Type == RemoteVolumeType.Blocks) @@ -412,20 +504,20 @@ namespace Duplicati.Library.Main.Operation } //Then we grab all remote volumes that have the missing blocks - foreach (var vol in new AsyncDownloader(mbl.GetMissingBlockSources().ToList(), backend)) + await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(mbl.GetMissingBlockSources().ToList(), cancellationToken).ConfigureAwait(false)) { try { - using (var tmpfile = vol.TempFile) - using (var f = new BlockVolumeReader(RestoreHandler.GetCompressionModule(vol.Name), tmpfile, m_options)) + using (tmpfile) + using (var f = new BlockVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options)) foreach (var b in f.Blocks) if (mbl.SetBlockRestored(b.Key, b.Value)) if (f.ReadBlock(b.Key, buffer) == b.Value) - w.AddBlock(b.Key, buffer, 0, (int)b.Value, Duplicati.Library.Interface.CompressionHint.Default); + w.AddBlock(b.Key, buffer, 0, (int)b.Value, CompressionHint.Default); } catch (Exception ex) { - Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileAccessError", ex, "Failed to access remote file: {0}", vol.Name); + Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileAccessError", ex, "Failed to access remote file: {0}", name); } } @@ -457,7 +549,7 @@ namespace Duplicati.Library.Main.Operation else { db.UpdateRemoteVolume(w.RemoteFilename, RemoteVolumeState.Uploading, -1, null, null); - backend.Put(w); + await backendManager.PutAsync(w, null, null, false, cancellationToken).ConfigureAwait(false); } } } @@ -472,6 +564,43 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteErrorMessage(LOGTAG, "CleanupMissingFileError", ex, "Failed to perform cleanup for missing file: {0}, message: {1}", n.Name, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + + foreach (var emptyIndexFile in emptyIndexFiles) + { + try + { + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); + return; + } + + progress++; + m_result.OperationProgressUpdater.UpdateProgress((float)progress / targetProgess); + + if (m_options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldDeleteEmptyIndexFile", "would delete empty index file {0}", emptyIndexFile.Name); + else + { + if (emptyIndexFile.Size > 2048) + { + Logging.Log.WriteWarningMessage(LOGTAG, "LargeEmptyIndexFile", null, "The empty index file {0} is larger than expected ({1} bytes), choosing not to delete it", emptyIndexFile.Name, emptyIndexFile.Size); + } + else + { + Logging.Log.WriteInformationMessage(LOGTAG, "DeletingEmptyIndexFile", "Deleting empty index file {0}", emptyIndexFile.Name); + await backendManager.DeleteAsync(emptyIndexFile.Name, emptyIndexFile.Size, false, cancellationToken).ConfigureAwait(false); + } + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "CleanupEmptyIndexFileError", ex, "Failed to perform cleanup for empty index file: {0}, message: {1}", emptyIndexFile.Name, ex.Message); + if (ex is System.Threading.ThreadAbortException) throw; } @@ -483,22 +612,58 @@ namespace Duplicati.Library.Main.Operation } m_result.OperationProgressUpdater.UpdateProgress(1); - backend.WaitForComplete(db, null); - db.WriteResults(); + await backendManager.WaitForEmptyAsync(db, null, cancellationToken).ConfigureAwait(false); + if (!m_options.Dryrun) + db.TerminatedWithActiveUploads = false; + } + } + + public async Task RunRepairBrokenFilesets(IBackendManager backendManager) + { + if (!File.Exists(m_options.Dbpath)) + throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); + + using (var db = new LocalRepairDatabase(m_options.Dbpath)) + using (var tr = new ReusableTransaction(db)) + { + var sets = db.GetFilesetsWithMissingFiles(null).ToList(); + if (sets.Count == 0) + return; + + Logging.Log.WriteInformationMessage(LOGTAG, "RepairingBrokenFilesets", "Repairing {0} broken filesets", sets.Count); + var ix = 0; + foreach (var entry in sets) + { + ix++; + Logging.Log.WriteInformationMessage(LOGTAG, "RepairingBrokenFileset", "Repairing broken fileset {0} of {1}: {2}", ix, sets.Count, entry.Value); + var volume = db.GetRemoteVolumeFromFilesetID(entry.Key, tr.Transaction); + var parsed = VolumeBase.ParseFilename(volume.Name); + using var tmpfile = await backendManager.GetAsync(volume.Name, volume.Hash, volume.Size, CancellationToken.None).ConfigureAwait(false); + using var stream = new FileStream(tmpfile, FileMode.Open, FileAccess.Read, FileShare.Read); + using var compressor = DynamicLoader.CompressionLoader.GetModule(parsed.CompressionModule, stream, ArchiveMode.Read, m_options.RawOptions); + if (compressor == null) + throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); + + // Clear out the old fileset + db.DeleteFilesetEntries(entry.Key, tr.Transaction); + using (var rdb = new LocalRecreateDatabase(db, m_options)) + RecreateDatabaseHandler.RecreateFilesetFromRemoteList(rdb, tr.Transaction, compressor, entry.Key, m_options, new FilterExpression()); + + tr.Commit(); + } + } } public void RunRepairCommon() { - if (!System.IO.File.Exists(m_options.Dbpath)) + if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); m_result.OperationProgressUpdater.UpdateProgress(0); using (var db = new LocalRepairDatabase(m_options.Dbpath)) { - db.SetResult(m_result); - Utility.UpdateOptionsFromDb(db, m_options); if (db.RepairInProgress || db.PartiallyRecreated) diff --git a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs new file mode 100644 index 000000000..af8a34d0a --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs @@ -0,0 +1,539 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Buffers; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Main.Database; +using Microsoft.Extensions.Caching.Memory; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that manages the block requests and responses to/from the + /// `FileProcessor` process by caching the blocks. + /// + internal class BlockManager + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Dictionary for data blocks that are being cached. Whenever a block + /// is requested, it checks if it is in the cache. If it is not, it + /// will request the block from the corresponding volume. The requester + /// will be given a `Task` that will be completed when the block is + /// available. The cache will also keep track of how many times a block + /// is requested, and only remove it from the cache when all requests + /// have been fulfilled. This is to ensure that the cache is not + /// prematurely evicted, while keeping the memory usage low. The + /// dictionary also keeps track of how many readers are accessing it, + /// and will retire the volume request channel when the last reader is + /// done. Once disposed, the dictionary will clean up the database + /// table that was used to keep track of the block counts. + /// + internal class SleepableDictionary : IDisposable + { + /// + /// Channel for submitting block requests from a volume. + /// + private readonly IWriteChannel m_volume_request; + /// + /// The dictionary holding the cached blocks. + /// + private readonly MemoryCache m_block_cache; + /// + /// The dictionary holding the `Task` for each block request in flight. + /// + private readonly ConcurrentDictionary> m_waiters = new(); + /// + /// The number of readers accessing this dictionary. Used during shutdown / cleanup. + /// + private int readers = 0; + /// + /// Internal stopwatch for profiling the cache eviction. + /// + private readonly Stopwatch sw_cacheevict; + /// + /// Internal stopwatch for profiling the `CheckCounts` method. + /// + private readonly Stopwatch sw_checkcounts; + /// + /// Internal stopwatch for profiling setting up the waiters. + /// + private readonly Stopwatch sw_get_wait; + /// + /// Internal stopwatch for profiling setting a block in the cache. + /// + private readonly Stopwatch sw_set_set; + /// + /// Internal stopwatch for profiling getting a waiter to notify that the block is available. + /// + private readonly Stopwatch sw_set_wake_get; + /// + /// Internal stopwatch for profiling for waking up the waiting request. + /// + private readonly Stopwatch sw_set_wake_set; + + /// + /// Dictionary for keeping track of how many times each block is requested. Used to determine when a block is no longer needed. + /// + private readonly Dictionary m_blockcount = new(); + /// + /// Lock for the block count dictionary. + /// + private readonly object m_blockcount_lock = new(); + /// + /// Dictionary for keeping track of how many times each volume is requested. Used to determine when a volume is no longer needed. + /// + private readonly Dictionary m_volumecount = new(); + /// + /// The options for the restore. + /// + private readonly Options m_options; + /// + /// The cache eviction options. Used for registering a callback when a block is evicted from the cache. + /// + private readonly MemoryCacheEntryOptions m_entry_options = new(); + /// + /// Dictionary to keep track of how many active readers are accessing each block. On eviction, the byte[] buffer can only be returned to the ArrayPool if there are no active readers. + /// + private readonly ConcurrentDictionary m_active_readers = []; + + /// + /// Initializes a new instance of the class. + /// + /// The database holding information about how many of each block this restore requires. + /// Channel for submitting block requests from a volume. + /// Number of readers accessing this dictionary. Used during shutdown / cleanup. + public SleepableDictionary(LocalRestoreDatabase db, IWriteChannel volume_request, Options options, int readers) + { + m_options = options; + m_volume_request = volume_request; + var cache_options = new MemoryCacheOptions(); + m_block_cache = new MemoryCache(cache_options); + m_entry_options.RegisterPostEvictionCallback(async (key, value, reason, state) => + { + bool was_present = false; + while (true) + { + lock (m_blockcount_lock) + { + if (m_active_readers.TryGetValue((long)key, out var ac)) + { + if (ac == 0) + { + was_present = true; + break; + } + } + else + break; + } + + await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + } + if (was_present) + ArrayPool.Shared.Return((byte[])value); + }); + this.readers = readers; + sw_cacheevict = options.InternalProfiling ? new() : null; + sw_checkcounts = options.InternalProfiling ? new() : null; + sw_get_wait = options.InternalProfiling ? new() : null; + sw_set_set = options.InternalProfiling ? new() : null; + sw_set_wake_get = options.InternalProfiling ? new() : null; + sw_set_wake_set = options.InternalProfiling ? new() : null; + + foreach (var (block_id, volume_id) in db.GetBlocksAndVolumeIDs(options.SkipMetadata)) + { + var bc = m_blockcount.TryGetValue(block_id, out var c); + m_blockcount[block_id] = bc ? c + 1 : 1; + var vc = m_volumecount.TryGetValue(volume_id, out var v); + m_volumecount[volume_id] = vc ? v + 1 : 1; + } + } + + /// + /// Decrements the block counters for the block and volume, and checks + /// if the block is still needed. If the block is no longer needed, it + /// will be removed from the cache. If the volume is no longer needed, + /// the VolumeDownloader will be notified. + /// + /// The block request to check. + public async Task CheckCounts(BlockRequest blockRequest) + { + long error_block_id = -1; + long error_volume_id = -1; + var emit_evict = false; + byte[] data = null; + + lock (m_blockcount_lock) + { + sw_checkcounts?.Start(); + + var active = m_active_readers.TryGetValue(blockRequest.BlockID, out var ac) ? ac - 1 : 0; + if (active == 0) + m_active_readers.Remove(blockRequest.BlockID, out var _); + else + m_active_readers[blockRequest.BlockID] = active; + + var block_count = m_blockcount.TryGetValue(blockRequest.BlockID, out var c) ? c - 1 : 0; + + if (block_count > 0) + { + m_blockcount[blockRequest.BlockID] = block_count; + } + else if (block_count == 0) + { + // Evict the block from the cache and check if the volume is no longer needed. + m_blockcount.Remove(blockRequest.BlockID); + data = m_block_cache.Get(blockRequest.BlockID); + m_block_cache.Remove(blockRequest.BlockID); + } + else // block_count < 0 + { + error_block_id = blockRequest.BlockID; + } + + var vol_count = m_volumecount.TryGetValue(blockRequest.VolumeID, out var vc) ? vc - 1 : 0; + if (vol_count > 0) + { + m_volumecount[blockRequest.VolumeID] = vol_count; + } + else if (vol_count == 0) + { + m_volumecount.Remove(blockRequest.VolumeID); + blockRequest.CacheDecrEvict = true; + emit_evict = true; + } + else // vol_count < 0 + { + error_volume_id = blockRequest.VolumeID; + } + sw_checkcounts?.Stop(); + } + + if (data != null) + ArrayPool.Shared.Return(data); + + // Notify the `VolumeManager` that it should evict the volume. + if (emit_evict) + await m_volume_request.WriteAsync(blockRequest).ConfigureAwait(false); + + if (error_block_id != -1) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BlockCountError", null, $"Block {blockRequest.BlockID} has a count below 0"); + } + + if (error_volume_id != -1) + { + Logging.Log.WriteWarningMessage(LOGTAG, "VolumeCountError", null, $"Volume {blockRequest.VolumeID} has a count below 0"); + } + } + + /// + /// Get a block from the cache. If the block is not in the cache, + /// it will request the block from the volume and return a `Task` + /// that will be completed when the block is available. + /// + /// The requested block. + /// A `Task` holding the data block. + public async Task Get(BlockRequest block_request) + { + Logging.Log.WriteExplicitMessage(LOGTAG, "BlockCacheGet", "Getting block {0} from cache", block_request.BlockID); + + lock (m_blockcount_lock) + { + m_active_readers[block_request.BlockID] = m_active_readers.TryGetValue(block_request.BlockID, out var c) ? c + 1 : 1; + } + + // Check if the block is already in the cache, and return it if it is. + if (m_block_cache.TryGetValue(block_request.BlockID, out byte[] value)) + return value; + + // If the block is not in the cache, request it from the volume. + sw_get_wait?.Start(); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var new_tcs = m_waiters.GetOrAdd(block_request.BlockID, tcs); + if (tcs == new_tcs) + { + Logging.Log.WriteExplicitMessage(LOGTAG, "BlockCacheGet", "Requesting block {0} from volume {1}", block_request.BlockID, block_request.VolumeID); + + // We are the first to request this block + await m_volume_request.WriteAsync(block_request).ConfigureAwait(false); + + sw_get_wait?.Stop(); + + // Add a timeout monitor + using var tcs1 = new CancellationTokenSource(); + var t = await Task.WhenAny(Task.Delay(TimeSpan.FromMinutes(5), tcs1.Token), new_tcs.Task).ConfigureAwait(false); + if (t != new_tcs.Task) + Logging.Log.WriteWarningMessage(LOGTAG, "BlockRequestTimeout", null, "Block request for block {0} has been in flight for over 5 minutes. This may be a deadlock.", block_request.BlockID); + } + else + { + sw_get_wait?.Stop(); + } + + return await new_tcs.Task; + } + + /// + /// Set a block in the cache. If the block is already in the cache, + /// it will be replaced. If the block is not in the cache, it will + /// be added. If the block is no longer needed, it will be removed + /// from the cache. If the block is requested while it is being + /// removed, the requester will be given a `Task` that will be + /// completed when the block is available. + /// + /// The block request related to the value. + /// The byte[] buffer holding the block data. + public void Set(BlockRequest blockRequest, byte[] value) + { + Logging.Log.WriteExplicitMessage(LOGTAG, "BlockCacheSet", "Setting block {0} in cache", blockRequest.BlockID); + + sw_set_set?.Start(); + m_block_cache.Set(blockRequest.BlockID, value); + sw_set_set?.Stop(); + + // Notify any waiters that the block is available. + sw_set_wake_get?.Start(); + if (m_waiters.TryRemove(blockRequest.BlockID, out var tcs)) + { + sw_set_wake_get?.Stop(); + sw_set_wake_set?.Start(); + tcs.SetResult(value); + sw_set_wake_set?.Stop(); + } + sw_set_wake_get?.Stop(); + + sw_cacheevict?.Start(); + if (m_block_cache.Count > m_options.RestoreCacheMax) + { + m_block_cache.Compact(m_options.RestoreCacheMax == 0 ? 1.0 : m_options.RestoreCacheEvict); + } + sw_cacheevict?.Stop(); + } + + /// + /// Retire the dictionary. This will decrement the number of readers + /// accessing the dictionary, and if there are no more readers, it + /// will retire the volume request channel, effectively shutting + /// down the restore process network. + /// + public void Retire() + { + if (Interlocked.Decrement(ref readers) <= 0) + { + m_volume_request.Retire(); + } + } + + /// + /// Clean up the dictionary. This will remove the database table + /// that was used to keep track of the block counts. + /// + public void Dispose() + { + // Verify that the tables are empty + var blockcount = m_blockcount.Sum(x => x.Value); + var volumecount = m_volumecount.Sum(x => x.Value); + + if (blockcount != 0) + { + var blocks = m_blockcount.Where(x => x.Value != 0).Select(x => x.Key).ToArray(); + var blockids = string.Join(", ", blocks); + Logging.Log.WriteWarningMessage(LOGTAG, "BlockCountError", null, $"Block count in SleepableDictionarys block table is not zero: {blockcount}{Environment.NewLine}"); + } + + if (m_active_readers.Count > 0) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BlockCountError", null, $"There are still {m_active_readers.Count} files being read by {m_active_readers.Sum(x => x.Value)} readers"); + } + + if (volumecount != 0) + { + var vols = m_volumecount.Where(x => x.Value != 0).Select(x => x.Key).ToArray(); + var volids = string.Join(", ", vols); + Logging.Log.WriteWarningMessage(LOGTAG, "VolumeCountError", null, $"Volume count in SleepableDictionarys volume table is not zero: {volumecount}{Environment.NewLine}Volumes: {volids}"); + } + + if (m_options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Sleepable dictionary - CheckCounts: {sw_checkcounts.ElapsedMilliseconds}ms, Get wait: {sw_get_wait.ElapsedMilliseconds}ms, Set set: {sw_set_set.ElapsedMilliseconds}ms, Set wake get: {sw_set_wake_get.ElapsedMilliseconds}ms, Set wake set: {sw_set_wake_set.ElapsedMilliseconds}ms, Cache evict: {sw_cacheevict.ElapsedMilliseconds}ms"); + } + + if (m_block_cache.Count > 0) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BlockCacheMismatch", null, $"Internal Block cache is not empty: {m_block_cache.Count}"); + Logging.Log.WriteWarningMessage(LOGTAG, "BlockCacheMismatch", null, $"Block counts in cache ({m_blockcount.Count}): {string.Join(", ", m_blockcount.Select(x => x.Value))}"); + } + } + + /// + /// Cancel all pending requests. This will set an exception on all + /// pending requests, effectively cancelling them. + /// + public void CancelAll() + { + foreach (var tcs in m_waiters.Values) + { + tcs.SetException(new RetiredException("Request waiter")); + } + } + } + + /// + /// Run the block manager process. This will create a cache for the + /// blocks, and start two tasks: one for reading blocks from the input + /// channel (data blocks from the volumes) and storing them in the + /// cache, and one for reading block requests from the `FileProcessor`, + /// accessing the cache for the blocks, and writing the resulting + /// blocks back to the `FileProcessor`. + /// + /// The named channels for the restore operation. + /// The database holding information about how many of each block this restore requires. + /// The restore options. + /// The channels for reading block requests from the `FileProcessor`. + /// The channels for writing block responses back to the `FileProcessor`. + public static Task Run(Channels channels, LocalRestoreDatabase db, Options options, IChannel[] fp_requests, IChannel>[] fp_responses) + { + return AutomationExtensions.RunTask( + new + { + Input = channels.DecompressedBlock.AsRead(), + Output = channels.VolumeRequestResponse.AsWrite() + }, + async self => + { + // Create a cache for the blocks, + using SleepableDictionary cache = new(db, self.Output, options, fp_requests.Length); + + // The volume consumer will read blocks from the input channel (data blocks from the volumes) and store them in the cache. + var volume_consumer = Task.Run(async () => + { + Stopwatch sw_read = options.InternalProfiling ? new() : null; + Stopwatch sw_set = options.InternalProfiling ? new() : null; + try + { + while (true) + { + sw_read?.Start(); + var (block_request, data) = await self.Input.ReadAsync().ConfigureAwait(false); + sw_read?.Stop(); + + sw_set?.Start(); + cache.Set(block_request, data); + sw_set?.Stop(); + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "BlockManager Volume consumer retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Volume consumer - Read: {sw_read.ElapsedMilliseconds}ms, Set: {sw_set.ElapsedMilliseconds}ms"); + } + + // Cancel any remaining readers - although there shouldn't be any. + cache.CancelAll(); + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "VolumeConsumerError", ex, "Error in volume consumer"); + self.Input.Retire(); + + // Cancel any remaining readers - although there shouldn't be any. + cache.CancelAll(); + cache.Retire(); + } + }); + + // The block handlers will read block requests from the `FileProcessor`, access the cache for the blocks, and write the resulting blocks to the `FileProcessor`. + var block_handlers = fp_requests.Zip(fp_responses, (req, res) => Task.Run(async () => + { + Stopwatch sw_req = options.InternalProfiling ? new() : null; + Stopwatch sw_resp = options.InternalProfiling ? new() : null; + Stopwatch sw_cache = options.InternalProfiling ? new() : null; + Stopwatch sw_get = options.InternalProfiling ? new() : null; + try + { + while (true) + { + sw_req?.Start(); + var block_request = await req.ReadAsync().ConfigureAwait(false); + sw_req?.Stop(); + if (block_request.CacheDecrEvict) + { + sw_cache?.Start(); + // Target file already had the block. + await cache.CheckCounts(block_request).ConfigureAwait(false); + sw_cache?.Stop(); + } + else + { + sw_get?.Start(); + var datatask = cache.Get(block_request); + sw_get?.Stop(); + + sw_resp?.Start(); + await res.WriteAsync(datatask).ConfigureAwait(false); + sw_resp?.Stop(); + } + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "BlockManager Block handler retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Block handler - Req: {sw_req.ElapsedMilliseconds}ms, Resp: {sw_resp.ElapsedMilliseconds}ms, Cache: {sw_cache.ElapsedMilliseconds}ms, Get: {sw_get.ElapsedMilliseconds}ms"); + } + + cache.Retire(); + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "BlockHandlerError", ex, "Error in block handler"); + req.Retire(); + res.Retire(); + cache.Retire(); + } + })).ToArray(); + + await Task.WhenAll([volume_consumer, .. block_handlers]).ConfigureAwait(false); + }); + } + } + +} diff --git a/Duplicati/Library/Main/Operation/Restore/Channels.cs b/Duplicati/Library/Main/Operation/Restore/Channels.cs new file mode 100644 index 000000000..9ed844e59 --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/Channels.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using CoCoL; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Named channels for the restore operation. + /// + internal class Channels + { + /// + /// The buffer size for the channels. The buffer size is the number of + /// messages that can be queued up before the sender blocks. + /// + public static int BufferSize; + + /// + /// Channel between and . + /// + public readonly IChannel FilesToRestore = ChannelManager.CreateChannel(buffersize: BufferSize); + + /// + /// Channel between and . + /// + public readonly IChannel DownloadRequest = ChannelManager.CreateChannel(buffersize: BufferSize); + + /// + /// Channel between and + /// + public readonly IChannel<(long, string, TempFile)> DecryptRequest = ChannelManager.CreateChannel<(long, string, TempFile)>(buffersize: BufferSize); + + /// + /// Channel between and + /// + public readonly IChannel<(BlockRequest, BlockVolumeReader)> DecompressionRequest = ChannelManager.CreateChannel<(BlockRequest, BlockVolumeReader)>(buffersize: BufferSize); + + /// + /// Channel between and holding the decompressed blocks. + public readonly IChannel<(BlockRequest, byte[])> DecompressedBlock = ChannelManager.CreateChannel<(BlockRequest, byte[])>(buffersize: BufferSize); + + /// + /// Channel between and / , used for requesting and responding volumes to the manager. + /// + public readonly IChannel VolumeRequestResponse = ChannelManager.CreateChannel(buffersize: BufferSize); + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/FileLister.cs b/Duplicati/Library/Main/Operation/Restore/FileLister.cs new file mode 100644 index 000000000..b115d6b8b --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/FileLister.cs @@ -0,0 +1,110 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Main.Database; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that holds the files that this particular restore operation needs to restore. + /// + internal class FileLister + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Runs the file lister process that lists the files that need to be restored + /// and sends them to the . + /// + /// The named channels for the restore operation. + /// The restore database, which is queried for the file list. + /// The restore options + /// The restore results + public static Task Run(Channels channels, LocalRestoreDatabase db, Options options, RestoreResults result) + { + return AutomationExtensions.RunTask( + new + { + Output = channels.FilesToRestore.AsWrite() + }, + async self => + { + Stopwatch sw_get_files = options.InternalProfiling ? new() : null; + Stopwatch sw_write_file = options.InternalProfiling ? new() : null; + Stopwatch sw_get_folders = options.InternalProfiling ? new() : null; + Stopwatch sw_write_folder = options.InternalProfiling ? new() : null; + + bool threw_exception = false; + + try + { + sw_get_files?.Start(); + var files = db.GetFilesAndSymlinksToRestore().OrderByDescending(x => x.Length).ToArray(); // Get started on big files first + result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_DownloadingRemoteFiles); + sw_get_files?.Stop(); + + sw_write_file?.Start(); + foreach (var file in files) + await self.Output.WriteAsync(file).ConfigureAwait(false); + sw_write_file?.Stop(); + + if (!options.SkipMetadata) + { + sw_get_folders?.Start(); + var folders = db.GetFolderMetadataToRestore().ToArray(); + sw_get_folders?.Stop(); + + sw_write_folder?.Start(); + foreach (var folder in folders) + await self.Output.WriteAsync(folder).ConfigureAwait(false); + sw_write_folder?.Stop(); + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "FileListerError", ex, "Error during file listing"); + threw_exception = true; + throw; + } + finally + { + if (!threw_exception) + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "File lister retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Get files: {sw_get_files.ElapsedMilliseconds}ms, Write files: {sw_write_file.ElapsedMilliseconds}ms, Get folders: {sw_get_folders.ElapsedMilliseconds}ms, Write folders: {sw_write_folder.ElapsedMilliseconds}ms"); + } + } + }); + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs new file mode 100644 index 000000000..1ebd2504c --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs @@ -0,0 +1,916 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that handles each file that needs to be restored. It starts by + /// identifying the blocks that need to be restored. Then it verifies the + /// which of the target file blocks are missing. Then it checks whether it + /// can use local blocks to restore the file. Finally, it restores the file + /// by downloading the missing blocks and writing them to the target file. + /// It also verifies the file hash and truncates the file if necessary. + /// If enabled, it also restores the metadata for the file. + /// + internal class FileProcessor + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + /// + /// The number of file processors that are still restoring files. It is set by the . + /// + public static int file_processors_restoring_files; + + /// + /// Runs the file processor process that restores the files that need to be restored. + /// + /// The named channels for the restore operation. + /// The restore database, which is queried for blocks, corresponding volumes and metadata for the files. + /// The channel to request blocks from the block manager. + /// The channel to receive blocks from the block manager. + /// The restore options. + /// The restore results. + public static Task Run(Channels channels, LocalRestoreDatabase db, IChannel block_request, IChannel> block_response, Options options, RestoreResults results) + { + return AutomationExtensions.RunTask( + new + { + Input = channels.FilesToRestore.AsRead() + }, + async self => + { + Stopwatch sw_file = options.InternalProfiling ? new() : null; + Stopwatch sw_block = options.InternalProfiling ? new() : null; + Stopwatch sw_meta = options.InternalProfiling ? new() : null; + Stopwatch sw_req = options.InternalProfiling ? new() : null; + Stopwatch sw_resp = options.InternalProfiling ? new() : null; + Stopwatch sw_work = options.InternalProfiling ? new() : null; + Stopwatch sw_work_verify_target = options.InternalProfiling ? new() : null; + Stopwatch sw_work_retarget = options.InternalProfiling ? new() : null; + Stopwatch sw_work_notify_local = options.InternalProfiling ? new() : null; + Stopwatch sw_work_verify_local = options.InternalProfiling ? new() : null; + Stopwatch sw_work_empty_file = options.InternalProfiling ? new() : null; + Stopwatch sw_work_hash = options.InternalProfiling ? new() : null; + Stopwatch sw_work_read = options.InternalProfiling ? new() : null; + Stopwatch sw_work_write = options.InternalProfiling ? new() : null; + Stopwatch sw_work_results = options.InternalProfiling ? new() : null; + Stopwatch sw_work_meta = options.InternalProfiling ? new() : null; + + // Indicates whether this FileProcessor is still restoring files + var decremented = false; + + try + { + using var filehasher = HashFactory.CreateHasher(options.FileHashAlgorithm); + using var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm); + var buffer = new byte[options.Blocksize]; + + while (true) + { + // Get the next file to restore. + sw_file?.Start(); + var file = await self.Input.ReadAsync().ConfigureAwait(false); + sw_file?.Stop(); + + if (file.BlocksetID == LocalDatabase.FOLDER_BLOCKSET_ID && !options.SkipMetadata) + { + // Check if there are other FileProcessor's still restoring files + if (!decremented) + { + await RendezvousBeforeProcessingFolderMetadata(); + decremented = true; + } + + await RestoreMetadata(db, file, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp); + + continue; + } + + // Get information about the blocks for the file + // TODO rather than keeping all of the blocks in memory, we could do a single pass over the blocks using a cursor, only keeping the relevant block requests in memory. Maybe even only a single block request at a time. + sw_block?.Start(); + var blocks = db.GetBlocksFromFile(file.BlocksetID).ToArray(); + sw_block?.Stop(); + + sw_work_verify_target?.Start(); + // Verify the target file blocks that may already exist. + var (bytes_verified, missing_blocks, verified_blocks) = await VerifyTargetBlocks(file, blocks, filehasher, blockhasher, buffer, options, results).ConfigureAwait(false); + long bytes_written = 0; + sw_work_verify_target?.Stop(); + if (blocks.Length != missing_blocks.Count + verified_blocks.Count) + { + Logging.Log.WriteErrorMessage(LOGTAG, "BlockCountMismatch", null, $"Block count mismatch for {file.TargetPath} - expected: {blocks.Length}, actual: {missing_blocks.Count + verified_blocks.Count}"); + continue; + } + + sw_work_retarget?.Start(); + // Check if the target file needs to be retargeted + if (missing_blocks.Count > 0 && !options.Overwrite && SystemIO.IO_OS.FileExists(file.TargetPath)) + { + var new_name = GenerateNewName(file, db, filehasher); + if (SystemIO.IO_OS.FileExists(new_name)) + { + // The file already exists, which it only does when it matches the target hash. So we can skip the file. + Logging.Log.WriteInformationMessage(LOGTAG, "FileAlreadyExists", null, $"File {file.TargetPath} already exists and matches the target hash as a copy: {new_name}"); + missing_blocks.Clear(); + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetargetingFile", "Retargeting file {0} to {1}", file.TargetPath, new_name); + var new_file = new FileRequest(file.ID, file.OriginalPath, new_name, file.Hash, file.Length, file.BlocksetID); + if (options.UseLocalBlocks) + { + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have copied {verified_blocks.Count} blocks ({verified_blocks.Count * options.Blocksize} bytes) from ""{file.TargetPath}"" to ""{new_file.TargetPath}"""); + } + else + { + await CopyOldTargetBlocksToNewTarget(file, new_file, buffer, verified_blocks).ConfigureAwait(false); + } + } + else + { + verified_blocks.Clear(); + missing_blocks = [.. blocks.Select(x => { x.CacheDecrEvict = false; return x; })]; + } + file = new_file; + } + } + sw_work_retarget?.Stop(); + + sw_work_notify_local?.Start(); + // Notify to the cache that we use local blocks. + foreach (var block in verified_blocks) + { + sw_req?.Start(); + await block_request.WriteAsync( + new BlockRequest( + block.BlockID, + block.BlockOffset, + block.BlockHash, + block.BlockSize, + block.VolumeID, + true + ) + ).ConfigureAwait(false); + sw_req?.Stop(); + } + sw_work_notify_local?.Stop(); + + sw_work_verify_local?.Start(); + if (missing_blocks.Count > 0 && options.UseLocalBlocks) + { + // Verify the local blocks at the original restore path that may be used to restore the file. + (bytes_written, missing_blocks) = await VerifyLocalBlocks(file, missing_blocks, blocks.Length, filehasher, blockhasher, buffer, options, results, block_request).ConfigureAwait(false); + } + sw_work_verify_local?.Stop(); + + bool empty_file_or_symlink = false; + if (file.BlocksetID != LocalDatabase.SYMLINK_BLOCKSET_ID && (blocks.Length == 0 || (blocks.Length == 1 && blocks[0].BlockSize == 0))) + { + sw_work_empty_file?.Start(); + empty_file_or_symlink = true; + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have created empty file ""{file.TargetPath}"""); + } + else + { + var foldername = SystemIO.IO_OS.PathGetDirectoryName(file.TargetPath); + if (!SystemIO.IO_OS.DirectoryExists(foldername)) + { + SystemIO.IO_OS.DirectoryCreate(foldername); + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, @$"Creating missing folder ""{foldername}"" for file ""{file.TargetPath}"""); + } + + // Create an empty file, or truncate to 0 + using var fs = SystemIO.IO_OS.FileOpenWrite(file.TargetPath); + fs.SetLength(0); + if (missing_blocks.Count != 0) + { + await block_request.WriteAsync( + new BlockRequest( + blocks[0].BlockID, + blocks[0].BlockOffset, + blocks[0].BlockHash, + blocks[0].BlockSize, + blocks[0].VolumeID, + true + ) + ).ConfigureAwait(false); + } + } + sw_work_empty_file?.Stop(); + } + else if (missing_blocks.Count > 0) + { + if (missing_blocks.Any(x => x.VolumeID < 0)) + { + Logging.Log.WriteWarningMessage(LOGTAG, "NegativeVolumeID", null, $"{file.TargetPath} has a negative volume ID, skipping"); + continue; + } + + sw_work_hash?.Start(); + filehasher.Initialize(); + sw_work_hash?.Stop(); + + FileStream fs = null; + try + { + // Open the target file + if (options.Dryrun) + { + // If dryrun, open the file for read only to verify the file hash. + if (File.Exists(file.TargetPath)) + { + fs = SystemIO.IO_OS.FileOpenRead(file.TargetPath); + } + else + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Tried opening ""{file.TargetPath}"" for reading, but it doesn't exist."); + } + } + else + { + var foldername = SystemIO.IO_OS.PathGetDirectoryName(file.TargetPath); + if (!SystemIO.IO_OS.DirectoryExists(foldername)) + { + SystemIO.IO_OS.DirectoryCreate(foldername); + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, @$"Creating missing folder ""{foldername}"" for file ""{file.TargetPath}"""); + } + fs = SystemIO.IO_OS.FileOpenReadWrite(file.TargetPath); + } + + sw_req?.Start(); + // Burst the block requests to speed up the restore + int burst = Channels.BufferSize; + int j = 0; + for (int i = 0; i < (int)Math.Min(missing_blocks.Count, burst); i++) + { + await block_request.WriteAsync( + new BlockRequest( + missing_blocks[i].BlockID, + missing_blocks[i].BlockOffset, + missing_blocks[i].BlockHash, + missing_blocks[i].BlockSize, + missing_blocks[i].VolumeID, + false + ) + ).ConfigureAwait(false); + } + sw_req?.Stop(); + + if (!options.Dryrun && options.RestorePreAllocate) + { + // Preallocate the file size to avoid fragmentation / help the operating system / filesystem. + fs.SetLength(file.Length); + } + + for (int i = 0; i < blocks.Length; i++) + { + // Each response block is not verified against the block hash, as the processes downloading the blocks should do that. + if (j < missing_blocks.Count && missing_blocks[j].BlockOffset == i) + { + // Read the block from the response and issue a new request, if more blocks are missing + sw_resp?.Start(); + var data = await (await block_response.ReadAsync().ConfigureAwait(false)).ConfigureAwait(false); + sw_resp?.Stop(); + + sw_req?.Start(); + if (j < missing_blocks.Count - burst) + { + await block_request.WriteAsync( + new BlockRequest( + missing_blocks[j + burst].BlockID, + missing_blocks[j + burst].BlockOffset, + missing_blocks[j + burst].BlockHash, + missing_blocks[j + burst].BlockSize, + missing_blocks[j + burst].VolumeID, + false + ) + ).ConfigureAwait(false); + } + sw_req?.Stop(); + + // Hash the block to verify the file hash + sw_work_hash?.Start(); + filehasher.TransformBlock(data, 0, (int)blocks[i].BlockSize, data, 0); + sw_work_hash?.Stop(); + if (options.Dryrun) + { + // Simulate writing the block + fs?.Seek(fs.Position + blocks[i].BlockSize, SeekOrigin.Begin); + } + else + { + // Write the block to the file + sw_work_write?.Start(); + await fs.WriteAsync(data.AsMemory(0, (int)blocks[i].BlockSize)).ConfigureAwait(false); + sw_work_write?.Stop(); + } + + // Keep track of metrics + bytes_written += blocks[i].BlockSize; + j++; + sw_req?.Start(); + await block_request.WriteAsync( + new BlockRequest( + blocks[i].BlockID, + blocks[i].BlockOffset, + blocks[i].BlockHash, + blocks[i].BlockSize, + blocks[i].VolumeID, + true + ) + ).ConfigureAwait(false); + sw_req?.Stop(); + } + else + { + // Not a missing block, so read from the file + sw_work_read?.Start(); + var read = await fs?.ReadAsync(buffer, 0, buffer.Length); + sw_work_read?.Stop(); + sw_work_hash?.Start(); + filehasher.TransformBlock(buffer, 0, read, buffer, 0); + sw_work_hash?.Stop(); + } + } + + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have restored {bytes_written} bytes of ""{file.TargetPath}"""); + } + + // Verify the file hash + sw_work_hash?.Start(); + filehasher.TransformFinalBlock([], 0, 0); + sw_work_hash?.Stop(); + + sw_work?.Start(); + if (Convert.ToBase64String(filehasher.Hash) != file.Hash) + { + Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.TargetPath} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); + throw new Exception("File hash mismatch"); + } + + // Truncate the file if it is larger than the expected size. + if (fs?.Length > file.Length) + { + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {fs.Length} to {file.Length}"); + } + else + { + fs.SetLength(file.Length); + } + } + sw_work?.Stop(); + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + block_request.Retire(); + block_response.Retire(); + throw; + } + finally + { + fs?.Dispose(); + } + } + + if (!options.SkipMetadata) + { + empty_file_or_symlink |= await RestoreMetadata(db, file, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp).ConfigureAwait(false); + sw_work_meta?.Stop(); + } + + // TODO legacy restore doesn't count metadata restore as a restored file. + + sw_work_results?.Start(); + if (empty_file_or_symlink || bytes_written > 0) + { + // Keep track of the restored files and their sizes + lock (results) + { + results.RestoredFiles++; + results.SizeOfRestoredFiles += bytes_written; + } + } + sw_work_results?.Stop(); + + Logging.Log.WriteVerboseMessage(LOGTAG, "RestoredFile", "Restored file {0}", file.TargetPath); + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "File processor retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"File: {sw_file.ElapsedMilliseconds}ms, Block: {sw_block.ElapsedMilliseconds}ms, Meta: {sw_meta.ElapsedMilliseconds}ms, Req: {sw_req.ElapsedMilliseconds}ms, Resp: {sw_resp.ElapsedMilliseconds}ms, Work: {sw_work.ElapsedMilliseconds}ms, VerifyTarget: {sw_work_verify_target.ElapsedMilliseconds}ms, Retarget: {sw_work_retarget.ElapsedMilliseconds}ms, NotifyLocal: {sw_work_notify_local.ElapsedMilliseconds}ms, VerifyLocal: {sw_work_verify_local.ElapsedMilliseconds}ms, EmptyFile: {sw_work_empty_file.ElapsedMilliseconds}ms, Hash: {sw_work_hash.ElapsedMilliseconds}ms, Read: {sw_work_read.ElapsedMilliseconds}ms, Write: {sw_work_write.ElapsedMilliseconds}ms, Results: {sw_work_results.ElapsedMilliseconds}ms, Meta: {sw_work_meta.ElapsedMilliseconds}ms"); + } + return; + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "FileProcessingError", ex, "Error during file processing"); + throw; + } + finally + { + if (!decremented) + Interlocked.Decrement(ref file_processors_restoring_files); + + block_request.Retire(); + block_response.Retire(); + } + }); + } + + /// + /// Copies the blocks that were verified in the old target to the new target file. + /// + /// The old target file. + /// The new target file. + /// The blocks in the old file that were verified. + private static async Task CopyOldTargetBlocksToNewTarget(FileRequest old_file, FileRequest new_file, byte[] buffer, List verified_blocks) + { + using var fs_old = SystemIO.IO_OS.FileOpenRead(old_file.TargetPath); + using var fs_new = SystemIO.IO_OS.FileOpenWrite(new_file.TargetPath); + + foreach (var block in verified_blocks) + { + fs_old.Seek(block.BlockOffset * block.BlockSize, SeekOrigin.Begin); + fs_new.Seek(block.BlockOffset * block.BlockSize, SeekOrigin.Begin); + + await fs_old.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + await fs_new.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); ; + } + } + + /// + /// Generates a new name for the target file. It starts by appending + /// the restore time to the file name. If that name is also already + /// taken, it appends a number to the name. It keeps incrementing the + /// number until it finds a name that is not taken. If it encounters a + /// file with the same name and the correct hash, it will retarget the + /// filename to that file, assuming that the file is the correct one. + /// + /// The original target file. + /// The restore database. + /// The file hasher used to verify whether any of the + /// The new filename. If the file already exists, its file hash matches the target hash. + private static string GenerateNewName(FileRequest request, LocalRestoreDatabase database, System.Security.Cryptography.HashAlgorithm filehasher) + { + var ext = SystemIO.IO_OS.PathGetExtension(request.TargetPath) ?? ""; + if (!string.IsNullOrEmpty(ext) && !ext.StartsWith(".", StringComparison.Ordinal)) + ext = "." + ext; + + // First we try with a simple date append, assuming that there are not many conflicts there + var newname = SystemIO.IO_OS.PathChangeExtension(request.TargetPath, null) + "." + database.RestoreTime.ToLocalTime().ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); + var tr = newname + ext; + var c = 0; + var max_tries = 100; + while (SystemIO.IO_OS.FileExists(tr) && c < max_tries) + { + try + { + // If we have a file with the correct name, + // it is most likely the file we want + filehasher.Initialize(); + + string key; + using (var file = SystemIO.IO_OS.FileOpenRead(tr)) + key = Convert.ToBase64String(filehasher.ComputeHash(file)); + + if (key == request.Hash) + { + //TODO: Also needs metadata check to make correct decision. + // We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored. + break; + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToReadRestoreTarget", ex, "Failed to read candidate restore target {0}", tr); + } + tr = newname + " (" + (c++).ToString() + ")" + ext; + } + + if (c >= max_tries) + { + Logging.Log.WriteErrorMessage(LOGTAG, "TooManyConflicts", null, "Too many conflicts when trying to find a new name for the target file"); + throw new Exception("Too many conflicts when trying to find a new name for the target file"); + } + + newname = tr; + + return newname; + } + + /// + /// Rendezvous with the other FileProcessor's before processing folder metadata. + /// + /// An awaitable task that completes once all of the FileProcessor's have rendezvoused. + private static async Task RendezvousBeforeProcessingFolderMetadata() + { + Interlocked.Decrement(ref file_processors_restoring_files); + + while (file_processors_restoring_files > 0) + { + await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + } + } + + /// + /// Restores the metadata for a file. + /// + /// The command to execute to retrieve the metadata blocks. + /// The file to restore. + /// The channel to request blocks from the block manager. + /// The channel to receive blocks from the block manager. + /// The restore options. + /// The stopwatch for internal profiling of the metadata processing. + /// The stopwatch for internal profiling of the general processing. + /// The stopwatch for internal profiling of the block requests. + /// The stopwatch for internal profiling of the block responses. + private static async Task RestoreMetadata(LocalRestoreDatabase db, FileRequest file, IChannel block_request, IChannel> block_response, Options options, Stopwatch sw_meta, Stopwatch sw_work, Stopwatch sw_req, Stopwatch sw_resp) + { + sw_meta?.Start(); + var blocks = db.GetMetadataBlocksFromFile(file.ID); + sw_meta?.Stop(); + + using var ms = new MemoryStream(); + + foreach (var block in blocks) + { + sw_work?.Stop(); + sw_req?.Start(); + await block_request.WriteAsync( + new BlockRequest( + block.BlockID, + block.BlockOffset, + block.BlockHash, + block.BlockSize, + block.VolumeID, + false + ) + ).ConfigureAwait(false); + sw_req?.Stop(); + + sw_resp?.Start(); + var data = await (await block_response.ReadAsync().ConfigureAwait(false)).ConfigureAwait(false); + sw_resp?.Stop(); + + sw_work?.Start(); + ms.Write(data, 0, (int)block.BlockSize); + sw_work?.Stop(); + + sw_req?.Start(); + await block_request.WriteAsync( + new BlockRequest( + block.BlockID, + block.BlockOffset, + block.BlockHash, + block.BlockSize, + block.VolumeID, + true + ) + ).ConfigureAwait(false); + sw_req?.Stop(); + + sw_work?.Start(); + } + ms.Seek(0, SeekOrigin.Begin); + + return RestoreHandler.ApplyMetadata(file.TargetPath, ms, options.RestorePermissions, options.RestoreSymlinkMetadata, options.Dryrun); + } + + /// + /// Verifies the target blocks of a file that may already exist. + /// + /// The target file. + /// The metadata about the blocks that make up the file. + /// A hasher for the file. + /// A hasher for a data block. + /// A buffer to read and write data blocks. + /// The Duplicati configuration options. + /// The restoration results. + /// An awaitable `Task`, which returns a collection of data blocks that are missing. + private static async Task<(long, List, List)> VerifyTargetBlocks(FileRequest file, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results) + { + long bytes_read = 0; + List missing_blocks = []; + List verified_blocks = []; + + // Check if the file exists + if (File.Exists(file.TargetPath)) + { + filehasher.Initialize(); + try + { + using var f = SystemIO.IO_OS.FileOpenRead(file.TargetPath); + for (int i = 0; i < blocks.Length; i++) + { + var read = await f.ReadAsync(buffer, 0, (int)blocks[i].BlockSize).ConfigureAwait(false); + if (read == blocks[i].BlockSize) + { + // Block is present + filehasher.TransformBlock(buffer, 0, read, buffer, 0); + var blockhash = Convert.ToBase64String(blockhasher.ComputeHash(buffer, 0, read)); + if (blockhash == blocks[i].BlockHash) + { + // Block matches + bytes_read += read; + verified_blocks.Add(blocks[i]); + } + else + { + // Block mismatch + missing_blocks.Add(blocks[i]); + } + } + else + { + // Block is missing + missing_blocks.Add(blocks[i]); + if (f.Position == f.Length) + { + // No more file - the rest of the blocks are missing. + missing_blocks.AddRange(blocks.Skip(i + 1)); + break; + } + } + } + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + } + + // If all of the individual blocks have been verified. + if (missing_blocks.Count == 0) + { + // Verify the file hash + filehasher.TransformFinalBlock([], 0, 0); + if (Convert.ToBase64String(filehasher.Hash) == file.Hash) + { + // Truncate the file if it is larger than the expected size. + FileInfo fi = new(file.TargetPath); + if (file.Length < fi.Length) + { + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {fi.Length} to {file.Length}"); + } + else + { + // Reopen file with write permission + fi.IsReadOnly = false; // The metadata handler will revert this back later. + using var f = SystemIO.IO_OS.FileOpenWrite(file.TargetPath); + f.SetLength(file.Length); + } + } + } + } + } + else + { + // The file doesn't exist, so all blocks are missing. + missing_blocks.AddRange(blocks); + } + + return (bytes_read, missing_blocks, verified_blocks); + } + + /// + /// Verifies the local blocks at the original restore path that may be used to restore the file. + /// + /// The file to restore. Contains both the target and original paths. + /// The collection of blocks that are currently missing. + /// The total number of blocks for the file. + /// A hasher for the file. + /// A hasher for the data block. + /// A buffer to read and write data blocks. + /// The Duplicati configuration options. + /// The restoration results. + /// The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present. + /// An awaitable `Task`, which returns a collection of data blocks that are missing. + private static async Task<(long, List)> VerifyLocalBlocks(FileRequest file, List blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results, IChannel block_request) + { + if (file.TargetPath == file.OriginalPath) + { + // The original file is the same as the target file, so no new blocks can be used. + return (0, blocks); + } + + List missing_blocks = []; + List verified_blocks = []; + + // Check if the file exists + if (File.Exists(file.OriginalPath)) + { + filehasher.Initialize(); + + // Open both files, as the target file is still being read to produce the overall file hash, if all the blocks are present across both the target and original files. + using var f_original = SystemIO.IO_OS.FileOpenRead(file.OriginalPath); + using var f_target = options.Dryrun ? + (SystemIO.IO_OS.FileExists(file.TargetPath) ? + SystemIO.IO_OS.FileOpenRead(file.TargetPath) : + null) : + SystemIO.IO_OS.FileOpenReadWrite(file.TargetPath); + long bytes_read = 0; + long bytes_written = 0; + + int j = 0; + for (long i = 0; i < total_blocks; i++) + { + int read; + if (j < blocks.Count && blocks[j].BlockOffset == i) + { + // The current block is a missing block + try + { + f_original.Seek(i * options.Blocksize, SeekOrigin.Begin); + read = await f_original.ReadAsync(buffer, 0, (int)blocks[j].BlockSize).ConfigureAwait(false); + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + throw; + } + + if (read == blocks[j].BlockSize) + { + var blockhash = Convert.ToBase64String(blockhasher.ComputeHash(buffer, 0, read)); + if (blockhash != blocks[j].BlockHash) + { + missing_blocks.Add(blocks[j]); + } + else + { + if (!options.Dryrun) + { + try + { + f_target?.Seek(blocks[j].BlockOffset * options.Blocksize, SeekOrigin.Begin); + await f_target?.WriteAsync(buffer, 0, read); + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + throw; + } + } + bytes_read += read; + bytes_written += read; + await block_request.WriteAsync( + new BlockRequest( + blocks[j].BlockID, + blocks[j].BlockOffset, + blocks[j].BlockHash, + blocks[j].BlockSize, + blocks[j].VolumeID, + true + ) + ).ConfigureAwait(false); + verified_blocks.Add(blocks[j]); + } + } + else + { + missing_blocks.Add(blocks[j]); + if (f_original.Position == f_original.Length) + { + missing_blocks.AddRange(blocks.Skip(j + 1)); + break; + } + } + + j++; + } + else + { + // The current block is not a missing block - read from the target file. + try + { + f_target?.Seek(i * options.Blocksize, SeekOrigin.Begin); + read = await f_target?.ReadAsync(buffer, 0, options.Blocksize); + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + throw; + } + } + filehasher.TransformBlock(buffer, 0, read, buffer, 0); + } + + if (missing_blocks.Count == 0) + { + // No more blocks are missing, so check the file hash. + filehasher.TransformFinalBlock([], 0, 0); + if (Convert.ToBase64String(filehasher.Hash) == file.Hash) + { + // Truncate the file if it is larger than the expected size. + if (file.Length < f_target?.Length) + { + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have truncated ""{file.TargetPath}"" from {f_target.Length} to {file.Length}"); + } + else + { + try + { + f_target.SetLength(file.Length); + } + catch (Exception) + { + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + throw; + } + } + } + } + else + { + Logging.Log.WriteErrorMessage(LOGTAG, "FileHashMismatch", null, $"File hash mismatch for {file.TargetPath} - expected: {file.Hash}, actual: {Convert.ToBase64String(filehasher.Hash)}"); + lock (results) + { + results.BrokenLocalFiles.Add(file.TargetPath); + } + } + } + + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "DryrunRestore", @$"Would have restored {verified_blocks.Count} blocks ({bytes_written} bytes) from ""{file.OriginalPath}"" to ""{file.TargetPath}"""); + } + + return (bytes_written, missing_blocks); + } + else + { + // The original file is no longer present, so all blocks are missing. + return (0, blocks); + } + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/Interfaces.cs b/Duplicati/Library/Main/Operation/Restore/Interfaces.cs new file mode 100644 index 000000000..f53ca643f --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/Interfaces.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Represents a block request that the `VolumeDownloader` process will use to download a block from the backend. + /// + /// The block ID in the database. + /// The offset in blocks this block represents in the target file. + /// The hash of the block. + /// The size of the block. + /// The ID of the volume in which the block is stored remotely. + /// Flag indicating that this block request should either decrement the block counter for BlockID (for BlockManager) or evict the VolumeID (for VolumeDownloader). + public class BlockRequest(long blockID, long blockOffset, string blockHash, long blockSize, long volumeID, bool cacheDecrEvict) + { // Total = 77 bytes + public long BlockID { get; } = blockID; + public long BlockOffset { get; } = blockOffset; + public string BlockHash { get; } = blockHash; + public long BlockSize { get; } = blockSize; + public long VolumeID { get; } = volumeID; + public bool CacheDecrEvict { get; set; } = cacheDecrEvict; + } + + /// + /// Represents a file request that the `FileLister` process sends to the `FileProcessor` process. + /// + /// The File ID. + /// The original path of the file. + /// The target path of the file. + /// The file hash. + /// The length of the file. + /// The BlocksetID of the file. + public class FileRequest(long ID, string OriginalPath, string TargetPath, string Hash, long Length, long BlocksetID) + { + public long ID { get; } = ID; + public string OriginalPath { get; } = OriginalPath; + public string TargetPath { get; } = TargetPath; + public string Hash { get; } = Hash; + public long Length { get; } = Length; + public long BlocksetID { get; } = BlocksetID; + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs new file mode 100644 index 000000000..0d2de024f --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs @@ -0,0 +1,121 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that decompresses the volumes that the `VolumeDecryptor` process has decrypted. + /// + internal class VolumeDecompressor + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Runs the volume decompressor process. + /// + /// The named channels for the restore operation. + /// The restore options + public static Task Run(Channels channels, Options options) + { + return AutomationExtensions.RunTask( + new + { + Input = channels.DecompressionRequest.AsRead(), + Output = channels.DecompressedBlock.AsWrite() + }, + async self => + { + Stopwatch sw_read = options.InternalProfiling ? new() : null; + Stopwatch sw_write = options.InternalProfiling ? new() : null; + Stopwatch sw_decompress_alloc = options.InternalProfiling ? new() : null; + Stopwatch sw_decompress_locking = options.InternalProfiling ? new() : null; + Stopwatch sw_decompress_read = options.InternalProfiling ? new() : null; + Stopwatch sw_verify = options.InternalProfiling ? new() : null; + + try + { + using var block_hasher = HashFactory.CreateHasher(options.BlockHashAlgorithm); + + while (true) + { + sw_read?.Start(); + // Get the block request and volume from the `VolumeDecryptor` process. + var (block_request, volume_reader) = await self.Input.ReadAsync().ConfigureAwait(false); + sw_read?.Stop(); + + sw_decompress_alloc?.Start(); + var data = ArrayPool.Shared.Rent(options.Blocksize); + sw_decompress_alloc?.Stop(); + sw_decompress_locking?.Start(); + lock (volume_reader) // The BlockVolumeReader is not thread-safe + { + sw_decompress_locking?.Stop(); + sw_decompress_read?.Start(); + volume_reader.ReadBlock(block_request.BlockHash, data); + sw_decompress_read?.Stop(); + } + + sw_verify?.Start(); + var hash = Convert.ToBase64String(block_hasher.ComputeHash(data, 0, (int)block_request.BlockSize)); + if (hash != block_request.BlockHash) + { + Logging.Log.WriteErrorMessage(LOGTAG, "InvalidBlock", null, $"Invalid block detected for block {block_request.BlockID} in volume {block_request.VolumeID}, expected hash: {block_request.BlockHash}, actual hash: {hash}"); + } + sw_verify?.Stop(); + + sw_write?.Start(); + // Send the block to the `BlockManager` process. + await self.Output.WriteAsync((block_request, data)).ConfigureAwait(false); + sw_write?.Stop(); + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "Volume decompressor retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Read: {sw_read.ElapsedMilliseconds}ms, Write: {sw_write.ElapsedMilliseconds}ms, Decompress allocate: {sw_decompress_alloc.ElapsedMilliseconds}ms, Decompress lock: {sw_decompress_locking.ElapsedMilliseconds}ms, Decompress read: {sw_decompress_read.ElapsedMilliseconds}ms, Verify: {sw_verify.ElapsedMilliseconds}ms"); + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "DecompressionError", ex, "Error during decompression"); + self.Input.Retire(); + self.Output.Retire(); + throw; + } + }); + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs new file mode 100644 index 000000000..0d769c256 --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Main.Volumes; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that decrypts the volumes that the `VolumeDownloader` process has downloaded. + /// + internal class VolumeDecryptor + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Runs the volume decryptor process. + /// + /// The named channels for the restore operation. + /// The backend manager. + /// The restore options. + public static Task Run(Channels channels, IBackendManager backend, Options options) + { + return AutomationExtensions.RunTask( + new + { + Input = channels.DecryptRequest.AsRead(), + Output = channels.VolumeRequestResponse.AsWrite() + }, + async self => + { + Stopwatch sw_read = options.InternalProfiling ? new() : null; + Stopwatch sw_write = options.InternalProfiling ? new() : null; + Stopwatch sw_decrypt = options.InternalProfiling ? new() : null; + try + { + while (true) + { + // Get the block request and volume from the `VolumeDownloader` process. + sw_read?.Start(); + var (volume_id, volume_name, volume) = await self.Input.ReadAsync().ConfigureAwait(false); + sw_read?.Stop(); + + sw_decrypt?.Start(); + var tmpfile = backend.DecryptFile(volume, volume_name, options); + var bvr = new BlockVolumeReader(options.CompressionModule, tmpfile, options); + sw_decrypt?.Stop(); + + sw_write?.Start(); + // Pass the decrypted volume to the `VolumeDecompressor` process. + await self.Output.WriteAsync((volume_id, tmpfile, bvr)).ConfigureAwait(false); + sw_write?.Stop(); + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "Volume decryptor retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Read: {sw_read.ElapsedMilliseconds}ms, Decrypt: {sw_decrypt.ElapsedMilliseconds}ms, Write: {sw_write.ElapsedMilliseconds}ms"); + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "DecryptionError", ex, "Error during decryption"); + self.Input.Retire(); + self.Output.Retire(); + throw; + } + }); + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs new file mode 100644 index 000000000..b615c4d8e --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that starts the download of the requested volumes. + /// + internal class VolumeDownloader + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Runs the volume downloader process. + /// + /// The named channels for the restore operation. + /// The local restore database, used to find volume information given the volume ID. + /// The backend to use for downloading the volumes. + /// The restore options. + /// The restore results. + public static Task Run(Channels channels, LocalRestoreDatabase db, IBackendManager backend, Options options, RestoreResults results) + { + return AutomationExtensions.RunTask( + new + { + Input = channels.DownloadRequest.AsRead(), + Output = channels.DecryptRequest.AsWrite() + }, + async self => + { + Stopwatch sw_read = options.InternalProfiling ? new() : null; + Stopwatch sw_write = options.InternalProfiling ? new() : null; + Stopwatch sw_wait = options.InternalProfiling ? new() : null; + + try + { + while (true) + { + // Get the block request from the `BlockManager` process. + sw_read?.Start(); + var volume_id = await self.Input.ReadAsync().ConfigureAwait(false); + sw_read?.Stop(); + + // Trigger the download. + sw_wait?.Start(); + TempFile f; + var (volume_name, size, hash) = db.GetVolumeInfo(volume_id).First(); + try + { + f = await backend.GetDirectAsync(volume_name, hash, size, CancellationToken.None).ConfigureAwait(false); + } + catch (Exception) + { + lock (results) + results.BrokenRemoteFiles.Add(volume_name); + + throw; + } + sw_wait?.Stop(); + + // Pass the download handle (which may or may not have downloaded already) to the `VolumeDecryptor` process. + sw_write?.Start(); + await self.Output.WriteAsync((volume_id, volume_name, f)).ConfigureAwait(false); + sw_write?.Stop(); + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "Volume downloader retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"Read: {sw_read.ElapsedMilliseconds}ms, Write: {sw_write.ElapsedMilliseconds}ms, Wait: {sw_wait.ElapsedMilliseconds}ms"); + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "DownloadError", ex, "Error during download"); + self.Input.Retire(); + self.Output.Retire(); + throw; + } + }); + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs new file mode 100644 index 000000000..700de7b88 --- /dev/null +++ b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs @@ -0,0 +1,157 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using CoCoL; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Duplicati.Library.Main.Operation.Restore +{ + + /// + /// Process that manages the volumes that the `VolumeDownloader` process has downloaded. + /// It is responsible for fetching the volumes from the backend and caching them. + /// + internal class VolumeManager + { + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// Runs the volume manager process. + /// + /// The named channels for the restore operation. + /// The restore options. + public static Task Run(Channels channels, Options options) + { + return AutomationExtensions.RunTask( + new + { + VolumeRequestResponse = channels.VolumeRequestResponse.AsRead(), + DecompressRequest = channels.DecompressionRequest.AsWrite(), + DownloadRequest = channels.DownloadRequest.AsWrite(), + }, + async self => + { + Dictionary cache = []; + Dictionary tmpfiles = []; + Dictionary> in_flight = []; + + Stopwatch sw_cache_set = options.InternalProfiling ? new() : null; + Stopwatch sw_cache_evict = options.InternalProfiling ? new() : null; + Stopwatch sw_query = options.InternalProfiling ? new() : null; + Stopwatch sw_backend = options.InternalProfiling ? new() : null; + Stopwatch sw_request = options.InternalProfiling ? new() : null; + Stopwatch sw_wakeup = options.InternalProfiling ? new() : null; + + try + { + while (true) + { + var msg = await self.VolumeRequestResponse.ReadAsync().ConfigureAwait(false); + + switch (msg) + { + case BlockRequest request: + { + if (request.CacheDecrEvict) + { + sw_cache_evict?.Start(); + cache.Remove(request.VolumeID, out var reader); + reader?.Dispose(); + tmpfiles.Remove(request.VolumeID, out var tmpfile); + tmpfile?.Dispose(); + sw_cache_evict?.Stop(); + } + else + { + Logging.Log.WriteExplicitMessage(LOGTAG, "VolumeRequest", "Requesting block {0} from volume {1}", request.BlockID, request.VolumeID); + sw_request?.Start(); + if (cache.TryGetValue(request.VolumeID, out BlockVolumeReader reader)) + { + Logging.Log.WriteExplicitMessage(LOGTAG, "VolumeRequest", "Block {0} found in cache", request.BlockID); + await self.DecompressRequest.WriteAsync((request, reader)).ConfigureAwait(false); + } + else + { + Logging.Log.WriteExplicitMessage(LOGTAG, "VolumeRequest", "Block {0} not found in cache, requesting volume {1}", request.BlockID, request.VolumeID); + if (in_flight.TryGetValue(request.VolumeID, out var waiters)) + { + waiters.Add(request); + } + else + { + await self.DownloadRequest.WriteAsync(request.VolumeID).ConfigureAwait(false); + in_flight[request.VolumeID] = [request]; + } + } + sw_request?.Stop(); + } + break; + } + case (long volume_id, TempFile tmpfile, BlockVolumeReader reader): + { + sw_cache_set?.Start(); + cache[volume_id] = reader; + tmpfiles[volume_id] = tmpfile; + sw_cache_set?.Stop(); + sw_wakeup?.Start(); + foreach (var request in in_flight[volume_id]) + { + Logging.Log.WriteExplicitMessage(LOGTAG, "VolumeRequest", "Requesting block {0} from volume {1}", request.BlockID, volume_id); + await self.DecompressRequest.WriteAsync((request, reader)).ConfigureAwait(false); + } + in_flight.Remove(volume_id); + sw_wakeup?.Stop(); + break; + } + default: + throw new InvalidOperationException("Unexpected message type"); + } + } + } + catch (RetiredException) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RetiredProcess", null, "Volume manager retired"); + + if (options.InternalProfiling) + { + Logging.Log.WriteProfilingMessage(LOGTAG, "InternalTimings", $"CacheSet: {sw_cache_set.ElapsedMilliseconds}ms, CacheEvict: {sw_cache_evict.ElapsedMilliseconds}ms, Query: {sw_query.ElapsedMilliseconds}ms, Backend: {sw_backend.ElapsedMilliseconds}ms, Request: {sw_request.ElapsedMilliseconds}ms, Wakeup: {sw_wakeup.ElapsedMilliseconds}ms"); + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "VolumeManagerError", ex, "Error during volume manager"); + throw; + } + } + ); + } + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs index faeabc77b..3f77083c6 100644 --- a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs @@ -1,111 +1,106 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Operation { internal class RestoreControlFilesHandler { private readonly Options m_options; - private readonly string m_backendurl; private readonly RestoreControlFilesResults m_result; - public RestoreControlFilesHandler(string backendurl, Options options, RestoreControlFilesResults result) + public RestoreControlFilesHandler(Options options, RestoreControlFilesResults result) { m_options = options; - m_backendurl = backendurl; m_result = result; } - public void Run(IEnumerable filterstrings = null, Library.Utility.IFilter compositefilter = null) + public async Task RunAsync(IEnumerable filterstrings, IBackendManager backendManager, Library.Utility.IFilter compositefilter) { if (string.IsNullOrEmpty(m_options.Restorepath)) throw new Exception("Cannot restore control files without --restore-path"); - if (!System.IO.Directory.Exists(m_options.Restorepath)) - System.IO.Directory.CreateDirectory(m_options.Restorepath); - - using (var tmpdb = new Library.Utility.TempFile()) - using (var db = new Database.LocalDatabase(System.IO.File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "RestoreControlFiles", true)) - using (var backend = new BackendManager(m_backendurl, m_options, m_result.BackendWriter, db)) + if (!Directory.Exists(m_options.Restorepath)) + Directory.CreateDirectory(m_options.Restorepath); + + using (var tmpdb = new TempFile()) + using (var db = new Database.LocalDatabase(File.Exists(m_options.Dbpath) ? m_options.Dbpath : (string)tmpdb, "RestoreControlFiles", true)) { - m_result.SetDatabase(db); - - var filter = Library.Utility.JoinedFilterExpression.Join(new Library.Utility.FilterExpression(filterstrings), compositefilter); - + var filter = JoinedFilterExpression.Join(new FilterExpression(filterstrings), compositefilter); + try { - var filteredList = ListFilesHandler.ParseAndFilterFilesets(backend.List(), m_options); + var filteredList = ListFilesHandler.ParseAndFilterFilesets(await backendManager.ListAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false), m_options); if (filteredList.Count == 0) throw new Exception("No filesets found on remote target"); - - Exception lastEx = new Exception("No suitable files found on remote target"); - - foreach(var fileversion in filteredList) + + var lastEx = new Exception("No suitable files found on remote target"); + + foreach (var fileversion in filteredList) try { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); return; - } - + } + var file = fileversion.Value.File; var entry = db.GetRemoteVolume(file.Name); var res = new List(); - using (var tmpfile = backend.Get(file.Name, entry.Size < 0 ? file.Size : entry.Size, entry.Hash)) - using (var tmp = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(file.Name), tmpfile, m_options)) - foreach (var cf in tmp.ControlFiles) - if (Library.Utility.FilterExpression.Matches(filter, cf.Key)) + using (var tmpfile = await backendManager.GetAsync(file.Name, entry.Hash, entry.Size < 0 ? file.Size : entry.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + using (var tmp = new Volumes.FilesetVolumeReader(RestoreHandler.GetCompressionModule(file.Name), tmpfile, m_options)) + foreach (var cf in tmp.ControlFiles) + if (FilterExpression.Matches(filter, cf.Key)) { - var targetpath = System.IO.Path.Combine(m_options.Restorepath, cf.Key); - using (var ts = System.IO.File.Create(targetpath)) - Library.Utility.Utility.CopyStream(cf.Value, ts); + var targetpath = Path.Combine(m_options.Restorepath, cf.Key); + using (var ts = File.Create(targetpath)) + await Library.Utility.Utility.CopyStreamAsync(cf.Value, ts, m_result.TaskControl.ProgressToken).ConfigureAwait(false); res.Add(targetpath); } - + m_result.SetResult(res); - + lastEx = null; break; } - catch(Exception ex) + catch (Exception ex) { lastEx = ex; - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortException()) throw; } - + if (lastEx != null) throw lastEx; } finally { - backend.WaitForComplete(db, null); + await backendManager.WaitForEmptyAsync(db, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } - - db.WriteResults(); } } } diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index 33833b6fa..df2499bbb 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -1,1053 +1,1178 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Linq; -using Duplicati.Library.Interface; -using Duplicati.Library.Common.IO; -using Duplicati.Library.Main.Database; -using Duplicati.Library.Main.Volumes; -using Duplicati.Library.Utility; - -namespace Duplicati.Library.Main.Operation -{ - internal class RestoreHandler - { - /// - /// The tag used for logging - /// - private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - private readonly string m_backendurl; - private readonly Options m_options; - private byte[] m_blockbuffer; - private readonly RestoreResults m_result; - private static readonly string DIRSEP = Util.DirectorySeparatorString; - - public RestoreHandler(string backendurl, Options options, RestoreResults result) - { - m_options = options; - m_backendurl = backendurl; - m_result = result; - } - - /// - /// Gets the compression module by parsing the filename - /// - /// The filename to parse - /// The compression module - public static string GetCompressionModule(string filename) - { - var tmp = VolumeBase.ParseFilename(filename); - if (tmp == null) - throw new UserInformationException(string.Format("Unable to parse filename to valid entry: {0}", filename), "FailedToParseRemoteName"); - - return tmp.CompressionModule; - } - - public static RecreateDatabaseHandler.NumberedFilterFilelistDelegate FilterNumberedFilelist(DateTime time, long[] versions, bool singleTimeMatch = false) - { - if (time.Kind == DateTimeKind.Unspecified) - throw new Exception("Unspecified datetime instance, must be either local or UTC"); - - // Make sure the resolution is the same (i.e. no milliseconds) - if (time.Ticks > 0) - time = Library.Utility.Utility.DeserializeDateTime(Library.Utility.Utility.SerializeDateTime(time)).ToUniversalTime(); - - return - _lst => - { - // Unwrap, so we do not query the remote storage twice - var lst = (from n in _lst - where n.FileType == RemoteVolumeType.Files - orderby n.Time descending - select n).ToArray(); - - var numbers = lst.Zip(Enumerable.Range(0, lst.Length), (a, b) => new KeyValuePair(b, a)).ToList(); - - if (time.Ticks > 0 && versions != null && versions.Length > 0) - return from n in numbers - where (singleTimeMatch ? n.Value.Time == time : n.Value.Time <= time) && versions.Contains(n.Key) - select n; - else if (time.Ticks > 0) - return from n in numbers - where (singleTimeMatch ? n.Value.Time == time : n.Value.Time <= time) - select n; - else if (versions != null && versions.Length > 0) - return from n in numbers - where versions.Contains(n.Key) - select n; - else - return numbers; - }; - } - - public void Run(string[] paths, Library.Utility.IFilter filter = null) - { - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_Begin); - - // If we have both target paths and a filter, combine into a single filter - filter = Library.Utility.JoinedFilterExpression.Join(new Library.Utility.FilterExpression(paths), filter); - - if (!m_options.NoLocalDb && SystemIO.IO_OS.FileExists(m_options.Dbpath)) - { - using (var db = new LocalRestoreDatabase(m_options.Dbpath)) - { - db.SetResult(m_result); - DoRun(db, filter, m_result); - db.WriteResults(); - } - - return; - } - - - Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, building a temporary database"); - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_RecreateDatabase); - - using (var tmpdb = new Library.Utility.TempFile()) - { - RecreateDatabaseHandler.NumberedFilterFilelistDelegate filelistfilter = FilterNumberedFilelist(m_options.Time, m_options.Version); - - // Simultaneously with downloading blocklists, we patch as much as we can from the blockvolumes - // This prevents repeated downloads, except for cases where the blocklists refer blocks - // that have been previously handled. A local blockvolume cache can reduce this issue - using (var database = new LocalRestoreDatabase(tmpdb)) - { - using (var metadatastorage = new RestoreHandlerMetadataStorage()) - { - // TODO: When UpdateMissingBlocksTable is implemented, the localpatcher - // (removed in revision 9ce1e807 ("Remove unused variables and fields") can be activated - // and this will reduce the need for multiple downloads of the same volume - // TODO: This will need some work to preserve the missing block list for use with --fh-dryrun - m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result); - using (new Logging.Timer(LOGTAG, "RecreateTempDbForRestore", "Recreate temporary database for restore")) - new RecreateDatabaseHandler(m_backendurl, m_options, (RecreateDatabaseResults)m_result.RecreateDatabaseResults) - .DoRun(database, false, filter, filelistfilter, null); - - if (!m_options.SkipMetadata) - ApplyStoredMetadata(m_options, metadatastorage); - } - - //If we have --version set, we need to adjust, as the db has only the required versions - //TODO: Bit of a hack to set options that way - if (m_options.Version != null && m_options.Version.Length > 0) - m_options.RawOptions["version"] = string.Join(",", Enumerable.Range(0, m_options.Version.Length).Select(x => x.ToString())); - - DoRun(database, filter, m_result); - } - } - } - - private static void PatchWithBlocklist(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage) - { - var blocksize = options.Blocksize; - var updateCounter = 0L; - var fullblockverification = options.FullBlockVerification; - - using (var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) - using (var blockmarker = database.CreateBlockMarker()) - using (var volumekeeper = database.GetMissingBlockData(blocks, options.Blocksize)) - { - foreach (var restorelist in volumekeeper.FilesWithMissingBlocks) - { - var targetpath = restorelist.Path; - - if (options.Dryrun) - { - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchFile", "Would patch file with remote data: {0}", targetpath); - } - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingFile", "Patching file with remote data: {0}", targetpath); - - try - { - var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); - if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) - { - Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); - SystemIO.IO_OS.DirectoryCreate(folderpath); - } - - // TODO: Much faster if we iterate the volume and checks what blocks are used, - // because the compressors usually like sequential reading - using (var file = SystemIO.IO_OS.FileOpenWrite(targetpath)) - foreach (var targetblock in restorelist.Blocks) - { - file.Position = targetblock.Offset; - var size = blocks.ReadBlock(targetblock.Key, blockbuffer); - if (targetblock.Size == size) - { - var valid = !fullblockverification; - if (!valid) - { - var key = Convert.ToBase64String(blockhasher.ComputeHash(blockbuffer, 0, size)); - if (targetblock.Key == key) - valid = true; - else - Logging.Log.WriteWarningMessage(LOGTAG, "InvalidBlock", null, "Invalid block detected for {0}, expected hash: {1}, actual hash: {2}", targetpath, targetblock.Key, key); - } - - if (valid) - { - file.Write(blockbuffer, 0, size); - blockmarker.SetBlockRestored(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false); - } - } - else - { - Logging.Log.WriteWarningMessage(LOGTAG, "WrongBlockSize", null, "Block with hash {0} should have size {1} but has size {2}", targetblock.Key, targetblock.Size, size); - } - } - - if ((++updateCounter) % 20 == 0) - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "PatchFailed", ex, "Failed to patch file: \"{0}\", message: {1}, message: {1}", targetpath, ex.Message); - if (options.UnittestMode) - throw; - } - } - } - - if (!options.SkipMetadata) - { - foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocks) - { - var targetpath = restoremetadata.Path; - Logging.Log.WriteVerboseMessage(LOGTAG, "RecordingMetadata", "Recording metadata from remote data: {0}", targetpath); - - try - { - // TODO: When we support multi-block metadata this needs to deal with it - using (var ms = new System.IO.MemoryStream()) - { - foreach (var targetblock in restoremetadata.Blocks) - { - ms.Position = targetblock.Offset; - var size = blocks.ReadBlock(targetblock.Key, blockbuffer); - if (targetblock.Size == size) - { - ms.Write(blockbuffer, 0, size); - blockmarker.SetBlockRestored(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true); - } - } - - ms.Position = 0; - metadatastorage.Add(targetpath, ms); - //blockmarker.RecordMetadata(restoremetadata.FileID, ms); - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "MetatdataRecordFailed", ex, "Failed to record metadata for file: \"{0}\", message: {1}", targetpath, ex.Message); - if (options.UnittestMode) - throw; - } - } - } - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - blockmarker.Commit(); - } - } - - private static void ApplyStoredMetadata(Options options, RestoreHandlerMetadataStorage metadatastorage) - { - foreach (var metainfo in metadatastorage.Records) - { - var targetpath = metainfo.Key; - - if (options.Dryrun) - { - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchMetadata", "Would patch metadata with remote data: {0}", targetpath); - } - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingMetadata", "Patching metadata with remote data: {0}", targetpath); - try - { - var folderpath = Duplicati.Library.Utility.Utility.GetParent(targetpath, false); - if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) - { - Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for target {1}", folderpath, targetpath); - SystemIO.IO_OS.DirectoryCreate(folderpath); - } - - ApplyMetadata(targetpath, metainfo.Value, options.RestorePermissions, options.RestoreSymlinkMetadata, options.Dryrun); - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "MetadataWriteFailed", ex, "Failed to apply metadata to file: \"{0}\", message: {1}", targetpath, ex.Message); - if (options.UnittestMode) - throw; - } - } - } - } - - private void DoRun(LocalDatabase dbparent, Library.Utility.IFilter filter, RestoreResults result) - { - //In this case, we check that the remote storage fits with the database. - //We can then query the database and find the blocks that we need to do the restore - using (var database = new LocalRestoreDatabase(dbparent)) - using (var backend = new BackendManager(m_backendurl, m_options, result.BackendWriter, database)) - using (var metadatastorage = new RestoreHandlerMetadataStorage()) - { - database.SetResult(m_result); - Utility.UpdateOptionsFromDb(database, m_options); - Utility.VerifyOptionsAndUpdateDatabase(database, m_options); - m_blockbuffer = new byte[m_options.Blocksize]; - - if (!m_options.NoBackendverification) - { - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); - FilelistProcessor.VerifyRemoteList(backend, m_options, database, result.BackendWriter, false, null); - } - - //Figure out what files are to be patched, and what blocks are needed - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList); - using (new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList")) - PrepareBlockAndFileList(database, m_options, filter, result); - - //Make the entire output setup - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders); - using (new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory")) - CreateDirectoryStructure(database, m_options, result); - - //If we are patching an existing target folder, do not touch stuff that is already updated - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForExistingFiles); - using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) - using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) - using (new Logging.Timer(LOGTAG, "ScanForExistingTargetBlocks", "ScanForExistingTargetBlocks")) - ScanForExistingTargetBlocks(database, m_blockbuffer, blockhasher, filehasher, m_options, result); - - //Look for existing blocks in the original source files only - if (m_options.UseLocalBlocks && !string.IsNullOrEmpty(m_options.Restorepath)) - { - using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) - using (new Logging.Timer(LOGTAG, "ScanForExistingSourceBlocksFast", "ScanForExistingSourceBlocksFast")) - { - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForLocalBlocks); - ScanForExistingSourceBlocksFast(database, m_options, m_blockbuffer, blockhasher, result); - } - } - - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(database, null); - return; - } - - // If other local files already have the blocks we want, we use them instead of downloading - if (m_options.UseLocalBlocks) - { - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PatchWithLocalBlocks); - using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) - using (new Logging.Timer(LOGTAG, "PatchWithLocalBlocks", "PatchWithLocalBlocks")) - ScanForExistingSourceBlocks(database, m_options, m_blockbuffer, blockhasher, result, metadatastorage); - } - - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(database, null); - return; - } - - // Fill BLOCKS with remote sources - List volumes; - using (new Logging.Timer(LOGTAG, "GetMissingVolumes", "GetMissingVolumes")) - volumes = database.GetMissingVolumes().ToList(); - - if (volumes.Count > 0) - { - Logging.Log.WriteInformationMessage(LOGTAG, "RemoteFileCount", "{0} remote files are required to restore", volumes.Count); - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_DownloadingRemoteFiles); - } - - var brokenFiles = new List(); - foreach (var blockvolume in new AsyncDownloader(volumes, backend)) - try - { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(database, null); - return; - } - - using (var tmpfile = blockvolume.TempFile) - using (var blocks = new BlockVolumeReader(GetCompressionModule(blockvolume.Name), tmpfile, m_options)) - PatchWithBlocklist(database, blocks, m_options, result, m_blockbuffer, metadatastorage); - } - catch (Exception ex) - { - brokenFiles.Add(blockvolume.Name); - Logging.Log.WriteErrorMessage(LOGTAG, "PatchingFailed", ex, "Failed to patch with remote file: \"{0}\", message: {1}", blockvolume.Name, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - } - - var fileErrors = 0L; - - // Restore empty files. They might not have any blocks so don't appear in any volume. - foreach (var file in database.GetFilesToRestore(true).Where(item => item.Length == 0)) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreEmptyFile", "Restoring empty file \"{0}\"", file.Path); - - try - { - SystemIO.IO_OS.DirectoryCreate(SystemIO.IO_OS.PathGetDirectoryName(file.Path)); - // Just create the file and close it right away, empty statement is intentional. - using (SystemIO.IO_OS.FileCreate(file.Path)) - { - } - } - catch (Exception ex) - { - fileErrors++; - Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFileFailed", ex, "Failed to restore empty file: \"{0}\". Error message was: {1}", file.Path, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - } - } - - // Enforcing the length of files is now already done during ScanForExistingTargetBlocks - // and thus not necessary anymore. - - // Apply metadata - if (!m_options.SkipMetadata) - ApplyStoredMetadata(m_options, metadatastorage); - - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PostRestoreVerify); - - if (m_options.PerformRestoredFileVerification) - { - // After all blocks in the files are restored, verify the file hash - using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) - using (new Logging.Timer(LOGTAG, "RestoreVerification", "RestoreVerification")) - foreach (var file in database.GetFilesToRestore(true)) - { - try - { - if (m_result.TaskControlRendevouz() == TaskControlState.Stop) - { - backend.WaitForComplete(database, null); - return; - } - - Logging.Log.WriteVerboseMessage(LOGTAG, "TestFileIntegrity", "Testing restored file integrity: {0}", file.Path); - - string key; - long size; - using (var fs = SystemIO.IO_OS.FileOpenRead(file.Path)) - { - size = fs.Length; - key = Convert.ToBase64String(filehasher.ComputeHash(fs)); - } - - if (key != file.Hash) - throw new Exception(string.Format("Failed to restore file: \"{0}\". File hash is {1}, expected hash is {2}", file.Path, key, file.Hash)); - result.RestoredFiles++; - result.SizeOfRestoredFiles += size; - } - catch (Exception ex) - { - fileErrors++; - Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFileFailed", ex, "Failed to restore file: \"{0}\". Error message was: {1}", file.Path, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - } - } - } - - if (fileErrors > 0 && brokenFiles.Count > 0) - Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}{2}", fileErrors, Environment.NewLine, string.Join(Environment.NewLine, brokenFiles)); - else if (fileErrors > 0) - Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files", fileErrors); - else if (result.RestoredFiles == 0) - Logging.Log.WriteWarningMessage(LOGTAG, "NoFilesRestored", null, "Restore completed without errors but no files were restored"); - - // Drop the temp tables - database.DropRestoreTable(); - backend.WaitForComplete(database, null); - } - - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_Complete); - result.EndTime = DateTime.UtcNow; - } - - private static void ApplyMetadata(string path, System.IO.Stream stream, bool restorePermissions, bool restoreSymlinkMetadata, bool dryrun) - { - using (var tr = new System.IO.StreamReader(stream)) - using (var jr = new Newtonsoft.Json.JsonTextReader(tr)) - { - var metadata = new Newtonsoft.Json.JsonSerializer().Deserialize>(jr); - string k; - long t; - System.IO.FileAttributes fa; - - // If this is dry-run, we stop after having deserialized the metadata - if (dryrun) - return; - - var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal); - var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path; - - // Make the symlink first, otherwise we cannot apply metadata to it - if (metadata.TryGetValue("CoreSymlinkTarget", out k)) - SystemIO.IO_OS.CreateSymlink(targetpath, k, isDirTarget); - // If the target is a folder, make sure we create it first - else if (isDirTarget && !SystemIO.IO_OS.DirectoryExists(targetpath)) - SystemIO.IO_OS.DirectoryCreate(targetpath); - - // Avoid setting restoring symlink metadata, as that writes the symlink target, not the symlink itself - if (!restoreSymlinkMetadata && Snapshots.SnapshotUtility.IsSymlink(SystemIO.IO_OS, targetpath)) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "no-symlink-metadata-restored", "Not applying metadata to symlink: {0}", targetpath); - return; - } - - if (metadata.TryGetValue("CoreLastWritetime", out k) && long.TryParse(k, out t)) - { - if (isDirTarget) - SystemIO.IO_OS.DirectorySetLastWriteTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); - else - SystemIO.IO_OS.FileSetLastWriteTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); - } - - if (metadata.TryGetValue("CoreCreatetime", out k) && long.TryParse(k, out t)) - { - if (isDirTarget) - SystemIO.IO_OS.DirectorySetCreationTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); - else - SystemIO.IO_OS.FileSetCreationTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); - } - - if (metadata.TryGetValue("CoreAttributes", out k) && Enum.TryParse(k, true, out fa)) - SystemIO.IO_OS.SetFileAttributes(targetpath, fa); - - SystemIO.IO_OS.SetMetadata(path, metadata, restorePermissions); - } - } - - private static void ScanForExistingSourceBlocksFast(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result) - { - // Fill BLOCKS with data from known local source files - using (var blockmarker = database.CreateBlockMarker()) - { - var updateCount = 0L; - foreach (var entry in database.GetFilesAndSourceBlocksFast(options.Blocksize)) - { - var targetpath = entry.TargetPath; - var targetfileid = entry.TargetFileID; - var sourcepath = entry.SourcePath; - var patched = false; - - try - { - if (SystemIO.IO_OS.FileExists(sourcepath)) - { - var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); - if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) - { - Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); - SystemIO.IO_OS.DirectoryCreate(folderpath); - } - - using (var targetstream = options.Dryrun ? null : SystemIO.IO_OS.FileOpenWrite(targetpath)) - { - try - { - using (var sourcestream = SystemIO.IO_OS.FileOpenRead(sourcepath)) - { - foreach (var block in entry.Blocks) - { - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - //TODO: Handle metadata - - if (sourcestream.Length > block.Offset) - { - sourcestream.Position = block.Offset; - - int size = Library.Utility.Utility.ForceStreamRead(sourcestream, blockbuffer, blockbuffer.Length); - if (size == block.Size) - { - var key = Convert.ToBase64String(hasher.ComputeHash(blockbuffer, 0, size)); - if (key == block.Hash) - { - patched = true; - if (!options.Dryrun) - { - targetstream.Position = block.Offset; - targetstream.Write(blockbuffer, 0, size); - } - - blockmarker.SetBlockRestored(targetfileid, block.Index, key, block.Size, false); - } - } - } - } - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, sourcepath, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - } - } - - if ((++updateCount) % 20 == 0) - { - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - } - - } - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "LocalSourceMissing", "Local source file not found: {0}", sourcepath); - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - if (options.UnittestMode) - throw; - } - - if (patched) - Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath); - else - Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath); - - if (patched && options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", "Would patch file with local data: {0}", targetpath); - } - - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - blockmarker.Commit(); - } - } - - private static void ScanForExistingSourceBlocks(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result, RestoreHandlerMetadataStorage metadatastorage) - { - // Fill BLOCKS with data from known local source files - using (var blockmarker = database.CreateBlockMarker()) - { - var updateCount = 0L; - foreach (var restorelist in database.GetFilesAndSourceBlocks(options.SkipMetadata, options.Blocksize)) - { - var targetpath = restorelist.TargetPath; - var targetfileid = restorelist.TargetFileID; - var patched = false; - try - { - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); - if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) - { - Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); - SystemIO.IO_OS.DirectoryCreate(folderpath); - } - - using (var file = options.Dryrun ? null : SystemIO.IO_OS.FileOpenWrite(targetpath)) - foreach (var targetblock in restorelist.Blocks) - { - foreach (var source in targetblock.Blocksources) - { - try - { - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - if (SystemIO.IO_OS.FileExists(source.Path)) - { - if (source.IsMetadata) - { - // TODO: Handle this by reconstructing - // metadata from file and checking the hash - - continue; - } - else - { - using (var sourcefile = SystemIO.IO_OS.FileOpenRead(source.Path)) - { - sourcefile.Position = source.Offset; - int size = Library.Utility.Utility.ForceStreamRead(sourcefile, blockbuffer, blockbuffer.Length); - if (size == targetblock.Size) - { - var key = Convert.ToBase64String(hasher.ComputeHash(blockbuffer, 0, size)); - if (key == targetblock.Hash) - { - if (!options.Dryrun) - { - if (targetblock.IsMetadata) - metadatastorage.Add(targetpath, new System.IO.MemoryStream(blockbuffer, 0, size)); - else - { - file.Position = targetblock.Offset; - file.Write(blockbuffer, 0, size); - } - } - - blockmarker.SetBlockRestored(targetfileid, targetblock.Index, key, targetblock.Size, false); - patched = true; - break; - } - } - } - } - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, source.Path, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - } - } - } - - if ((++updateCount) % 20 == 0) - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message); - if (options.UnittestMode) - throw; - } - - if (patched) - Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath); - else - Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath); - - if (patched && options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", string.Format("Would patch file with local data: {0}", targetpath)); - } - - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - blockmarker.Commit(); - } - } - - private static void PrepareBlockAndFileList(LocalRestoreDatabase database, Options options, Library.Utility.IFilter filter, RestoreResults result) - { - // Create a temporary table FILES by selecting the files from fileset that matches a specific operation id - // Delete all entries from the temp table that are excluded by the filter(s) - using (new Logging.Timer(LOGTAG, "PrepareRestoreFileList", "PrepareRestoreFileList")) - { - var c = database.PrepareRestoreFilelist(options.Time, options.Version, filter); - result.OperationProgressUpdater.UpdatefileCount(c.Item1, c.Item2, true); - } - - using (new Logging.Timer(LOGTAG, "SetTargetPaths", "SetTargetPaths")) - if (!string.IsNullOrEmpty(options.Restorepath)) - { - // Find the largest common prefix - var largest_prefix = options.DontCompressRestorePaths ? "" : database.GetLargestPrefix(); - - Logging.Log.WriteVerboseMessage(LOGTAG, "MappingRestorePath", "Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Util.AppendDirSeparator(options.Restorepath)); - - // Set the target paths, special care with C:\ and / - database.SetTargetPaths(largest_prefix, Util.AppendDirSeparator(options.Restorepath)); - } - else - { - database.SetTargetPaths("", ""); - } - - // Create a temporary table BLOCKS that lists all blocks that needs to be recovered - using (new Logging.Timer(LOGTAG, "FindMissingBlocks", "FindMissingBlocks")) - database.FindMissingBlocks(options.SkipMetadata); - - // Create temporary tables and triggers that automatically track progress - using (new Logging.Timer(LOGTAG, "CreateProgressTracker", "CreateProgressTracker")) - database.CreateProgressTracker(false); - - } - - private static void CreateDirectoryStructure(LocalRestoreDatabase database, Options options, RestoreResults result) - { - // This part is not protected by try/catch as we need the target folder to exist - if (!string.IsNullOrEmpty(options.Restorepath)) - if (!SystemIO.IO_OS.DirectoryExists(options.Restorepath)) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", options.Restorepath); - - if (options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", options.Restorepath); - else - SystemIO.IO_OS.DirectoryCreate(options.Restorepath); - } - - foreach (var folder in database.GetTargetFolders()) - { - try - { - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - if (!SystemIO.IO_OS.DirectoryExists(folder)) - { - result.RestoredFolders++; - - Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", folder); - - if (options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", folder); - else - SystemIO.IO_OS.DirectoryCreate(folder); - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateFailed", ex, "Failed to create folder: \"{0}\", message: {1}", folder, ex.Message); - if (options.UnittestMode) - throw; - } - } - } - - private static void ScanForExistingTargetBlocks(LocalRestoreDatabase database, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm blockhasher, System.Security.Cryptography.HashAlgorithm filehasher, Options options, RestoreResults result) - { - // Scan existing files for existing BLOCKS - using (var blockmarker = database.CreateBlockMarker()) - { - var updateCount = 0L; - foreach (var restorelist in database.GetExistingFilesWithBlocks()) - { - var rename = !options.Overwrite; - var targetpath = restorelist.TargetPath; - var targetfileid = restorelist.TargetFileID; - var targetfilehash = restorelist.TargetHash; - var targetfilelength = restorelist.Length; - if (SystemIO.IO_OS.FileExists(targetpath)) - { - try - { - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - - var currentfilelength = SystemIO.IO_OS.FileLength(targetpath); - var wasTruncated = false; - - // Adjust file length in overwrite mode if necessary (smaller is ok, will be extended during restore) - // We do it before scanning for blocks. This allows full verification on files that only needs to - // be truncated (i.e. forthwritten log files). - if (!rename && currentfilelength > targetfilelength) - { - var currentAttr = SystemIO.IO_OS.GetFileAttributes(targetpath); - if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0) // clear readonly attribute - { - if (options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribute", "Would reset read-only attribute on file: {0}", targetpath); - else SystemIO.IO_OS.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly); - } - if (options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldTruncateFile", "Would truncate file '{0}' to length of {1:N0} bytes", targetpath, targetfilelength); - else - { - using (var file = SystemIO.IO_OS.FileOpenWrite(targetpath)) - file.SetLength(targetfilelength); - currentfilelength = targetfilelength; - } - wasTruncated = true; - } - - // If file size does not match and we have to rename on conflict, - // the whole scan can be skipped here because all blocks have to be restored anyway. - // For the other cases, we will check block and and file hashes and look for blocks - // to be restored and files that can already be verified. - if (!rename || currentfilelength == targetfilelength) - { - // a file hash for verification will only be necessary if the file has exactly - // the wanted size so we have a chance to already mark the file as data-verified. - bool calcFileHash = (currentfilelength == targetfilelength); - if (calcFileHash) filehasher.Initialize(); - - using (var file = SystemIO.IO_OS.FileOpenRead(targetpath)) - using (var block = new Blockprocessor(file, blockbuffer)) - foreach (var targetblock in restorelist.Blocks) - { - var size = block.Readblock(); - if (size <= 0) - break; - - //TODO: Handle Metadata - - bool blockhashmatch = false; - if (size == targetblock.Size) - { - // Parallelize file hash calculation on rename. Running read-only on same array should not cause conflicts or races. - // Actually, in future always calculate the file hash and mark the file data as already verified. - - System.Threading.Tasks.Task calcFileHashTask = null; - if (calcFileHash) - calcFileHashTask = System.Threading.Tasks.Task.Run( - () => filehasher.TransformBlock(blockbuffer, 0, size, blockbuffer, 0)); - - var key = Convert.ToBase64String(blockhasher.ComputeHash(blockbuffer, 0, size)); - - if (calcFileHashTask != null) calcFileHashTask.Await(); // wait because blockbuffer will be overwritten. - - if (key == targetblock.Hash) - { - blockmarker.SetBlockRestored(targetfileid, targetblock.Index, key, size, false); - blockhashmatch = true; - } - } - if (calcFileHash && !blockhashmatch) // will not be necessary anymore - { - filehasher.TransformFinalBlock(blockbuffer, 0, 0); // So a new initialize will not throw - calcFileHash = false; - if (rename) // file does not match. So break. - break; - } - } - - bool fullfilehashmatch = false; - if (calcFileHash) // now check if files are identical - { - filehasher.TransformFinalBlock(blockbuffer, 0, 0); - var filekey = Convert.ToBase64String(filehasher.Hash); - fullfilehashmatch = (filekey == targetfilehash); - } - - if (!rename && !fullfilehashmatch && !wasTruncated) // Reset read-only attribute (if set) to overwrite - { - var currentAttr = SystemIO.IO_OS.GetFileAttributes(targetpath); - if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0) - { - if (options.Dryrun) - Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribyte", "Would reset read-only attribute on file: {0}", targetpath); - else SystemIO.IO_OS.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly); - } - } - - if (fullfilehashmatch) - { - //TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place. - blockmarker.SetFileDataVerified(targetfileid); - Logging.Log.WriteVerboseMessage(LOGTAG, "TargetExistsInCorrectVersion", "Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : ""); - rename = false; - } - else if (rename) - { - // The new file will have none of the correct blocks, - // even if the scanned file had some - blockmarker.SetAllBlocksMissing(targetfileid); - } - } - - if ((++updateCount) % 20 == 0) - { - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - if (result.TaskControlRendevouz() == TaskControlState.Stop) - return; - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "TargetFileReadError", ex, "Failed to read target file: \"{0}\", message: {1}", targetpath, ex.Message); - if (ex is System.Threading.ThreadAbortException) - throw; - if (options.UnittestMode) - throw; - } - } - else - { - Logging.Log.WriteVerboseMessage(LOGTAG, "MissingTargetFile", "Target file does not exist: {0}", targetpath); - rename = false; - } - - if (rename) - { - //Select a new filename - var ext = SystemIO.IO_OS.PathGetExtension(targetpath) ?? ""; - if (!string.IsNullOrEmpty(ext) && !ext.StartsWith(".", StringComparison.Ordinal)) - ext = "." + ext; - - // First we try with a simple date append, assuming that there are not many conflicts there - var newname = SystemIO.IO_OS.PathChangeExtension(targetpath, null) + "." + database.RestoreTime.ToLocalTime().ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); - var tr = newname + ext; - var c = 0; - while (SystemIO.IO_OS.FileExists(tr) && c < 1000) - { - try - { - // If we have a file with the correct name, - // it is most likely the file we want - filehasher.Initialize(); - - string key; - using (var file = SystemIO.IO_OS.FileOpenRead(tr)) - key = Convert.ToBase64String(filehasher.ComputeHash(file)); - - if (key == targetfilehash) - { - //TODO: Also needs metadata check to make correct decision. - // We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored. - blockmarker.SetAllBlocksRestored(targetfileid, false); - blockmarker.SetFileDataVerified(targetfileid); - break; - } - } - catch (Exception ex) - { - Logging.Log.WriteWarningMessage(LOGTAG, "FailedToReadRestoreTarget", ex, "Failed to read candidate restore target {0}", tr); - if (options.UnittestMode) - throw; - } - tr = newname + " (" + (c++).ToString() + ")" + ext; - } - - newname = tr; - - Logging.Log.WriteVerboseMessage(LOGTAG, "TargetFileRetargeted", "Target file exists and will be restored to: {0}", newname); - database.UpdateTargetPath(targetfileid, newname); - } - - } - - blockmarker.UpdateProcessed(result.OperationProgressUpdater); - blockmarker.Commit(); - } - } - } -} +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using Duplicati.Library.Interface; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Main.Volumes; +using Duplicati.Library.Utility; +using CoCoL; +using System.Threading.Tasks; +using System.Threading; + +namespace Duplicati.Library.Main.Operation +{ + internal class RestoreHandler + { + /// + /// The tag used for logging + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + private readonly Options m_options; + private byte[] m_blockbuffer; + private readonly RestoreResults m_result; + private static readonly string DIRSEP = Util.DirectorySeparatorString; + + public RestoreHandler(Options options, RestoreResults result) + { + m_options = options; + m_result = result; + } + + /// + /// Gets the compression module by parsing the filename + /// + /// The filename to parse + /// The compression module + public static string GetCompressionModule(string filename) + { + var tmp = VolumeBase.ParseFilename(filename); + if (tmp == null) + throw new UserInformationException(string.Format("Unable to parse filename to valid entry: {0}", filename), "FailedToParseRemoteName"); + + return tmp.CompressionModule; + } + + public static RecreateDatabaseHandler.NumberedFilterFilelistDelegate FilterNumberedFilelist(DateTime time, long[] versions, bool singleTimeMatch = false) + { + if (time.Kind == DateTimeKind.Unspecified) + throw new Exception("Unspecified datetime instance, must be either local or UTC"); + + // Make sure the resolution is the same (i.e. no milliseconds) + if (time.Ticks > 0) + time = Library.Utility.Utility.DeserializeDateTime(Library.Utility.Utility.SerializeDateTime(time)).ToUniversalTime(); + + return + _lst => + { + // Unwrap, so we do not query the remote storage twice + var lst = (from n in _lst + where n.FileType == RemoteVolumeType.Files + orderby n.Time descending + select n).ToArray(); + + var numbers = lst.Zip(Enumerable.Range(0, lst.Length), (a, b) => new KeyValuePair(b, a)).ToList(); + + if (time.Ticks > 0 && versions != null && versions.Length > 0) + return from n in numbers + where (singleTimeMatch ? n.Value.Time == time : n.Value.Time <= time) && versions.Contains(n.Key) + select n; + else if (time.Ticks > 0) + return from n in numbers + where (singleTimeMatch ? n.Value.Time == time : n.Value.Time <= time) + select n; + else if (versions != null && versions.Length > 0) + return from n in numbers + where versions.Contains(n.Key) + select n; + else + return numbers; + }; + } + + public async Task RunAsync(string[] paths, IBackendManager backendManager, Library.Utility.IFilter filter) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_Begin); + + // If we have both target paths and a filter, combine them into a single filter + filter = JoinedFilterExpression.Join(new FilterExpression(paths), filter); + + LocalRestoreDatabase db = null; + TempFile tmpdb = null; + try + { + if (!m_options.NoLocalDb && SystemIO.IO_OS.FileExists(m_options.Dbpath)) + { + db = new LocalRestoreDatabase(m_options.Dbpath); + } + else + { + Logging.Log.WriteInformationMessage(LOGTAG, "NoLocalDatabase", "No local database, building a temporary database"); + tmpdb = new TempFile(); + RecreateDatabaseHandler.NumberedFilterFilelistDelegate filelistfilter = FilterNumberedFilelist(m_options.Time, m_options.Version); + db = new LocalRestoreDatabase(tmpdb); + m_result.RecreateDatabaseResults = new RecreateDatabaseResults(m_result); + using (new Logging.Timer(LOGTAG, "RecreateTempDbForRestore", "Recreate temporary database for restore")) + await new RecreateDatabaseHandler(m_options, (RecreateDatabaseResults)m_result.RecreateDatabaseResults) + .DoRunAsync(backendManager, db, false, filter, filelistfilter, null) + .ConfigureAwait(false); + + if (!m_options.SkipMetadata) + ApplyStoredMetadata(m_options, new RestoreHandlerMetadataStorage()); + + //If we have --version set, we need to adjust, as the db has only the required versions + //TODO: Bit of a hack to set options that way + if (m_options.Version != null && m_options.Version.Length > 0) + m_options.RawOptions["version"] = string.Join(",", Enumerable.Range(0, m_options.Version.Length).Select(x => x.ToString())); + } + + if (m_options.RestoreLegacy) + await DoRunAsync(backendManager, db, filter, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + else + await DoRunNewAsync(backendManager, db, filter, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + } + finally + { + db?.Dispose(); + tmpdb?.Dispose(); + } + } + + private static void PatchWithBlocklist(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage) + { + var blocksize = options.Blocksize; + var updateCounter = 0L; + var fullblockverification = options.FullBlockVerification; + + using (var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) + using (var blockmarker = database.CreateBlockMarker()) + using (var volumekeeper = database.GetMissingBlockData(blocks, options.Blocksize)) + { + foreach (var restorelist in volumekeeper.FilesWithMissingBlocks) + { + var targetpath = restorelist.Path; + + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchFile", "Would patch file with remote data: {0}", targetpath); + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingFile", "Patching file with remote data: {0}", targetpath); + + try + { + var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); + if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) + { + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); + SystemIO.IO_OS.DirectoryCreate(folderpath); + } + + // TODO: Much faster if we iterate the volume and checks what blocks are used, + // because the compressors usually like sequential reading + using (var file = SystemIO.IO_OS.FileOpenWrite(targetpath)) + foreach (var targetblock in restorelist.Blocks) + { + file.Position = targetblock.Offset; + var size = blocks.ReadBlock(targetblock.Key, blockbuffer); + if (targetblock.Size == size) + { + var valid = !fullblockverification; + if (!valid) + { + var key = Convert.ToBase64String(blockhasher.ComputeHash(blockbuffer, 0, size)); + if (targetblock.Key == key) + valid = true; + else + Logging.Log.WriteWarningMessage(LOGTAG, "InvalidBlock", null, "Invalid block detected for {0}, expected hash: {1}, actual hash: {2}", targetpath, targetblock.Key, key); + } + + if (valid) + { + file.Write(blockbuffer, 0, size); + blockmarker.SetBlockRestored(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false); + } + } + else + { + Logging.Log.WriteWarningMessage(LOGTAG, "WrongBlockSize", null, "Block with hash {0} should have size {1} but has size {2}", targetblock.Key, targetblock.Size, size); + } + } + + if ((++updateCounter) % 20 == 0) + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "PatchFailed", ex, "Failed to patch file: \"{0}\", message: {1}, message: {1}", targetpath, ex.Message); + if (options.UnittestMode) + throw; + } + } + } + + if (!options.SkipMetadata) + { + foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocks) + { + var targetpath = restoremetadata.Path; + Logging.Log.WriteVerboseMessage(LOGTAG, "RecordingMetadata", "Recording metadata from remote data: {0}", targetpath); + + try + { + // TODO: When we support multi-block metadata this needs to deal with it + using (var ms = new System.IO.MemoryStream()) + { + foreach (var targetblock in restoremetadata.Blocks) + { + ms.Position = targetblock.Offset; + var size = blocks.ReadBlock(targetblock.Key, blockbuffer); + if (targetblock.Size == size) + { + ms.Write(blockbuffer, 0, size); + blockmarker.SetBlockRestored(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true); + } + } + + ms.Position = 0; + metadatastorage.Add(targetpath, ms); + //blockmarker.RecordMetadata(restoremetadata.FileID, ms); + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "MetatdataRecordFailed", ex, "Failed to record metadata for file: \"{0}\", message: {1}", targetpath, ex.Message); + if (options.UnittestMode) + throw; + } + } + } + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + blockmarker.Commit(); + } + } + + private static void ApplyStoredMetadata(Options options, RestoreHandlerMetadataStorage metadatastorage) + { + foreach (var metainfo in metadatastorage.Records) + { + var targetpath = metainfo.Key; + + if (options.Dryrun) + { + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchMetadata", "Would patch metadata with remote data: {0}", targetpath); + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "PatchingMetadata", "Patching metadata with remote data: {0}", targetpath); + try + { + var folderpath = Duplicati.Library.Utility.Utility.GetParent(targetpath, false); + if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) + { + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for target {1}", folderpath, targetpath); + SystemIO.IO_OS.DirectoryCreate(folderpath); + } + + ApplyMetadata(targetpath, metainfo.Value, options.RestorePermissions, options.RestoreSymlinkMetadata, options.Dryrun); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "MetadataWriteFailed", ex, "Failed to apply metadata to file: \"{0}\", message: {1}", targetpath, ex.Message); + if (options.UnittestMode) + throw; + } + } + } + } + + /// + /// Perform the restore operation. + /// This is the new implementation, which utilizes a CSP network of processes to perform the restore. + /// + /// The database containing information about the restore. + /// The filter of which files to restore. + private async Task DoRunNewAsync(IBackendManager backendManager, LocalRestoreDatabase database, Library.Utility.IFilter filter, CancellationToken cancellationToken) + { + // Perform initial setup + Utility.UpdateOptionsFromDb(database, m_options); + Utility.VerifyOptionsAndUpdateDatabase(database, m_options); + + // Verify the backend if necessary + if (!m_options.NoBackendverification) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, null).ConfigureAwait(false); + } + + // Prepare the block and file list and create the directory structure + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList); + using (new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList")) + PrepareBlockAndFileList(database, m_options, filter, m_result); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders); + using (new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory")) + await CreateDirectoryStructure(database, m_options, m_result).ConfigureAwait(false); + + using var setup_log_timer = new Logging.Timer(LOGTAG, "RestoreNetworkSetup", "RestoreNetworkSetup"); + // Create the channels between BlockManager and FileProcessor + Restore.Channels.BufferSize = m_options.RestoreChannelBufferSize; + var fileprocessor_requests = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(_ => ChannelManager.CreateChannel(buffersize: Restore.Channels.BufferSize)).ToArray(); + var fileprocessor_responses = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(_ => ChannelManager.CreateChannel>(buffersize: Restore.Channels.BufferSize)).ToArray(); + + // Create the process network + Restore.Channels channels = new(); + var filelister = Restore.FileLister.Run(channels, database, m_options, m_result); + Restore.FileProcessor.file_processors_restoring_files = m_options.RestoreFileProcessors; + var fileprocessors = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(i => Restore.FileProcessor.Run(channels, database, fileprocessor_requests[i], fileprocessor_responses[i], m_options, m_result)).ToArray(); + var blockmanager = Restore.BlockManager.Run(channels, database, m_options, fileprocessor_requests, fileprocessor_responses); + var volumecache = Restore.VolumeManager.Run(channels, m_options); + var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, backendManager, m_options, m_result)).ToArray(); + var volumedecryptors = Enumerable.Range(0, m_options.RestoreVolumeDecryptors).Select(i => Restore.VolumeDecryptor.Run(channels, backendManager, m_options)).ToArray(); + var volumedecompressors = Enumerable.Range(0, m_options.RestoreVolumeDecompressors).Select(i => Restore.VolumeDecompressor.Run(channels, m_options)).ToArray(); + + setup_log_timer.Dispose(); + + // Wait for the network to complete + Task[] all = + [ + filelister, + ..fileprocessors, + blockmanager, + volumecache, + ..volumedownloaders, + ..volumedecryptors, + ..volumedecompressors + ]; + + // Start the progress updater + using (new Logging.Timer(LOGTAG, "RestoreNetworkWait", "RestoreNetworkWait")) + using (var kill_updater = new CancellationTokenSource()) + { + var updater = Task.Run(async () => + { + while (!kill_updater.Token.IsCancellationRequested) + { + m_result.OperationProgressUpdater.UpdatefilesProcessed(m_result.RestoredFiles, m_result.SizeOfRestoredFiles); + await Task.Delay(1000, kill_updater.Token).ConfigureAwait(false); + } + }, kill_updater.Token); + + await Task.WhenAll(all).ConfigureAwait(false); + kill_updater.Cancel(); + } + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PostRestoreVerify); + + // If any errors occurred, log them + if (m_result.BrokenRemoteFiles.Count > 0 || m_result.BrokenLocalFiles.Count > 0) + { + var nl = Environment.NewLine; + int maxN = 10; + long remoteFirstN = Math.Min(maxN, m_result.BrokenRemoteFiles.Count); + string remoteFirst = remoteFirstN < m_result.BrokenRemoteFiles.Count ? $"first {maxN} " : string.Empty; + long localFirstN = Math.Min(maxN, m_result.BrokenLocalFiles.Count); + string localFirst = localFirstN < m_result.BrokenLocalFiles.Count ? $"first {maxN} " : string.Empty; + + string remoteMessage = m_result.BrokenRemoteFiles.Count > 0 ? $"Failed to download {m_result.BrokenRemoteFiles.Count} remote files." : string.Empty; + string remoteList = m_result.BrokenRemoteFiles.Count > 0 ? $"The following {remoteFirst}remote files failed to download, which may be the cause:{nl}{string.Join(nl, m_result.BrokenRemoteFiles.Take(maxN))}{nl}" : string.Empty; + string localMessage = m_result.BrokenLocalFiles.Count > 0 ? $"Failed to restore {m_result.BrokenLocalFiles.Count} local files." : string.Empty; + string localList = m_result.BrokenLocalFiles.Count > 0 ? $"The following {localFirst}local files failed to restore:{nl}{string.Join(nl, m_result.BrokenLocalFiles.Take(maxN))}{nl}" : string.Empty; + + Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFailures", null, $"{remoteMessage}{nl}{localMessage}{nl}{remoteList}{nl}{localList}"); + } + else if (m_result.RestoredFiles == 0) + Logging.Log.WriteWarningMessage(LOGTAG, "NoFilesRestored", null, "Restore completed without errors but no files were restored"); + + // Drop the temp tables + database.DropRestoreTable(); + await backendManager.WaitForEmptyAsync(database, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + + // Report that the restore is complete + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_Complete); + m_result.EndTime = DateTime.UtcNow; + } + + /// + /// Perform the restore operation. + /// This is the legacy implementation, which performs the restore in a single thread. Kept as in case the new implementation fails. + /// + /// The database containing information about the restore. + /// The filter of which files to restore. + private async Task DoRunAsync(IBackendManager backendManager, LocalRestoreDatabase database, Library.Utility.IFilter filter, CancellationToken cancellationToken) + { + //In this case, we check that the remote storage fits with the database. + //We can then query the database and find the blocks that we need to do the restore + //using (var database = new LocalRestoreDatabase(dbparent)) + using (var metadatastorage = new RestoreHandlerMetadataStorage()) + { + Utility.UpdateOptionsFromDb(database, m_options); + Utility.VerifyOptionsAndUpdateDatabase(database, m_options); + m_blockbuffer = new byte[m_options.Blocksize]; + + if (!m_options.NoBackendverification) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, null).ConfigureAwait(false); + } + + //Figure out what files are to be patched, and what blocks are needed + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList); + using (new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList")) + PrepareBlockAndFileList(database, m_options, filter, m_result); + + //Make the entire output setup + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders); + using (new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory")) + await CreateDirectoryStructure(database, m_options, m_result); + + //If we are patching an existing target folder, do not touch stuff that is already updated + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForExistingFiles); + using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) + using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) + using (new Logging.Timer(LOGTAG, "ScanForExistingTargetBlocks", "ScanForExistingTargetBlocks")) + await ScanForExistingTargetBlocks(database, m_blockbuffer, blockhasher, filehasher, m_options, m_result).ConfigureAwait(false); + + //Look for existing blocks in the original source files only + if (m_options.UseLocalBlocks && !string.IsNullOrEmpty(m_options.Restorepath)) + { + using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) + using (new Logging.Timer(LOGTAG, "ScanForExistingSourceBlocksFast", "ScanForExistingSourceBlocksFast")) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForLocalBlocks); + await ScanForExistingSourceBlocksFast(database, m_options, m_blockbuffer, blockhasher, m_result).ConfigureAwait(false); + } + } + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); + return; + } + + // If other local files already have the blocks we want, we use them instead of downloading + if (m_options.UseLocalBlocks) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PatchWithLocalBlocks); + using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) + using (new Logging.Timer(LOGTAG, "PatchWithLocalBlocks", "PatchWithLocalBlocks")) + await ScanForExistingSourceBlocks(database, m_options, m_blockbuffer, blockhasher, m_result, metadatastorage).ConfigureAwait(false); + } + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); + return; + } + + // Fill BLOCKS with remote sources + List volumes; + using (new Logging.Timer(LOGTAG, "GetMissingVolumes", "GetMissingVolumes")) + volumes = database.GetMissingVolumes().ToList(); + + if (volumes.Count > 0) + { + Logging.Log.WriteInformationMessage(LOGTAG, "RemoteFileCount", "{0} remote files are required to restore", volumes.Count); + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_DownloadingRemoteFiles); + } + + var brokenFiles = new List(); + + using (new Logging.Timer(LOGTAG, "PatchWithBlocklist", "PatchWithBlocklist")) + await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(volumes, cancellationToken).ConfigureAwait(false)) + { + try + { + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); + return; + } + + using (tmpfile) + using (var blocks = new BlockVolumeReader(GetCompressionModule(name), tmpfile, m_options)) + PatchWithBlocklist(database, blocks, m_options, m_result, m_blockbuffer, metadatastorage); + } + catch (Exception ex) + { + brokenFiles.Add(name); + Logging.Log.WriteErrorMessage(LOGTAG, "PatchingFailed", ex, "Failed to patch with remote file: \"{0}\", message: {1}", name, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + + var fileErrors = 0L; + + // Restore empty files. They might not have any blocks so don't appear in any volume. + foreach (var file in database.GetFilesToRestore(true).Where(item => item.Length == 0)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreEmptyFile", "Restoring empty file \"{0}\"", file.Path); + + try + { + SystemIO.IO_OS.DirectoryCreate(SystemIO.IO_OS.PathGetDirectoryName(file.Path)); + // Just create the file and close it right away, empty statement is intentional. + using (SystemIO.IO_OS.FileCreate(file.Path)) + { + } + } + catch (Exception ex) + { + fileErrors++; + Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFileFailed", ex, "Failed to restore empty file: \"{0}\". Error message was: {1}", file.Path, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + + // Enforcing the length of files is now already done during ScanForExistingTargetBlocks + // and thus not necessary anymore. + + // Apply metadata + if (!m_options.SkipMetadata) + ApplyStoredMetadata(m_options, metadatastorage); + + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PostRestoreVerify); + + + if (m_options.PerformRestoredFileVerification) + { + // After all blocks in the files are restored, verify the file hash + using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) + using (new Logging.Timer(LOGTAG, "RestoreVerification", "RestoreVerification")) + foreach (var file in database.GetFilesToRestore(true)) + { + try + { + if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + { + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); + return; + } + + Logging.Log.WriteVerboseMessage(LOGTAG, "TestFileIntegrity", "Testing restored file integrity: {0}", file.Path); + + string key; + long size; + using (var fs = SystemIO.IO_OS.FileOpenRead(file.Path)) + { + size = fs.Length; + key = Convert.ToBase64String(filehasher.ComputeHash(fs)); + } + + if (key != file.Hash) + throw new Exception(string.Format("Failed to restore file: \"{0}\". File hash is {1}, expected hash is {2}", file.Path, key, file.Hash)); + m_result.RestoredFiles++; + m_result.SizeOfRestoredFiles += size; + } + catch (Exception ex) + { + fileErrors++; + Logging.Log.WriteErrorMessage(LOGTAG, "RestoreFileFailed", ex, "Failed to restore file: \"{0}\". Error message was: {1}", file.Path, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + } + + if (fileErrors > 0 && brokenFiles.Count > 0) + Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files, additionally the following files failed to download, which may be the cause:{1}{2}", fileErrors, Environment.NewLine, string.Join(Environment.NewLine, brokenFiles)); + else if (fileErrors > 0) + Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files", fileErrors); + else if (m_result.RestoredFiles == 0) + Logging.Log.WriteWarningMessage(LOGTAG, "NoFilesRestored", null, "Restore completed without errors but no files were restored"); + + // Drop the temp tables + database.DropRestoreTable(); + await backendManager.WaitForEmptyAsync(database, null, cancellationToken).ConfigureAwait(false); + } + + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_Complete); + m_result.EndTime = DateTime.UtcNow; + } + + public static bool ApplyMetadata(string path, System.IO.Stream stream, bool restorePermissions, bool restoreSymlinkMetadata, bool dryrun) + { + // TODO This has been modified to return a bool indicating if anything was written to properly report the number of files restored. The legacy restore doesn't check this, which produces an error in the CI where it reports that no files have been restored, even though one have. It's in Duplicati/UnitTests/SymLinkTests.cs the test SymLinkTests.SymLinkExists() that fails on the very last assert that there are 0 warnings. + using (var tr = new System.IO.StreamReader(stream)) + using (var jr = new Newtonsoft.Json.JsonTextReader(tr)) + { + var metadata = new Newtonsoft.Json.JsonSerializer().Deserialize>(jr); + string k; + long t; + System.IO.FileAttributes fa; + var wrote_something = false; + + // If this is dry-run, we stop after having deserialized the metadata + if (dryrun) + return wrote_something; + + var isDirTarget = path.EndsWith(DIRSEP, StringComparison.Ordinal); + var targetpath = isDirTarget ? path.Substring(0, path.Length - 1) : path; + + // Make the symlink first, otherwise we cannot apply metadata to it + if (metadata.TryGetValue("CoreSymlinkTarget", out k)) + { + // Check if the target exists, and overwrite it if it does. + if (SystemIO.IO_OS.FileExists(targetpath)) + { + SystemIO.IO_OS.FileDelete(targetpath); + } + else if (SystemIO.IO_OS.DirectoryExists(targetpath)) + { + SystemIO.IO_OS.DirectoryDelete(targetpath, false); + } + SystemIO.IO_OS.CreateSymlink(targetpath, k, isDirTarget); + wrote_something = true; + } + // If the target is a folder, make sure we create it first + else if (isDirTarget && !SystemIO.IO_OS.DirectoryExists(targetpath)) + SystemIO.IO_OS.DirectoryCreate(targetpath); + + // Avoid setting restoring symlink metadata, as that writes the symlink target, not the symlink itself + if (!restoreSymlinkMetadata && Snapshots.SnapshotUtility.IsSymlink(SystemIO.IO_OS, targetpath)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "no-symlink-metadata-restored", "Not applying metadata to symlink: {0}", targetpath); + return wrote_something; + } + + if (metadata.TryGetValue("CoreLastWritetime", out k) && long.TryParse(k, out t)) + { + if (isDirTarget) + SystemIO.IO_OS.DirectorySetLastWriteTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); + else + SystemIO.IO_OS.FileSetLastWriteTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); + } + + if (metadata.TryGetValue("CoreCreatetime", out k) && long.TryParse(k, out t)) + { + if (isDirTarget) + SystemIO.IO_OS.DirectorySetCreationTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); + else + SystemIO.IO_OS.FileSetCreationTimeUtc(targetpath, new DateTime(t, DateTimeKind.Utc)); + } + + if (metadata.TryGetValue("CoreAttributes", out k) && Enum.TryParse(k, true, out fa)) + SystemIO.IO_OS.SetFileAttributes(targetpath, fa); + + SystemIO.IO_OS.SetMetadata(path, metadata, restorePermissions); + return wrote_something; + } + } + + private static async Task ScanForExistingSourceBlocksFast(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result) + { + // Fill BLOCKS with data from known local source files + using (var blockmarker = database.CreateBlockMarker()) + { + var updateCount = 0L; + foreach (var entry in database.GetFilesAndSourceBlocksFast(options.Blocksize)) + { + var targetpath = entry.TargetPath; + var targetfileid = entry.TargetFileID; + var sourcepath = entry.SourcePath; + var patched = false; + + try + { + if (SystemIO.IO_OS.FileExists(sourcepath)) + { + var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); + if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) + { + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); + SystemIO.IO_OS.DirectoryCreate(folderpath); + } + + using (var targetstream = options.Dryrun ? null : SystemIO.IO_OS.FileOpenWrite(targetpath)) + { + try + { + using (var sourcestream = SystemIO.IO_OS.FileOpenRead(sourcepath)) + { + foreach (var block in entry.Blocks) + { + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + //TODO: Handle metadata + + if (sourcestream.Length > block.Offset) + { + sourcestream.Position = block.Offset; + + int size = Library.Utility.Utility.ForceStreamRead(sourcestream, blockbuffer, blockbuffer.Length); + if (size == block.Size) + { + var key = Convert.ToBase64String(hasher.ComputeHash(blockbuffer, 0, size)); + if (key == block.Hash) + { + patched = true; + if (!options.Dryrun) + { + targetstream.Position = block.Offset; + targetstream.Write(blockbuffer, 0, size); + } + + blockmarker.SetBlockRestored(targetfileid, block.Index, key, block.Size, false); + } + } + } + } + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, sourcepath, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + + if ((++updateCount) % 20 == 0) + { + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + } + + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "LocalSourceMissing", "Local source file not found: {0}", sourcepath); + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message); + if (ex.IsAbortException()) + throw; + if (options.UnittestMode) + throw; + } + + if (patched) + Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath); + else + Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath); + + if (patched && options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", "Would patch file with local data: {0}", targetpath); + } + + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + blockmarker.Commit(); + } + } + + private static async Task ScanForExistingSourceBlocks(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result, RestoreHandlerMetadataStorage metadatastorage) + { + // Fill BLOCKS with data from known local source files + using (var blockmarker = database.CreateBlockMarker()) + { + var updateCount = 0L; + foreach (var restorelist in database.GetFilesAndSourceBlocks(options.SkipMetadata, options.Blocksize)) + { + var targetpath = restorelist.TargetPath; + var targetfileid = restorelist.TargetFileID; + var patched = false; + try + { + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + var folderpath = SystemIO.IO_OS.PathGetDirectoryName(targetpath); + if (!options.Dryrun && !SystemIO.IO_OS.DirectoryExists(folderpath)) + { + Logging.Log.WriteWarningMessage(LOGTAG, "CreateMissingFolder", null, "Creating missing folder {0} for file {1}", folderpath, targetpath); + SystemIO.IO_OS.DirectoryCreate(folderpath); + } + + using (var file = options.Dryrun ? null : SystemIO.IO_OS.FileOpenWrite(targetpath)) + foreach (var targetblock in restorelist.Blocks) + { + foreach (var source in targetblock.Blocksources) + { + try + { + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + if (SystemIO.IO_OS.FileExists(source.Path)) + { + if (source.IsMetadata) + { + // TODO: Handle this by reconstructing + // metadata from file and checking the hash + + continue; + } + else + { + using (var sourcefile = SystemIO.IO_OS.FileOpenRead(source.Path)) + { + sourcefile.Position = source.Offset; + int size = Library.Utility.Utility.ForceStreamRead(sourcefile, blockbuffer, blockbuffer.Length); + if (size == targetblock.Size) + { + var key = Convert.ToBase64String(hasher.ComputeHash(blockbuffer, 0, size)); + if (key == targetblock.Hash) + { + if (!options.Dryrun) + { + if (targetblock.IsMetadata) + metadatastorage.Add(targetpath, new System.IO.MemoryStream(blockbuffer, 0, size)); + else + { + file.Position = targetblock.Offset; + file.Write(blockbuffer, 0, size); + } + } + + blockmarker.SetBlockRestored(targetfileid, targetblock.Index, key, targetblock.Size, false); + patched = true; + break; + } + } + } + } + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with data from local file \"{1}\", message: {2}", targetpath, source.Path, ex.Message); + if (ex.IsAbortException()) + throw; + } + } + } + + if ((++updateCount) % 20 == 0) + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "PatchingFileLocalFailed", ex, "Failed to patch file: \"{0}\" with local data, message: {1}", targetpath, ex.Message); + if (options.UnittestMode) + throw; + } + + if (patched) + Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is patched with some local data: {0}", targetpath); + else + Logging.Log.WriteVerboseMessage(LOGTAG, "FilePatchedWithLocal", "Target file is not patched any local data: {0}", targetpath); + + if (patched && options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldPatchWithLocal", string.Format("Would patch file with local data: {0}", targetpath)); + } + + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + blockmarker.Commit(); + } + } + + private static void PrepareBlockAndFileList(LocalRestoreDatabase database, Options options, Library.Utility.IFilter filter, RestoreResults result) + { + // Create a temporary table FILES by selecting the files from fileset that matches a specific operation id + // Delete all entries from the temp table that are excluded by the filter(s) + using (new Logging.Timer(LOGTAG, "PrepareRestoreFileList", "PrepareRestoreFileList")) + { + var c = database.PrepareRestoreFilelist(options.Time, options.Version, filter); + result.OperationProgressUpdater.UpdatefileCount(c.Item1, c.Item2, true); + } + + using (new Logging.Timer(LOGTAG, "SetTargetPaths", "SetTargetPaths")) + if (!string.IsNullOrEmpty(options.Restorepath)) + { + // Find the largest common prefix + var largest_prefix = options.DontCompressRestorePaths ? "" : database.GetLargestPrefix(); + + Logging.Log.WriteVerboseMessage(LOGTAG, "MappingRestorePath", "Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Util.AppendDirSeparator(options.Restorepath)); + + // Set the target paths, special care with C:\ and / + database.SetTargetPaths(largest_prefix, Util.AppendDirSeparator(options.Restorepath)); + } + else + { + database.SetTargetPaths("", ""); + } + + // Create a temporary table BLOCKS that lists all blocks that needs to be recovered + using (new Logging.Timer(LOGTAG, "FindMissingBlocks", "FindMissingBlocks")) + database.FindMissingBlocks(options.SkipMetadata); + + // Create temporary tables and triggers that automatically track progress + using (new Logging.Timer(LOGTAG, "CreateProgressTracker", "CreateProgressTracker")) + database.CreateProgressTracker(false); + + } + + private static async Task CreateDirectoryStructure(LocalRestoreDatabase database, Options options, RestoreResults result) + { + // This part is not protected by try/catch as we need the target folder to exist + if (!string.IsNullOrEmpty(options.Restorepath)) + if (!SystemIO.IO_OS.DirectoryExists(options.Restorepath)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", options.Restorepath); + + if (options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", options.Restorepath); + else + SystemIO.IO_OS.DirectoryCreate(options.Restorepath); + } + + foreach (var folder in database.GetTargetFolders()) + { + try + { + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + if (!SystemIO.IO_OS.DirectoryExists(folder)) + { + result.RestoredFolders++; + + Logging.Log.WriteVerboseMessage(LOGTAG, "CreateFolder", "Creating folder: {0}", folder); + + if (options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldCreateFolder", "Would create folder: {0}", folder); + else + SystemIO.IO_OS.DirectoryCreate(folder); + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FolderCreateFailed", ex, "Failed to create folder: \"{0}\", message: {1}", folder, ex.Message); + if (options.UnittestMode) + throw; + } + } + } + + private static async Task ScanForExistingTargetBlocks(LocalRestoreDatabase database, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm blockhasher, System.Security.Cryptography.HashAlgorithm filehasher, Options options, RestoreResults result) + { + // Scan existing files for existing BLOCKS + using (var blockmarker = database.CreateBlockMarker()) + { + var updateCount = 0L; + foreach (var restorelist in database.GetExistingFilesWithBlocks()) + { + var rename = !options.Overwrite; + var targetpath = restorelist.TargetPath; + var targetfileid = restorelist.TargetFileID; + var targetfilehash = restorelist.TargetHash; + var targetfilelength = restorelist.Length; + if (SystemIO.IO_OS.FileExists(targetpath)) + { + try + { + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + + var currentfilelength = SystemIO.IO_OS.FileLength(targetpath); + var wasTruncated = false; + + // Adjust file length in overwrite mode if necessary (smaller is ok, will be extended during restore) + // We do it before scanning for blocks. This allows full verification on files that only needs to + // be truncated (i.e. forthwritten log files). + if (!rename && currentfilelength > targetfilelength) + { + var currentAttr = SystemIO.IO_OS.GetFileAttributes(targetpath); + if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0) // clear readonly attribute + { + if (options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribute", "Would reset read-only attribute on file: {0}", targetpath); + else SystemIO.IO_OS.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly); + } + if (options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldTruncateFile", "Would truncate file '{0}' to length of {1:N0} bytes", targetpath, targetfilelength); + else + { + using (var file = SystemIO.IO_OS.FileOpenWrite(targetpath)) + file.SetLength(targetfilelength); + currentfilelength = targetfilelength; + } + wasTruncated = true; + } + + // If file size does not match and we have to rename on conflict, + // the whole scan can be skipped here because all blocks have to be restored anyway. + // For the other cases, we will check block and and file hashes and look for blocks + // to be restored and files that can already be verified. + if (!rename || currentfilelength == targetfilelength) + { + // a file hash for verification will only be necessary if the file has exactly + // the wanted size so we have a chance to already mark the file as data-verified. + bool calcFileHash = (currentfilelength == targetfilelength); + if (calcFileHash) filehasher.Initialize(); + + using (var file = SystemIO.IO_OS.FileOpenRead(targetpath)) + using (var block = new Blockprocessor(file, blockbuffer)) + foreach (var targetblock in restorelist.Blocks) + { + var size = block.Readblock(); + if (size <= 0) + break; + + //TODO: Handle Metadata + + bool blockhashmatch = false; + if (size == targetblock.Size) + { + // Parallelize file hash calculation on rename. Running read-only on same array should not cause conflicts or races. + // Actually, in future always calculate the file hash and mark the file data as already verified. + + System.Threading.Tasks.Task calcFileHashTask = null; + if (calcFileHash) + calcFileHashTask = System.Threading.Tasks.Task.Run( + () => filehasher.TransformBlock(blockbuffer, 0, size, blockbuffer, 0)); + + var key = Convert.ToBase64String(blockhasher.ComputeHash(blockbuffer, 0, size)); + + if (calcFileHashTask != null) await calcFileHashTask.ConfigureAwait(false); // wait because blockbuffer will be overwritten. + + if (key == targetblock.Hash) + { + blockmarker.SetBlockRestored(targetfileid, targetblock.Index, key, size, false); + blockhashmatch = true; + } + } + if (calcFileHash && !blockhashmatch) // will not be necessary anymore + { + filehasher.TransformFinalBlock(blockbuffer, 0, 0); // So a new initialize will not throw + calcFileHash = false; + if (rename) // file does not match. So break. + break; + } + } + + bool fullfilehashmatch = false; + if (calcFileHash) // now check if files are identical + { + filehasher.TransformFinalBlock(blockbuffer, 0, 0); + var filekey = Convert.ToBase64String(filehasher.Hash); + fullfilehashmatch = (filekey == targetfilehash); + } + + if (!rename && !fullfilehashmatch && !wasTruncated) // Reset read-only attribute (if set) to overwrite + { + var currentAttr = SystemIO.IO_OS.GetFileAttributes(targetpath); + if ((currentAttr & System.IO.FileAttributes.ReadOnly) != 0) + { + if (options.Dryrun) + Logging.Log.WriteDryrunMessage(LOGTAG, "WouldResetReadOnlyAttribyte", "Would reset read-only attribute on file: {0}", targetpath); + else SystemIO.IO_OS.SetFileAttributes(targetpath, currentAttr & ~System.IO.FileAttributes.ReadOnly); + } + } + + if (fullfilehashmatch) + { + //TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place. + blockmarker.SetFileDataVerified(targetfileid); + Logging.Log.WriteVerboseMessage(LOGTAG, "TargetExistsInCorrectVersion", "Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : ""); + rename = false; + } + else if (rename) + { + // The new file will have none of the correct blocks, + // even if the scanned file had some + blockmarker.SetAllBlocksMissing(targetfileid); + } + } + + if ((++updateCount) % 20 == 0) + { + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + return; + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "TargetFileReadError", ex, "Failed to read target file: \"{0}\", message: {1}", targetpath, ex.Message); + if (ex.IsAbortException()) + throw; + if (options.UnittestMode) + throw; + } + } + else + { + Logging.Log.WriteVerboseMessage(LOGTAG, "MissingTargetFile", "Target file does not exist: {0}", targetpath); + rename = false; + } + + if (rename) + { + //Select a new filename + var ext = SystemIO.IO_OS.PathGetExtension(targetpath) ?? ""; + if (!string.IsNullOrEmpty(ext) && !ext.StartsWith(".", StringComparison.Ordinal)) + ext = "." + ext; + + // First we try with a simple date append, assuming that there are not many conflicts there + var newname = SystemIO.IO_OS.PathChangeExtension(targetpath, null) + "." + database.RestoreTime.ToLocalTime().ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture); + var tr = newname + ext; + var c = 0; + while (SystemIO.IO_OS.FileExists(tr) && c < 1000) + { + try + { + // If we have a file with the correct name, + // it is most likely the file we want + filehasher.Initialize(); + + string key; + using (var file = SystemIO.IO_OS.FileOpenRead(tr)) + key = Convert.ToBase64String(filehasher.ComputeHash(file)); + + if (key == targetfilehash) + { + //TODO: Also needs metadata check to make correct decision. + // We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored. + blockmarker.SetAllBlocksRestored(targetfileid, false); + blockmarker.SetFileDataVerified(targetfileid); + break; + } + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "FailedToReadRestoreTarget", ex, "Failed to read candidate restore target {0}", tr); + if (options.UnittestMode) + throw; + } + tr = newname + " (" + (c++).ToString() + ")" + ext; + } + + newname = tr; + + Logging.Log.WriteVerboseMessage(LOGTAG, "TargetFileRetargeted", "Target file exists and will be restored to: {0}", newname); + database.UpdateTargetPath(targetfileid, newname); + } + + } + + blockmarker.UpdateProcessed(result.OperationProgressUpdater); + blockmarker.Commit(); + } + } + } +} diff --git a/Duplicati/Library/Main/Operation/RestoreHandlerMetadataStorage.cs b/Duplicati/Library/Main/Operation/RestoreHandlerMetadataStorage.cs index 209125e33..6e0cf0356 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandlerMetadataStorage.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandlerMetadataStorage.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs index 4464f5ef8..07071de20 100644 --- a/Duplicati/Library/Main/Operation/SystemInfoHandler.cs +++ b/Duplicati/Library/Main/Operation/SystemInfoHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,28 +23,27 @@ using System; using System.Collections.Generic; using System.Linq; using System.Globalization; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { internal static class SystemInfoHandler { - public static void Run(SystemInfoResults results) - { - results.Lines = GetSystemInfo().ToArray(); - } + public static Task RunAsync(SystemInfoResults results) + => Task.Run(() => results.Lines = GetSystemInfo().ToArray()); public static IEnumerable GetSystemInfo() { yield return string.Format("Duplicati: {0} ({1})", Library.Utility.Utility.getEntryAssembly().FullName, System.Reflection.Assembly.GetExecutingAssembly().FullName); yield return string.Format("Autoupdate urls: {0}", string.Join(";", AutoUpdater.AutoUpdateSettings.URLs)); - yield return string.Format("Update folder: {0}", AutoUpdater.UpdaterManager.UPDATEDIR); + yield return string.Format("Default data folder: {0}", AutoUpdater.DataFolderManager.DATAFOLDER); yield return string.Format("Install folder: {0}", AutoUpdater.UpdaterManager.INSTALLATIONDIR); yield return string.Format("Version name: \"{0}\" ({1})", AutoUpdater.UpdaterManager.SelfVersion.Displayname, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); yield return string.Format("Current Version folder {0}", System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)); yield return string.Format("OS: {0}", Library.UsageReporter.OSInfoHelper.PlatformString); - yield return string.Format("OSType: {0}", OperatingSystem.IsWindows() ? "Windows" : OperatingSystem.IsLinux() ? "Linux" : OperatingSystem.IsMacOS() ? "MacOS" : "Unknown"); + yield return string.Format("OSType: {0}", AutoUpdater.UpdaterManager.OperatingSystemName); yield return string.Format("64bit: {0} ({1})", Environment.Is64BitOperatingSystem, Environment.Is64BitProcess); yield return string.Format("Machinename: {0}", Environment.MachineName); diff --git a/Duplicati/Library/Main/Operation/TestFilterHandler.cs b/Duplicati/Library/Main/Operation/TestFilterHandler.cs index 2e80bebc8..b00d37f7a 100644 --- a/Duplicati/Library/Main/Operation/TestFilterHandler.cs +++ b/Duplicati/Library/Main/Operation/TestFilterHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,11 +19,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.IO; -using System.Threading; -using Duplicati.Library.Snapshots; using CoCoL; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { @@ -43,38 +44,40 @@ namespace Duplicati.Library.Main.Operation m_result = results; } - public void Run(string[] sources, Library.Utility.IFilter filter, CancellationToken token) + public async Task RunAsync(string[] sources, Library.Utility.IFilter filter) { - var sourcefilter = new Library.Utility.FilterExpression(sources, true); + var stopToken = m_result.TaskControl.ProgressToken; - using (var snapshot = BackupHandler.GetSnapshot(sources, m_options)) - using (new IsolatedChannelScope()) + using (var provider = await BackupHandler.GetSourceProvider(sources, m_options, stopToken).ConfigureAwait(false)) { - var source = Operation.Backup.FileEnumerationProcess.Run(sources, snapshot, null, - m_options.FileAttributeFilter, sourcefilter, filter, m_options.SymlinkPolicy, + Backup.Channels channels = new(); + var source = Backup.FileEnumerationProcess.Run(channels, provider, null, + m_options.FileAttributeFilter, filter, m_options.SymlinkPolicy, m_options.HardlinkPolicy, m_options.ExcludeEmptyFolders, m_options.IgnoreFilenames, - BackupHandler.GetBlacklistedPaths(m_options), null, m_result.TaskReader, token); + BackupHandler.GetBlacklistedPaths(m_options), null, m_result.TaskControl, null, stopToken); - var sink = CoCoL.AutomationExtensions.RunTask( - new { source = Operation.Backup.Channels.SourcePaths.ForRead }, + var sink = CoCoL.AutomationExtensions.RunTask(new + { + source = channels.SourcePaths.AsRead() + }, async self => { - while (true) + while (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - var path = await self.source.ReadAsync(); - var fa = FileAttributes.Normal; - try { fa = snapshot.GetAttributes(path); } - catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); } + var entry = await self.source.ReadAsync(); + var fa = entry.IsFolder + ? FileAttributes.Directory + : FileAttributes.Normal; + + try { fa = entry.Attributes; } + catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", entry.Path, ex.Message); } // Analyze symlinks - var isSymlink = snapshot.IsSymlink(path, fa); - string symlinkTarget = null; + string? symlinkTarget = null; + try { symlinkTarget = entry.SymlinkTarget; } + catch (Exception ex) { Logging.Log.WriteExplicitMessage(LOGTAG, "SymlinkTargetReadFailure", ex, "Failed to read symlink target for path: {0}", entry.Path); } - if (isSymlink) - try { symlinkTarget = snapshot.GetSymlinkTarget(path); } - catch (Exception ex) { Logging.Log.WriteExplicitMessage(LOGTAG, "SymlinkTargetReadFailure", ex, "Failed to read symlink target for path: {0}", path); } - - if (isSymlink && m_options.SymlinkPolicy == Options.SymlinkStrategy.Store && !string.IsNullOrWhiteSpace(symlinkTarget)) + if (m_options.SymlinkPolicy == Options.SymlinkStrategy.Store && !string.IsNullOrWhiteSpace(symlinkTarget)) { // Skip stored symlinks continue; @@ -83,39 +86,50 @@ namespace Duplicati.Library.Main.Operation // Go for the symlink target, as we know we follow symlinks if (!string.IsNullOrWhiteSpace(symlinkTarget)) { - path = symlinkTarget; + var targetEntry = await provider.GetEntry(symlinkTarget, false, stopToken).ConfigureAwait(false); fa = FileAttributes.Normal; - try { fa = snapshot.GetAttributes(path); } - catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", path, ex.Message); } + + try { fa = targetEntry!.Attributes; } + catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "FailedAttributeRead", "Failed to read attributes from {0}: {1}", entry.Path, ex.Message); } + + // If we guessed wrong and the symlink target is a folder, we need to fetch it with the correct flag + if (fa.HasFlag(FileAttributes.Directory)) + targetEntry = await provider.GetEntry(symlinkTarget, true, stopToken).ConfigureAwait(false); + + // No such target + if (targetEntry == null) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "SymlinkTargetNotFound", "Symlink target not found: {0}", symlinkTarget); + continue; + } } // Proceed with non-folders - if (!((fa & FileAttributes.Directory) == FileAttributes.Directory)) + if (!entry.IsFolder) { m_result.FileCount++; var size = -1L; try { - size = snapshot.GetFileSize(path); + size = entry.Size; m_result.FileSize += size; } catch (Exception ex) { - Logging.Log.WriteVerboseMessage(LOGTAG, "SizeReadFailed", "Failed to read length of file {0}: {1}", path, ex.Message); + Logging.Log.WriteVerboseMessage(LOGTAG, "SizeReadFailed", "Failed to read length of file {0}: {1}", entry.Path, ex.Message); } - if (m_options.SkipFilesLargerThan == long.MaxValue || m_options.SkipFilesLargerThan == 0 || size < m_options.SkipFilesLargerThan) - Logging.Log.WriteVerboseMessage(LOGTAG, "IncludeFile", "Including file: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size)); + Logging.Log.WriteVerboseMessage(LOGTAG, "IncludeFile", "Including file: {0} ({1})", entry.Path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size)); else - Logging.Log.WriteVerboseMessage(LOGTAG, "ExcludeLargeFile", "Excluding file due to size: {0} ({1})", path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size)); + Logging.Log.WriteVerboseMessage(LOGTAG, "ExcludeLargeFile", "Excluding file due to size: {0} ({1})", entry.Path, size < 0 ? "unknown" : Duplicati.Library.Utility.Utility.FormatSizeString(size)); } } } ); - System.Threading.Tasks.Task.WhenAll(source, sink).WaitForTaskOrThrow(); + await Task.WhenAll(source, sink).ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/TestHandler.cs b/Duplicati/Library/Main/Operation/TestHandler.cs index 9a2bf8ddb..b50bc7917 100644 --- a/Duplicati/Library/Main/Operation/TestHandler.cs +++ b/Duplicati/Library/Main/Operation/TestHandler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,8 +24,9 @@ using Duplicati.Library.Main.Database; using System.Linq; using System.Collections.Generic; using Duplicati.Library.Interface; -using System.Security.Cryptography; using Duplicati.Library.Utility; +using System.Threading; +using System.Threading.Tasks; namespace Duplicati.Library.Main.Operation { @@ -37,36 +38,30 @@ namespace Duplicati.Library.Main.Operation private static readonly string LOGTAG = Logging.Log.LogTagFromType(); private readonly Options m_options; - private readonly string m_backendurl; private readonly TestResults m_results; - public TestHandler(string backendurl, Options options, TestResults results) + public TestHandler(Options options, TestResults results) { m_options = options; - m_backendurl = backendurl; m_results = results; } - public void Run(long samples) + public async Task RunAsync(long samples, IBackendManager backendManager) { if (!System.IO.File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); using (var db = new LocalTestDatabase(m_options.Dbpath)) - using (var backend = new BackendManager(m_backendurl, m_options, m_results.BackendWriter, db)) { - db.SetResult(m_results); Utility.UpdateOptionsFromDb(db, m_options); Utility.VerifyOptionsAndUpdateDatabase(db, m_options); db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, !m_options.DisableFilelistConsistencyChecks, null); - FilelistProcessor.VerifyRemoteList(backend, m_options, db, m_results.BackendWriter, true, null); - - DoRun(samples, db, backend); - db.WriteResults(); + await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_results.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, null).ConfigureAwait(false); + await DoRunAsync(samples, db, backendManager).ConfigureAwait(false); } } - public void DoRun(long samples, LocalTestDatabase db, BackendManager backend) + public async Task DoRunAsync(long samples, LocalTestDatabase db, IBackendManager backend) { var files = db.SelectTestTargets(samples, m_options).ToList(); @@ -76,13 +71,14 @@ namespace Duplicati.Library.Main.Operation if (m_options.FullRemoteVerification != Options.RemoteTestStrategy.False) { - foreach (var vol in new AsyncDownloader(files, backend)) + await foreach (var (tf, hash, size, name) in backend.GetFilesOverlappedAsync(files, m_results.TaskControl.ProgressToken).ConfigureAwait(false)) { + var vol = new RemoteVolume(name, hash, size); try { - if (m_results.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_results.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { - backend.WaitForComplete(db, null); + await backend.WaitForEmptyAsync(db, null, m_results.TaskControl.ProgressToken).ConfigureAwait(false); m_results.EndTime = DateTime.UtcNow; return; } @@ -91,7 +87,7 @@ namespace Duplicati.Library.Main.Operation m_results.OperationProgressUpdater.UpdateProgress((float)progress / files.Count); KeyValuePair>> res; - using (var tf = vol.TempFile) + using (tf) res = TestVolumeInternals(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2); m_results.AddResult(res.Key, res.Value); @@ -125,7 +121,7 @@ namespace Duplicati.Library.Main.Operation { m_results.AddResult(vol.Name, new KeyValuePair[] { new KeyValuePair(Duplicati.Library.Interface.TestEntryStatus.Error, ex.Message) }); Logging.Log.WriteErrorMessage(LOGTAG, "RemoteFileProcessingFailed", ex, "Failed to process file {0}", vol.Name); - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortException()) { m_results.EndTime = DateTime.UtcNow; throw; @@ -139,7 +135,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (m_results.TaskControlRendevouz() == TaskControlState.Stop) + if (!await m_results.TaskControl.ProgressRendevouz().ConfigureAwait(false)) { m_results.EndTime = DateTime.UtcNow; return; @@ -152,10 +148,10 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "MissingRemoteHash", "No hash or size recorded for {0}, performing full verification", f.Name); KeyValuePair>> res; - string hash; - long size; - using (var tf = backend.GetWithInfo(f.Name, out size, out hash)) + (var tf, var hash, var size) = await backend.GetWithInfoAsync(f.Name, f.Hash, f.Size, m_results.TaskControl.ProgressToken).ConfigureAwait(false); + + using (tf) res = TestVolumeInternals(db, f, tf, m_options, 1); m_results.AddResult(res.Key, res.Value); @@ -176,16 +172,19 @@ namespace Duplicati.Library.Main.Operation } } else - backend.GetForTesting(f.Name, f.Size, f.Hash); + { + using (var tf = await backend.GetAsync(f.Name, f.Hash, f.Size, m_results.TaskControl.ProgressToken).ConfigureAwait(false)) + { } + } db.UpdateVerificationCount(f.Name); - m_results.AddResult(f.Name, new KeyValuePair[0]); + m_results.AddResult(f.Name, []); } catch (Exception ex) { - m_results.AddResult(f.Name, new KeyValuePair[] { new KeyValuePair(Duplicati.Library.Interface.TestEntryStatus.Error, ex.Message) }); + m_results.AddResult(f.Name, [new KeyValuePair(TestEntryStatus.Error, ex.Message)]); Logging.Log.WriteErrorMessage(LOGTAG, "FailedToProcessFile", ex, "Failed to process file {0}", f.Name); - if (ex is System.Threading.ThreadAbortException) + if (ex.IsAbortOrCancelException()) { m_results.EndTime = DateTime.UtcNow; throw; diff --git a/Duplicati/Library/Main/Operation/VacuumHandler.cs b/Duplicati/Library/Main/Operation/VacuumHandler.cs index a7d74d151..5d2fea1dc 100644 --- a/Duplicati/Library/Main/Operation/VacuumHandler.cs +++ b/Duplicati/Library/Main/Operation/VacuumHandler.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; @@ -37,15 +37,15 @@ namespace Duplicati.Library.Main.Operation m_result = result; } - public virtual void Run() - { - using (var db = new Database.LocalDatabase(m_options.Dbpath, "Vacuum", false)) + public virtual Task RunAsync() + => Task.Run(() => { - m_result.SetDatabase(db); - m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Vacuum_Running); - db.Vacuum(); - m_result.EndTime = DateTime.UtcNow; - } - } + using (var db = new Database.LocalDatabase(m_options.Dbpath, "Vacuum", false)) + { + m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Vacuum_Running); + db.Vacuum(); + m_result.EndTime = DateTime.UtcNow; + } + }); } } diff --git a/Duplicati/Library/Main/OperationPhase.cs b/Duplicati/Library/Main/OperationPhase.cs index e3a099581..234ab7112 100644 --- a/Duplicati/Library/Main/OperationPhase.cs +++ b/Duplicati/Library/Main/OperationPhase.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/Options.cs b/Duplicati/Library/Main/Options.cs index 8e40edffc..6b9c92373 100644 --- a/Duplicati/Library/Main/Options.cs +++ b/Duplicati/Library/Main/Options.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in +// +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -25,6 +25,8 @@ using System.Collections.Generic; using Duplicati.Library.Interface; using Duplicati.Library.Utility; using System.Globalization; +using System.Threading; +using Duplicati.Library.Utility.Options; namespace Duplicati.Library.Main { @@ -34,7 +36,13 @@ namespace Duplicati.Library.Main /// public class Options { + /// + /// The default block hash algorithm + /// private const string DEFAULT_BLOCK_HASH_ALGORITHM = "SHA256"; + /// + /// The default file hash algorithm + /// private const string DEFAULT_FILE_HASH_ALGORITHM = "SHA256"; /// @@ -67,11 +75,21 @@ namespace Duplicati.Library.Main /// private const string DEFAULT_LOG_RETENTION = "30D"; + /// + /// The default activity timeout + /// + private const string DEFAULT_READ_WRITE_TIMEOUT = "30s"; + /// /// The default number of compressor instances /// private readonly int DEFAULT_COMPRESSORS = Math.Max(1, Environment.ProcessorCount / 2); + /// + /// The default number of file processors instances + /// + private readonly int DEFAULT_FILE_PROCESSORS = Math.Max(1, Environment.ProcessorCount / 2); + /// /// The default number of hasher instances /// @@ -82,6 +100,41 @@ namespace Duplicati.Library.Main /// private const int DEFAULT_QUOTA_WARNING_THRESHOLD = 10; + /// + /// The default value for the maximum size of the restore cache + /// + private const string DEFAULT_RESTORE_CACHE_MAX = "4gb"; + + /// + /// The default value for the percentage of the restore cache to evict when full + /// + private const int DEFAULT_RESTORE_CACHE_EVICT = 50; + + /// + /// The default value for the number of file processors during restore + /// + private readonly int DEFAULT_RESTORE_FILE_PROCESSORS = Math.Max(1, Environment.ProcessorCount / 2); + + /// + /// The default value for the number of volume decryptors during restore + /// + private readonly int DEFAULT_RESTORE_VOLUME_DECRYPTORS = Math.Max(1, Environment.ProcessorCount / 2); + + /// + /// The default value for the number of volume decompressors during restore + /// + private readonly int DEFAULT_RESTORE_VOLUME_DECOMPRESSORS = Math.Max(1, Environment.ProcessorCount / 2); + + /// + /// The default value for the number of volume downloaders during restore + /// + private readonly int DEFAULT_RESTORE_VOLUME_DOWNLOADERS = Math.Max(1, Environment.ProcessorCount / 2); + + /// + /// The default value for the size of the channel buffers during restore + /// + private readonly int DEFAULT_RESTORE_CHANNEL_BUFFER_SIZE = Environment.ProcessorCount; + /// /// An enumeration that describes the supported strategies for an optimization /// @@ -229,7 +282,11 @@ namespace Duplicati.Library.Main /// /// Returns a list of options that are intentionally duplicate /// - public static string[] KnownDuplicates => ["auth-password", "auth-username", "accept-any-ssl-certificate", "accept-specified-ssl-hash"]; + public static readonly IReadOnlySet KnownDuplicates = + AuthOptionsHelper.GetOptions().Select(x => x.Name) + .Concat(SslOptionsHelper.GetOptions().Select(x => x.Name)) + .Concat(TimeoutOptionsHelper.GetOptions().Select(x => x.Name)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); /// /// A default backup name @@ -272,7 +329,7 @@ namespace Duplicati.Library.Main new CommandLineArgument("disable-time-tolerance", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisabletimetoleranceShort, Strings.Options.DisabletimetoleranceLong, "false"), new CommandLineArgument("tempdir", CommandLineArgument.ArgumentType.Path, Strings.Options.TempdirShort, Strings.Options.TempdirLong, System.IO.Path.GetTempPath()), - new CommandLineArgument("thread-priority", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.ThreadpriorityShort, Strings.Options.ThreadpriorityLong, "normal", null, new string[] {"highest", "high", "abovenormal", "normal", "belownormal", "low", "lowest", "idle" }), + new CommandLineArgument("thread-priority", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.ThreadpriorityShort, Strings.Options.ThreadpriorityLong, "normal", null, new string[] {"highest", "high", "abovenormal", "normal", "belownormal", "low", "lowest", "idle" }, Strings.Options.ThreadpriorityDeprecated), new CommandLineArgument("prefix", CommandLineArgument.ArgumentType.String, Strings.Options.PrefixShort, Strings.Options.PrefixLong, "duplicati"), @@ -289,6 +346,7 @@ namespace Duplicati.Library.Main new CommandLineArgument("asynchronous-upload-folder", CommandLineArgument.ArgumentType.Path, Strings.Options.AsynchronousuploadfolderShort, Strings.Options.AsynchronousuploadfolderLong, System.IO.Path.GetTempPath()), new CommandLineArgument("disable-streaming-transfers", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisableStreamingShort, Strings.Options.DisableStreamingLong, "false"), + new CommandLineArgument("read-write-timeout", CommandLineArgument.ArgumentType.Timespan, Strings.Options.ReadWriteTimeoutShort, Strings.Options.ReadWriteTimeoutLong, DEFAULT_READ_WRITE_TIMEOUT), new CommandLineArgument("throttle-upload", CommandLineArgument.ArgumentType.Size, Strings.Options.ThrottleuploadShort, Strings.Options.ThrottleuploadLong, "0kb"), new CommandLineArgument("throttle-download", CommandLineArgument.ArgumentType.Size, Strings.Options.ThrottledownloadShort, Strings.Options.ThrottledownloadLong, "0kb"), @@ -297,6 +355,7 @@ namespace Duplicati.Library.Main new CommandLineArgument("upload-unchanged-backups", CommandLineArgument.ArgumentType.Boolean, Strings.Options.UploadUnchangedBackupsShort, Strings.Options.UploadUnchangedBackupsLong, "false"), new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, "off", null, Enum.GetNames(typeof(OptimizationStrategy))), + new CommandLineArgument("snapshot-provider", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotproviderShort, Strings.Options.SnapshotproviderLong, OperatingSystem.IsWindows() ? Snapshots.SnapshotProvider.AlphaVSS.ToString() : Snapshots.SnapshotProvider.LVM.ToString(), null, (OperatingSystem.IsWindows() ? [Snapshots.SnapshotProvider.AlphaVSS, Snapshots.SnapshotProvider.Wmic] : new [] { Snapshots.SnapshotProvider.LVM }).Select(x => x.ToString()).ToArray()), new CommandLineArgument("vss-exclude-writers", CommandLineArgument.ArgumentType.String, Strings.Options.VssexcludewritersShort, Strings.Options.VssexcludewritersLong, "{e8132975-6f93-4464-a53e-1050253ae220}"), new CommandLineArgument("vss-use-mapping", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VssusemappingShort, Strings.Options.VssusemappingLong, "false"), new CommandLineArgument("usn-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.UsnpolicyShort, Strings.Options.UsnpolicyLong, "off", null, Enum.GetNames(typeof(OptimizationStrategy))), @@ -334,12 +393,13 @@ namespace Duplicati.Library.Main new CommandLineArgument("symlink-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SymlinkpolicyShort, Strings.Options.SymlinkpolicyLong("store", "ignore", "follow"), Enum.GetName(typeof(SymlinkStrategy), SymlinkStrategy.Store), null, Enum.GetNames(typeof(SymlinkStrategy))), new CommandLineArgument("hardlink-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.HardlinkpolicyShort, Strings.Options.HardlinkpolicyLong("first", "all", "none"), Enum.GetName(typeof(HardlinkStrategy), HardlinkStrategy.All), null, Enum.GetNames(typeof(HardlinkStrategy))), new CommandLineArgument("exclude-files-attributes", CommandLineArgument.ArgumentType.String, Strings.Options.ExcludefilesattributesShort, Strings.Options.ExcludefilesattributesLong(Enum.GetNames(typeof(System.IO.FileAttributes)))), - new CommandLineArgument("backup-name", CommandLineArgument.ArgumentType.String, Strings.Options.BackupnameShort, Strings.Options.BackupnameLong, DefaultBackupName), new CommandLineArgument("compression-extension-file", CommandLineArgument.ArgumentType.Path, Strings.Options.CompressionextensionfileShort, Strings.Options.CompressionextensionfileLong(DEFAULT_COMPRESSED_EXTENSION_FILE), DEFAULT_COMPRESSED_EXTENSION_FILE), + new CommandLineArgument("machine-id", CommandLineArgument.ArgumentType.String, Strings.Options.MachineidShort, Strings.Options.MachineidLong, Library.AutoUpdater.DataFolderManager.InstallID), + new CommandLineArgument("machine-name", CommandLineArgument.ArgumentType.String, Strings.Options.MachinenameShort, Strings.Options.MachinenameLong, Library.AutoUpdater.DataFolderManager.MachineName), new CommandLineArgument("backup-id", CommandLineArgument.ArgumentType.String, Strings.Options.BackupidShort, Strings.Options.BackupidLong, ""), - new CommandLineArgument("machine-id", CommandLineArgument.ArgumentType.String, Strings.Options.MachineidShort, Strings.Options.MachineidLong, Library.AutoUpdater.UpdaterManager.InstallID), - new CommandLineArgument("machine-name", CommandLineArgument.ArgumentType.String, Strings.Options.MachinenameShort, Strings.Options.MachinenameLong, Library.AutoUpdater.UpdaterManager.MachineName), + new CommandLineArgument("backup-name", CommandLineArgument.ArgumentType.String, Strings.Options.BackupnameShort, Strings.Options.BackupnameLong, DefaultBackupName), + new CommandLineArgument("next-scheduled-run", CommandLineArgument.ArgumentType.String, Strings.Options.NextscheduledrunShort, Strings.Options.NextscheduledrunLong), new CommandLineArgument("verbose", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VerboseShort, Strings.Options.VerboseLong, "false", null, null, Strings.Options.VerboseDeprecated), new CommandLineArgument("full-result", CommandLineArgument.ArgumentType.Boolean, Strings.Options.FullresultShort, Strings.Options.FullresultLong, "false"), @@ -353,7 +413,6 @@ namespace Duplicati.Library.Main new CommandLineArgument("restore-permissions", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RestorepermissionsShort, Strings.Options.RestorepermissionsLong, "false"), new CommandLineArgument("skip-restore-verification", CommandLineArgument.ArgumentType.Boolean, Strings.Options.SkiprestoreverificationShort, Strings.Options.SkiprestoreverificationLong, "false"), new CommandLineArgument("disable-filepath-cache", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisablefilepathcacheShort, Strings.Options.DisablefilepathcacheLong, "true", null, null, Strings.Options.DisablefilepathcacheDeprecated), - new CommandLineArgument("use-block-cache", CommandLineArgument.ArgumentType.Boolean, Strings.Options.UseblockcacheShort, Strings.Options.UseblockcacheLong, "false"), new CommandLineArgument("changed-files", CommandLineArgument.ArgumentType.Path, Strings.Options.ChangedfilesShort, Strings.Options.ChangedfilesLong), new CommandLineArgument("deleted-files", CommandLineArgument.ArgumentType.Path, Strings.Options.DeletedfilesShort, Strings.Options.DeletedfilesLong("changed-files")), new CommandLineArgument("disable-synthetic-filelist", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisablesyntheticfilelistShort, Strings.Options.DisablesyntehticfilelistLong, "false"), @@ -392,12 +451,14 @@ namespace Duplicati.Library.Main new CommandLineArgument("repair-only-paths", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RepaironlypathsShort, Strings.Options.RepaironlypathsLong, "false"), new CommandLineArgument("repair-force-block-use", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RepaironlypathsShort, Strings.Options.RepaironlypathsLong, "false"), + new CommandLineArgument("repair-ignore-outdated-database", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RepairignoreoutdateddatabaseShort, Strings.Options.RepairignoreoutdateddatabaseLong, "false"), new CommandLineArgument("force-locale", CommandLineArgument.ArgumentType.String, Strings.Options.ForcelocaleShort, Strings.Options.ForcelocaleLong), new CommandLineArgument("force-actual-date", CommandLineArgument.ArgumentType.Boolean, Strings.Options.ForceActualDateShort, Strings.Options.ForceActualDateLong, "false"), new CommandLineArgument("concurrency-max-threads", CommandLineArgument.ArgumentType.Integer, Strings.Options.ConcurrencymaxthreadsShort, Strings.Options.ConcurrencymaxthreadsLong, "0"), new CommandLineArgument("concurrency-block-hashers", CommandLineArgument.ArgumentType.Integer, Strings.Options.ConcurrencyblockhashersShort, Strings.Options.ConcurrencyblockhashersLong, DEFAULT_BLOCK_HASHERS.ToString()), new CommandLineArgument("concurrency-compressors", CommandLineArgument.ArgumentType.Integer, Strings.Options.ConcurrencycompressorsShort, Strings.Options.ConcurrencycompressorsLong, DEFAULT_COMPRESSORS.ToString()), + new CommandLineArgument("concurrency-fileprocessors", CommandLineArgument.ArgumentType.Integer, Strings.Options.ConcurrencyfileprocessorsShort, Strings.Options.ConcurrencyfileprocessorsLong, DEFAULT_FILE_PROCESSORS.ToString()), new CommandLineArgument("auto-vacuum", CommandLineArgument.ArgumentType.Boolean, Strings.Options.AutoVacuumShort, Strings.Options.AutoVacuumLong, "false"), new CommandLineArgument("disable-file-scanner", CommandLineArgument.ArgumentType.Boolean, Strings.Options.DisablefilescannerShort, Strings.Options.DisablefilescannerLong, "false"), @@ -411,6 +472,22 @@ namespace Duplicati.Library.Main new CommandLineArgument("auto-compact-interval", CommandLineArgument.ArgumentType.Timespan, Strings.Options.AutoCompactIntervalShort, Strings.Options.AutoCompactIntervalLong, "0m"), new CommandLineArgument("auto-vacuum-interval", CommandLineArgument.ArgumentType.Timespan, Strings.Options.AutoVacuumIntervalShort, Strings.Options.AutoVacuumIntervalLong, "0m"), + + new CommandLineArgument("secret-provider", CommandLineArgument.ArgumentType.String, Strings.Options.SecretProviderShort, Strings.Options.SecretProviderLong(Library.AutoUpdater.PackageHelper.GetExecutableName(AutoUpdater.PackageHelper.NamedExecutable.SecretTool))), + new CommandLineArgument("secret-provider-pattern", CommandLineArgument.ArgumentType.String, Strings.Options.SecretProviderPatternShort, Strings.Options.SecretProviderPatternLong, SecretProviderHelper.DEFAULT_PATTERN), + new CommandLineArgument("secret-provider-cache", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SecretProviderCacheShort, Strings.Options.SecretProviderCacheLong, Enum.GetName(SecretProviderHelper.CachingLevel.None), null, Enum.GetNames(typeof(SecretProviderHelper.CachingLevel))), + new CommandLineArgument("cpu-intensity", CommandLineArgument.ArgumentType.Integer, Strings.Options.CPUIntensityShort, Strings.Options.CPUIntensityLong, "10", null, ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]), + + new CommandLineArgument("restore-cache-max", CommandLineArgument.ArgumentType.Size, Strings.Options.RestoreCacheMaxShort, Strings.Options.RestoreCacheMaxLong, DEFAULT_RESTORE_CACHE_MAX), + new CommandLineArgument("restore-cache-evict", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreCacheEvictShort, Strings.Options.RestoreCacheEvictLong, DEFAULT_RESTORE_CACHE_EVICT.ToString()), + new CommandLineArgument("restore-file-processors", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreFileprocessorsShort, Strings.Options.RestoreFileprocessorsLong, DEFAULT_RESTORE_FILE_PROCESSORS.ToString()), + new CommandLineArgument("restore-legacy", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RestoreLegacyShort, Strings.Options.RestoreLegacyLong, "false"), + new CommandLineArgument("restore-preallocate-size", CommandLineArgument.ArgumentType.Boolean, Strings.Options.RestorePreallocateSizeShort, Strings.Options.RestorePreallocateSizeLong, "false"), + new CommandLineArgument("restore-volume-decompressors", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreVolumeDecompressorsShort, Strings.Options.RestoreVolumeDecompressorsLong, DEFAULT_RESTORE_VOLUME_DECOMPRESSORS.ToString()), + new CommandLineArgument("restore-volume-decryptors", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreVolumeDecryptorsShort, Strings.Options.RestoreVolumeDecryptorsLong, DEFAULT_RESTORE_VOLUME_DECRYPTORS.ToString()), + new CommandLineArgument("restore-volume-downloaders", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreVolumeDownloadersShort, Strings.Options.RestoreVolumeDownloadersLong, DEFAULT_RESTORE_VOLUME_DOWNLOADERS.ToString()), + new CommandLineArgument("restore-channel-buffer-size", CommandLineArgument.ArgumentType.Integer, Strings.Options.RestoreChannelBufferSizeShort, Strings.Options.RestoreChannelBufferSizeLong, DEFAULT_RESTORE_CHANNEL_BUFFER_SIZE.ToString()), + new CommandLineArgument("internal-profiling", CommandLineArgument.ArgumentType.Boolean, Strings.Options.InternalProfilingShort, Strings.Options.InternalProfilingLong, "false"), }); return lst; @@ -624,20 +701,6 @@ namespace Duplicati.Library.Main } } - /// - /// Gets the process priority - /// - public string ThreadPriority - { - get - { - if (!m_options.ContainsKey("thread-priority") || string.IsNullOrEmpty(m_options["thread-priority"])) - return null; - else - return m_options["thread-priority"]; - } - } - /// /// A value indicating if missing folders should be created automatically /// @@ -699,7 +762,7 @@ namespace Duplicati.Library.Main /// /// Gets the time frames and intervals for the retention policy - /// + /// public List RetentionPolicy { get @@ -817,6 +880,26 @@ namespace Duplicati.Library.Main /// public bool DisableStreamingTransfers { get { return GetBool("disable-streaming-transfers"); } } + /// + /// The maximum time to allow inactivity before a connection is closed. + /// Returns Timeout.Infinite if disabled. + /// + public int ReadWriteTimeout + { + get + { + var v = m_options.GetValueOrDefault("read-write-timeout"); + if (string.IsNullOrWhiteSpace(v)) + v = DEFAULT_READ_WRITE_TIMEOUT; + + var res = Library.Utility.Timeparser.ParseTimeSpan(v); + if (res.Ticks <= 0) + return Timeout.Infinite; + + return (int)res.TotalMilliseconds; + } + } + /// /// Gets the delay period to retry uploads /// @@ -955,6 +1038,24 @@ namespace Duplicati.Library.Main } } + /// + /// Gets the snapshot strategy to use + /// + public Snapshots.SnapshotProvider SnapShotProvider + { + get + { + if (!m_options.TryGetValue("snapshot-provider", out var provider)) + provider = ""; + + Snapshots.SnapshotProvider r; + if (!Enum.TryParse(provider, true, out r)) + r = OperatingSystem.IsWindows() ? Snapshots.SnapshotProvider.AlphaVSS : Snapshots.SnapshotProvider.LVM; + + return r; + } + } + /// /// Gets a flag indicating if advisory locking should be ignored /// @@ -1297,7 +1398,7 @@ namespace Duplicati.Library.Main { if (m_options.TryGetValue("machine-id", out var tmp)) return tmp; - return Library.AutoUpdater.UpdaterManager.InstallID; + return Library.AutoUpdater.DataFolderManager.InstallID; } } /// @@ -1411,18 +1512,6 @@ namespace Duplicati.Library.Main get { return Library.Utility.Utility.ParseBoolOption(m_options, "disable-synthetic-filelist"); } } - /// - /// Flag indicating if the in-memory block cache is used - /// - public bool UseBlockCache - { - get - { - return Library.Utility.Utility.ParseBoolOption(m_options, "use-block-cache"); - } - } - - /// /// Gets the compact threshold /// @@ -1629,7 +1718,7 @@ namespace Duplicati.Library.Main } /// - /// Gets a flag indicating if compacting should not be done automatically + /// Gets a flag indicating if missing source elements should be ignored /// public bool AllowMissingSource { @@ -1762,6 +1851,15 @@ namespace Duplicati.Library.Main get { return Library.Utility.Utility.ParseBoolOption(m_options, "repair-only-paths"); } } + /// + /// Gets a flag indicating if the repair process will ignore outdated database + /// + /// true if repair process will ignore outdated database; otherwise, false. + public bool RepairIgnoreOutdatedDatabase + { + get { return Library.Utility.Utility.ParseBoolOption(m_options, "repair-ignore-outdated-database"); } + } + /// /// Gets a flag indicating if the repair process will always use blocks /// @@ -1898,6 +1996,24 @@ namespace Duplicati.Library.Main } } + /// + /// Gets the number of concurrent file processors + /// + public int ConcurrencyFileprocessors + { + get + { + string value; + if (!m_options.TryGetValue("concurrency-fileprocessors", out value)) + value = null; + + if (string.IsNullOrEmpty(value)) + return DEFAULT_FILE_PROCESSORS; + else + return Math.Max(1, int.Parse(value)); + } + } + /// /// Gets a lookup table with compression hints, the key is the file extension with the leading period /// @@ -1932,6 +2048,24 @@ namespace Duplicati.Library.Main } } + /// + /// Gets the CPU intensity level indicating target CPU utilization. 1 is the lowest, 10 is the highest. Default is 10. + /// + public int CPUIntensity + { + get + { + string value; + if (!m_options.TryGetValue("cpu-intensity", out value)) + value = null; + + if (string.IsNullOrEmpty(value)) + return 10; + else + return Math.Max(1, Math.Min(10, int.Parse(value))); + } + } + /// /// Gets a compression hint from a filename /// @@ -1946,7 +2080,7 @@ namespace Duplicati.Library.Main } /// - /// Gets a list of modules, the key indicates if they are loaded + /// Gets a list of modules, the key indicates if they are loaded /// public List> LoadedModules { get { return m_loadedModules; } } @@ -1962,6 +2096,163 @@ namespace Duplicati.Library.Main return Library.Utility.Utility.ParseBoolOption(m_options, name); } + /// + /// Gets the maximum number of data blocks to keep in the cache. If set to 0, the cache is effictively disabled, but some is still kept for bookkeeping. + /// + public long RestoreCacheMax + { + get + { + if (!m_options.TryGetValue("restore-cache-max", out string v)) + v = DEFAULT_RESTORE_CACHE_MAX; + + long max_cache = Sizeparser.ParseSize(v, "mb"); + + if (max_cache > 0 && max_cache < Blocksize) + throw new ArgumentOutOfRangeException(nameof(max_cache), string.Format("The maximum cache size cannot be less than the blocksize if not explicitly 0: {0} < {1}", max_cache, Blocksize)); + + return max_cache / Blocksize; + } + } + + /// + /// Gets the ratio of cache entries to evict when the cache is full + /// + public float RestoreCacheEvict + { + get + { + m_options.TryGetValue("restore-cache-evict", out string s); + if (string.IsNullOrEmpty(s)) + { + return DEFAULT_RESTORE_CACHE_EVICT / 100f; + } + + int percentage; + try + { + percentage = int.Parse(s, CultureInfo.InvariantCulture); + } + catch (Exception ex) + { + throw new ArgumentException("The value provided for the restore-cache-evict option must lie between 0 and 100", ex); + } + + if ((percentage < 0) || (percentage > 100)) + { + throw new ArgumentOutOfRangeException(nameof(percentage), "The value provided for the restore-cache-evict option must lie between 0 and 100"); + } + + return percentage / 100f; + } + } + + /// + /// Gets the number of file processors to use in the restore process + /// + public int RestoreFileProcessors + { + get + { + if (!m_options.TryGetValue("restore-file-processors", out string v)) + v = null; + + if (string.IsNullOrEmpty(v)) + return DEFAULT_RESTORE_FILE_PROCESSORS; + else + return int.Parse(v); + } + } + + /// + /// Gets whether to use the legacy restore method + /// + public bool RestoreLegacy + { + get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-legacy"); } + } + + /// + /// Gets whether to preallocate files during restore + /// + public bool RestorePreAllocate + { + get { return Library.Utility.Utility.ParseBoolOption(m_options, "restore-pre-allocate"); } + } + + /// + /// Gets the number of volume decryptors to use in the restore process + /// + public int RestoreVolumeDecryptors + { + get + { + if (!m_options.TryGetValue("restore-volume-decryptors", out string v)) + v = null; + + if (string.IsNullOrEmpty(v)) + return DEFAULT_RESTORE_VOLUME_DECRYPTORS; + else + return int.Parse(v); + } + } + + /// + /// Gets the number of volume decompressors to use in the restore process + /// + public int RestoreVolumeDecompressors + { + get + { + if (!m_options.TryGetValue("restore-volume-decompressors", out string v)) + v = null; + + if (string.IsNullOrEmpty(v)) + return DEFAULT_RESTORE_VOLUME_DECOMPRESSORS; + else + return int.Parse(v); + } + } + + /// + /// Gets the number of volume downloaders to use in the restore process + /// + public int RestoreVolumeDownloaders + { + get + { + if (!m_options.TryGetValue("restore-volume-downloaders", out string v)) + v = null; + + if (string.IsNullOrEmpty(v)) + return DEFAULT_RESTORE_VOLUME_DOWNLOADERS; + else + return int.Parse(v); + } + } + + public int RestoreChannelBufferSize + { + get + { + if (!m_options.TryGetValue("restore-channel-buffer-size", out string v)) + v = null; + + if (string.IsNullOrEmpty(v)) + return DEFAULT_RESTORE_CHANNEL_BUFFER_SIZE; + else + return int.Parse(v); + } + } + + /// + /// Toggles whether internal profiling is enabled and should be logged. + /// + public bool InternalProfiling + { + get { return Library.Utility.Utility.ParseBoolOption(m_options, "internal-profiling"); } + } + /// /// Class for handling a single RetentionPolicy timeframe-interval-pair /// diff --git a/Duplicati/Library/Main/ProcessController.cs b/Duplicati/Library/Main/ProcessController.cs index 4ac7035ff..31a14f99c 100755 --- a/Duplicati/Library/Main/ProcessController.cs +++ b/Duplicati/Library/Main/ProcessController.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,7 +21,8 @@ using System; using System.Linq; -using Duplicati.Library.Common; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Utility; namespace Duplicati.Library.Main @@ -78,6 +79,11 @@ namespace Duplicati.Library.Main /// private bool m_hasStartedBackgroundMode = false; + /// + /// A timer used to prevent sleep + /// + private CancellationTokenSource m_timerCancellation; + /// /// Initializes a new instance of the class. /// @@ -86,7 +92,7 @@ namespace Duplicati.Library.Main { if (options == null) return; - + try { Start(options); @@ -96,7 +102,6 @@ namespace Duplicati.Library.Main { Logging.Log.WriteWarningMessage(LOGTAG, "ProcessControllerStartError", ex, "Failed to start the process controller: {0}", ex.Message); } - } /// @@ -108,7 +113,30 @@ namespace Duplicati.Library.Main { try { - Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED); + m_timerCancellation?.Cancel(); + m_timerCancellation = new CancellationTokenSource(); + + Task.Run(async () => + { + try + { + while (!m_timerCancellation.Token.IsCancellationRequested) + { + if (OperatingSystem.IsWindows()) + Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS | Win32.EXECUTION_STATE.ES_SYSTEM_REQUIRED); + await Task.Delay(TimeSpan.FromSeconds(10), m_timerCancellation.Token); + } + } + catch (TaskCanceledException) + { + // Ignore + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "SleepPrevetionError", ex, "Failed to set sleep prevention"); + } + }); + m_runningSleepPrevention = true; } catch (Exception ex) @@ -220,7 +248,7 @@ namespace Duplicati.Library.Main { m_originalNiceClass = 0; // Only allowed for "best-effort" and "realtime" - m_originalNiceLevel = -1; + m_originalNiceLevel = -1; } else if (string.Equals(ioclass, "best-effort", StringComparison.OrdinalIgnoreCase)) { @@ -293,6 +321,9 @@ namespace Duplicati.Library.Main if (m_runningSleepPrevention) { m_runningSleepPrevention = false; + m_timerCancellation?.Dispose(); + m_timerCancellation = null; + Win32.SetThreadExecutionState(Win32.EXECUTION_STATE.ES_CONTINUOUS); } } @@ -463,7 +494,7 @@ namespace Duplicati.Library.Main { Stop(); } - catch(Exception ex) + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "ProcessControllerStopError", ex, "Failed to stop the process controller: {0}", ex.Message); } diff --git a/Duplicati/Library/Main/ProgressClasses.cs b/Duplicati/Library/Main/ProgressClasses.cs index fd87597d3..875f771fa 100644 --- a/Duplicati/Library/Main/ProgressClasses.cs +++ b/Duplicati/Library/Main/ProgressClasses.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index 7ac2bbff0..1aee06d9e 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using Duplicati.Library.Interface; @@ -26,6 +26,8 @@ using Duplicati.Library.Main.Database; using Duplicati.Library.Logging; using System.Linq; using Newtonsoft.Json; +using System.Text.Json.Serialization; +using Duplicati.Library.Main.Operation.Common; namespace Duplicati.Library.Main { @@ -53,12 +55,22 @@ namespace Duplicati.Library.Main internal interface ISetCommonOptions { + bool Interrupted { get; set; } + bool Fatal { get; set; } DateTime EndTime { get; set; } DateTime BeginTime { get; set; } IMessageSink MessageSink { get; set; } OperationMode MainOperation { get; } + } - void SetDatabase(LocalDatabase db); + internal interface ITaskControlProvider + { + ITaskControl TaskControl { get; } + } + + internal interface IBackendWriterProvider + { + IBackendWriter BackendWriter { get; } } internal class BackendWriter : BasicResults, IBackendWriter, IBackendStatstics, IParsedBackendStatistics @@ -87,6 +99,7 @@ namespace Duplicati.Library.Main public long UnknownFileCount { get; set; } public long KnownFileCount { get; set; } public long KnownFileSize { get; set; } + public long KnownFilesets { get; set; } public DateTime LastBackupDate { get; set; } public long BackupListCount { get; set; } public long TotalQuotaSpace { get; set; } @@ -137,23 +150,8 @@ namespace Duplicati.Library.Main IBackendProgressUpdater IBackendWriter.BackendProgressUpdater { get { return base.BackendProgressUpdater; } } } - public interface ITaskControl - { - void Pause(); - void Resume(); - void Stop(bool allowCurrentFileToFinish); - void Abort(); - } - internal enum TaskControlState - { - Run, - Pause, - Stop, - Abort - } - - internal abstract class BasicResults : IBasicResults, ISetCommonOptions, ITaskControl, Logging.ILogDestination + internal abstract class BasicResults : IBasicResults, ISetCommonOptions, Logging.ILogDestination, ITaskControlProvider, IBackendWriterProvider { /// /// The tag used for logging @@ -179,15 +177,11 @@ namespace Duplicati.Library.Main } } - protected LocalDatabase m_db; protected readonly BasicResults m_parent; protected System.Threading.Thread m_callerThread; protected readonly object m_lock = new object(); protected readonly Queue m_dbqueue; - private TaskControlState m_controlState = TaskControlState.Run; - private readonly System.Threading.ManualResetEvent m_pauseEvent = new System.Threading.ManualResetEvent(true); - public virtual ParsedResultType ParsedResult { get @@ -203,7 +197,8 @@ namespace Duplicati.Library.Main } } public bool Interrupted { get; set; } - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public bool Fatal { get; set; } // ReSharper disable once UnusedMember.Global @@ -220,10 +215,11 @@ namespace Duplicati.Library.Main protected readonly Library.Utility.FileBackedStringList m_warnings; protected readonly Library.Utility.FileBackedStringList m_errors; protected Library.Utility.FileBackedStringList m_retryAttempts; - + protected IMessageSink m_messageSink; - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IMessageSink MessageSink { get { return m_messageSink; } @@ -262,27 +258,10 @@ namespace Duplicati.Library.Main } } - public void SetDatabase(LocalDatabase db) + public void FlushLog(LocalDatabase db) { if (m_parent != null) - { - m_parent.SetDatabase(db); - } - else - { - lock (m_lock) - { - m_db = db; - if (m_db != null) - db.SetResult(this); - } - } - } - - public void FlushLog() - { - if (m_parent != null) - m_parent.FlushLog(); + m_parent.FlushLog(db); else { lock (m_lock) @@ -290,7 +269,7 @@ namespace Duplicati.Library.Main while (m_dbqueue.Count > 0) { var el = m_dbqueue.Dequeue(); - m_db.LogMessage(el.Type, el.Message, el.Exception, null); + db.LogMessage(el.Type, el.Message, el.Exception, null); } } } @@ -331,21 +310,24 @@ namespace Duplicati.Library.Main } - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IEnumerable Messages { get { return m_messages; } } // ReSharper disable once UnusedMember.Global // This is referenced in the logs. - public int MessagesActualLength { get { return Messages == null ? 0 : Messages.Count(); } } + public int MessagesActualLength { get { return Messages == null ? 0 : Messages.Count(); } } - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IEnumerable Warnings { get { return m_warnings; } } // ReSharper disable once UnusedMember.Global // This is referenced in the logs. public int WarningsActualLength { get { return Warnings == null ? 0 : Warnings.Count(); } } - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IEnumerable Errors { get { return m_errors; } } // ReSharper disable once UnusedMember.Global @@ -353,15 +335,17 @@ namespace Duplicati.Library.Main public int ErrorsActualLength { get { return Errors == null ? 0 : Errors.Count(); } } [JsonProperty(PropertyName = "Messages")] + [JsonPropertyName("Messages")] public IEnumerable LimitedMessages { get { return Messages?.Take(SERIALIZATION_LIMIT); } } [JsonProperty(PropertyName = "Warnings")] + [JsonPropertyName("Warnings")] public IEnumerable LimitedWarnings { get { return Warnings?.Take(SERIALIZATION_LIMIT); } } [JsonProperty(PropertyName = "Errors")] + [JsonPropertyName("Errors")] public IEnumerable LimitedErrors { get { return Errors?.Take(SERIALIZATION_LIMIT); } } - protected readonly Operation.Common.TaskControl m_taskController; - public Operation.Common.ITaskReader TaskReader { get { return m_taskController; } } - + protected readonly TaskControl m_taskController; + public ITaskControl TaskControl => m_parent?.TaskControl ?? m_taskController; protected BasicResults() { this.BeginTime = DateTime.UtcNow; @@ -396,134 +380,10 @@ namespace Duplicati.Library.Main } } - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IBackendWriter BackendWriter { get { return (IBackendWriter)this.BackendStatistics; } } - public event Action StateChangedEvent; - - /// - /// Request that this task pauses. - /// - public void Pause() - { - if (m_parent != null) - m_parent.Pause(); - else - { - lock (m_lock) - if (m_controlState == TaskControlState.Run) - { - m_pauseEvent.Reset(); - m_controlState = TaskControlState.Pause; - } - - if (StateChangedEvent != null) - StateChangedEvent(m_controlState); - } - } - - /// - /// Request that this task resumes. - /// - public void Resume() - { - if (m_parent != null) - m_parent.Resume(); - else - { - lock (m_lock) - if (m_controlState == TaskControlState.Pause) - { - m_pauseEvent.Set(); - m_controlState = TaskControlState.Run; - } - - if (StateChangedEvent != null) - StateChangedEvent(m_controlState); - } - } - - /// - /// Request that this task stops. - /// - public void Stop(bool allowCurrentFileToFinish) - { - if (m_parent != null) - m_parent.Stop(allowCurrentFileToFinish); - else - { - lock (m_lock) - if (m_controlState != TaskControlState.Abort) - { - m_controlState = TaskControlState.Stop; - m_pauseEvent.Set(); - if (!allowCurrentFileToFinish) - { - m_taskController.Stop(true); - } - } - - if (StateChangedEvent != null) - StateChangedEvent(m_controlState); - } - } - - /// - /// Request that this task aborts. - /// - public void Abort() - { - if (m_parent != null) - m_parent.Abort(); - else - { - lock (m_lock) - { - m_controlState = TaskControlState.Abort; - m_pauseEvent.Set(); - } - - if (StateChangedEvent != null) - StateChangedEvent(m_controlState); - } - } - - /// - /// Helper method that the current running task can call to obtain the current state - /// - public TaskControlState TaskControlRendevouz() - { - if (m_parent != null) - return m_parent.TaskControlRendevouz(); - else - { - // If we are paused, go into pause mode - m_pauseEvent.WaitOne(); - - // If we are aborted, throw exception - if (m_controlState == TaskControlState.Abort) - { - System.Threading.Thread.CurrentThread.Interrupt(); - - // For some reason, aborting the current thread does not always throw an exception - throw new CancelException(); - } - - return m_controlState; - } - } - - /// - /// Helper method to check if abort is requested - /// - public bool IsAbortRequested() - { - if (m_parent != null) - return m_parent.IsAbortRequested(); - else - return m_controlState == TaskControlState.Abort; - } - /// /// Returns a that represents the current . /// @@ -565,7 +425,7 @@ namespace Duplicati.Library.Main } } - internal class BackupResults : BasicResults, IBackupResults + internal class BackupResults : BasicResults, IBackupResults, IResultsWithVacuum { public long DeletedFiles { get; internal set; } public long DeletedFolders { get; internal set; } @@ -581,6 +441,7 @@ namespace Duplicati.Library.Main public long AddedFolders { get; internal set; } public long TooLargeFiles { get; internal set; } public long FilesWithError { get; internal set; } + public long TimestampChangedFiles { get; internal set; } public long ModifiedFolders { get; internal set; } public long ModifiedSymlinks { get; internal set; } public long AddedSymlinks { get; internal set; } @@ -591,7 +452,7 @@ namespace Duplicati.Library.Main public override OperationMode MainOperation { get { return OperationMode.Backup; } } public ICompactResults CompactResults { get; internal set; } - public IVacuumResults VacuumResults { get; internal set; } + public IVacuumResults VacuumResults { get; set; } public IDeleteResults DeleteResults { get; internal set; } public IRepairResults RepairResults { get; internal set; } public ITestResults TestResults { get; internal set; } @@ -610,19 +471,19 @@ namespace Duplicati.Library.Main return ParsedResultType.Fatal; } else if ((CompactResults != null && CompactResults.ParsedResult == ParsedResultType.Error) || - (VacuumResults != null && VacuumResults.ParsedResult == ParsedResultType.Error) || - (DeleteResults != null && DeleteResults.ParsedResult == ParsedResultType.Error) || - (RepairResults != null && RepairResults.ParsedResult == ParsedResultType.Error) || - (TestResults != null && TestResults.ParsedResult == ParsedResultType.Error) || + (VacuumResults != null && VacuumResults.ParsedResult == ParsedResultType.Error) || + (DeleteResults != null && DeleteResults.ParsedResult == ParsedResultType.Error) || + (RepairResults != null && RepairResults.ParsedResult == ParsedResultType.Error) || + (TestResults != null && TestResults.ParsedResult == ParsedResultType.Error) || (Errors != null && Errors.Any()) || FilesWithError > 0) { return ParsedResultType.Error; } else if ((CompactResults != null && CompactResults.ParsedResult == ParsedResultType.Warning) || - (VacuumResults != null && VacuumResults.ParsedResult == ParsedResultType.Warning) || - (DeleteResults != null && DeleteResults.ParsedResult == ParsedResultType.Warning) || - (RepairResults != null && RepairResults.ParsedResult == ParsedResultType.Warning) || - (TestResults != null && TestResults.ParsedResult == ParsedResultType.Warning) || + (VacuumResults != null && VacuumResults.ParsedResult == ParsedResultType.Warning) || + (DeleteResults != null && DeleteResults.ParsedResult == ParsedResultType.Warning) || + (RepairResults != null && RepairResults.ParsedResult == ParsedResultType.Warning) || + (TestResults != null && TestResults.ParsedResult == ParsedResultType.Warning) || (Warnings != null && Warnings.Any()) || PartialBackup) { return ParsedResultType.Warning; @@ -635,8 +496,16 @@ namespace Duplicati.Library.Main } } - internal class RestoreResults : BasicResults, Library.Interface.IRestoreResults + internal class RestoreResults : BasicResults, IRestoreResults { + /// + /// The list of broken local files - i.e. locally stored files that raised an error during restore. + /// + public Library.Utility.FileBackedStringList BrokenLocalFiles { get; internal set; } = []; + /// + /// The list of broken remote files - i.e. remotely stored files that raised an error during restore. + /// + public Library.Utility.FileBackedStringList BrokenRemoteFiles { get; internal set; } = []; public long RestoredFiles { get; internal set; } public long SizeOfRestoredFiles { get; internal set; } public long RestoredFolders { get; internal set; } @@ -677,7 +546,7 @@ namespace Duplicati.Library.Main } } - internal class ListResultFile : Duplicati.Library.Interface.IListResultFile + internal class ListResultFile : IListResultFile { public string Path { get; private set; } public IEnumerable Sizes { get; private set; } @@ -688,7 +557,7 @@ namespace Duplicati.Library.Main } } - internal class ListResultFileset : Duplicati.Library.Interface.IListResultFileset + internal class ListResultFileset : IListResultFileset { public long Version { get; private set; } public int IsFullBackup { get; private set; } @@ -705,32 +574,32 @@ namespace Duplicati.Library.Main } } - internal class ListResults : BasicResults, Duplicati.Library.Interface.IListResults + internal class ListResults : BasicResults, IListResults { - private IEnumerable m_filesets; - private IEnumerable m_files; + private IEnumerable m_filesets; + private IEnumerable m_files; public bool EncryptedFiles { get; set; } - public void SetResult(IEnumerable filesets, IEnumerable files) + public void SetResult(IEnumerable filesets, IEnumerable files) { m_filesets = filesets; m_files = files; } - public IEnumerable Filesets { get { return m_filesets; } } - public IEnumerable Files { get { return m_files; } } + public IEnumerable Filesets { get { return m_filesets; } } + public IEnumerable Files { get { return m_files; } } public override OperationMode MainOperation { get { return OperationMode.List; } } } - internal class ListAffectedResults : BasicResults, Duplicati.Library.Interface.IListAffectedResults + internal class ListAffectedResults : BasicResults, IListAffectedResults { - private IEnumerable m_filesets; - private IEnumerable m_files; - private IEnumerable m_logs; - private IEnumerable m_volumes; + private IEnumerable m_filesets; + private IEnumerable m_files; + private IEnumerable m_logs; + private IEnumerable m_volumes; - public void SetResult(IEnumerable filesets, IEnumerable files, IEnumerable logs, IEnumerable volumes) + public void SetResult(IEnumerable filesets, IEnumerable files, IEnumerable logs, IEnumerable volumes) { m_filesets = filesets; m_files = files; @@ -738,17 +607,18 @@ namespace Duplicati.Library.Main m_volumes = volumes; } - public IEnumerable Filesets { get { return m_filesets; } } - public IEnumerable Files { get { return m_files; } } - public IEnumerable LogMessages { get { return m_logs; } } - public IEnumerable RemoteVolumes { get { return m_volumes; } } + public IEnumerable Filesets { get { return m_filesets; } } + public IEnumerable Files { get { return m_files; } } + public IEnumerable LogMessages { get { return m_logs; } } + public IEnumerable RemoteVolumes { get { return m_volumes; } } public override OperationMode MainOperation { get { return OperationMode.ListAffected; } } } - internal class DeleteResults : BasicResults, Duplicati.Library.Interface.IDeleteResults + internal class DeleteResults : BasicResults, IDeleteResults { - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IEnumerable> DeletedSets { get; private set; } // ReSharper disable once UnusedMember.Global @@ -756,6 +626,7 @@ namespace Duplicati.Library.Main public int DeletedSetsActualLength { get { return DeletedSets == null ? 0 : DeletedSets.Count(); } } [JsonProperty(PropertyName = "DeletedSets")] + [JsonPropertyName("DeletedSets")] public IEnumerable> LimitedDeletedSets { get { return DeletedSets?.Take(SERIALIZATION_LIMIT); } } public bool Dryrun { get; private set; } @@ -793,7 +664,7 @@ namespace Duplicati.Library.Main } } - internal class RecreateDatabaseResults : BasicResults, Library.Interface.IRecreateDatabaseResults + internal class RecreateDatabaseResults : BasicResults, IRecreateDatabaseResults { public override OperationMode MainOperation { get { return OperationMode.Repair; } } @@ -801,13 +672,13 @@ namespace Duplicati.Library.Main public RecreateDatabaseResults(BasicResults p) : base(p) { } } - internal class CreateLogDatabaseResults : BasicResults, Library.Interface.ICreateLogDatabaseResults + internal class CreateLogDatabaseResults : BasicResults, ICreateLogDatabaseResults { public override OperationMode MainOperation { get { return OperationMode.CreateLogDb; } } public string TargetPath { get; internal set; } } - internal class RestoreControlFilesResults : BasicResults, Library.Interface.IRestoreControlFilesResults + internal class RestoreControlFilesResults : BasicResults, IRestoreControlFilesResults { public IEnumerable Files { get; private set; } @@ -815,7 +686,7 @@ namespace Duplicati.Library.Main public void SetResult(IEnumerable files) { this.Files = files; } } - internal class ListRemoteResults : BasicResults, Library.Interface.IListRemoteResults + internal class ListRemoteResults : BasicResults, IListRemoteResults { public IEnumerable Files { get; private set; } @@ -823,13 +694,13 @@ namespace Duplicati.Library.Main public void SetResult(IEnumerable files) { this.Files = files; } } - internal class RepairResults : BasicResults, Library.Interface.IRepairResults + internal class RepairResults : BasicResults, IRepairResults { public override OperationMode MainOperation { get { return OperationMode.Repair; } } public RepairResults() : base() { } public RepairResults(BasicResults p) : base(p) { } - public Library.Interface.IRecreateDatabaseResults RecreateDatabaseResults { get; internal set; } + public IRecreateDatabaseResults RecreateDatabaseResults { get; internal set; } public override ParsedResultType ParsedResult { @@ -858,7 +729,7 @@ namespace Duplicati.Library.Main } } - internal class CompactResults : BasicResults, Library.Interface.ICompactResults + internal class CompactResults : BasicResults, ICompactResults, IResultsWithVacuum { public long DeletedFileCount { get; internal set; } public long DownloadedFileCount { get; internal set; } @@ -868,7 +739,7 @@ namespace Duplicati.Library.Main public long UploadedFileSize { get; internal set; } public bool Dryrun { get; internal set; } - public IVacuumResults VacuumResults { get; internal set; } + public IVacuumResults VacuumResults { get; set; } public override OperationMode MainOperation { get { return OperationMode.Compact; } } @@ -876,7 +747,7 @@ namespace Duplicati.Library.Main public CompactResults(BasicResults p) : base(p) { } } - internal class ListChangesResults : BasicResults, Library.Interface.IListChangesResults + internal class ListChangesResults : BasicResults, IListChangesResults { public override OperationMode MainOperation { get { return OperationMode.ListChanges; } } @@ -949,7 +820,8 @@ namespace Duplicati.Library.Main public override OperationMode MainOperation { get { return OperationMode.Test; } } private readonly List>>> m_verifications = new List>>>(); - [JsonIgnore] + [System.Text.Json.Serialization.JsonIgnore] + [Newtonsoft.Json.JsonIgnore] public IEnumerable>>> Verifications { get { return m_verifications; } } // ReSharper disable once UnusedMember.Global @@ -957,6 +829,7 @@ namespace Duplicati.Library.Main public int VerificationsActualLength { get { return Verifications == null ? 0 : Verifications.Count(); } } [JsonProperty(PropertyName = "Verifications")] + [JsonPropertyName("Verifications")] public IEnumerable>>> LimitedVerifications { get { return Verifications?.Take(SERIALIZATION_LIMIT); } } public KeyValuePair>> AddResult(string volume, IEnumerable> changes) diff --git a/Duplicati/Library/Main/SecretProviderHelper.cs b/Duplicati/Library/Main/SecretProviderHelper.cs new file mode 100644 index 000000000..f16409477 --- /dev/null +++ b/Duplicati/Library/Main/SecretProviderHelper.cs @@ -0,0 +1,540 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Duplicati.Library.DynamicLoader; +using Duplicati.Library.Interface; +using Duplicati.Library.Logging; +using Duplicati.Library.Utility; +using Google.Protobuf.WellKnownTypes; + +namespace Duplicati.Library.Main; + +/// +/// Helper to apply secret provider to arguments +/// +public static class SecretProviderHelper +{ + /// + /// The log tag + /// + private static readonly string LOGTAG = Log.LogTagFromType(); + + /// + /// The default pattern to use for matching + /// + public const string DEFAULT_PATTERN = "$"; + + /// + /// The different levels of caching permitted for the secrets + /// + public enum CachingLevel + { + /// + /// Values are always fetched from the provider + /// + None, + /// + /// Values are cached in memory and used if the provider is not available + /// + InMemory, + /// + /// Values are cached in memory and saved to disk with encryption. + /// If the provider is not available, the values are fetched from disk. + /// + Persistent + } + + /// + /// Creates an instance of a secret provider with caching enabled + /// + /// The configuration string + /// The caching level + /// The folder to persist the cache to + /// The salt to use for hashing + /// The pattern to use for matching + /// The cancellation token + /// The secret provider instance + public static async Task CreateInstanceAsync(string config, CachingLevel cachingLevel, string persistedFolder, string salt, string pattern, CancellationToken cancelToken) + { + var provider = SecretProviderLoader.CreateInstance(config); + var sp = WrapWithCache(config, provider, cachingLevel, persistedFolder, salt, pattern); + await sp.InitializeAsync(new System.Uri(config), cancelToken).ConfigureAwait(false); + + return sp; + } + + /// + /// Wraps a secret provider with caching + /// + /// The configuration string + /// The provider to wrap + /// The caching level + /// The folder to persist the cache to + /// The salt to use for hashing + /// The pattern to use for matching + /// The wrapped secret provider + public static ISecretProvider WrapWithCache(string config, ISecretProvider provider, CachingLevel cachingLevel, string persistedFolder, string salt, string? pattern) + => new SecretProviderCached(config, provider, cachingLevel, persistedFolder, salt, pattern); + + /// + /// Applies the secret provider to the arguments. + /// Note that this method modifes the arguments and options in place. + /// + /// The arguments to modify, of type + /// The arguments to modify, of type + /// The options to modify + /// The persisted secret cache folder + /// The fallback provider to use if no provider is specified + /// The cancellation token + /// The secret provider + public static async Task ApplySecretProviderAsync(System.Uri?[] realUriArguments, Library.Utility.Uri[] internalUriArguments, Dictionary options, string persistedFolder, ISecretProvider? fallbackProvider, CancellationToken cancellationToken) + { + var provider = options.GetValueOrDefault("secret-provider"); + if (string.IsNullOrWhiteSpace(provider) && fallbackProvider == null) + return null; + + var pattern = options.GetValueOrDefault("secret-provider-pattern"); + + ISecretProvider secretProvider; + if (string.IsNullOrWhiteSpace(provider)) + { + secretProvider = fallbackProvider + ?? throw new InvalidOperationException("No secret provider specified"); + } + else + { + var newProvider = SecretProviderLoader.CreateInstance(provider); + + // Weak salt, but semi-static + string salt; + using (var hasher = HashFactory.CreateHasher(HashFactory.SHA256)) + salt = Environment.MachineName.ComputeHashToHex(hasher); + + var cachingLevel = Library.Utility.Utility.ParseEnumOption(options, "secret-provider-cache", CachingLevel.None); + + secretProvider = WrapWithCache(provider, newProvider, cachingLevel, persistedFolder, salt, pattern); + await secretProvider.InitializeAsync(new System.Uri(provider), cancellationToken).ConfigureAwait(false); + } + + if (string.IsNullOrWhiteSpace(pattern) && secretProvider is SecretProviderCached cached) + pattern = cached.Pattern; + + if (string.IsNullOrWhiteSpace(pattern)) + pattern = DEFAULT_PATTERN; + + await ReplaceSecretsAsync(secretProvider, realUriArguments, internalUriArguments, options, pattern, cancellationToken).ConfigureAwait(false); + return secretProvider; + } + + /// + /// Helper method that finds all secrets matching the prefix and replaces them with the resolved values + /// + /// The secret provider to use + /// The arguments to modify, of type + /// The arguments to modify, of type + /// Any options to update + /// The prefix to look for + /// The cancellation token + /// An awaitable task + public static async Task ReplaceSecretsAsync(ISecretProvider provider, System.Uri?[] realUriArguments, Library.Utility.Uri[] internalUriArguments, Dictionary options, string matchpattern, CancellationToken cancelToken) + { + // Unwrap ${} to support ${name is long} + var suffix = string.Empty; + var matcher = @"(\w|/)"; + if (matchpattern.EndsWith("{}") || matchpattern.EndsWith("()") || matchpattern.EndsWith("[]")) + { + suffix = matchpattern[^1..]; + matchpattern = matchpattern[..^1]; + matcher = @"[^" + Regex.Escape(suffix) + "]"; + } + + // For the values, they could be urls, so we need to look inside the strings + var pattern = new Regex(@$"{Regex.Escape(matchpattern)}(?{matcher}+){Regex.Escape(suffix)}", RegexOptions.ExplicitCapture); + + // When we get the secrets, replace these values + var optionsMap = options + .Where(x => !x.Key.StartsWith("secret-provider", StringComparison.OrdinalIgnoreCase)) + .Select(x => (x.Key, Secret: GetKey(x.Value, pattern))) + .Where(x => !string.IsNullOrWhiteSpace(x.Secret)) + .GroupBy(x => x.Secret!) + .ToDictionary(x => x.Key, x => x.Select(y => y.Key).ToArray()); + + var realUriMap = realUriArguments + .Zip(Enumerable.Range(0, realUriArguments.Length)) + .Where(x => !string.IsNullOrWhiteSpace(x.First?.Query)) + .Select(x => (Source: x.Second, Params: HttpUtility.ParseQueryString(x.First!.Query))) + .SelectMany(x => x.Params.AllKeys.Select(k => (Source: x.Source, Key: k, Value: x.Params[k]))) + .Select(x => (x.Source, x.Key, Secret: GetKey(x.Value, pattern))) + .Where(x => !string.IsNullOrWhiteSpace(x.Secret)) + .GroupBy(x => x.Secret!) + .ToDictionary(x => x.Key, x => x.Select(y => (y.Source, y.Key)).ToArray()); + + var internalUriMap = internalUriArguments + .Zip(Enumerable.Range(0, internalUriArguments.Length)) + .Select(x => (Source: x.Second, Value: x.First)) + .Where(x => !string.IsNullOrWhiteSpace(x.Value.Query)) + .Select(x => (Source: x.Source, Params: x.Value.QueryParameters)) + .SelectMany(x => x.Params.AllKeys.Select(k => (Source: x.Source, Key: k, Value: x.Params[k]))) + .Select(x => (x.Source, x.Key, Secret: GetKey(x.Value, pattern))) + .Where(x => !string.IsNullOrWhiteSpace(x.Secret)) + .GroupBy(x => x.Secret!) + .ToDictionary(x => x.Key, x => x.Select(y => (y.Source, y.Key)).ToArray()); + + var secrets = realUriMap.Keys + .Concat(internalUriMap.Keys) + .Concat(optionsMap.Keys) + .Distinct() + .ToArray(); + + if (secrets.Length == 0) + return; + + var translated = await provider.ResolveSecretsAsync(secrets, cancelToken).ConfigureAwait(false); + + // Sanity check the results to guard against faulty providers + if (translated.Any(x => string.IsNullOrWhiteSpace(x.Value))) + throw new InvalidOperationException("The secret provider returned an empty key"); + + // Update options by replacing values + foreach (var v in optionsMap) + foreach (var k in v.Value) + options[k] = translated[v.Key]; + + // Update real uri arguments by replacing values + foreach (var v in realUriMap) + foreach (var (s, k) in v.Value) + { + var builder = new UriBuilder(realUriArguments[s]!); + var query = HttpUtility.ParseQueryString(builder.Query); + query[k] = translated[v.Key]; + builder.Query = query.ToString(); + realUriArguments[s] = builder.Uri; + } + + // Update internal uri arguments by replacing values + foreach (var v in internalUriMap) + foreach (var (s, k) in v.Value) + { + var uri = internalUriArguments[s]; + var kp = uri.QueryParameters; + kp[k] = Library.Utility.Uri.UrlEncode(translated[v.Key]); + uri = uri.SetQuery(Library.Utility.Uri.BuildUriQuery(kp)); + internalUriArguments[s] = uri; + } + + return; + } + + /// + /// Gets the key from a value using the pattern + /// + /// The value to get the key from + /// The pattern to use + /// The key or null if not found + private static string? GetKey(string? value, Regex pattern) + { + if (string.IsNullOrWhiteSpace(value)) + return null; + + var match = pattern.Match(value); + if (!match.Success || string.IsNullOrWhiteSpace(match.Groups["key"].Value) || match.Length != value.Length) + return null; + + return match.Groups["key"].Value; + } + + /// + /// A cache for secret provider values + /// + private class SecretProviderCached : ISecretProvider + { + /// + /// The provider being cached + /// + private readonly ISecretProvider _provider; + /// + /// A flag indicating if the provider has been initialized + /// + private bool _initialized; + /// + /// The caching level + /// + private readonly CachingLevel _cachingLevel; + /// + /// The configuration string + /// + private readonly string _config; + /// + /// The salt used for hashing and uniqueness + /// + private readonly string _salt; + /// + /// The persisted file; null if not persistent + /// + private readonly string? _persistedFile; + /// + /// The passphrase used to encrypt the persisted file + /// + private readonly string? _passphrase; + /// + /// The lock object guarding _cache + /// + private static readonly object _lock = new(); + /// + /// The in-memory cache of secrets + /// + private static readonly Dictionary> _cache = new(); + + /// + /// The pattern associated with the provider + /// + public string Pattern { get; } + + /// + /// Creates a new instance of the secret provider cache + /// + /// The configuration string + /// The provider to cache + /// The caching level + /// The folder to persist the cache to + /// The salt to use for hashing + /// The pattern to use for matching + public SecretProviderCached(string config, ISecretProvider provider, CachingLevel cachingLevel, string persistedFolder, string salt, string? pattern) + { + _provider = provider; + _cachingLevel = cachingLevel; + _config = config; + _salt = salt; + Pattern = pattern ?? DEFAULT_PATTERN; + if (cachingLevel == CachingLevel.Persistent) + { + // Create a unique file name for the cache, tied to the configuration + var name = Convert.ToBase64String(Library.Utility.Utility.RepeatedHashWithSalt(config, salt))[..12]; + _persistedFile = Path.Combine(persistedFolder, $"secret-cache-{name}.json.aes"); + + // If either the salt of the config changes, we loose the cache, both the filename and password will fail + using (var hasher = HashFactory.CreateHasher(HashFactory.SHA256)) + _passphrase = Convert.ToBase64String($"{_salt}:{_config}".ComputeHash(hasher)); + } + else + { + _persistedFile = null; + _passphrase = null; + } + } + + /// + public string Key => _provider.Key; + + /// + public string DisplayName => _provider.DisplayName; + + /// + public string Description => _provider.Description; + + /// + public IList SupportedCommands => _provider.SupportedCommands; + + /// + public async Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + try + { + // Always initialize the provider, and use this if possible + await _provider.InitializeAsync(config, cancellationToken).ConfigureAwait(false); + _initialized = true; + } + catch + { + if (_cachingLevel == CachingLevel.None) + throw; + if (_cachingLevel == CachingLevel.InMemory && !_cache.ContainsKey(_config)) + throw; + if (_cachingLevel == CachingLevel.Persistent) + { + await LoadCacheAsync(cancellationToken).ConfigureAwait(false); + if (!_cache.ContainsKey(_config)) + throw; + } + } + } + + /// + /// Loads the cache from disk, failing silently if the file could not be read + /// + /// The cancellation token + /// An awaitable task + private async Task LoadCacheAsync(CancellationToken cancellationToken) + { + bool tryLoad; + lock (_lock) + tryLoad = _cachingLevel == CachingLevel.Persistent && !_cache.ContainsKey(_config) && File.Exists(_persistedFile); + + if (tryLoad && !string.IsNullOrEmpty(_passphrase)) + { + // Load from disk + try + { + using (var fs = new FileStream(_persistedFile!, FileMode.Open, FileAccess.Read)) + using (var ms = new MemoryStream()) + { + var decOpts = SharpAESCrypt.DecryptionOptions.Default with { LeaveOpen = true }; + await SharpAESCrypt.AESCrypt.DecryptAsync(_passphrase, fs, ms, decOpts, cancellationToken).ConfigureAwait(false); + + ms.Position = 0; + var res = await System.Text.Json.JsonSerializer.DeserializeAsync>(ms, cancellationToken: cancellationToken).ConfigureAwait(false) + ?? throw new InvalidOperationException("Failed to deserialize the cache"); + + lock (_lock) + if (!_cache.ContainsKey(_config)) + _cache[_config] = res; + } + } + catch (Exception ex) + { + Log.WriteWarningMessage(LOGTAG, "LoadPersistedCacheError", ex, "Failed to load cache from disk: {0}", ex.Message); + } + } + } + + /// + /// Saves the cache to disk + /// + /// The cancellation token + /// An awaitable task + private async Task SaveCacheAsync(CancellationToken cancellationToken) + { + bool trySave; + lock (_lock) + trySave = _initialized && _cachingLevel == CachingLevel.Persistent && _cache.ContainsKey(_config); + + if (trySave && !string.IsNullOrEmpty(_passphrase)) + { + try + { + using (var ms = new MemoryStream()) + { + Dictionary data; + lock (_lock) + data = _cache[_config].ToDictionary(k => k.Key, k => k.Value); + + await System.Text.Json.JsonSerializer.SerializeAsync(ms, data, cancellationToken: cancellationToken).ConfigureAwait(false); + ms.Position = 0; + + var encOpts = SharpAESCrypt.EncryptionOptions.Default; + using (var fs = new FileStream(_persistedFile!, FileMode.Create, FileAccess.Write)) + await SharpAESCrypt.AESCrypt.EncryptAsync(_passphrase, ms, fs, encOpts, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Log.WriteWarningMessage(LOGTAG, "SavePersistedCacheError", ex, "Failed to save cache to disk: {0}", ex.Message); + } + } + } + + /// + /// Gets the cached values for the given keys + /// + /// The keys to get + /// The cached values or null if not found + private Dictionary? GetFromCache(IEnumerable keys) + { + if (_cachingLevel == CachingLevel.InMemory || _cachingLevel == CachingLevel.Persistent) + { + lock (_lock) + if (_cache.ContainsKey(_config) && keys.All(x => _cache[_config].ContainsKey(x))) + return keys.ToDictionary(k => k, k => _cache[_config][k]); + } + + return null; + + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + // Don't call the provider if it was not initialized + if (!_initialized) + { + var cached = GetFromCache(keys); + if (cached != null) + return cached; + + throw new InvalidOperationException("The provider has not been initialized"); + } + + // Always call the provider to get fresh values, if it was initialized + Dictionary result; + try + { + result = await _provider.ResolveSecretsAsync(keys, cancellationToken).ConfigureAwait(false); + } + catch + { + if (_cachingLevel == CachingLevel.None) + throw; + + if (_cachingLevel == CachingLevel.InMemory || _cachingLevel == CachingLevel.Persistent) + { + var cached = GetFromCache(keys); + if (cached != null) + return cached; + } + + throw; + } + + // We have a result, cache it + if (_cachingLevel == CachingLevel.InMemory || _cachingLevel == CachingLevel.Persistent) + { + lock (_lock) + { + if (!_cache.ContainsKey(_config)) + { + _cache[_config] = result; + } + else + { + foreach (var k in result) + _cache[_config][k.Key] = k.Value; + } + } + + if (_cachingLevel == CachingLevel.Persistent) + await SaveCacheAsync(cancellationToken).ConfigureAwait(false); + } + + + return result; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Main/Strings.cs b/Duplicati/Library/Main/Strings.cs index d90d461bf..bb5bfca54 100644 --- a/Duplicati/Library/Main/Strings.cs +++ b/Duplicati/Library/Main/Strings.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in +// +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using Duplicati.Library.Localization.Short; @@ -32,15 +32,8 @@ namespace Duplicati.Library.Main.Strings public static string NoSourceFoldersError { get { return LC.L(@"No source folders specified for backup"); } } public static string SourceIsMissingError(string foldername) { return LC.L(@"Backup aborted since the source path {0} does not exist. Please verify that the source path exists, or remove the source path from the backup configuration, or set the allow-missing-source option.", foldername); } public static string SourceUnauthorizedError(string foldername) { return LC.L(@"Unauthorized to access source folder {0}, aborting backup", foldername); } - public static string UnsupportedBooleanValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not parse into a valid boolean. This will be treated as if it was set to ""true""", optionname, value); } - public static string UnsupportedEnumerationValue(string optionname, string value, string[] values) { return LC.L(@"The option --{0} does not support the value ""{1}"". Supported values are: {2}", optionname, value, string.Join(", ", values)); } - public static string UnsupportedFlagsValue(string optionname, string value, string[] values) { return LC.L(@"The option --{0} does not support the value ""{1}"". Supported flag values are: {2}", optionname, value, string.Join(", ", values)); } - public static string UnsupportedIntegerValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid integer", optionname, value); } public static string UnsupportedOptionDisabledModuleWarning(string optionname, string modulename) { return LC.L(@"The option --{0} is not supported because the module {1} is not currently loaded", optionname, modulename); } public static string UnsupportedOptionWarning(string optionname) { return LC.L(@"The supplied option --{0} is not supported and will be ignored", optionname); } - public static string UnsupportedPathValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid path", optionname, value); } - public static string UnsupportedSizeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid size", optionname, value); } - public static string UnsupportedTimeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid time", optionname, value); } public static string StartingOperationMessage(OperationMode operationname) { return LC.L(@"The operation {0} has started", operationname); } public static string CompletedOperationMessage(OperationMode operationname) { return LC.L(@"The operation {0} has completed", operationname); } public static string FailedOperationMessage(OperationMode operationname, string errormessage) { return LC.L(@"The operation {0} has failed with error: {1}", operationname, errormessage); } @@ -48,7 +41,6 @@ namespace Duplicati.Library.Main.Strings public static string FailedForceLocaleError(string exMsg) { return LC.L(@"Failed to apply 'force-locale' setting. Please try to update .NET-Framework. Exception was: ""{0}"" ", exMsg); } public static string SourceVolumeNameInvalidError(string filename) { return LC.L(@"The source {0} uses an invalid volume name, aborting backup", filename); } public static string SourceVolumeNameNotFoundError(string filename, Guid volumeGuid) { return LC.L(@"The source {0} is on volume {1}, which could not be found, aborting backup", filename, volumeGuid); } - public static string NonQualifiedSizeValue(string optionname, string value) { return LC.L(@"The size ""{1}"" supplied to --{0} does not have a multiplier (b, kb, mb, etc). A multiplier is recommended to avoid unexpected changes if the program is updated.", optionname, value); } } internal static class Options @@ -95,12 +87,15 @@ namespace Duplicati.Library.Main.Strings public static string SkipfileslargerthanShort { get { return LC.L(@"Limit the size of files being backed up"); } } public static string TempdirLong { get { return LC.L(@"Use this option to supply an alternative folder for temporary storage. By default the system default temporary folder is used. Note that also SQLite will put temporary files in this temporary folder."); } } public static string TempdirShort { get { return LC.L(@"Temporary storage folder"); } } + public static string ThreadpriorityDeprecated { get { return LC.L(@"The option --thread-priority has no effect, use the operating system controls to set the process priority"); } } public static string ThreadpriorityLong { get { return LC.L(@"Select another thread priority for the process. Use this to set Duplicati to be more or less CPU intensive."); } } public static string ThreadpriorityShort { get { return LC.L(@"Thread priority"); } } public static string DblocksizeLong { get { return LC.L(@"This option can change the maximum size of dblock files. Changing the size can be useful if the backend has a limit on the size of each individual file."); } } public static string DblocksizeShort { get { return LC.L(@"Limit the size of the volumes"); } } public static string DisableStreamingLong { get { return LC.L(@"Use this option to disallow usage of the streaming interface, which means that transfer progress bars will not show, and bandwidth throttle settings will be ignored."); } } public static string DisableStreamingShort { get { return LC.L(@"Disable use of the streaming transfer method"); } } + public static string ReadWriteTimeoutShort { get { return LC.L(@"Set the read/write timeout for the connection"); } } + public static string ReadWriteTimeoutLong { get { return LC.L(@"The read/write timeout is the maximum amount of time to wait for any activity during a transfer. If no activity is detected for this period, the connection is considered broken and the transfer is aborted. Set to 0s to disabled"); } } public static string DontreadmanifestsLong { get { return LC.L(@"Use this option to make sure the contents of the manifest file are not read. This also implies that file hashes are not checked either. Use only for disaster recovery."); } } public static string DontreadmanifestsShort { get { return LC.L(@"Disable manifests verification"); } } public static string CompressionmoduleLong { get { return LC.L(@"Duplicati supports pluggable compression modules. Use this option to select a module to use for compression. This is only applied when creating new volumes, when reading an existing file, the filename is used to select the compression module."); } } @@ -113,9 +108,11 @@ namespace Duplicati.Library.Main.Strings public static string EnablemoduleShort { get { return LC.L(@"Enable one or more modules"); } } public static string SnapshotpolicyLong { get { return LC.L(@"This setting controls the usage of snapshots, which allows Duplicati to backup files that are locked by other programs. If this is set to ""off"", Duplicati will not attempt to create a disk snapshot. Setting this to ""auto"" makes Duplicati attempt to create a snapshot, and fail silently if that was not allowed or supported (note that the OS may still log system warnings). A setting of ""on"" will also make Duplicati attempt to create a snapshot, but will produce a warning message in the log if it fails. Setting it to ""required"" will make Duplicati abort the backup if the snapshot creation fails. On windows this uses the Volume Shadow Copy Services (VSS) and requires administrative privileges. On Linux this uses Logical Volume Management (LVM) and requires root privileges."); } } public static string SnapshotpolicyShort { get { return LC.L(@"Control the use of disk snapshots"); } } + public static string SnapshotproviderLong { get { return LC.L(@"The snapshot provider implementation for Windows. The AlphaVSS is the most feature complete, but is not supported on Arm64. The WMIC based snapshot has less features, but is more portable. On Linux, only LVM is supported"); } } + public static string SnapshotproviderShort { get { return LC.L(@"The snapshot provider implementation to use"); } } public static string AsynchronousuploadfolderLong { get { return LC.L(@"The pre-generated volumes will be placed into the temporary folder by default. This option can set a different folder for placing the temporary volumes. Despite the name, this also works for synchronous runs."); } } public static string AsynchronousuploadfolderShort { get { return LC.L(@"The path where ready volumes are placed until uploaded"); } } - public static string AsynchronousuploadlimitLong { get { return LC.L(@"When performing asynchronous uploads, Duplicati will create volumes that can be uploaded. To prevent Duplicati from generating too many volumes, this option limits the number of pending uploads. Set to zero to disable the limit."); } } + public static string AsynchronousuploadlimitLong { get { return LC.L(@"When performing asynchronous uploads, Duplicati will create volumes that can be uploaded. To prevent Duplicati from generating too many volumes, this option limits the number of pending uploads. Set to zero to disable the limit. The volume(s) that are being created are not counted in this limit. Use the option --concurrency-compressors=1 to limit the number of volumes being created."); } } public static string AsynchronousuploadlimitShort { get { return LC.L(@"The number of volumes to create ahead of time"); } } public static string AsynchronousconcurrentuploadlimitLong { get { return LC.L(@"When performing asynchronous uploads, the maximum number of concurrent uploads allowed. Set to zero to disable the limit."); } } public static string AsynchronousconcurrentuploadlimitShort { get { return LC.L(@"The number of concurrent uploads allowed"); } } @@ -138,7 +135,7 @@ namespace Duplicati.Library.Main.Strings public static string DisabletimetoleranceShort { get { return LC.L(@"Deactivate tolerance when comparing times"); } } public static string ListverifyuploadsLong { get { return LC.L(@"Use this option to verify uploads by listing contents."); } } public static string ListverifyuploadsShort { get { return LC.L(@"Verify uploads by listing contents"); } } - public static string SynchronousuploadLong { get { return LC.L(@"Duplicati will upload files while scanning the disk and producing volumes, which usually makes the backup faster. Use this option to turn the behavior off, so that Duplicati will wait for each volume to complete."); } } + public static string SynchronousuploadLong { get { return LC.L(@"Disables uploading multiple files concurrently to preserve bandwith. This will have the same effect as setting --asynchronous-upload-limit=1 but additionally wait for related uploads. The volume that is being created is not counted in the upload limit."); } } public static string SynchronousuploadShort { get { return LC.L(@"Upload files synchronously"); } } public static string NoconnectionreuseLong { get { return LC.L(@"Duplicati will attempt to perform multiple operations on a single connection, as this avoids repeated login attempts, and thus speeds up the process. Use this option to ensure that each operation is performed on a seperate connection."); } } public static string NoconnectionreuseShort { get { return LC.L(@"Do not re-use connections"); } } @@ -168,6 +165,8 @@ namespace Duplicati.Library.Main.Strings public static string MachineidShort { get { return LC.L(@"Machine ID"); } } public static string MachinenameLong { get { return LC.L(@"The name of the machine running the backup. This can be used to identify the machine when sending mail or running scripts."); } } public static string MachinenameShort { get { return LC.L(@"Machine name"); } } + public static string NextscheduledrunShort { get { return LC.L(@"The time of the next scheduled run"); } } + public static string NextscheduledrunLong { get { return LC.L(@"This property is a reporting option and does not affect the actual scheduled time. Use this option to inform a reporting destination about the next expected time the backup will run."); } } public static string CompressionextensionfileLong(string path) { return LC.L(@"Use this option to point to a text file where each line contains a file extension that indicates a non-compressible file. Files that have an extension found in the file will not be compressed, but simply stored in the archive. The file format ignores any lines that do not start with a period, and considers a space to indicate the end of the extension. A default file is supplied, that also serves as an example. The default file is placed in {0}.", path); } public static string CompressionextensionfileShort { get { return LC.L(@"Manage non-compressible file extensions"); } } public static string BlocksizeLong { get { return LC.L(@"The block size determines how files are fragmented. Choosing a large value will cause a larger overhead on file changes, choosing a small value will cause a large overhead on storage of file lists. Note that the value cannot be changed after remote files are created."); } } @@ -181,8 +180,6 @@ namespace Duplicati.Library.Main.Strings public static string DisablefilepathcacheLong { get { return LC.L(@"Use this option to reduce the memory footprint by not keeping paths and modification timestamps in memory."); } } public static string DisablefilepathcacheShort { get { return LC.L(@"Reduce memory footprint by disabling in-memory lookups"); } } public static string DisablefilepathcacheDeprecated { get { return LC.L(@"The option --{0} is no longer used and has been deprecated.", "disable-filepath-cache"); } } - public static string UseblockcacheLong { get { return LC.L(@"Use this option to increase speed in exchange for extra memory use."); } } - public static string UseblockcacheShort { get { return LC.L(@"Store an in-memory block cache"); } } public static string NobackendverificationLong { get { return LC.L(@"If this option is set, the local database is not compared to the remote filelist on startup. The intended usage for this option is to work correctly in cases where the filelisting is broken or unavailable."); } } public static string NobackendverificationShort { get { return LC.L(@"Do not query backend at startup"); } } public static string IndexfilepolicyLong { get { return LC.L(@"The index files are used to limit the need for downloading dblock files when there is no local database present. The more information is recorded in the index files, the faster operations can proceed without the database. The tradeoff is that larger index files take up more remote space and which may never be used."); } } @@ -225,7 +222,7 @@ namespace Duplicati.Library.Main.Strings public static string UploadverificationfileShort { get { return LC.L(@"Determine if verification files are uploaded"); } } public static string BackendtestsamplesLong(string optionname) { return LC.L(@"After a backup is completed, some (dblock, dindex, dlist) files from the remote backend are selected for verification. Use this option to change how many. If the option --{0} is also provided, the number of samples tested is the maximum implied by the two options. If this value is set to 0 or the option --{1} is set, no remote files are verified.", "backup-test-percentage", optionname); } public static string BackendtestsamplesShort { get { return LC.L(@"The number of samples to test after a backup"); } } - public static string BackendtestpercentageLong { get { return LC.L(@"After a backup is completed, some (dblock, dindex, dlist) files from the remote backend are selected for verification. Use this option to specify the percentage (between 0 and 100) of files to test. If the option --{0} is also provided, the number of samples tested is the maximum implied by the two options. If the option --{1} is provided, no remote files are verified.", "backup-test-samples", "no-backend-verification"); } } + public static string BackendtestpercentageLong { get { return LC.L(@"After a backup is completed, some samples (one sample is 1 dblock, 1 dindex, and 1 dlist) files from the remote backend are selected for verification. Use this option to specify the percentage (between 0 and 100) of samples to test. If the option --{0} is also provided, the number of samples tested is the maximum implied by the two options. If the option --{1} is provided, no remote files are verified.", "backup-test-samples", "no-backend-verification"); } } public static string BackendtestpercentageShort { get { return LC.L(@"The percentage of samples to test after a backup"); } } public static string FullremoteverificationLong(string optionname) { return LC.L(@"After a backup is completed, some (dblock, dindex, dlist) files from the remote backend are selected for verification. Use this option to turn on full verification, which will decrypt the files and examine the insides of each volume, instead of simply verifying the external hash. If the option --{0} is set, no remote files are verified. This option is automatically set when then verification is performed directly. ListAndIndexes is like True but only dlist and index volumes are handled.", optionname); } public static string FullremoteverificationShort { get { return LC.L(@"Activate in-depth verification of files"); } } @@ -255,6 +252,8 @@ namespace Duplicati.Library.Main.Strings public static string LogretentionShort { get { return LC.L(@"Clean up old log data"); } } public static string RepaironlypathsLong { get { return LC.L(@"Use this option to build a searchable local database which only contains path information. This option is usable for quickly building a database to locate certain content without needing to reconstruct all information. The resulting database can be searched, but cannot be used to restore data with."); } } public static string RepaironlypathsShort { get { return LC.L(@"Repair database with paths"); } } + public static string RepairignoreoutdateddatabaseLong { get { return LC.L(@"Use this option to ignore that the remote destination contains newer contents than the database. This should only be used when the database is known to be correct, but the remote destination is not, as data can be lost otherwise."); } } + public static string RepairignoreoutdateddatabaseShort { get { return LC.L(@"Ignore outdated database files"); } } public static string ForcelocaleLong { get { return LC.L(@"By default, your system locale and culture settings will be used. In some cases you may prefer to run with another locale, for example to get messages in another language. Use this option to set the locale. Supply a blank string to choose the ""Invariant Culture""."); } } public static string ForcelocaleShort { get { return LC.L(@"Force the locale setting"); } } public static string ForceActualDateLong { get { return LC.L(@"By default, dates are displayed in the calendar format, meaning ""Today"" or ""Last Thursday"". By setting this option, only the actual dates are displayed, ""Nov 12, 2018, 8:01 AM"" for example."); } } @@ -267,6 +266,8 @@ namespace Duplicati.Library.Main.Strings public static string ConcurrencyblockhashersShort { get { return LC.L(@"Specify the number of concurrent hashing processes"); } } public static string ConcurrencycompressorsLong { get { return LC.L(@"Use this option to set the number of processes that perform compression of output data."); } } public static string ConcurrencycompressorsShort { get { return LC.L(@"Specify the number of concurrent compression processes"); } } + public static string ConcurrencyfileprocessorsShort { get { return LC.L(@"[EXPERIMENTAL]Specify the number of concurrent files to open"); } } + public static string ConcurrencyfileprocessorsLong { get { return LC.L(@"Use this option to set the number of concurrent files to open. This could accelerate big backups involving lot of files, such as an initial backup"); } } public static string DisablesyntehticfilelistLong { get { return LC.L(@"If Duplicati detects that the previous backup did not complete, it will generate a filelist that is a merge of the last completed backup and the contents that were uploaded in the incomplete backup session."); } } public static string DisablesyntheticfilelistShort { get { return LC.L(@"Disable synthetic filelist"); } } public static string CheckfiletimeonlyLong { get { return LC.L(@"This option instructs Duplicati to not look at metadata or filesize when deciding to scan a file for changes. Use this option if you have a large number of files and notice that the scanning takes a long time with unmodified files."); } } @@ -314,6 +315,36 @@ namespace Duplicati.Library.Main.Strings public static string AutoCompactIntervalShort { get { return LC.L("Minimum time between auto compactions"); } } public static string AutoVacuumIntervalLong { get { return LC.L("The minimum amount of time that must elapse after the last vacuum before another will be automatically triggered at the end of a backup job. Automatic vacuum can be a long-running process and may not be desirable to run after every single backup."); } } public static string AutoVacuumIntervalShort { get { return LC.L("Minimum time between auto vacuums"); } } + public static string SecretProviderShort { get { return LC.L("Secret provider to use for reading credentials"); } } + public static string SecretProviderLong(string toolname) { return LC.L("Configures a secret provider to use for reading credentials. Use the commandline tool {0} to test the provider and see supported options. This value is interpreted as an environment variable if it starts with '$' or begins and ends with '%'.", toolname); } + public static string SecretProviderPatternShort { get { return LC.L("Pattern for secrets"); } } + public static string SecretProviderPatternLong { get { return LC.L("Use this option to specify a pattern for secret provider options. The pattern is used to find values that are intended to be translated by the secret provider. Patterns are treated as a prefix, with support for braces."); } } + public static string SecretProviderCacheShort { get { return LC.L("Cache rules for the secret provider"); } } + public static string SecretProviderCacheLong { get { return LC.L("Use this option to set the allowed caching of credentials from the secret provider. Setting a cache level may reduce the security but allow the backups to continue despite provider outages."); } } + + public static string CPUIntensityShort { get { return LC.L("CPU intensity level"); } } + public static string CPUIntensityLong { get { return LC.L("Set the CPU intensity level to limit CPU resource utilization. A higher number translates into a higher utilization budget. E.g. 10 would mean no restrictions. Must be an integer between 1-10."); } } + + public static string RestoreCacheMaxShort { get { return LC.L("Maximum cache size for restoring files"); } } + public static string RestoreCacheMaxLong { get { return LC.L($"Use this option to set the maximum size of the cache used for restoring files. The cache is used to store the data blocks that are downloaded from the remote storage. It assumes that the value is divisable by the block size, except for when it is 0, which disables the block cache."); } } + public static string RestoreCacheEvictShort { get { return LC.L("Eviction ratio of the data block cache during restore"); } } + public static string RestoreCacheEvictLong { get { return LC.L("Use this option to set the eviction ratio of the data block cache during restore. The eviction ratio is the percentage of the cache that is evicted when the cache is full. The default value is 50, which means that 50% of the cache is evicted when the cache is full."); } } + public static string RestoreFileprocessorsShort { get { return LC.L("Number of concurrent FileProcessors processes used during restore"); } } + public static string RestoreFileprocessorsLong { get { return LC.L("Use this option to set the number of concurrent FileProcessors processes used during restore. A FileProcessor processes one file at a time, and increasing the number of FileProcessors may improve restore performance."); } } + public static string RestoreLegacyShort { get { return LC.L("Use legacy restore method"); } } + public static string RestoreLegacyLong { get { return LC.L("Use this option to use the legacy restore method. The legacy restore method is slower than the new method, but may be more reliable in some cases."); } } + public static string RestorePreallocateSizeShort { get { return LC.L("Preallocate size of restored files"); } } + public static string RestorePreallocateSizeLong { get { return LC.L("Use this option to toggle whether to set the size of the restored files before they are written to disk. This can help to reduce fragmentation and improve performance on some filesystems."); } } + public static string RestoreVolumeDecompressorsShort { get { return LC.L("Number of concurrent FileDecompressor processes used during restore"); } } + public static string RestoreVolumeDecompressorsLong { get { return LC.L("Use this option to set the number of concurrent FileDecompressor processes used during restore. A FileDecompressor processes one volume at a time, and increasing the number of FileDecompressors may improve restore performance if the bottleneck is decompression."); } } + public static string RestoreVolumeDecryptorsShort { get { return LC.L("Number of concurrent FileDecryptor processes used during restore"); } } + public static string RestoreVolumeDecryptorsLong { get { return LC.L("Use this option to set the number of concurrent FileDecryptor processes used during restore. A FileDecryptor processes one volume at a time, and increasing the number of FileDecryptors may improve restore performance if the bottleneck is decryption."); } } + public static string RestoreVolumeDownloadersShort { get { return LC.L("Number of concurrent FileDownloader processes used during restore"); } } + public static string RestoreVolumeDownloadersLong { get { return LC.L("Use this option to set the number of concurrent FileDownloader processes used during restore. A FileDownloader processes one volume at a time, and increasing the number of FileDownloaders may improve restore performance if the bottleneck is downloading."); } } + public static string RestoreChannelBufferSizeShort { get { return LC.L("Size of buffers of the channels used during restore"); } } + public static string RestoreChannelBufferSizeLong { get { return LC.L("Use this option to set the size of the buffers of the channels used during restore. The buffers are used to allow for better asynchronous communication between the processes in the restore flow. Increasing the buffer size may improve restore performance."); } } + public static string InternalProfilingShort { get { return LC.L("Enable internal profiling"); } } + public static string InternalProfilingLong { get { return LC.L("Use this option to enable internal profiling. Profiling is used to measure the performance of the internal code. The profiling data is written to the log file and can be used to identify performance bottlenecks."); } } } internal static class Common @@ -324,4 +355,9 @@ namespace Duplicati.Library.Main.Strings public static string SnapshotFailedError(string message) { return LC.L(@"Failed to create a snapshot: {0}", message); } } + internal static class BackendMananger + { + public static string EncryptionModuleNotFound(string name) { return LC.L(@"The encryption module {0} was not found", name); } + } + } diff --git a/Duplicati/Library/Main/Utility.cs b/Duplicati/Library/Main/Utility.cs index d7ac964c5..844d28b56 100644 --- a/Duplicati/Library/Main/Utility.cs +++ b/Duplicati/Library/Main/Utility.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -167,7 +167,6 @@ namespace Duplicati.Library.Main newDict.Add("passphrase", Library.Utility.Utility.ByteArrayAsHexString(Library.Utility.Utility.RepeatedHashWithSalt(options.Passphrase, salt, 1200))); } - var needsUpdate = false; foreach (var k in newDict) if (!opts.ContainsKey(k.Key)) diff --git a/Duplicati/Library/Main/Volumes/BlockVolumeReader.cs b/Duplicati/Library/Main/Volumes/BlockVolumeReader.cs index 821f2c790..e2badef51 100644 --- a/Duplicati/Library/Main/Volumes/BlockVolumeReader.cs +++ b/Duplicati/Library/Main/Volumes/BlockVolumeReader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; diff --git a/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs b/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs index 8c0de528e..865c91807 100644 --- a/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs b/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs index e46a26faa..54a808501 100644 --- a/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs +++ b/Duplicati/Library/Main/Volumes/FilesetVolumeReader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs index 1a637fc62..4bf79d88e 100644 --- a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs @@ -1,28 +1,32 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json; using Duplicati.Library.Interface; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Main.Operation.Common; +using System.Threading.Tasks; +using Duplicati.Library.Utility; namespace Duplicati.Library.Main.Volumes { @@ -146,7 +150,7 @@ namespace Duplicati.Library.Main.Volumes m_writer.WriteValue(name); if (metahash != null) WriteMetaProperties(metahash, metasize, metablockhash, metablocklisthashes); - + m_writer.WriteEndObject(); } @@ -222,5 +226,59 @@ namespace Duplicati.Library.Main.Volumes sr.Write(FilesetData.GetFilesetInstance(isFullBackup)); } } + + /// + /// Probes for an unused filename, using the current time as a starting point + /// + /// The database to check for clashes + /// The options to use for the filename + /// The starting time to probe from + /// The time to increment by each probe + /// The maximum number of tries to probe + /// The first unused filename + internal static Task ProbeUnusedFilenameName(DatabaseCommon database, Options options, DateTime start, TimeSpan increment = default, int maxTries = 60) + => ProbeUnusedFilenameName(name => database.GetRemoteVolumeIDAsync(name), options, start, increment, maxTries); + + /// + /// Probes for an unused filename, using the current time as a starting point + /// + /// The database to check for clashes + /// The options to use for the filename + /// The starting time to probe from + /// The time to increment by each probe + /// The maximum number of tries to probe + /// The first unused filename + internal static DateTime ProbeUnusedFilenameName(LocalDatabase database, Options options, DateTime start, TimeSpan increment = default, int maxTries = 60) + => ProbeUnusedFilenameName((name) => Task.FromResult(database.GetRemoteVolumeID(name)), options, start, increment, maxTries).Await(); + + /// + /// Probes for an unused filename, using the current time as a starting point + /// + /// The function to use to probe for the ID + /// The options to use for the filename + /// The starting time to probe from + /// The time to increment by each probe + /// The maximum number of tries to probe + /// The first unused filename + private static async Task ProbeUnusedFilenameName(Func> ProbeForId, Options options, DateTime start, TimeSpan increment = default, int maxTries = 60) + { + if (increment == default) + increment = TimeSpan.FromSeconds(1); + + var s = 1; + var time = start; + + while (s < maxTries) + { + var id = await ProbeForId(GenerateFilename(RemoteVolumeType.Files, options, null, time)).ConfigureAwait(false); + if (id < 0) + return time; + + time += increment; + } + + throw new UserInformationException($"Failed to find an unused filename, tried {increment} * {maxTries} from {start}", "FailedToFindUnusedFilename"); + + } } } diff --git a/Duplicati/Library/Main/Volumes/IFileEntry.cs b/Duplicati/Library/Main/Volumes/IFileEntry.cs index 6fc1ae766..f70afebda 100644 --- a/Duplicati/Library/Main/Volumes/IFileEntry.cs +++ b/Duplicati/Library/Main/Volumes/IFileEntry.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Main/Volumes/IParsedVolume.cs b/Duplicati/Library/Main/Volumes/IParsedVolume.cs index 133a9e1fe..6dc30ab97 100644 --- a/Duplicati/Library/Main/Volumes/IParsedVolume.cs +++ b/Duplicati/Library/Main/Volumes/IParsedVolume.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -18,21 +18,18 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Duplicati.Library.Main.Volumes -{ - public interface IParsedVolume - { - RemoteVolumeType FileType { get; } - string Prefix { get; } - string Guid { get; } - DateTime Time { get; } - string CompressionModule { get; } - string EncryptionModule { get; } - Library.Interface.IFileEntry File { get; } - } -} +using System; + +namespace Duplicati.Library.Main.Volumes +{ + public interface IParsedVolume + { + RemoteVolumeType FileType { get; } + string Prefix { get; } + string Guid { get; } + DateTime Time { get; } + string CompressionModule { get; } + string EncryptionModule { get; } + Interface.IFileEntry File { get; } + } +} diff --git a/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs b/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs index 297600de3..198c0bccd 100644 --- a/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs +++ b/Duplicati/Library/Main/Volumes/IndexVolumeReader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; diff --git a/Duplicati/Library/Main/Volumes/IndexVolumeWriter.cs b/Duplicati/Library/Main/Volumes/IndexVolumeWriter.cs index 7a49a6330..fa5620c74 100644 --- a/Duplicati/Library/Main/Volumes/IndexVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/IndexVolumeWriter.cs @@ -1,27 +1,25 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; using Newtonsoft.Json; using Duplicati.Library.Interface; @@ -32,6 +30,9 @@ namespace Duplicati.Library.Main.Volumes { private StreamWriter m_streamwriter = null; private JsonWriter m_writer = null; +#if DEBUG + private readonly HashSet m_knownBlocklisthashes = new(); +#endif private long m_volumes = 0; private long m_blocks = 0; @@ -87,10 +88,17 @@ namespace Duplicati.Library.Main.Volumes finally { m_streamwriter = null; } } - public void WriteBlocklist(string hash, byte[] data, int offset, int size) + public void WriteBlocklist(string hash, byte[] data, int offset, int size) { - if (size % m_blockhashsize != 0) - throw new InvalidDataException($"Attempted to write a blocklist with {size} bytes which is not evenly divisible with {m_blockhashsize}"); +#if DEBUG + // Nothing breaks with duplicates, but it should not be there + if (m_knownBlocklisthashes.Contains(hash)) + throw new InvalidOperationException($"Attempted to write a blocklist with hash {hash} more than once"); + m_knownBlocklisthashes.Add(hash); +#endif + + if (size % m_blockhashsize != 0) + throw new InvalidDataException($"Attempted to write a blocklist with {size} bytes which is not evenly divisible with {m_blockhashsize}"); m_blocklists++; //Filenames are encoded with "modified Base64 for URL" https://en.wikipedia.org/wiki/Base64#URL_applications, using (var s = m_compression.CreateFile(INDEX_BLOCKLIST_FOLDER + Library.Utility.Utility.Base64PlainToBase64Url(hash), CompressionHint.Noncompressible, DateTime.UtcNow)) @@ -99,28 +107,35 @@ namespace Duplicati.Library.Main.Volumes public void WriteBlocklist(string hash, Stream source) { - m_blocklists++; - //Filenames are encoded with "modified Base64 for URL" https://en.wikipedia.org/wiki/Base64#URL_applications, - using (var s = m_compression.CreateFile(INDEX_BLOCKLIST_FOLDER + Library.Utility.Utility.Base64PlainToBase64Url(hash), CompressionHint.Noncompressible, DateTime.UtcNow)) - { - var size = Library.Utility.Utility.CopyStream(source, s); - if (size % m_blockhashsize != 0) - throw new InvalidDataException($"Wrote a blocklist with {size} bytes which is not evenly divisible with {m_blockhashsize}"); - } +#if DEBUG + // Nothing breaks with duplicates, but it should not be there + if (m_knownBlocklisthashes.Contains(hash)) + throw new InvalidOperationException($"Attempted to write a blocklist with hash {hash} more than once"); + m_knownBlocklisthashes.Add(hash); +#endif + + m_blocklists++; + //Filenames are encoded with "modified Base64 for URL" https://en.wikipedia.org/wiki/Base64#URL_applications, + using (var s = m_compression.CreateFile(INDEX_BLOCKLIST_FOLDER + Library.Utility.Utility.Base64PlainToBase64Url(hash), CompressionHint.Noncompressible, DateTime.UtcNow)) + { + var size = Library.Utility.Utility.CopyStream(source, s); + if (size % m_blockhashsize != 0) + throw new InvalidDataException($"Wrote a blocklist with {size} bytes which is not evenly divisible with {m_blockhashsize}"); + } } public void CopyFrom(IndexVolumeReader rd, Func filename_mapping) { - foreach(var n in rd.Volumes) + foreach (var n in rd.Volumes) { this.StartVolume(filename_mapping(n.Filename)); - foreach(var x in n.Blocks) + foreach (var x in n.Blocks) this.AddBlock(x.Key, x.Value); this.FinishVolume(n.Hash, n.Length); } - - foreach(var b in rd.BlockLists) - using(var s = b.Data) + + foreach (var b in rd.BlockLists) + using (var s = b.Data) this.WriteBlocklist(b.Hash, s); } diff --git a/Duplicati/Library/Main/Volumes/InvalidManifestException.cs b/Duplicati/Library/Main/Volumes/InvalidManifestException.cs index a13dc9810..f079e7238 100644 --- a/Duplicati/Library/Main/Volumes/InvalidManifestException.cs +++ b/Duplicati/Library/Main/Volumes/InvalidManifestException.cs @@ -1,40 +1,46 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using Duplicati.Library.Interface; namespace Duplicati.Library.Main.Volumes { [Serializable] - public class InvalidManifestException : Exception + public class InvalidManifestException : UserInformationException { + private static string GetMessage(string fieldname, string value, string expected) + { + // Make a custom message if this is a version mismatch + if (string.Equals(fieldname, "version", StringComparison.OrdinalIgnoreCase) || string.Equals(fieldname, "encoding", StringComparison.OrdinalIgnoreCase)) + return $"Invalid manifest detected, the field {fieldname} has value {value} but the value {expected} was expected.\nThe most likely cause for this issue is that you are attempting to read a backup created with a newer version of Duplicati.\nPlease upgrade your Duplicati installation to the latest version to resolve this issue."; + + return $"Invalid manifest detected, the field {fieldname} has value {value} but the value {expected} was expected. This could be a configuration issue, or be caused by mixing files from different backups."; + } public InvalidManifestException(string fieldname, string value, string expected) - : base(string.Format("Invalid manifest detected, the field {0} has value {1} but the value {2} was expected", fieldname, value, expected)) + : base(GetMessage(fieldname, value, expected), "InvalidManifest") { } public InvalidManifestException(string message) - : base(message) + : base(message, "InvalidManifest") { } } diff --git a/Duplicati/Library/Main/Volumes/VolumeBase.cs b/Duplicati/Library/Main/Volumes/VolumeBase.cs index bdfcc2ca3..dc54f7f16 100644 --- a/Duplicati/Library/Main/Volumes/VolumeBase.cs +++ b/Duplicati/Library/Main/Volumes/VolumeBase.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -83,7 +83,7 @@ namespace Duplicati.Library.Main.Volumes throw new InvalidManifestException("FileHash", d.FileHash, filehash); } } - + private class ParsedVolume : IParsedVolume { public RemoteVolumeType FileType { get; private set; } @@ -104,11 +104,11 @@ namespace Duplicati.Library.Main.Volumes dict[RemoteVolumeType.Blocks] = "dblock"; dict[RemoteVolumeType.Files] = "dlist"; dict[RemoteVolumeType.Index] = "dindex"; - + var reversedict = new Dictionary(System.StringComparer.OrdinalIgnoreCase); - foreach(var x in dict) + foreach (var x in dict) reversedict[x.Value] = x.Key; - + REMOTE_TYPENAME_MAP = dict; REVERSE_REMOTE_TYPENAME_MAP = reversedict; FILENAME_REGEXP = new System.Text.RegularExpressions.Regex(@"(?[^\-]+)\-(([i|b|I|B](?[0-9A-Fa-f]+))|((? protected override string DEFAULT_SUBJECT => string.Empty; + /// + /// The default number of retries + /// + private const int DEFAULT_RETRIES = 3; + /// + /// The default delay between retries + /// + private const string DEFAULT_RETRY_DELAY = "1s"; #endregion #region Private variables @@ -133,6 +162,25 @@ namespace Duplicati.Library.Modules.Builtin /// private string m_extraParameters; + /// + /// Option to accept any SSL certificate + /// + private bool m_acceptAnyCertificate; + + /// + /// Specific hashes to be accepted by the certificate validator + /// + private string[] m_acceptSpecificCertificates; + + /// + /// The number of retries to attempt + /// + private int m_retries; + /// + /// The delay between retries + /// + private TimeSpan m_retryDelay; + #endregion @@ -163,30 +211,31 @@ namespace Duplicati.Library.Modules.Builtin /// /// Gets a list of supported commandline arguments /// - public override IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlShort, Strings.SendHttpMessage.SendhttpurlLong), - new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageShort, Strings.SendHttpMessage.SendhttpmessageLong, DEFAULT_BODY), - new CommandLineArgument(OPTION_MESSAGE_PARAMETER_NAME, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageparameternameShort, Strings.SendHttpMessage.SendhttpmessageparameternameLong, DEFAULT_MESSAGE_PARAMETER_NAME), - new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpextraparametersShort, Strings.SendHttpMessage.SendhttpextraparametersLong), - new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttplevelShort, Strings.SendHttpMessage.SendhttplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), - new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), + public override IList SupportedCommands => + [ + new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlShort, Strings.SendHttpMessage.SendhttpurlLong), + new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageShort, Strings.SendHttpMessage.SendhttpmessageLong, DEFAULT_BODY), + new CommandLineArgument(OPTION_MESSAGE_PARAMETER_NAME, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageparameternameShort, Strings.SendHttpMessage.SendhttpmessageparameternameLong, DEFAULT_MESSAGE_PARAMETER_NAME), + new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpextraparametersShort, Strings.SendHttpMessage.SendhttpextraparametersLong), + new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttplevelShort, Strings.SendHttpMessage.SendhttplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), - new CommandLineArgument(OPTION_VERB, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.HttpverbShort, Strings.SendHttpMessage.HttpverbLong, "POST"), - new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), - new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), - new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), + new CommandLineArgument(OPTION_VERB, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.HttpverbShort, Strings.SendHttpMessage.HttpverbLong, "POST"), + new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), + new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), + new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), - new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), + new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), - new CommandLineArgument(OPTION_URL_FORM, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlsformShort, Strings.SendHttpMessage.SendhttpurlsformLong), - new CommandLineArgument(OPTION_URL_JSON, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlsjsonShort, Strings.SendHttpMessage.SendhttpurlsjsonLong), - }); - } - } + new CommandLineArgument(OPTION_URL_FORM, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlsformShort, Strings.SendHttpMessage.SendhttpurlsformLong), + new CommandLineArgument(OPTION_URL_JSON, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlsjsonShort, Strings.SendHttpMessage.SendhttpurlsjsonLong), + + new CommandLineArgument(OPTION_ACCEPT_ANY_CERTIFICATE, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.AcceptAnyCertificateShort, Strings.SendHttpMessage.AcceptAnyCertificateLong), + new CommandLineArgument(OPTION_ACCEPT_SPECIFIED_CERTIFICATE, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.AcceptSpecifiedCertificateShort, Strings.SendHttpMessage.AcceptSpecifiedCertificateLong), + + new CommandLineArgument(OPTION_SEND_HTTP_RETRIES, CommandLineArgument.ArgumentType.Integer, Strings.SendHttpMessage.SendHttpRetriesShort, Strings.SendHttpMessage.SendHttpRetriesLong, DEFAULT_RETRIES.ToString()), + new CommandLineArgument(OPTION_SEND_HTTP_RETRY_DELAY, CommandLineArgument.ArgumentType.Integer, Strings.SendHttpMessage.SendHttpRetryDelayShort, Strings.SendHttpMessage.SendHttpRetryDelayLong, DEFAULT_RETRY_DELAY), + ]; protected override string SubjectOptionName => OPTION_MESSAGE; protected override string BodyOptionName => OPTION_MESSAGE; @@ -196,6 +245,7 @@ namespace Duplicati.Library.Modules.Builtin protected override string LogFilterOptionName => OPTION_LOG_FILTER; protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES; protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT; + protected override string ExtraDataOptionName => OPTION_EXTRA_PARAMETERS; /// /// This method is the interception where the module can interact with the execution environment and modify the settings. @@ -242,12 +292,18 @@ namespace Duplicati.Library.Modules.Builtin m_messageParameterName = DEFAULT_MESSAGE_PARAMETER_NAME; commandlineOptions.TryGetValue(OPTION_EXTRA_PARAMETERS, out m_extraParameters); + m_acceptAnyCertificate = Utility.Utility.ParseBoolOption(commandlineOptions.AsReadOnly(), OPTION_ACCEPT_ANY_CERTIFICATE); + m_acceptSpecificCertificates = commandlineOptions.ContainsKey(OPTION_ACCEPT_SPECIFIED_CERTIFICATE) ? commandlineOptions[OPTION_ACCEPT_SPECIFIED_CERTIFICATE].Split([",", ";"], StringSplitOptions.RemoveEmptyEntries) : null; + + m_retries = Utility.Utility.ParseIntOption(commandlineOptions.AsReadOnly(), OPTION_SEND_HTTP_RETRIES, DEFAULT_RETRIES); + m_retryDelay = Utility.Utility.ParseTimespanOption(commandlineOptions.AsReadOnly(), OPTION_SEND_HTTP_RETRY_DELAY, DEFAULT_RETRY_DELAY); + return true; } #endregion - private async Task SendMessage(HttpClient client, SendRequestType target, string subject, string body) + private async Task SendMessage(HttpClient client, SendRequestType target, string subject, string body) { byte[] data; MediaTypeHeaderValue contenttype; @@ -259,11 +315,17 @@ namespace Duplicati.Library.Modules.Builtin } else { + const int chuncksize = 2048; contenttype = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - var postData = $"{m_messageParameterName}={System.Uri.EscapeDataString(body)}"; + var sb = new StringBuilder(); + sb.Append(m_messageParameterName).Append("="); + for (int i = 0; i < body.Length; i += chuncksize) + sb.Append(System.Uri.EscapeDataString(body.Substring(i, Math.Min(chuncksize, body.Length - i)))); + if (!string.IsNullOrEmpty(m_extraParameters)) - postData += $"&{System.Uri.EscapeDataString(m_extraParameters)}"; - data = Encoding.UTF8.GetBytes(postData); + sb.Append("&").Append(m_extraParameters); + + data = Encoding.UTF8.GetBytes(sb.ToString()); } var request = new HttpRequestMessage @@ -274,7 +336,9 @@ namespace Duplicati.Library.Modules.Builtin }; request.Content.Headers.ContentType = contenttype; - try + Exception lastEx = null; + + await RetryHelper.Retry(async () => { var response = await client.SendAsync(request); var responseContent = await response.Content.ReadAsStringAsync(); @@ -288,18 +352,17 @@ namespace Duplicati.Library.Modules.Builtin ); response.EnsureSuccessStatusCode(); - } - catch (Exception ex) + lastEx = null; + }, (ex, retry) => { - Logging.Log.WriteWarningMessage(LOGTAG, "HttpResponseError", ex, "HTTP Response request failed for: {0}", target.Url); - return ex; - } + Logging.Log.WriteWarningMessage(LOGTAG, "HttpResponseError", ex, "HTTP Response request attempt {0} of {1} failed for: {2}", retry, m_retries, target.Url); + lastEx = ex; + }, m_retries, m_retryDelay, CancellationToken.None); - return null; + return lastEx; } private Dictionary m_cachedBodyResults; - private string m_form_body = string.Empty; protected override string ReplaceTemplate(string input, object result, Exception exception, bool subjectline) { @@ -327,14 +390,17 @@ namespace Duplicati.Library.Modules.Builtin if (m_report_targets == null || m_cachedBodyResults == null) return; - using var client = new HttpClient(); + using HttpClientHandler httpHandler = new HttpClientHandler(); + HttpClientHelper.ConfigureHandlerCertificateValidator(httpHandler, m_acceptAnyCertificate, m_acceptSpecificCertificates); + + using var client = new HttpClient(httpHandler); Exception ex = null; foreach (var target in m_report_targets) { if (m_cachedBodyResults.TryGetValue(target.Format, out var result)) - ex ??= SendMessage(client, target, subject, result).ConfigureAwait(false).GetAwaiter().GetResult(); + ex ??= SendMessage(client, target, subject, result).Await(); } if (ex != null) diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index 255393ff1..c740bd613 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,9 +22,6 @@ using System; using System.Collections.Generic; using System.Linq; using Duplicati.Library.Interface; -using Duplicati.Library.Logging; -using System.Net.NetworkInformation; -using Duplicati.Library.Modules.Builtin.ResultSerialization; using Artalk.Xmpp.Client; namespace Duplicati.Library.Modules.Builtin @@ -53,6 +50,11 @@ namespace Duplicati.Library.Modules.Builtin /// Option used to specify report body /// private const string OPTION_MESSAGE = "send-xmpp-message"; + /// + /// Option used to specify extra parameters + /// + private const string OPTION_EXTRA_PARAMETERS = "send-xmpp-extra-parameters"; + /// /// Option used to specify report level /// @@ -136,26 +138,23 @@ namespace Duplicati.Library.Modules.Builtin /// /// Gets a list of supported commandline arguments /// - public override IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpptoShort, Strings.SendJabberMessage.SendxmpptoLong), - new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppmessageShort, Strings.SendJabberMessage.SendxmppmessageLong, DEFAULT_BODY), - new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppusernameShort, Strings.SendJabberMessage.SendxmppusernameLong), - new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpppasswordShort, Strings.SendJabberMessage.SendxmpppasswordLong), - new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpplevelShort, Strings.SendJabberMessage.SendxmpplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), - new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendJabberMessage.SendxmppanyoperationShort, Strings.SendJabberMessage.SendxmppanyoperationLong), + public override IList SupportedCommands => + [ + new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpptoShort, Strings.SendJabberMessage.SendxmpptoLong), + new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppmessageShort, Strings.SendJabberMessage.SendxmppmessageLong, DEFAULT_BODY), + new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppextraparametersShort, Strings.SendJabberMessage.SendxmppextraparametersLong), + new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppusernameShort, Strings.SendJabberMessage.SendxmppusernameLong), + new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpppasswordShort, Strings.SendJabberMessage.SendxmpppasswordLong), + new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpplevelShort, Strings.SendJabberMessage.SendxmpplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendJabberMessage.SendxmppanyoperationShort, Strings.SendJabberMessage.SendxmppanyoperationLong), - new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), - new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), - new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), + new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), + new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), + new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), + + new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), + ]; - new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), - }); - } - } protected override string SubjectOptionName => OPTION_MESSAGE; protected override string BodyOptionName => OPTION_MESSAGE; @@ -165,6 +164,7 @@ namespace Duplicati.Library.Modules.Builtin protected override string LogFilterOptionName => OPTION_LOG_FILTER; protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES; protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT; + protected override string ExtraDataOptionName => OPTION_EXTRA_PARAMETERS; /// /// This method is the interception where the module can interact with the execution environment and modify the settings. @@ -193,10 +193,9 @@ namespace Duplicati.Library.Modules.Builtin return base.ReplaceTemplate(input, result, exception, subjectline); } - protected override async void SendMessage(string subject, string body) + protected override void SendMessage(string subject, string body) { - - var uri = new Library.Utility.Uri(m_username.Contains("://") ? m_username : "http://" + m_username); + var uri = new Utility.Uri(m_username.Contains("://") ? m_username : "http://" + m_username); var resource = uri.Path ?? ""; if (resource.StartsWith("/", StringComparison.Ordinal)) resource = resource.Substring(1); diff --git a/Duplicati/Library/Modules/Builtin/SendMail.cs b/Duplicati/Library/Modules/Builtin/SendMail.cs index e17282497..d33b7964b 100644 --- a/Duplicati/Library/Modules/Builtin/SendMail.cs +++ b/Duplicati/Library/Modules/Builtin/SendMail.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,21 +19,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net; using System.Text; -using System.Text.RegularExpressions; using Duplicati.Library.Interface; -using Duplicati.Library.Logging; -using Duplicati.Library.Utility; -using System.Net.NetworkInformation; -using System.Net.Sockets; using MailKit.Net.Smtp; using MimeKit; -using Duplicati.Library.Modules.Builtin.ResultSerialization; using DnsClient; namespace Duplicati.Library.Modules.Builtin @@ -87,6 +79,11 @@ namespace Duplicati.Library.Modules.Builtin /// Option used to specify what format the result is sent in. /// private const string OPTION_RESULT_FORMAT = "send-mail-result-output-format"; + /// + /// Option used to specify extra parameters + /// + private const string OPTION_EXTRA_PARAMETERS = "send-mail-extra-parameters"; + /// /// Option used to set the log level for mail reports /// @@ -158,29 +155,25 @@ namespace Duplicati.Library.Modules.Builtin /// /// Gets a list of supported commandline arguments /// - public override IList SupportedCommands - { - get - { - return new List(new ICommandLineArgument[] { - new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionRecipientShort, Strings.SendMail.OptionRecipientLong), - new CommandLineArgument(OPTION_SENDER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSenderShort, Strings.SendMail.OptionSenderLong, DEFAULT_SENDER), - new CommandLineArgument(OPTION_SUBJECT, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSubjectShort, Strings.SendMail.OptionSubjectLong(OPTION_BODY), DEFAULT_SUBJECT), - new CommandLineArgument(OPTION_BODY, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionBodyShort, Strings.SendMail.OptionBodyLong, DEFAULT_BODY), - new CommandLineArgument(OPTION_SERVER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionServerShort, Strings.SendMail.OptionServerLong), - new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionUsernameShort, Strings.SendMail.OptionUsernameLong), - new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionPasswordShort, Strings.SendMail.OptionPasswordLong), - new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSendlevelShort, Strings.SendMail.OptionSendlevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string [] { "All" }).ToArray()), - new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), + public override IList SupportedCommands => + [ + new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionRecipientShort, Strings.SendMail.OptionRecipientLong), + new CommandLineArgument(OPTION_SENDER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSenderShort, Strings.SendMail.OptionSenderLong, DEFAULT_SENDER), + new CommandLineArgument(OPTION_SUBJECT, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSubjectShort, Strings.SendMail.OptionSubjectLong(OPTION_BODY), DEFAULT_SUBJECT), + new CommandLineArgument(OPTION_BODY, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionBodyShort, Strings.SendMail.OptionBodyLong, DEFAULT_BODY), + new CommandLineArgument(OPTION_SERVER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionServerShort, Strings.SendMail.OptionServerLong), + new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionUsernameShort, Strings.SendMail.OptionUsernameLong), + new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionPasswordShort, Strings.SendMail.OptionPasswordLong), + new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSendlevelShort, Strings.SendMail.OptionSendlevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string [] { "All" }).ToArray()), + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), + new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendMail.SendmailextraparametersShort, Strings.SendMail.SendmailextraparametersLong), - new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), - new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), - new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), + new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), + new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), + new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), - new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), - }); - } - } + new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), + ]; protected override string SubjectOptionName => OPTION_SUBJECT; protected override string BodyOptionName => OPTION_BODY; @@ -190,6 +183,7 @@ namespace Duplicati.Library.Modules.Builtin protected override string LogFilterOptionName => OPTION_LOG_FILTER; protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES; protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT; + protected override string ExtraDataOptionName => OPTION_EXTRA_PARAMETERS; /// /// This method is the interception where the module can interact with the execution environment and modify the settings. @@ -277,7 +271,7 @@ namespace Duplicati.Library.Modules.Builtin lastServer = server; try { - using (MemoryStream ms = new MemoryStream()) + using (var ms = new MemoryStream()) { try { diff --git a/Duplicati/Library/Modules/Builtin/SendTelegramMessage.cs b/Duplicati/Library/Modules/Builtin/SendTelegramMessage.cs index e86de3627..6777765b7 100644 --- a/Duplicati/Library/Modules/Builtin/SendTelegramMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendTelegramMessage.cs @@ -1,8 +1,32 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using Uri = System.Uri; namespace Duplicati.Library.Modules.Builtin; @@ -32,6 +56,10 @@ public class SendTelegramMessage : ReportHelper /// private const string OPTION_CHANNEL = "send-telegram-channel-id"; /// + /// Option used to specify the topic ID to be used in telegram groups. + /// + private const string OPTION_TOPICID = "send-telegram-topid-id"; + /// /// Option used to specify report body /// private const string OPTION_MESSAGE = "send-telegram-message"; @@ -47,6 +75,10 @@ public class SendTelegramMessage : ReportHelper /// Option used to specify what format the result is sent in. /// private const string OPTION_RESULT_FORMAT = "send-telegram-result-output-format"; + /// + /// Option used to specify extra parameters + /// + private const string OPTION_EXTRA_PARAMETERS = "send-telegram-extra-parameters"; /// /// Option used to set the log level @@ -61,6 +93,11 @@ public class SendTelegramMessage : ReportHelper /// private const string OPTION_MAX_LOG_LINES = "send-telegram-max-log-lines"; + /// + /// Telegram message maximum length. + /// + private const int TELEGRAM_MAX_LENGTH = 4096; + #endregion #region Option defaults @@ -102,21 +139,21 @@ public class SendTelegramMessage : ReportHelper /// /// Gets a list of supported commandline arguments /// - public override IList SupportedCommands - => [ - new CommandLineArgument(OPTION_CHANNEL, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendtelegramchannelShort, Strings.SendTelegramMessage.SendtelegramchannelLong), - new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendtelegrammessageShort, Strings.SendTelegramMessage.SendtelegrammessageLong, DEFAULT_BODY), - new CommandLineArgument(OPTION_BOTID, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendtelegrambotidShort, Strings.SendTelegramMessage.SendtelegrambotidLong), - new CommandLineArgument(OPTION_APIKEY, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendtelegramapikeyShort, Strings.SendTelegramMessage.SendtelegramapikeyLong), - new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendtelegramlevelShort, Strings.SendTelegramMessage.SendtelegramlevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), - new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendTelegramMessage.SendtelegramanyoperationShort, Strings.SendTelegramMessage.SendtelegramanyoperationLong), - - new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), - new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), - new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), - - new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), - ]; + public override IList SupportedCommands => + [ + new CommandLineArgument(OPTION_CHANNEL, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramChannelShort, Strings.SendTelegramMessage.SendTelegramChannelLong), + new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramMessageShort, Strings.SendTelegramMessage.SendTelegramMessageLong, DEFAULT_BODY), + new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramExtraParametersShort, Strings.SendTelegramMessage.SendTelegramExtraParametersLong), + new CommandLineArgument(OPTION_BOTID, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramBotIdShort, Strings.SendTelegramMessage.SendTelegramBotIdLong), + new CommandLineArgument(OPTION_APIKEY, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramApiKeyShort, Strings.SendTelegramMessage.SendTelegramApiKeyLong), + new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramLevelShort, Strings.SendTelegramMessage.SendTelegramlevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendTelegramMessage.SendTelegramManyOperationShort, Strings.SendTelegramMessage.SendTelegramManyOperationLong), + new CommandLineArgument(OPTION_LOG_LEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.OptionLoglevelShort, Strings.ReportHelper.OptionLoglevelLong, DEFAULT_LOG_LEVEL.ToString(), null, Enum.GetNames(typeof(Logging.LogMessageType))), + new CommandLineArgument(OPTION_LOG_FILTER, CommandLineArgument.ArgumentType.String, Strings.ReportHelper.OptionLogfilterShort, Strings.ReportHelper.OptionLogfilterLong), + new CommandLineArgument(OPTION_MAX_LOG_LINES, CommandLineArgument.ArgumentType.Integer, Strings.ReportHelper.OptionmaxloglinesShort, Strings.ReportHelper.OptionmaxloglinesLong, DEFAULT_LOGLINES.ToString()), + new CommandLineArgument(OPTION_RESULT_FORMAT, CommandLineArgument.ArgumentType.Enumeration, Strings.ReportHelper.ResultFormatShort, Strings.ReportHelper.ResultFormatLong(Enum.GetNames(typeof(ResultExportFormat))), DEFAULT_EXPORT_FORMAT.ToString(), null, Enum.GetNames(typeof(ResultExportFormat))), + new CommandLineArgument(OPTION_TOPICID, CommandLineArgument.ArgumentType.String, Strings.SendTelegramMessage.SendTelegramTopicShort, Strings.SendTelegramMessage.SendTelegramTopicLong), + ]; protected override string SubjectOptionName => OPTION_MESSAGE; protected override string BodyOptionName => OPTION_MESSAGE; @@ -126,6 +163,7 @@ public class SendTelegramMessage : ReportHelper protected override string LogFilterOptionName => OPTION_LOG_FILTER; protected override string LogLinesOptionName => OPTION_MAX_LOG_LINES; protected override string ResultFormatOptionName => OPTION_RESULT_FORMAT; + protected override string ExtraDataOptionName => OPTION_EXTRA_PARAMETERS; /// /// The server username @@ -139,6 +177,10 @@ public class SendTelegramMessage : ReportHelper /// The Telegram ChannelID /// private string m_channelId; + /// + /// The telegram TopicID + /// + private string m_topicId; /// /// This method is the interception where the module can interact with the execution environment and modify the settings. @@ -153,48 +195,102 @@ public class SendTelegramMessage : ReportHelper commandlineOptions.TryGetValue(OPTION_BOTID, out m_botid); commandlineOptions.TryGetValue(OPTION_APIKEY, out m_apikey); + commandlineOptions.TryGetValue(OPTION_TOPICID, out m_topicId); return true; } #endregion + /// protected override string ReplaceTemplate(string input, object result, Exception exception, bool subjectline) { // No need to do the expansion as we throw away the result - if (subjectline) - return string.Empty; - return base.ReplaceTemplate(input, result, exception, subjectline); + return subjectline ? string.Empty : base.ReplaceTemplate(input, result, exception, subjectline); } + /// + /// Sends message to telegram + /// + /// Header to add to message + /// Body of message protected override async void SendMessage(string subject, string body) { try { + if (string.IsNullOrEmpty(body)) + return; + + // Combine subject and body + body = string.Join(Environment.NewLine, subject, body); + + // Split message into chunks if needed + var messages = body.Length <= TELEGRAM_MAX_LENGTH + ? [body] + : Enumerable.Range(0, (body.Length + TELEGRAM_MAX_LENGTH - 1) / TELEGRAM_MAX_LENGTH) + .Select(i => body.Substring( + i * TELEGRAM_MAX_LENGTH, + Math.Min(TELEGRAM_MAX_LENGTH, body.Length - i * TELEGRAM_MAX_LENGTH))) + .ToArray(); + + // Send all chunks sequentially + for (var i = 0; i < messages.Length; i++) + await SendMessageChunk(messages[i], i + 1, messages.Length).ConfigureAwait(false); + } + catch (Exception e) + { + Logging.Log.WriteWarningMessage(LOGTAG, "telegramSendError", e, + "Failed to process message for telegram: {0}", e.Message); + } + } + + /// + /// Sends the message/partial message to telegram + /// + /// body or partial body to send + /// Part number + /// Total parts in the original message + private async Task SendMessageChunk(string message, int partNumber, int totalParts) + { + try + { + if (string.IsNullOrWhiteSpace(m_botid)) + throw new Exception("Telegram Bot ID is required and not set"); + if (string.IsNullOrWhiteSpace(m_apikey)) + throw new Exception("Telegram API Key is required and not set"); var p = new { chat_id = Uri.EscapeDataString(m_channelId), - parse_mode = "Text", - text = Uri.EscapeDataString(body), + message_thread_id = string.IsNullOrWhiteSpace(m_topicId) ? null : Uri.EscapeDataString(m_topicId), + text = Uri.EscapeDataString(totalParts > 1 + ? $"Part {partNumber}/{totalParts}:\n{message}" + : message), botId = Uri.EscapeDataString(m_botid), apiKey = Uri.EscapeDataString(m_apikey) }; - var url = $"https://api.telegram.org/bot{p.botId}:{p.apiKey}/sendMessage?chat_id={p.chat_id}&parse_mode={p.parse_mode}&text={p.text}"; - using var client = new HttpClient { Timeout = REQUEST_TIMEOUT }; - var response = await client.GetAsync(url); - var responseContent = await response.Content.ReadAsStringAsync(); + var baseUrl = $"https://api.telegram.org/bot{p.botId}:{p.apiKey}/sendMessage"; + var url = $"{baseUrl}?chat_id={p.chat_id}&text={p.text}" + + $"{(!string.IsNullOrWhiteSpace(m_topicId) ? $"&message_thread_id={m_topicId}" : string.Empty)}"; - if (responseContent.Contains("\"ok\":true")) - return; + using var timeoutToken = new CancellationTokenSource(); + timeoutToken.CancelAfter(REQUEST_TIMEOUT); - Logging.Log.WriteWarningMessage(LOGTAG, "telegramSendError", null, "Failed to send to telegram messages: {0}", responseContent); + using var client = HttpClientHelper.CreateClient(); + var response = await client.GetAsync(url, timeoutToken.Token).ConfigureAwait(false); + var responseContent = await response.Content.ReadAsStringAsync(timeoutToken.Token).ConfigureAwait(false); + + if (!responseContent.Contains("\"ok\":true")) + Logging.Log.WriteWarningMessage(LOGTAG, "telegramSendError", null, + "Failed to send to telegram messages part {0}/{1}: {2}", + partNumber, totalParts, responseContent); } catch (Exception e) { - Logging.Log.WriteWarningMessage(LOGTAG, "telegramSendError", e, "Failed to send to telegram messages: {0}", e.Message); + Logging.Log.WriteWarningMessage(LOGTAG, "telegramSendError", e, + "Failed to send to telegram messages part {0}/{1}: {2}", + partNumber, totalParts, e.Message); } } - } diff --git a/Duplicati/Library/Modules/Builtin/Strings.cs b/Duplicati/Library/Modules/Builtin/Strings.cs index 1f7f26dc7..b89368802 100644 --- a/Duplicati/Library/Modules/Builtin/Strings.cs +++ b/Duplicati/Library/Modules/Builtin/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -62,6 +62,8 @@ namespace Duplicati.Library.Modules.Builtin.Strings { public static string Description { get { return LC.L(@"This module works internaly to parse source parameters to backup Hyper-V virtual machines"); } } public static string DisplayName { get { return LC.L(@"Configure Hyper-V module"); } } + public static string IgnoreConsistencyWarningShort { get { return LC.L(@"Ignore consistency warning"); } } + public static string IgnoreConsistencyWarningLong { get { return LC.L(@"This option will suppress the consistency warning that is normally issued when running on a client version of Windows. Enable this option if you are running on a client version of Windows and you are sure that crash-level consistency is acceptable for your use."); } } } internal static class MSSQLOptions { @@ -134,6 +136,8 @@ To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STAR public static string SendMailLog(string message) { return LC.L(@"Whole SMTP communication: {0}", message); } public static string SendMailFailedRetryError(string failedserver, string message, string retryserver) { return LC.L(@"Failed to send email with server: {0}, message: {1}, retrying with {2}", failedserver, message, retryserver); } public static string SendMailSuccess(string server) { return LC.L(@"Email sent successfully using server: {0}", server); } + public static string SendmailextraparametersLong { get { return LC.L(@"Use this option to set extra parameters for the message body. This parameter can either be a querystring (e.g. 'parameter1=value1¶meter2=value2') or a JSON key/value object."); } } + public static string SendmailextraparametersShort { get { return LC.L(@"Extra parameters for the message sent"); } } } internal static class SendJabberMessage { @@ -161,15 +165,18 @@ You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The sp public static string SendxmppanyoperationLong { get { return LC.L(@"By default, messages will only be sent after a backup operation. Use this option to send messages for all operations."); } } public static string SendxmppanyoperationShort { get { return LC.L(@"Send messages for all operations"); } } public static string LoginTimeoutError { get { return LC.L(@"Timeout occurred while logging in to jabber server"); } } + public static string SendxmppextraparametersLong { get { return LC.L(@"Use this option to set extra parameters for the message body. This parameter can either be a querystring (e.g. 'parameter1=value1¶meter2=value2') or a JSON key/value object."); } } + public static string SendxmppextraparametersShort { get { return LC.L(@"Extra parameters for the message sent"); } } } internal static class SendTelegramMessage { - public static string Description { get { return LC.L(@"This module provides support for sending status reports via Telegram messages"); } } - public static string DisplayName { get { return LC.L(@"Telegram report module"); } } - public static string SendtelegramchannelLong { get { return LC.L(@"Use this option to set the channel ID."); } } - public static string SendtelegramchannelShort { get { return LC.L(@"Telegram channel ID"); } } - public static string SendtelegrammessageLong { get { return LC.L(@"This value can be a filename. If the file exists, the file contents will be used as the message. + public static string Description => LC.L(@"This module provides support for sending status reports via Telegram messages"); + public static string DisplayName => LC.L(@"Telegram report module"); + public static string SendTelegramChannelLong => LC.L(@"Use this option to set the channel ID."); + public static string SendTelegramChannelShort => LC.L(@"Telegram channel ID"); + public static string SendTelegramMessageLong => + LC.L(@"This value can be a filename. If the file exists, the file contents will be used as the message. In the message, certain tokens are replaced: %OPERATIONNAME% - The name of the operation, normally ""Backup"" @@ -177,19 +184,22 @@ In the message, certain tokens are replaced: %LOCALPATH% - The path to the local files or folders involved in the operation (if any) %PARSEDRESULT% - The parsed result, if the operation is a backup. Possible values are: Error, Warning, Success -All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed."); } } - public static string SendtelegrammessageShort { get { return LC.L(@"The message template"); } } - public static string SendtelegrambotidLong { get { return LC.L(@"Use this option to set a bot ID for the bot that will send the message."); } } +All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed."); - public static string SendtelegrambotidShort { get { return LC.L(@"The Telegram bot ID"); } } - public static string SendtelegramapikeyLong { get { return LC.L(@"Use this option to set a API key for the bot that will send the message."); } } - public static string SendtelegramapikeyShort { get { return LC.L(@"The Telegram API key"); } } - public static string SendtelegramlevelLong(string success, string warning, string error, string fatal, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"", ""{3}"". + public static string SendTelegramMessageShort => LC.L(@"The message template"); + public static string SendTelegramBotIdLong => LC.L(@"Use this option to set a bot ID for the bot that will send the message."); + public static string SendTelegramBotIdShort => LC.L(@"The Telegram bot ID"); + public static string SendTelegramApiKeyLong => LC.L(@"Use this option to set a API key for the bot that will send the message."); + public static string SendTelegramApiKeyShort => LC.L(@"The Telegram API key"); + public static string SendTelegramTopicShort => LC.L(@"The Telegram topic ID"); + public static string SendTelegramTopicLong => LC.L(@"Topic ID for the Topic in the telegram group. For more information on Telegram setup, refer to documentation."); + public static string SendTelegramlevelLong(string success, string warning, string error, string fatal, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"", ""{3}"". You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The special value ""{4}"" is a shorthand for ""{0},{1},{2},{3}"" and will cause all backup operations to send a message.", success, warning, error, fatal, all); } - public static string SendtelegramlevelShort { get { return LC.L(@"The messages to send"); } } - public static string SendtelegramanyoperationLong { get { return LC.L(@"By default, messages will only be sent after a backup operation. Use this option to send messages for all operations."); } } - public static string SendtelegramanyoperationShort { get { return LC.L(@"Send messages for all operations"); } } - public static string LoginTimeoutError { get { return LC.L(@"Timeout occurred while sending to Telegram server"); } } + public static string SendTelegramLevelShort => LC.L(@"The messages to send"); + public static string SendTelegramManyOperationLong => LC.L(@"By default, messages will only be sent after a backup operation. Use this option to send messages for all operations."); + public static string SendTelegramManyOperationShort => LC.L(@"Send messages for all operations"); + public static string SendTelegramExtraParametersLong { get { return LC.L(@"Use this option to set extra parameters for the message body. This parameter can either be a querystring (e.g. 'parameter1=value1¶meter2=value2') or a JSON key/value object."); } } + public static string SendTelegramExtraParametersShort { get { return LC.L(@"Extra parameters for the message sent"); } } } internal static class SendHttpMessage @@ -210,8 +220,8 @@ All command line options are also reported within %value%, e.g. %volsize%. Any u public static string SendhttpmessageShort { get { return LC.L(@"The message template"); } } public static string SendhttpmessageparameternameLong { get { return LC.L(@"Use this option to set a name of the parameter to send the message as."); } } public static string SendhttpmessageparameternameShort { get { return LC.L(@"The name of the parameter to send the message as"); } } - public static string SendhttpextraparametersLong { get { return LC.L(@"Use this option to set extra parameters to add to the http message, e.g. ""parameter1=value1¶meter2=value2"""); } } - public static string SendhttpextraparametersShort { get { return LC.L(@"Extra parameters to add to the http message"); } } + public static string SendhttpextraparametersLong { get { return LC.L(@"Use this option to set extra parameters for the message body. This parameter can either be a querystring (e.g. 'parameter1=value1¶meter2=value2') or a JSON key/value object."); } } + public static string SendhttpextraparametersShort { get { return LC.L(@"Extra parameters for the message sent"); } } public static string SendhttplevelLong(string success, string warning, string error, string fatal, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"", ""{3}"". You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The special value ""{4}"" is a shorthand for ""{0},{1},{2},{3}"" and will cause all backup operations to send a message.", success, warning, error, fatal, all); } public static string SendhttplevelShort { get { return LC.L(@"The messages to send"); } } @@ -223,6 +233,14 @@ You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The sp public static string SendhttpurlsformShort { get { return LC.L(@"HTTP report URLs for sending form data"); } } public static string SendhttpurlsjsonLong { get { return LC.L(@"Use this option to set HTTP report URLs for sending JSON data. This option accepts multiple URLs, seperated by a semi-colon. All URLs will receive the same data. Note that this option ignores the format and verb settings."); } } public static string SendhttpurlsjsonShort { get { return LC.L(@"HTTP report URLs for sending JSON data"); } } + public static string AcceptAnyCertificateLong { get { return LC.L(@"Use this option to accept any server certificate, regardless of what errors it may have. Please use --{0} instead, whenever possible.", "accept-specified-ssl-hash"); } } + public static string AcceptAnyCertificateShort { get { return LC.L(@"Accept any server certificate"); } } + public static string AcceptSpecifiedCertificateLong { get { return LC.L(@"If your server certificate is reported as invalid (e.g. with self-signed certificates), you can supply the certificate hash (SHA1) to approve it anyway. The hash value must be entered in hex format without spaces or colons. You can enter multiple hashes separated by commas."); } } + public static string AcceptSpecifiedCertificateShort { get { return LC.L(@"Optionally accept a known SSL certificate"); } } + public static string SendHttpRetriesLong { get { return LC.L(@"Use this option to set the number of retries to attempt if the HTTP request fails."); } } + public static string SendHttpRetriesShort { get { return LC.L(@"Set the number of retries"); } } + public static string SendHttpRetryDelayLong { get { return LC.L(@"Use this option to set the delay between retries."); } } + public static string SendHttpRetryDelayShort { get { return LC.L(@"Set the retry delay"); } } } internal static class ReportHelper diff --git a/Duplicati/Library/Modules/Builtin/run-script-example.bat b/Duplicati/Library/Modules/Builtin/run-script-example.bat index e858bbc2b..a1797c568 100644 --- a/Duplicati/Library/Modules/Builtin/run-script-example.bat +++ b/Duplicati/Library/Modules/Builtin/run-script-example.bat @@ -50,7 +50,12 @@ REM --run-script-with-arguments = REM If set to true, the script path will be parsed as a command line, and the REM arguments will be passed to the script. If set to false (default), REM the script path will used as a single path. -REM Use double- or single-quotes to pass arguments with spaces. +REM If you do not have spaces in your script path or arguments, simply enter +REM it as a string: +REM Example: --run-script-before="C:\path\to\script.bat arg1 arg2 --option1=a" +REM If you have spaces in the path or arguements, use double- or single-quotes +REM around the elements that have spaces, similar to how you would do +REM on the command line: REM Example: --run-script-before="\"C:\path to\script.bat\" \"arg1 \" arg2" diff --git a/Duplicati/Library/Modules/Builtin/run-script-example.sh b/Duplicati/Library/Modules/Builtin/run-script-example.sh index 8ce9ef965..4743a3a98 100755 --- a/Duplicati/Library/Modules/Builtin/run-script-example.sh +++ b/Duplicati/Library/Modules/Builtin/run-script-example.sh @@ -50,7 +50,11 @@ # If set to true, the script path will be parsed as a command line, and the # arguments will be passed to the script. If set to false (default), # the script path will used as a single path. -# Use double- or single-quotes to pass arguments with spaces. +# If you do not have spaces in your script path or arguments, simply enter +# it as a string: +# Example: --run-script-before="/path/to/script.sh arg1 arg2 --option=a" +# If you have spaces in the path or arguements, use double- or single-quotes +# around the elements that have spaces, similar to how you would do in a shell: # Example: --run-script-before="\"/path to/script.sh\" \"arg1 \" arg2" diff --git a/Duplicati/Library/RemoteControl/Duplicati.Library.RemoteControl.csproj b/Duplicati/Library/RemoteControl/Duplicati.Library.RemoteControl.csproj index 0abbc5b57..483a3cc66 100644 --- a/Duplicati/Library/RemoteControl/Duplicati.Library.RemoteControl.csproj +++ b/Duplicati/Library/RemoteControl/Duplicati.Library.RemoteControl.csproj @@ -2,7 +2,7 @@ net8.0 - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license Duplicati.Library.RemoteControl enable enable diff --git a/Duplicati/Library/RemoteControl/Exceptions.cs b/Duplicati/Library/RemoteControl/Exceptions.cs index d80109a10..ec57c71ea 100644 --- a/Duplicati/Library/RemoteControl/Exceptions.cs +++ b/Duplicati/Library/RemoteControl/Exceptions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/RemoteControl/ExchangeDataTypes.cs b/Duplicati/Library/RemoteControl/ExchangeDataTypes.cs index f09d408ed..6b6cf7fa4 100644 --- a/Duplicati/Library/RemoteControl/ExchangeDataTypes.cs +++ b/Duplicati/Library/RemoteControl/ExchangeDataTypes.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -45,6 +45,10 @@ internal enum MessageType /// Command, /// + /// A control message + /// + Control, + /// /// An unknown command /// Unknown @@ -56,7 +60,9 @@ internal enum MessageType /// The client token /// The client public key /// The version of the client -public record AuthMessage(string Token, string PublicKey, string ClientVersion, int ProtocolVersion); +/// The protocol version of the client +/// The optional metadata to send +public record AuthMessage(string Token, string PublicKey, string ClientVersion, int ProtocolVersion, Dictionary? Metadata); /// /// A message authentication response @@ -75,6 +81,35 @@ internal sealed record AuthResultMessage(bool? Accepted, bool? WillReplaceToken, /// The version of the server public sealed record WelcomeMessage(string PublicKeyHash, string MachineName, string ServerVersion, IEnumerable SupportedProtocolVersions); +/// +/// A message with a control request +/// +/// The control command being requested +/// The parameters for the command +public sealed record ControlRequestMessage(string Command, Dictionary Parameters) +{ + /// + /// The configure report url command + /// + public const string ConfigureReportUrlSet = "configure-report-url:set"; + /// + /// A control message to return if the report url is configured + /// + public const string ConfigureReportUrlGet = "configure-report-url:get"; + /// + /// The url parameter for the configure report url command + /// + public const string ConfigureReportUrlParameter = "url"; + +} +/// +/// A message with a control response +/// +/// Whether the command was successful +/// The output of the command +/// The error message if the command failed +public sealed record ControlResponseMessage(bool Success, Dictionary? Output, string? ErrorMessage); + /// /// A message to send a command /// @@ -180,6 +215,7 @@ internal sealed record EnvelopedMessage "auth" => MessageType.Auth, "pong" => MessageType.Pong, "command" => MessageType.Command, + "control" => MessageType.Control, _ => MessageType.Unknown }; } diff --git a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs index 7e3eb0242..e715ca407 100644 --- a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs +++ b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,10 +19,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Diagnostics; using System.Net; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text.Json; +using Duplicati.Library.AutoUpdater; using Duplicati.Library.Logging; namespace Duplicati.Library.RemoteControl; @@ -46,15 +48,15 @@ public class KeepRemoteConnection : IDisposable /// private static readonly TimeSpan ReconnectInterval = TimeSpan.FromSeconds(30); - /// - /// The interval between reconnect attempts - /// - private static readonly TimeSpan NoResponseTimeout = TimeSpan.FromSeconds(30); - /// /// The interval between heartbeats /// - private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(15); + private static readonly TimeSpan HeartbeatInterval = TimeSpan.FromSeconds(60); + + /// + /// The time between reconnect attempts if no response is received + /// + private static readonly TimeSpan NoResponseTimeout = HeartbeatInterval * 2; /// /// The minimum time between certificate refreshes @@ -133,12 +135,20 @@ public class KeepRemoteConnection : IDisposable /// private RSA? _serverPublicKey; + /// + /// The callback to call when connecting + /// + private readonly Func, Task>> _onConnect; /// /// The callback to call when rekeying /// private readonly Func _onReKey; /// - /// The callback to call when a message is received + /// The callback to call when a control message is received + /// + private readonly Func _onControl; + /// + /// The callback to call when a command message is received /// private readonly Func _onMessage; @@ -168,16 +178,32 @@ public class KeepRemoteConnection : IDisposable /// /// The url to use /// The JWT token to use + /// The certificate url to use /// The server keys to use /// The token to cancel the connection - private KeepRemoteConnection(string serverUrl, string JWT, string certificateUrl, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) + /// The callback to call when connecting + /// The callback to call when rekeying + /// The callback to call when a control message is received + /// The callback to call when a command message is received + private KeepRemoteConnection( + string serverUrl, + string JWT, + string certificateUrl, + IEnumerable serverKeys, + CancellationToken cancellationToken, + Func, Task>> onConnect, + Func onReKey, + Func onControl, + Func onMessage) { _serverUrl = serverUrl; _certificateUrl = certificateUrl; _token = JWT; _serverKeys = serverKeys; _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + _onConnect = onConnect; _onReKey = onReKey; + _onControl = onControl; _onMessage = onMessage; _client = new Websocket.Client.WebsocketClient(new Uri(serverUrl)); @@ -257,7 +283,7 @@ public class KeepRemoteConnection : IDisposable return; _lastMessageReceived = DateTimeOffset.Now; - Log.WriteMessage(LogMessageType.Information, LogTag, "WebsocketMessage", "Received message from server: {0}", msg); + Log.WriteMessage(LogMessageType.Verbose, LogTag, "WebsocketMessage", "Received message from server: {0}", msg); try { @@ -300,13 +326,28 @@ public class KeepRemoteConnection : IDisposable } _state = ConnectionState.WelcomeReceived; + + // Prepare basic metadata and allow additional metadata to be added + var metadata = await _onConnect(new Dictionary() { + { "client-version", UpdaterManager.SelfVersion?.Version ?? "0.0.0.0" }, + { "client-id", ClientId }, + { "client-uptime", (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString() }, + { "machine-name", DataFolderManager.MachineName }, + { "machine-id", DataFolderManager.MachineID }, + { "install-id", DataFolderManager.InstallID }, + { "machine-os", UpdaterManager.OperatingSystemName }, + { "package-id", UpdaterManager.PackageTypeId }, + { "update-channel", UpdaterManager.CurrentChannel.ToString() } + }); + SendEnvelope( welcomeEnvelope.RespondWith( new AuthMessage( _token, ClientKey.ExportRSAPublicKeyPem(), - AutoUpdater.UpdaterManager.SelfVersion?.Version ?? "0.0.0", - PROTOCOL_VERSION + UpdaterManager.SelfVersion?.Version ?? "0.0.0.0", + PROTOCOL_VERSION, + metadata ), "auth" ), @@ -352,6 +393,13 @@ public class KeepRemoteConnection : IDisposable )); break; + case MessageType.Control: + await _onControl(new ControlMessage( + envelope.GetPayload(), + response => SendEnvelope(envelope.RespondWith(response)) + )); + break; + default: throw new ProtocolViolationException("Unexpected message"); } @@ -403,13 +451,24 @@ public class KeepRemoteConnection : IDisposable /// The certificate url to use /// The server keys to use /// The token to cancel the connection + /// The callback to call when connecting /// The callback to call when rekeying - /// The callback to call when a message is received + /// The callback to call when a control message is received + /// The callback to call when a command message is received /// - public static Task Start(string serverUrl, string JWT, string certificateUrl, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) + public static Task Start( + string serverUrl, + string JWT, + string certificateUrl, + IEnumerable serverKeys, + CancellationToken cancellationToken, + Func, Task>> onConnect, + Func onReKey, + Func onControl, + Func onMessage) => Task.Run(async () => { - using var connection = new KeepRemoteConnection(serverUrl, JWT, certificateUrl, serverKeys, cancellationToken, onReKey, onMessage); + using var connection = new KeepRemoteConnection(serverUrl, JWT, certificateUrl, serverKeys, cancellationToken, onConnect, onReKey, onControl, onMessage); await connection._runnerTask; }); @@ -477,14 +536,24 @@ public class KeepRemoteConnection : IDisposable /// /// The url to use /// The JWT token to use - /// + /// The certificate url to use /// The server keys to use + /// The callback to call when connecting /// The callback to call when rekeying + /// The callback to call when a control message is received /// The callback to call when a message is received - /// The token to cancel the connection /// The connection object - public static KeepRemoteConnection CreateRemoteListener(string serverUrl, string JWT, string certificateUrl, IEnumerable serverKeys, CancellationToken cancellationToken, Func onReKey, Func onMessage) - => new KeepRemoteConnection(serverUrl, JWT, certificateUrl, serverKeys, cancellationToken, onReKey, onMessage); + public static KeepRemoteConnection CreateRemoteListener( + string serverUrl, + string JWT, + string certificateUrl, + IEnumerable serverKeys, + CancellationToken cancellationToken, + Func, Task>> onConnect, + Func onReKey, + Func onControl, + Func onMessage) + => new KeepRemoteConnection(serverUrl, JWT, certificateUrl, serverKeys, cancellationToken, onConnect, onReKey, onControl, onMessage); /// /// Requests a certificate refresh @@ -587,4 +656,38 @@ public class KeepRemoteConnection : IDisposable } } } + + /// + /// A wrapper for allowing external code to handle a control message + /// + public sealed class ControlMessage + { + /// + /// The callback method that will receive the response + /// + private readonly Func _respondCommand; + /// + /// The command request message + /// + public ControlRequestMessage ControlRequestMessage { get; } + + /// + /// Creates a new command message + /// + /// The command request message + /// The callback method that will receive the response + public ControlMessage(ControlRequestMessage controlRequestMessage, Func respondCommand) + { + ControlRequestMessage = controlRequestMessage; + _respondCommand = respondCommand; + } + + /// + /// Responds to the command message + /// + /// The response to send + /// True if the response was sent + public bool Respond(ControlResponseMessage response) + => _respondCommand(response); + } } diff --git a/Duplicati/Library/RemoteControl/PeriodicRefresher.cs b/Duplicati/Library/RemoteControl/PeriodicRefresher.cs index b04ce86ab..20c1d0fb4 100644 --- a/Duplicati/Library/RemoteControl/PeriodicRefresher.cs +++ b/Duplicati/Library/RemoteControl/PeriodicRefresher.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.RemoteControl; /// diff --git a/Duplicati/Library/RemoteControl/RegisterForRemote.cs b/Duplicati/Library/RemoteControl/RegisterForRemote.cs index 751d19fdd..6de30c610 100644 --- a/Duplicati/Library/RemoteControl/RegisterForRemote.cs +++ b/Duplicati/Library/RemoteControl/RegisterForRemote.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -219,12 +219,13 @@ public class RegisterForRemote : IDisposable var basics = new Dictionary { { "instanceId", ClientInstanceId }, - { "machineId", AutoUpdater.UpdaterManager.MachineID }, - { "machineName", AutoUpdater.UpdaterManager.MachineName }, - { "installId", AutoUpdater.UpdaterManager.InstallID }, + { "machineId", AutoUpdater.DataFolderManager.MachineID }, + { "machineName", AutoUpdater.DataFolderManager.MachineName }, + { "installId", AutoUpdater.DataFolderManager.InstallID }, { "localTime", DateTimeOffset.Now.ToString("o") }, { "version", AutoUpdater.UpdaterManager.SelfVersion?.Version }, - { "packageTypeId", AutoUpdater.UpdaterManager.PackageTypeId } + { "packageTypeId", AutoUpdater.UpdaterManager.PackageTypeId }, + { "operatingSystem", AutoUpdater.UpdaterManager.OperatingSystemName } }; // Pass any query parameters from the registration URL into the JSON body diff --git a/Duplicati/Library/RemoteControl/SharedTypes.cs b/Duplicati/Library/RemoteControl/SharedTypes.cs index 91f06ada3..8245ea680 100644 --- a/Duplicati/Library/RemoteControl/SharedTypes.cs +++ b/Duplicati/Library/RemoteControl/SharedTypes.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/RemoteControl/TransportHelper.cs b/Duplicati/Library/RemoteControl/TransportHelper.cs index 48783ae9c..9b5276d9a 100644 --- a/Duplicati/Library/RemoteControl/TransportHelper.cs +++ b/Duplicati/Library/RemoteControl/TransportHelper.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Security.Cryptography; using System.Text.Json; using Jose; diff --git a/Duplicati/Library/RestAPI/Abstractions/IScheduler.cs b/Duplicati/Library/RestAPI/Abstractions/IScheduler.cs index a170d73ce..1140bf84a 100644 --- a/Duplicati/Library/RestAPI/Abstractions/IScheduler.cs +++ b/Duplicati/Library/RestAPI/Abstractions/IScheduler.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using Duplicati.Library.Utility; diff --git a/Duplicati/Library/RestAPI/Abstractions/IWorkerThreadsManager.cs b/Duplicati/Library/RestAPI/Abstractions/IWorkerThreadsManager.cs index fad7d506a..aceea26b9 100644 --- a/Duplicati/Library/RestAPI/Abstractions/IWorkerThreadsManager.cs +++ b/Duplicati/Library/RestAPI/Abstractions/IWorkerThreadsManager.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. #nullable enable using System; using Duplicati.Library.Utility; @@ -11,7 +31,7 @@ public interface IWorkerThreadsManager Tuple? CurrentTask { get; } WorkerThread? WorkerThread { get; } - void UpdateThrottleSpeeds(); + void UpdateThrottleSpeeds(string? uploadSpeed, string? downloadSpeed); long AddTask(Runner.IRunnerData data, bool skipQueue = false); } \ No newline at end of file diff --git a/Duplicati/Library/RestAPI/BackupImportExportHandler.cs b/Duplicati/Library/RestAPI/BackupImportExportHandler.cs index fb3be4d07..0884b0aa9 100644 --- a/Duplicati/Library/RestAPI/BackupImportExportHandler.cs +++ b/Duplicati/Library/RestAPI/BackupImportExportHandler.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; diff --git a/Duplicati/Library/RestAPI/Database/Backup.cs b/Duplicati/Library/RestAPI/Database/Backup.cs index 28eaddce0..8c49f0c5c 100644 --- a/Duplicati/Library/RestAPI/Database/Backup.cs +++ b/Duplicati/Library/RestAPI/Database/Backup.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Database/Connection.cs b/Duplicati/Library/RestAPI/Database/Connection.cs index 382bd1cdf..ea15e5188 100644 --- a/Duplicati/Library/RestAPI/Database/Connection.cs +++ b/Duplicati/Library/RestAPI/Database/Connection.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -28,18 +28,24 @@ using Duplicati.Library.RestAPI; using Duplicati.Library.Encryption; using Duplicati.Library.DynamicLoader; using Duplicati.Library.Main; +using Duplicati.Library.AutoUpdater; +using System.Data; +using Duplicati.Library.Main.Database; + +#nullable enable namespace Duplicati.Server.Database { public class Connection : IDisposable { - private readonly System.Data.IDbConnection m_connection; - private readonly System.Data.IDbCommand m_errorcmd; + private readonly IDbConnection m_connection; + private readonly IDbCommand m_errorcmd; public readonly object m_lock = new object(); public const int ANY_BACKUP_ID = -1; public const int SERVER_SETTINGS_ID = -2; private readonly Dictionary m_temporaryBackups = new Dictionary(); private readonly bool m_encryptSensitiveFields; + private readonly EncryptedFieldHelper.KeyInstance? m_key; private static readonly HashSet _encryptedFields = BackendLoader.Backends.SelectMany(x => x.SupportedCommands ?? []) .Concat(EncryptionLoader.Modules.SelectMany(x => x.SupportedCommands ?? [])) @@ -59,18 +65,20 @@ namespace Duplicati.Server.Database ]) .ToHashSet(StringComparer.OrdinalIgnoreCase); - public Connection(System.Data.IDbConnection connection, bool disableFieldEncryption) + public Connection(IDbConnection connection, bool disableFieldEncryption, EncryptedFieldHelper.KeyInstance? key) { m_encryptSensitiveFields = !disableFieldEncryption; + m_key = key; m_connection = connection; m_errorcmd = m_connection.CreateCommand(); m_errorcmd.CommandText = @"INSERT INTO ""ErrorLog"" (""BackupID"", ""Message"", ""Exception"", ""Timestamp"") VALUES (?,?,?,?)"; - for (var i = 0; i < 4; i++) - m_errorcmd.Parameters.Add(m_errorcmd.CreateParameter()); + m_errorcmd.AddParameters(4); this.ApplicationSettings = new ServerSettings(this); } + public bool IsEncryptingFields => m_encryptSensitiveFields; + public void ReWriteAllFieldsIfEncryptionChanged() { // The token is automatically decrypted when the settings are loaded @@ -124,15 +132,16 @@ namespace Duplicati.Server.Database { if (!long.TryParse(backupid, out long id)) id = -1; - ((System.Data.IDbDataParameter)m_errorcmd.Parameters[0]).Value = id; - ((System.Data.IDbDataParameter)m_errorcmd.Parameters[1]).Value = message; - ((System.Data.IDbDataParameter)m_errorcmd.Parameters[2]).Value = ex?.ToString(); - ((System.Data.IDbDataParameter)m_errorcmd.Parameters[3]).Value = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow); + + m_errorcmd.SetParameterValue(0, id); + m_errorcmd.SetParameterValue(1, message); + m_errorcmd.SetParameterValue(2, ex?.ToString()); + m_errorcmd.SetParameterValue(3, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); m_errorcmd.ExecuteNonQuery(); } } - public void ExecuteWithCommand(Action f) + public void ExecuteWithCommand(Action f) { lock (m_lock) using (var cmd = m_connection.CreateCommand()) @@ -144,9 +153,9 @@ namespace Duplicati.Server.Database var scheduleId = GetScheduleIDsFromTags(new string[] { "ID=" + backup.ID }); return new Serializable.ImportExportStructure() { - CreatedByVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(), + CreatedByVersion = UpdaterManager.SelfVersion.Version ?? "Unknown", Backup = (Database.Backup)backup, - Schedule = (Database.Schedule)(scheduleId.Any() ? GetSchedule(scheduleId.First()) : null), + Schedule = scheduleId != null && scheduleId.Any() ? (Schedule?)GetSchedule(scheduleId.First()) : null, DisplayNames = SpecialFolders.GetSourceNames(backup) }; } @@ -179,34 +188,33 @@ namespace Duplicati.Server.Database m_temporaryBackups.Add(backup.ID, (Backup)backup); } - public IBackup GetTemporaryBackup(string id) + public IBackup? GetTemporaryBackup(string id) { if (string.IsNullOrEmpty(id)) return null; lock (m_lock) { - Backup b; - m_temporaryBackups.TryGetValue(id, out b); + m_temporaryBackups.TryGetValue(id, out var b); return b; } } public ServerSettings ApplicationSettings { get; private set; } - internal IDictionary GetMetadata(long id) + internal IDictionary GetMetadata(long id) { lock (m_lock) return ReadFromDb( - (rd) => new KeyValuePair( - ConvertToString(rd, 0), + (rd) => new KeyValuePair( + ConvertToString(rd, 0) ?? "", ConvertToString(rd, 1) ), @"SELECT ""Name"", ""Value"" FROM ""Metadata"" WHERE ""BackupID"" = ? ", id) .ToDictionary((k) => k.Key, (k) => k.Value); } - internal void SetMetadata(IDictionary values, long id, System.Data.IDbTransaction transaction) + internal void SetMetadata(IDictionary values, long id, IDbTransaction? transaction) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -238,7 +246,7 @@ namespace Duplicati.Server.Database .ToArray(); } - internal void SetFilters(IEnumerable values, long id, System.Data.IDbTransaction transaction = null) + internal void SetFilters(IEnumerable values, long id, IDbTransaction? transaction = null) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -264,14 +272,14 @@ namespace Duplicati.Server.Database { Filter = ConvertToString(rd, 0) ?? "", Name = ConvertToString(rd, 1) ?? "", - Value = DecryptSensitiveFields(ConvertToString(rd, 2) ?? "") + Value = DecryptSensitiveFields(ConvertToString(rd, 2) ?? "", m_key) //TODO: Attach the argument information }, @"SELECT ""Filter"", ""Name"", ""Value"" FROM ""Option"" WHERE ""BackupID"" = ?", id) .ToArray(); } - internal void SetSettings(IEnumerable values, long id, System.Data.IDbTransaction transaction = null) + internal void SetSettings(IEnumerable values, long id, IDbTransaction? transaction = null) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -281,7 +289,7 @@ namespace Duplicati.Server.Database { Filter = x.Filter, Name = x.Name, - Value = EncryptSensitiveFields(x.Name, x.Value) + Value = EncryptSensitiveFields(x.Name, x.Value, m_key) }).ToList(); OverwriteAndUpdateDb( @@ -302,7 +310,7 @@ namespace Duplicati.Server.Database } } - internal string[] GetSources(long id) + internal string?[] GetSources(long id) { lock (m_lock) return ReadFromDb( @@ -311,7 +319,7 @@ namespace Duplicati.Server.Database .ToArray(); } - internal void SetSources(IEnumerable values, long id, System.Data.IDbTransaction transaction) + internal void SetSources(IEnumerable values, long id, IDbTransaction transaction) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -359,7 +367,7 @@ namespace Duplicati.Server.Database } } - public IBackup GetBackup(string id) + public IBackup? GetBackup(string id) { if (string.IsNullOrWhiteSpace(id)) throw new ArgumentNullException(nameof(id)); @@ -367,7 +375,7 @@ namespace Duplicati.Server.Database return long.TryParse(id, out long lid) ? GetBackup(lid) : GetTemporaryBackup(id); } - internal IBackup GetBackup(long id) + internal IBackup? GetBackup(long id) { lock (m_lock) { @@ -378,7 +386,7 @@ namespace Duplicati.Server.Database Name = ConvertToString(rd, 1), Description = ConvertToString(rd, 2), Tags = (ConvertToString(rd, 3) ?? "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), - TargetURL = EncryptedFieldHelper.Decrypt(ConvertToString(rd, 4)), + TargetURL = EncryptedFieldHelper.Decrypt(ConvertToString(rd, 4), m_key), DBPath = ConvertToString(rd, 5), }, @"SELECT ""ID"", ""Name"", ""Description"", ""Tags"", ""TargetURL"", ""DBPath"" FROM ""Backup"" WHERE ID = ?", id) @@ -391,7 +399,7 @@ namespace Duplicati.Server.Database } } - public ISchedule GetSchedule(long id) + public ISchedule? GetSchedule(long id) { lock (m_lock) { @@ -460,12 +468,12 @@ namespace Duplicati.Server.Database } } - public void AddOrUpdateBackupAndSchedule(IBackup item, ISchedule schedule) + public void AddOrUpdateBackupAndSchedule(IBackup item, ISchedule? schedule) { AddOrUpdateBackup(item, true, schedule); } - public string ValidateBackup(IBackup item, ISchedule schedule) + public string? ValidateBackup(IBackup item, ISchedule? schedule) { if (string.IsNullOrWhiteSpace(item.Name)) return "Missing a name"; @@ -572,12 +580,7 @@ namespace Duplicati.Server.Database { using (var cmd = m_connection.CreateCommand()) { - cmd.Transaction = tr; - cmd.Parameters.Add(cmd.CreateParameter()); - ((System.Data.IDbDataParameter)cmd.Parameters[0]).Value = path; - cmd.Parameters.Add(cmd.CreateParameter()); - ((System.Data.IDbDataParameter)cmd.Parameters[1]).Value = item.ID; - + cmd.AddAndSetParameters(path, item.ID); cmd.CommandText = @"UPDATE ""Backup"" SET ""DBPath""=? WHERE ""ID""=?"; cmd.ExecuteNonQuery(); tr.Commit(); @@ -589,7 +592,7 @@ namespace Duplicati.Server.Database FIXMEGlobal.StatusEventNotifyer.SignalNewEvent(); } - private void AddOrUpdateBackup(IBackup item, bool updateSchedule, ISchedule schedule) + private void AddOrUpdateBackup(IBackup item, bool updateSchedule, ISchedule? schedule) { lock (m_lock) { @@ -602,7 +605,7 @@ namespace Duplicati.Server.Database for (var i = 0; i < 100; i++) { - var guess = System.IO.Path.Combine(folder, System.IO.Path.ChangeExtension(Duplicati.Library.Main.DatabaseLocator.GenerateRandomName(), ".sqlite")); + var guess = System.IO.Path.Combine(folder, System.IO.Path.ChangeExtension(CLIDatabaseLocator.GenerateRandomName(), ".sqlite")); if (!System.IO.File.Exists(guess)) { ((Backup)item).DBPath = guess; @@ -619,8 +622,8 @@ namespace Duplicati.Server.Database OverwriteAndUpdateDb( tr, null, - new object[] { long.Parse(item.ID ?? "-1") }, - new IBackup[] { item }, + [long.Parse(item.ID ?? "-1")], + [item], update ? @"UPDATE ""Backup"" SET ""Name""=?, ""Description""=?, ""Tags""=?, ""TargetURL""=? WHERE ""ID""=?" : @"INSERT INTO ""Backup"" (""Name"", ""Description"", ""Tags"", ""TargetURL"", ""DBPath"") VALUES (?,?,?,?,?)", @@ -636,8 +639,8 @@ namespace Duplicati.Server.Database n.Name, n.Description ?? "" , // Description is optional but the column is set to NOT NULL, an additional check is welcome string.Join(",", n.Tags ?? new string[0]), - m_encryptSensitiveFields ? EncryptedFieldHelper.Encrypt(n.TargetURL) : n.TargetURL, - update ? item.ID : n.DBPath + m_encryptSensitiveFields ? EncryptedFieldHelper.Encrypt(n.TargetURL, m_key) : n.TargetURL, + update ? item.ID! : n.DBPath }; }); @@ -649,9 +652,8 @@ namespace Duplicati.Server.Database item.ID = ExecuteScalarInt64(cmd).ToString(); } - var id = long.Parse(item.ID); - - if (long.Parse(item.ID) <= 0) + var id = long.Parse(item.ID ?? "-1"); + if (id <= 0) throw new Exception("Invalid addition, cannot update application settings through update method"); SetSources(item.Sources, id, tr); @@ -670,12 +672,19 @@ namespace Duplicati.Server.Database if (existing.Any()) { var cur = GetSchedule(existing.First()); - cur.AllowedDays = schedule.AllowedDays; - cur.Repeat = schedule.Repeat; - cur.Tags = schedule.Tags; - cur.Time = schedule.Time; + if (cur != null) + { + cur.AllowedDays = schedule.AllowedDays; + cur.Repeat = schedule.Repeat; + cur.Tags = schedule.Tags; + cur.Time = schedule.Time; - schedule = cur; + schedule = cur; + } + else + { + schedule.ID = -1; + } } else { @@ -706,7 +715,7 @@ namespace Duplicati.Server.Database } } - private void AddOrUpdateSchedule(ISchedule item, System.Data.IDbTransaction tr) + private void AddOrUpdateSchedule(ISchedule item, IDbTransaction tr) { lock (m_lock) { @@ -719,14 +728,14 @@ namespace Duplicati.Server.Database update ? @"UPDATE ""Schedule"" SET ""Tags""=?, ""Time""=?, ""Repeat""=?, ""LastRun""=?, ""Rule""=? WHERE ""ID""=?" : @"INSERT INTO ""Schedule"" (""Tags"", ""Time"", ""Repeat"", ""LastRun"", ""Rule"") VALUES (?,?,?,?,?)", - (n) => new object[] { + (n) => [ string.Join(",", n.Tags), Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(n.Time), n.Repeat, Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(n.LastRun), n.Rule ?? "", update ? (object)item.ID : null - }); + ]); if (!update) using (var cmd = m_connection.CreateCommand()) @@ -806,7 +815,7 @@ namespace Duplicati.Server.Database Name = ConvertToString(rd, 1), Description = ConvertToString(rd, 2), Tags = (ConvertToString(rd, 3) ?? "").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries), - TargetURL = EncryptedFieldHelper.Decrypt(ConvertToString(rd, 4)), + TargetURL = EncryptedFieldHelper.Decrypt(ConvertToString(rd, 4), m_key), DBPath = ConvertToString(rd, 5), }, @"SELECT ""ID"", ""Name"", ""Description"", ""Tags"", ""TargetURL"", ""DBPath"" FROM ""Backup"" ") @@ -874,8 +883,12 @@ namespace Duplicati.Server.Database FIXMEGlobal.DataConnection.ApplicationSettings.UnackedWarning = notifications.Any(x => x.ID != id && x.Type == Duplicati.Server.Serialization.NotificationType.Warning); } - FIXMEGlobal.NotificationUpdateService.IncrementLastNotificationUpdateId(); - FIXMEGlobal.StatusEventNotifyer.SignalNewEvent(); + // Guard against dismissing notifications before the provider is initialized + if (FIXMEGlobal.Provider != null) + { + FIXMEGlobal.NotificationUpdateService.IncrementLastNotificationUpdateId(); + FIXMEGlobal.StatusEventNotifyer.SignalNewEvent(); + } return true; } @@ -925,8 +938,7 @@ namespace Duplicati.Server.Database using (var tr = m_connection.BeginTransaction()) { cmd.Transaction = tr; - cmd.Parameters.Add(cmd.CreateParameter()); - ((System.Data.IDbDataParameter)cmd.Parameters[0]).Value = -1; + cmd.AddAndSetParameters(-1); cmd.CommandText = @"DELETE FROM ""Option"" WHERE ""BackupID"" = ?"; cmd.ExecuteNonQuery(); cmd.CommandText = @"DELETE FROM ""Metadata"" WHERE ""BackupID"" = ?"; @@ -937,9 +949,7 @@ namespace Duplicati.Server.Database cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); - cmd.Parameters.Add(cmd.CreateParameter()); - - ((System.Data.IDbDataParameter)cmd.Parameters[0]).Value = "ID=-1"; + cmd.AddAndSetParameters("ID=-1"); cmd.CommandText = @"DELETE FROM ""Schedule"" WHERE ""Tags"" = ?"; cmd.ExecuteNonQuery(); tr.Commit(); @@ -971,7 +981,7 @@ namespace Duplicati.Server.Database .ToDictionary(x => x.Key, x => x.Last().Value); } - public void SetUISettings(string scheme, IDictionary values, System.Data.IDbTransaction transaction = null) + public void SetUISettings(string scheme, IDictionary values, IDbTransaction? transaction = null) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -992,7 +1002,7 @@ namespace Duplicati.Server.Database } } - public void UpdateUISettings(string scheme, IDictionary values, System.Data.IDbTransaction transaction = null) + public void UpdateUISettings(string scheme, IDictionary values, IDbTransaction? transaction = null) { lock (m_lock) using (var tr = transaction == null ? m_connection.BeginTransaction() : null) @@ -1049,32 +1059,31 @@ namespace Duplicati.Server.Database { cmd.Transaction = tr; cmd.CommandText = @"DELETE FROM ""ErrorLog"" WHERE ""Timestamp"" < ?"; - cmd.Parameters.Add(cmd.CreateParameter()); - ((System.Data.IDataParameter)cmd.Parameters[0]).Value = t; + cmd.AddAndSetParameters(t); cmd.ExecuteNonQuery(); tr.Commit(); } } - private static DateTime ConvertToDateTime(System.Data.IDataReader rd, int index) + private static DateTime ConvertToDateTime(IDataReader rd, int index) { var unixTime = ConvertToInt64(rd, index); return unixTime == 0 ? new DateTime(0) : Library.Utility.Utility.EPOCH.AddSeconds(unixTime); } - private static bool ConvertToBoolean(System.Data.IDataReader rd, int index) + private static bool ConvertToBoolean(IDataReader rd, int index) { return ConvertToInt64(rd, index) == 1; } - private static string ConvertToString(System.Data.IDataReader rd, int index) + private static string? ConvertToString(IDataReader rd, int index) { var r = rd.GetValue(index); return r == null || r == DBNull.Value ? null : r.ToString(); } - private static long ConvertToInt64(System.Data.IDataReader rd, int index) + private static long ConvertToInt64(IDataReader rd, int index) { try { @@ -1088,24 +1097,24 @@ namespace Duplicati.Server.Database return -1; } - private static long ExecuteScalarInt64(System.Data.IDbCommand cmd, long defaultValue = -1) + private static long ExecuteScalarInt64(IDbCommand cmd, long defaultValue = -1) { using (var rd = cmd.ExecuteReader()) return rd.Read() ? ConvertToInt64(rd, 0) : defaultValue; } - private static string ExecuteScalarString(System.Data.IDbCommand cmd) + private static string? ExecuteScalarString(IDbCommand cmd) { using (var rd = cmd.ExecuteReader()) return rd.Read() ? ConvertToString(rd, 0) : null; } - private object ConvertToEnum(Type enumType, System.Data.IDataReader rd, int index, object @default) + private object? ConvertToEnum(Type enumType, IDataReader rd, int index, object? @default) { try { - return Enum.Parse(enumType, ConvertToString(rd, index)); + return Enum.Parse(enumType, ConvertToString(rd, index) ?? string.Empty, true); } catch { @@ -1115,13 +1124,13 @@ namespace Duplicati.Server.Database } // Overloaded function for legacy functionality - private bool DeleteFromDb(string tablename, long id, System.Data.IDbTransaction transaction = null) + private bool DeleteFromDb(string tablename, long id, IDbTransaction? transaction = null) { return DeleteFromDb(tablename, id, "ID", transaction); } // New function that allows to delete rows from tables with arbitrary identifier values (e.g. ID or BackupID) - private bool DeleteFromDb(string tablename, long id, string identifier, System.Data.IDbTransaction transaction = null) + private bool DeleteFromDb(string tablename, long id, string identifier, IDbTransaction? transaction = null) { if (transaction == null) { @@ -1138,9 +1147,7 @@ namespace Duplicati.Server.Database { cmd.Transaction = transaction; cmd.CommandText = string.Format(@"DELETE FROM ""{0}"" WHERE ""{1}""=?", tablename, identifier); - var p = cmd.CreateParameter(); - p.Value = id; - cmd.Parameters.Add(p); + cmd.AddAndSetParameters(id); var r = cmd.ExecuteNonQuery(); // Roll back the transaction if more than 1 ID was deleted. Multiple "BackupID" rows being deleted isn't a problem. @@ -1151,14 +1158,14 @@ namespace Duplicati.Server.Database } } - private static IEnumerable Read(System.Data.IDbCommand cmd, Func f) + private static IEnumerable Read(IDbCommand cmd, Func f) { using (var rd = cmd.ExecuteReader()) while (rd.Read()) yield return f(rd); } - private static IEnumerable Read(System.Data.IDataReader rd, Func f) + private static IEnumerable Read(IDataReader rd, Func f) { while (rd.Read()) yield return f(); @@ -1184,7 +1191,7 @@ namespace Duplicati.Server.Database select n).ToArray(); } - private IEnumerable ReadFromDb(string whereclause, params object[] args) + private IEnumerable ReadFromDb(string? whereclause, params object?[] args) { var properties = GetORMFields(); @@ -1219,10 +1226,11 @@ namespace Duplicati.Server.Database }, sql, args); } - private void OverwriteAndUpdateDb(System.Data.IDbTransaction transaction, string deleteSql, object[] deleteArgs, IEnumerable values, bool updateExisting) + private void OverwriteAndUpdateDb(IDbTransaction? transaction, string? deleteSql, object[]? deleteArgs, IEnumerable values, bool updateExisting) { var properties = GetORMFields(); - var idfield = properties.FirstOrDefault(x => x.Name == "ID"); + var idfield = properties.FirstOrDefault(x => x.Name == "ID") + ?? throw new Exception("No ID field found in type " + typeof(T).Name); properties = properties.Where(x => x.Name != "ID").ToArray(); string sql; @@ -1235,7 +1243,7 @@ namespace Duplicati.Server.Database string.Join(@", ", properties.Select(x => string.Format(@"""{0}""=?", x.Name))) ); - properties = properties.Union(new System.Reflection.PropertyInfo[] { idfield }).ToArray(); + properties = properties.Append(idfield).ToArray(); } else { @@ -1254,10 +1262,13 @@ namespace Duplicati.Server.Database { var val = x.GetValue(item, null); - if (x.PropertyType.IsEnum) - val = val.ToString(); - else if (x.PropertyType == typeof(DateTime)) - val = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds((DateTime)val); + if (val != null) + { + if (x.PropertyType.IsEnum) + val = val.ToString(); + else if (x.PropertyType == typeof(DateTime)) + val = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds((DateTime)val); + } return val; }).ToArray(); @@ -1275,24 +1286,18 @@ namespace Duplicati.Server.Database } } - private IEnumerable ReadFromDb(Func f, string sql, params object[] args) + private IEnumerable ReadFromDb(Func f, string sql, params object?[]? args) { using (var cmd = m_connection.CreateCommand()) { cmd.CommandText = sql; - if (args != null) - foreach (var a in args) - { - var p = cmd.CreateParameter(); - p.Value = a; - cmd.Parameters.Add(p); - } + cmd.AddAndSetParameters(args); return Read(cmd, f).ToArray(); } } - private void OverwriteAndUpdateDb(System.Data.IDbTransaction transaction, string deleteSql, object[] deleteArgs, IEnumerable values, string insertSql, Func f) + private void OverwriteAndUpdateDb(IDbTransaction? transaction, string? deleteSql, object[]? deleteArgs, IEnumerable values, string insertSql, Func f) { using (var cmd = m_connection.CreateCommand()) { @@ -1301,14 +1306,7 @@ namespace Duplicati.Server.Database if (!string.IsNullOrEmpty(deleteSql)) { cmd.CommandText = deleteSql; - if (deleteArgs != null) - foreach (var a in deleteArgs) - { - var p = cmd.CreateParameter(); - p.Value = a; - cmd.Parameters.Add(p); - } - + cmd.AddAndSetParameters(deleteArgs); cmd.ExecuteNonQuery(); cmd.Parameters.Clear(); } @@ -1321,12 +1319,9 @@ namespace Duplicati.Server.Database if (r == null) continue; - while (cmd.Parameters.Count < r.Length) - cmd.Parameters.Add(cmd.CreateParameter()); - - for (var i = 0; i < r.Length; i++) - ((System.Data.IDbDataParameter)cmd.Parameters[i]).Value = r[i]; - + if (cmd.Parameters.Count < r.Length) + cmd.AddParameters(r.Length - cmd.Parameters.Count); + cmd.SetParameterValues(r); cmd.ExecuteNonQuery(); } } @@ -1337,12 +1332,13 @@ namespace Duplicati.Server.Database /// /// The fieldname used to determine if it will be encrypted /// The field value + /// The encryption key /// The encrypted string or the original value - private static string EncryptSensitiveFields(string fieldName, string fieldValue) + private static string? EncryptSensitiveFields(string fieldName, string fieldValue, EncryptedFieldHelper.KeyInstance? key) { if (fieldValue != null) return _encryptedFields.Contains(fieldName) - ? EncryptedFieldHelper.Encrypt(fieldValue) + ? EncryptedFieldHelper.Encrypt(fieldValue, key) : fieldValue; return null; @@ -1352,12 +1348,13 @@ namespace Duplicati.Server.Database /// Decrypts sensitive fields /// /// The field value + /// The encryption key /// The decrypted string - private static string DecryptSensitiveFields(string fieldValue) + private static string? DecryptSensitiveFields(string? fieldValue, EncryptedFieldHelper.KeyInstance? key) { if (fieldValue != null) return EncryptedFieldHelper.IsEncryptedString(fieldValue) - ? EncryptedFieldHelper.Decrypt(fieldValue) + ? EncryptedFieldHelper.Decrypt(fieldValue, key) : fieldValue; return null; diff --git a/Duplicati/Library/RestAPI/Database/DatabaseConnectionSchemaMarker.cs b/Duplicati/Library/RestAPI/Database/DatabaseConnectionSchemaMarker.cs index 110f8477f..2a43d4923 100644 --- a/Duplicati/Library/RestAPI/Database/DatabaseConnectionSchemaMarker.cs +++ b/Duplicati/Library/RestAPI/Database/DatabaseConnectionSchemaMarker.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Duplicati/Library/RestAPI/Database/Filter.cs b/Duplicati/Library/RestAPI/Database/Filter.cs index 904b2bf4c..41646f32c 100644 --- a/Duplicati/Library/RestAPI/Database/Filter.cs +++ b/Duplicati/Library/RestAPI/Database/Filter.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Database/Notification.cs b/Duplicati/Library/RestAPI/Database/Notification.cs index a14d4f6c4..ed043b982 100644 --- a/Duplicati/Library/RestAPI/Database/Notification.cs +++ b/Duplicati/Library/RestAPI/Database/Notification.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Database/Schedule.cs b/Duplicati/Library/RestAPI/Database/Schedule.cs index 5a9d28202..8c267e6ff 100644 --- a/Duplicati/Library/RestAPI/Database/Schedule.cs +++ b/Duplicati/Library/RestAPI/Database/Schedule.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Database/ServerSettings.cs b/Duplicati/Library/RestAPI/Database/ServerSettings.cs index c8d31d5d1..dea19e8ec 100644 --- a/Duplicati/Library/RestAPI/Database/ServerSettings.cs +++ b/Duplicati/Library/RestAPI/Database/ServerSettings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -27,6 +27,10 @@ using Duplicati.Library.RestAPI; using System.Text.Json; using System.Text; using System.Security.Cryptography.X509Certificates; +using Duplicati.Library.Utility; +using Duplicati.Library.AutoUpdater; + +#nullable enable namespace Duplicati.Server.Database { @@ -37,7 +41,6 @@ namespace Duplicati.Server.Database public const string STARTUP_DELAY = "startup-delay"; public const string DOWNLOAD_SPEED_LIMIT = "max-download-speed"; public const string UPLOAD_SPEED_LIMIT = "max-upload-speed"; - public const string THREAD_PRIORITY = "thread-priority"; public const string LAST_WEBSERVER_PORT = "last-webserver-port"; public const string IS_FIRST_RUN = "is-first-run"; public const string SERVER_PORT_CHANGED = "server-port-changed"; @@ -66,17 +69,28 @@ namespace Duplicati.Server.Database public const string DISABLE_SIGNIN_TOKENS = "disable-signin-tokens"; public const string ENCRYPTED_FIELDS = "encrypted-fields"; public const string PRELOAD_SETTINGS_HASH = "preload-settings-hash"; + public const string TIMEZONE_OPTION = "server-timezone"; + public const string PAUSED_UNTIL = "paused-until"; + public const string LAST_UPDATE_CHECK_VERSION = "last-update-check-version"; + public const string ADDITIONAL_REPORT_URL = "additional-report-url"; + public const string BACKUP_LIST_SORT_ORDER = "backup-list-sort-order"; } - private readonly Dictionary settings; + private readonly Dictionary settings; private readonly Connection databaseConnection; - private Library.AutoUpdater.UpdateInfo m_latestUpdate; + private UpdateInfo? m_latestUpdate; internal ServerSettings(Connection con) { - settings = new Dictionary(); + settings = new Dictionary(); databaseConnection = con; ReloadSettings(); + + // Slightly hacky way to set this, but the rest of the code + // relies on this value, and the database has an override + // so we sync it here and in the property setter + if (Enum.TryParse(UpdateChannel, true, out var rt)) + UpdaterManager.CurrentChannel = rt; } public void ReloadSettings() @@ -84,17 +98,18 @@ namespace Duplicati.Server.Database lock (databaseConnection.m_lock) { settings.Clear(); - foreach (var n in typeof(CONST).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Static).Select(x => (string)x.GetValue(null))) - settings[n] = null; + foreach (var n in typeof(CONST).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Static).Select(x => (string?)x.GetValue(null))) + if (!string.IsNullOrWhiteSpace(n)) + settings[n] = null; foreach (var n in databaseConnection.GetSettings(Connection.SERVER_SETTINGS_ID)) settings[n.Name] = n.Value; } } - public void UpdateSettings(Dictionary newsettings, bool clearExisting) + public void UpdateSettings(Dictionary newsettings, bool clearExisting) { if (newsettings == null) - throw new ArgumentNullException(); + throw new ArgumentNullException(nameof(newsettings)); lock (databaseConnection.m_lock) { @@ -132,7 +147,7 @@ namespace Duplicati.Server.Database FIXMEGlobal.NotificationUpdateService.IncrementLastDataUpdateId(); FIXMEGlobal.StatusEventNotifyer.SignalNewEvent(); // If throttle options were changed, update now - FIXMEGlobal.WorkerThreadsManager.UpdateThrottleSpeeds(); + FIXMEGlobal.WorkerThreadsManager.UpdateThrottleSpeeds(UploadSpeedLimit, DownloadSpeedLimit); } // In case the usage reporter is enabled or disabled, refresh now @@ -140,7 +155,7 @@ namespace Duplicati.Server.Database FIXMEGlobal.StartOrStopUsageReporter(); } - public string StartupDelayDuration + public string? StartupDelayDuration { get { @@ -154,28 +169,24 @@ namespace Duplicati.Server.Database } } - public System.Threading.ThreadPriority? ThreadPriorityOverride + public DateTime? PausedUntil { get { - var tp = settings[CONST.THREAD_PRIORITY]; - if (string.IsNullOrEmpty(tp)) + if (long.TryParse(settings[CONST.PAUSED_UNTIL], out var t)) + return new DateTime(t, DateTimeKind.Utc); + else return null; - - System.Threading.ThreadPriority r; - if (Enum.TryParse(tp, true, out r)) - return r; - - return null; } set { lock (databaseConnection.m_lock) - settings[CONST.THREAD_PRIORITY] = value.HasValue ? Enum.GetName(typeof(System.Threading.ThreadPriority), value.Value) : null; + settings[CONST.PAUSED_UNTIL] = value?.ToUniversalTime().Ticks.ToString(); + SaveSettings(); } } - public string DownloadSpeedLimit + public string? DownloadSpeedLimit { get { @@ -189,7 +200,7 @@ namespace Duplicati.Server.Database } } - public string UploadSpeedLimit + public string? UploadSpeedLimit { get { @@ -207,7 +218,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBoolOption(settings, CONST.IS_FIRST_RUN); + return Utility.ParseBoolOption(settings, CONST.IS_FIRST_RUN); } set { @@ -221,7 +232,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.UNACKED_ERROR], false); + return Utility.ParseBool(settings[CONST.UNACKED_ERROR], false); } set { @@ -235,7 +246,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.UNACKED_WARNING], false); + return Utility.ParseBool(settings[CONST.UNACKED_WARNING], false); } set { @@ -249,7 +260,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.SERVER_PORT_CHANGED], false); + return Utility.ParseBool(settings[CONST.SERVER_PORT_CHANGED], false); } set { @@ -263,7 +274,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.DISABLE_TRAY_ICON_LOGIN], false); + return Utility.ParseBool(settings[CONST.DISABLE_TRAY_ICON_LOGIN], false); } set { @@ -277,7 +288,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.AUTOGENERATED_PASSPHRASE], false); + return Utility.ParseBool(settings[CONST.AUTOGENERATED_PASSPHRASE], false); } } @@ -285,7 +296,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.DISABLE_VISUAL_CAPTCHA], false); + return Utility.ParseBool(settings[CONST.DISABLE_VISUAL_CAPTCHA], false); } set { @@ -299,7 +310,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.DISABLE_SIGNIN_TOKENS], false); + return Utility.ParseBool(settings[CONST.DISABLE_SIGNIN_TOKENS], false); } set { @@ -393,7 +404,10 @@ namespace Duplicati.Server.Database if (string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(config)) return false; - return JsonSerializer.Deserialize(config).VerifyPassword(LegacyPreparePassword(password)); + var cfg = JsonSerializer.Deserialize(config) + ?? throw new Exception("Unable to deserialize PBKDF configuration"); + + return cfg.VerifyPassword(LegacyPreparePassword(password)); } /// @@ -403,16 +417,17 @@ namespace Duplicati.Server.Database /// The hashed password private string LegacyPreparePassword(string password) { - if (string.IsNullOrWhiteSpace(settings[CONST.SERVER_PASSPHRASE_SALT])) + var salt = settings[CONST.SERVER_PASSPHRASE_SALT]; + if (string.IsNullOrWhiteSpace(salt)) return password; - var buf = Convert.FromBase64String(settings[CONST.SERVER_PASSPHRASE_SALT]); + var buf = Convert.FromBase64String(salt); var sha256 = SHA256.Create(); var str = Encoding.UTF8.GetBytes(password); sha256.TransformBlock(str, 0, str.Length, str, 0); sha256.TransformFinalBlock(buf, 0, buf.Length); - return Convert.ToBase64String(sha256.Hash); + return Convert.ToBase64String(sha256.Hash ?? throw new CryptographicUnexpectedOperationException("Calculated hash value is null")); } /// @@ -470,7 +485,7 @@ namespace Duplicati.Server.Database SaveSettings(); } - public void SetAllowedHostnames(string allowedHostnames) + public void SetAllowedHostnames(string? allowedHostnames) { lock (databaseConnection.m_lock) settings[CONST.SERVER_ALLOWED_HOSTNAMES] = allowedHostnames; @@ -478,9 +493,9 @@ namespace Duplicati.Server.Database SaveSettings(); } - public string AllowedHostnames => settings[CONST.SERVER_ALLOWED_HOSTNAMES]; + public string? AllowedHostnames => settings[CONST.SERVER_ALLOWED_HOSTNAMES]; - public string JWTConfig + public string? JWTConfig { get => settings[CONST.JWT_CONFIG]; set @@ -491,7 +506,48 @@ namespace Duplicati.Server.Database } } - public string RemoteControlConfig + /// + /// The number of forever tokens that can be created. + /// A value of -1 means that forever tokens are disabled. + /// + /// The number of forever tokens that can be created. + /// + /// This setting is not persisted in the database, as it is meant to be a temporary setting. + /// + private int m_remainingForeverTokens = -1; + + /// + /// Enables forever tokens + /// + public void EnableForeverTokens() + { + lock (databaseConnection.m_lock) + if (m_remainingForeverTokens == -1) + m_remainingForeverTokens = 1; + } + + + /// + /// Consumes a forever token, if available + /// + /// True if a token was consumed, false if no tokens are available, null if forever tokens are disabled + public bool? ConsumeForeverToken() + { + lock (databaseConnection.m_lock) + { + if (m_remainingForeverTokens == -1) + return null; + if (m_remainingForeverTokens == 0) + return false; + + if (m_remainingForeverTokens > 0) + m_remainingForeverTokens--; + + return true; + } + } + + public string? RemoteControlConfig { get => settings[CONST.REMOTE_CONTROL_CONFIG]; set @@ -504,7 +560,7 @@ namespace Duplicati.Server.Database public bool RemoteControlEnabled { - get => Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.REMOTE_CONTROL_ENABLED], false); + get => Utility.ParseBool(settings[CONST.REMOTE_CONTROL_ENABLED], false); set { lock (databaseConnection.m_lock) @@ -556,7 +612,7 @@ namespace Duplicati.Server.Database { try { - return Duplicati.Library.Utility.Timeparser.ParseTimeInterval(UpdateCheckInterval, LastUpdateCheck); + return Timeparser.ParseTimeInterval(UpdateCheckInterval, LastUpdateCheck); } catch { @@ -565,11 +621,12 @@ namespace Duplicati.Server.Database } } - public Library.AutoUpdater.UpdateInfo UpdatedVersion + public Library.AutoUpdater.UpdateInfo? UpdatedVersion { get { - if (string.IsNullOrWhiteSpace(settings[CONST.UPDATE_CHECK_NEW_VERSION])) + var updateNew = settings[CONST.UPDATE_CHECK_NEW_VERSION]; + if (string.IsNullOrWhiteSpace(updateNew)) return null; try @@ -577,7 +634,7 @@ namespace Duplicati.Server.Database if (m_latestUpdate != null) return m_latestUpdate; - using (var tr = new System.IO.StringReader(settings[CONST.UPDATE_CHECK_NEW_VERSION])) + using (var tr = new System.IO.StringReader(updateNew)) return m_latestUpdate = Server.Serialization.Serializer.Deserialize(tr); } catch @@ -588,10 +645,10 @@ namespace Duplicati.Server.Database } set { - string result = null; + string? result = null; if (value != null) { - var sb = new System.Text.StringBuilder(); + var sb = new StringBuilder(); using (var tw = new System.IO.StringWriter(sb)) Server.Serialization.Serializer.SerializeJson(tw, value); @@ -606,7 +663,7 @@ namespace Duplicati.Server.Database } } - public string ServerListenInterface + public string? ServerListenInterface { get { @@ -624,7 +681,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.SERVER_DISABLE_HTTPS], false); + return Utility.ParseBool(settings[CONST.SERVER_DISABLE_HTTPS], false); } set { @@ -642,15 +699,16 @@ namespace Duplicati.Server.Database } } - public X509Certificate2? ServerSSLCertificate + public X509Certificate2Collection? ServerSSLCertificate { get { - if (String.IsNullOrEmpty(settings[CONST.SERVER_SSL_CERTIFICATE])) + var certificate = settings[CONST.SERVER_SSL_CERTIFICATE]; + if (string.IsNullOrEmpty(certificate)) return null; - return Library.Utility.Utility.LoadPfxCertificate( - Convert.FromBase64String(settings[CONST.SERVER_SSL_CERTIFICATE]), + return Utility.LoadPfxCertificate( + Convert.FromBase64String(certificate), settings[CONST.SERVER_SSL_CERTIFICATEPASSWORD], // Need to allow loading of plain-text certificates for backwards compatibility allowUnsafeCertificateLoad: true @@ -668,8 +726,13 @@ namespace Duplicati.Server.Database } else { + if (!value.Any(x => x.HasPrivateKey)) + throw new ArgumentException("The certificate must have a private key"); + var password = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); - var certdata = Convert.ToBase64String(value.Export(X509ContentType.Pkcs12, password)); + var certdata = Convert.ToBase64String(value.Export(X509ContentType.Pkcs12, password) + ?? throw new CryptographicUnexpectedOperationException("Exported certificate data is null")); + lock (databaseConnection.m_lock) { settings[CONST.SERVER_SSL_CERTIFICATE] = certdata; @@ -685,7 +748,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.HAS_FIXED_INVALID_BACKUPID], false); + return Utility.ParseBool(settings[CONST.HAS_FIXED_INVALID_BACKUPID], false); } set { @@ -695,7 +758,7 @@ namespace Duplicati.Server.Database } } - public string UpdateChannel + public string? UpdateChannel { get { @@ -706,10 +769,15 @@ namespace Duplicati.Server.Database lock (databaseConnection.m_lock) settings[CONST.UPDATE_CHANNEL] = value; SaveSettings(); + + if (string.IsNullOrWhiteSpace(value)) + UpdaterManager.CurrentChannel = AutoUpdateSettings.DefaultUpdateChannel; + else if (Enum.TryParse(value, true, out var rt)) + UpdaterManager.CurrentChannel = rt; } } - public string UsageReporterLevel + public string? UsageReporterLevel { get { @@ -727,7 +795,7 @@ namespace Duplicati.Server.Database { get { - return Duplicati.Library.Utility.Utility.ParseBool(settings[CONST.ENCRYPTED_FIELDS], false); + return Utility.ParseBool(settings[CONST.ENCRYPTED_FIELDS], false); } set { @@ -737,7 +805,7 @@ namespace Duplicati.Server.Database } } - public string PreloadSettingsHash + public string? PreloadSettingsHash { get { @@ -750,6 +818,78 @@ namespace Duplicati.Server.Database SaveSettings(); } } + + public TimeZoneInfo Timezone + { + get + { + var id = settings[CONST.TIMEZONE_OPTION]; + + // All times are stored in UTC in the database, prior to introducing the timezone option + if (string.IsNullOrEmpty(id)) + return TimeZoneInfo.Utc; + + try + { + if (!string.IsNullOrEmpty(id)) + return TimeZoneHelper.GetTimeZoneById(id) + ?? TimeZoneInfo.Local; + } + catch + { + } + return TimeZoneInfo.Local; + } + set + { + lock (databaseConnection.m_lock) + settings[CONST.TIMEZONE_OPTION] = value?.Id; + SaveSettings(); + } + } + + public string? LastConfigIssueCheckVersion + { + get + { + return settings[CONST.LAST_UPDATE_CHECK_VERSION]; + } + set + { + lock (databaseConnection.m_lock) + settings[CONST.LAST_UPDATE_CHECK_VERSION] = value; + SaveSettings(); + } + } + + public string? AdditionalReportUrl + { + get + { + return settings[CONST.ADDITIONAL_REPORT_URL]; + } + set + { + lock (databaseConnection.m_lock) + settings[CONST.ADDITIONAL_REPORT_URL] = value; + SaveSettings(); + } + } + + public string? BackupListSortOrder + { + get + { + return settings[CONST.BACKUP_LIST_SORT_ORDER]; + } + set + { + lock (databaseConnection.m_lock) + settings[CONST.BACKUP_LIST_SORT_ORDER] = value; + SaveSettings(); + } + } + } } diff --git a/Duplicati/Library/RestAPI/Database/Setting.cs b/Duplicati/Library/RestAPI/Database/Setting.cs index 3c5cff529..0bb256b41 100644 --- a/Duplicati/Library/RestAPI/Database/Setting.cs +++ b/Duplicati/Library/RestAPI/Database/Setting.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Database/TempFile.cs b/Duplicati/Library/RestAPI/Database/TempFile.cs index 284403531..2c4984271 100644 --- a/Duplicati/Library/RestAPI/Database/TempFile.cs +++ b/Duplicati/Library/RestAPI/Database/TempFile.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/Duplicati.Library.RestAPI.csproj b/Duplicati/Library/RestAPI/Duplicati.Library.RestAPI.csproj index d6431fcd8..128b34ea1 100644 --- a/Duplicati/Library/RestAPI/Duplicati.Library.RestAPI.csproj +++ b/Duplicati/Library/RestAPI/Duplicati.Library.RestAPI.csproj @@ -1,8 +1,8 @@ - + net8.0 - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/RestAPI/EventPollNotify.cs b/Duplicati/Library/RestAPI/EventPollNotify.cs index 4c32f1943..1999de638 100644 --- a/Duplicati/Library/RestAPI/EventPollNotify.cs +++ b/Duplicati/Library/RestAPI/EventPollNotify.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; namespace Duplicati.Server diff --git a/Duplicati/Library/RestAPI/FIXMEGlobal.cs b/Duplicati/Library/RestAPI/FIXMEGlobal.cs index 47d369901..8b6253cda 100644 --- a/Duplicati/Library/RestAPI/FIXMEGlobal.cs +++ b/Duplicati/Library/RestAPI/FIXMEGlobal.cs @@ -1,4 +1,24 @@ - +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using Duplicati.Server; using System; using System.Collections.Generic; @@ -6,6 +26,7 @@ using Duplicati.Library.RestAPI.Abstractions; using Duplicati.Library.Utility; using Duplicati.WebserverCore.Abstractions; using Microsoft.Extensions.DependencyInjection; +using Duplicati.Library.Interface; namespace Duplicati.Library.RestAPI { @@ -101,5 +122,14 @@ namespace Duplicati.Library.RestAPI /// public static readonly object MainLock = new object(); + /// + /// The shared secret provider from the server invocation + /// + public static ISecretProvider SecretProvider { get; set; } + /// + /// Flag to indicate if the settings encryption key was provided externally + /// + public static bool SettingsEncryptionKeyProvidedExternally { get; set; } + } } diff --git a/Duplicati/Library/RestAPI/LiveControls.cs b/Duplicati/Library/RestAPI/LiveControls.cs index 66db81114..8cffe137a 100644 --- a/Duplicati/Library/RestAPI/LiveControls.cs +++ b/Duplicati/Library/RestAPI/LiveControls.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -32,25 +32,34 @@ namespace Duplicati.Server /// public class LiveControls : ILiveControls { + /// + /// Event that is activated the the live control state changes + /// + public sealed record LiveControlEvent + { + /// + /// The new state of the live control + /// + public required LiveControlState State { get; init; } + /// + /// A value that indicates if the transfers are paused + /// + public required bool TransfersPaused { get; init; } + /// + /// The time when processing will resume, or zero if paused indefinitely + /// + public required DateTime WaitTimeExpiration { get; init; } + } + /// /// The tag used for logging /// private static readonly string LOGTAG = Duplicati.Library.Logging.Log.LogTagFromType(); /// - /// An event that is activated when the pause state changes + /// An callback that is activated when the pause state changes /// - public event EventHandler StateChanged; - - /// - /// An event that is activated when the thread priority changes - /// - public event EventHandler ThreadPriorityChanged; - - /// - /// An event that is activated when the throttle speed changes - /// - public event EventHandler ThrottleSpeedChanged; + public Action StateChanged; /// /// The possible states for the live control @@ -72,6 +81,11 @@ namespace Duplicati.Server /// private LiveControlState m_state; + /// + /// A value that indicates if the transfers are paused + /// + private bool m_transfersPaused = false; + /// /// A value that indicates if the current pause state is caused by being suspended /// @@ -80,86 +94,28 @@ namespace Duplicati.Server /// /// The time to pause for, used to ensure that a user set pause can override the suspend pause /// - private DateTime m_suspendMinimumPause = new DateTime(0); + private DateTime m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc); /// /// Gets the current state for the control /// public LiveControlState State { get { return m_state; } } + /// + /// Gets a value that indicates if the backups are running + /// public bool IsPaused => State == LiveControlState.Paused; /// - /// The internal variable that tracks the the priority + /// Gets a value that indicates if the transfers are paused /// - private System.Threading.ThreadPriority? m_priority; - - /// - /// The internal variable that tracks the upload limit - /// - private long? m_uploadLimit; - - /// - /// The internal variable that tracks the download limit - /// - private long? m_downloadLimit; + public bool TransfersPaused => m_transfersPaused; /// /// The object that ensures concurrent operations /// private readonly object m_lock = new object(); - /// - /// Gets the current overridden thread priority - /// - public System.Threading.ThreadPriority? ThreadPriority - { - get { return m_priority; } - set - { - if (m_priority != value) - { - m_priority = value; - if (ThreadPriorityChanged != null) - ThreadPriorityChanged(this, null); - } - } - } - - /// - /// Gets the current upload limit in bps - /// - public long? UploadLimit - { - get { return m_uploadLimit; } - set - { - if (m_uploadLimit != value) - { - m_uploadLimit = value; - if (ThrottleSpeedChanged != null) - ThrottleSpeedChanged(this, null); - } - } - } - - /// - /// Gets the download limit in bps - /// - public long? DownloadLimit - { - get { return m_downloadLimit; } - set - { - if (m_downloadLimit != value) - { - m_downloadLimit = value; - if (ThrottleSpeedChanged != null) - ThrottleSpeedChanged(this, null); - } - } - } - /// /// The timer that is activated after a pause period. /// @@ -168,7 +124,7 @@ namespace Duplicati.Server /// /// The time that the current pause is expected to expire /// - private DateTime m_waitTimeExpiration = new DateTime(0); + private DateTime m_waitTimeExpiration = new DateTime(0, DateTimeKind.Utc); /// /// The connection to use @@ -185,6 +141,20 @@ namespace Duplicati.Server Init(); } + /// + /// Clamps the milliseconds to a valid range for the timer + /// + /// The duration to clamp + /// The clamped milliseconds + private static long ClampMilliseconds(TimeSpan duration) + { + if (duration.TotalMilliseconds < 100) + return 100; + if (duration > TimeSpan.FromHours(24)) + return (long)TimeSpan.FromHours(24).TotalMilliseconds; + return (long)duration.TotalMilliseconds; + } + /// /// Constructs a new instance of the LiveControl /// @@ -196,38 +166,38 @@ namespace Duplicati.Server if (!string.IsNullOrEmpty(settings.StartupDelayDuration) && settings.StartupDelayDuration != "0") { - long milliseconds = 0; - try { milliseconds = (long)Duplicati.Library.Utility.Timeparser.ParseTimeSpan(settings.StartupDelayDuration).TotalMilliseconds; } + var startupDelay = new TimeSpan(0); + try { startupDelay = Library.Utility.Timeparser.ParseTimeSpan(settings.StartupDelayDuration); } catch { } - if (milliseconds > 0) + if (startupDelay.Ticks > 0) { - m_waitTimeExpiration = DateTime.Now.AddMilliseconds(milliseconds); - m_waitTimer.Change(milliseconds, System.Threading.Timeout.Infinite); + m_waitTimeExpiration = DateTime.UtcNow.Add(startupDelay); + m_waitTimer.Change(ClampMilliseconds(startupDelay), System.Threading.Timeout.Infinite); m_state = LiveControlState.Paused; } } - m_priority = settings.ThreadPriorityOverride; - if (!string.IsNullOrEmpty(settings.DownloadSpeedLimit)) - try + var pausedUntil = settings.PausedUntil; + if (pausedUntil != null) + { + if (pausedUntil.Value.Ticks == 0) { - m_downloadLimit = Library.Utility.Sizeparser.ParseSize(settings.DownloadSpeedLimit, "kb"); + m_waitTimeExpiration = new DateTime(0, DateTimeKind.Utc); + m_waitTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); + m_state = LiveControlState.Paused; } - catch (Exception ex) + else if (pausedUntil.Value > DateTime.UtcNow && pausedUntil.Value > m_waitTimeExpiration) { - Library.Logging.Log.WriteErrorMessage(LOGTAG, "ParseDownloadLimitError", ex, "Failed to parse download limit: {0}", settings.DownloadSpeedLimit); - } - - if (!string.IsNullOrEmpty(settings.UploadSpeedLimit)) - try - { - m_uploadLimit = Library.Utility.Sizeparser.ParseSize(settings.UploadSpeedLimit, "kb"); - } - catch (Exception ex) - { - Library.Logging.Log.WriteErrorMessage(LOGTAG, "ParseUploadLimitError", ex, "Failed to parse upload limit: {0}", settings.UploadSpeedLimit); + var period = pausedUntil.Value - DateTime.UtcNow; + if (period.TotalMilliseconds > 100) + { + m_waitTimeExpiration = pausedUntil.Value; + m_waitTimer.Change(ClampMilliseconds(period), System.Threading.Timeout.Infinite); + m_state = LiveControlState.Paused; + } } + } try { @@ -247,6 +217,21 @@ namespace Duplicati.Server Resume(); } + /// + /// Creates a new event object + /// + /// A new event object + private LiveControlEvent CreateEvent() + { + lock (m_lock) + return new LiveControlEvent() + { + State = m_state, + TransfersPaused = m_transfersPaused, + WaitTimeExpiration = m_waitTimeExpiration + }; + } + /// /// Internal helper to reset the timeout timer /// @@ -256,13 +241,13 @@ namespace Duplicati.Server lock (m_lock) if (!string.IsNullOrEmpty(timeout)) { - long milliseconds = (long)Duplicati.Library.Utility.Timeparser.ParseTimeSpan(timeout).TotalMilliseconds; - m_waitTimeExpiration = DateTime.Now.AddMilliseconds(milliseconds); - m_waitTimer.Change(milliseconds, System.Threading.Timeout.Infinite); + var delay = Library.Utility.Timeparser.ParseTimeSpan(timeout); + m_waitTimeExpiration = DateTime.UtcNow.Add(delay); + m_waitTimer.Change(ClampMilliseconds(delay), System.Threading.Timeout.Infinite); } else { - m_waitTimeExpiration = new DateTime(0); + m_waitTimeExpiration = new DateTime(0, DateTimeKind.Utc); m_waitTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite); } } @@ -272,33 +257,42 @@ namespace Duplicati.Server /// private void SetPauseMode() { + LiveControlEvent ev = null; lock (m_lock) { if (m_state == LiveControlState.Running) { m_state = LiveControlState.Paused; if (StateChanged != null) - StateChanged(this, null); + ev = CreateEvent(); } } + + if (ev != null) + StateChanged(ev); } /// /// Pauses the backups until resumed /// - public void Pause() + public void Pause(bool alsoTransfers) { + LiveControlEvent ev = null; lock (m_lock) { - var fireEvent = m_waitTimeExpiration.Ticks != 0 && m_state == LiveControlState.Paused && StateChanged != null; + m_transfersPaused = alsoTransfers; + m_waitTimeExpiration = new DateTime(0, DateTimeKind.Utc); ResetTimer(null); - if (fireEvent) - StateChanged(this, null); + if (m_state == LiveControlState.Paused) + ev = StateChanged == null ? null : CreateEvent(); else SetPauseMode(); } + + if (ev != null) + StateChanged(ev); } /// @@ -306,6 +300,7 @@ namespace Duplicati.Server /// public void Resume() { + LiveControlEvent ev = null; lock (m_lock) { if (m_state == LiveControlState.Paused) @@ -313,39 +308,52 @@ namespace Duplicati.Server //Make sure that the timer is cleared ResetTimer(null); + m_transfersPaused = false; + m_waitTimeExpiration = new DateTime(0, DateTimeKind.Utc); m_state = LiveControlState.Running; if (StateChanged != null) - StateChanged(this, null); + ev = CreateEvent(); } } + + if (ev != null) + StateChanged(ev); } /// /// Suspends the backups for a given period /// /// The duration to wait - public void Pause(string timeout) + /// If true, also pause the transfers + public void Pause(string timeout, bool alsoTransfers) { - Pause(Duplicati.Library.Utility.Timeparser.ParseTimeSpan(timeout)); + Pause(Duplicati.Library.Utility.Timeparser.ParseTimeSpan(timeout), alsoTransfers); } /// /// Suspends the backups for a given period /// /// The duration to wait - public void Pause(TimeSpan timeout) + /// If true, also pause the transfers + public void Pause(TimeSpan timeout, bool alsoTransfers) { + LiveControlEvent ev = null; lock (m_lock) { - m_waitTimeExpiration = DateTime.Now.AddMilliseconds((long)timeout.TotalMilliseconds); - m_waitTimer.Change((long)timeout.TotalMilliseconds, System.Threading.Timeout.Infinite); + m_waitTimeExpiration = DateTime.UtcNow.Add(timeout); + m_waitTimer.Change(ClampMilliseconds(timeout), System.Threading.Timeout.Infinite); + m_transfersPaused = alsoTransfers; + //We change the time, so we issue a new event - if (m_state == LiveControlState.Paused && StateChanged != null) - StateChanged(this, null); + if (m_state == LiveControlState.Paused) + ev = StateChanged == null ? null : CreateEvent(); else SetPauseMode(); } + + if (ev != null) + StateChanged(ev); } /// @@ -381,7 +389,7 @@ namespace Duplicati.Server { this.SetPauseMode(); m_pausedForSuspend = true; - m_suspendMinimumPause = new DateTime(0); + m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc); } else { @@ -399,7 +407,7 @@ namespace Duplicati.Server //If we have been been paused due to suspending, we un-pause now if (m_pausedForSuspend) { - long delayTicks = (m_suspendMinimumPause - DateTime.Now).Ticks; + long delayTicks = (m_suspendMinimumPause - DateTime.UtcNow).Ticks; var appset = m_connection.ApplicationSettings; if (!string.IsNullOrEmpty(appset.StartupDelayDuration) && appset.StartupDelayDuration != "0") @@ -408,7 +416,7 @@ namespace Duplicati.Server if (delayTicks > 0) { - this.Pause(TimeSpan.FromTicks(delayTicks)); + this.Pause(TimeSpan.FromTicks(delayTicks), true); } else { @@ -417,7 +425,7 @@ namespace Duplicati.Server } m_pausedForSuspend = false; - m_suspendMinimumPause = new DateTime(0); + m_suspendMinimumPause = new DateTime(0, DateTimeKind.Utc); } } diff --git a/Duplicati/Library/RestAPI/LogWriteHandler.cs b/Duplicati/Library/RestAPI/LogWriteHandler.cs index 0d40aff96..72aa92f60 100644 --- a/Duplicati/Library/RestAPI/LogWriteHandler.cs +++ b/Duplicati/Library/RestAPI/LogWriteHandler.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/RestAPI/NotificationUpdateService.cs b/Duplicati/Library/RestAPI/NotificationUpdateService.cs index 2bbec0ce3..ee78f322a 100644 --- a/Duplicati/Library/RestAPI/NotificationUpdateService.cs +++ b/Duplicati/Library/RestAPI/NotificationUpdateService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.RestAPI; public interface INotificationUpdateService diff --git a/Duplicati/Library/RestAPI/Runner.cs b/Duplicati/Library/RestAPI/Runner.cs index 7b8353673..6a8c63ffc 100644 --- a/Duplicati/Library/RestAPI/Runner.cs +++ b/Duplicati/Library/RestAPI/Runner.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -35,11 +35,11 @@ namespace Duplicati.Server Duplicati.Server.Serialization.Interface.IBackup Backup { get; } IDictionary ExtraOptions { get; } string[] FilterStrings { get; } - void Stop(bool allowCurrentFileToFinish); + void Stop(); void Abort(); - void Pause(); + void Pause(bool alsoTransfers); void Resume(); - void UpdateThrottleSpeed(); + void UpdateThrottleSpeed(string uploadSpeed, string downloadSpeed); void SetController(Duplicati.Library.Main.Controller controller); } @@ -62,38 +62,30 @@ namespace Duplicati.Server Controller = controller; } - public void Stop(bool allowCurrentFileToFinish) + public void Stop() { - var c = Controller; - if (c != null) - c.Stop(allowCurrentFileToFinish); + Controller?.Stop(); } public void Abort() { - var c = Controller; - if (c != null) - c.Abort(); + Controller?.Abort(); } - public void Pause() + public void Pause(bool alsoTransfers) { - var c = Controller; - if (c != null) - c.Pause(); + Controller?.Pause(alsoTransfers); } public void Resume() { - var c = Controller; - if (c != null) - c.Resume(); + Controller?.Resume(); } public long OriginalUploadSpeed { get; set; } public long OriginalDownloadSpeed { get; set; } - public void UpdateThrottleSpeed() + public void UpdateThrottleSpeed(string uploadSpeed, string downloadSpeed) { var controller = this.Controller; if (controller == null) @@ -107,15 +99,15 @@ namespace Duplicati.Server try { - if (!string.IsNullOrWhiteSpace(FIXMEGlobal.DataConnection.ApplicationSettings.UploadSpeedLimit)) - server_upload_throttle = Duplicati.Library.Utility.Sizeparser.ParseSize(FIXMEGlobal.DataConnection.ApplicationSettings.UploadSpeedLimit, "kb"); + if (!string.IsNullOrWhiteSpace(uploadSpeed)) + server_upload_throttle = Duplicati.Library.Utility.Sizeparser.ParseSize(uploadSpeed, "kb"); } catch { } try { - if (!string.IsNullOrWhiteSpace(FIXMEGlobal.DataConnection.ApplicationSettings.DownloadSpeedLimit)) - server_download_throttle = Duplicati.Library.Utility.Sizeparser.ParseSize(FIXMEGlobal.DataConnection.ApplicationSettings.DownloadSpeedLimit, "kb"); + if (!string.IsNullOrWhiteSpace(downloadSpeed)) + server_download_throttle = Duplicati.Library.Utility.Sizeparser.ParseSize(downloadSpeed, "kb"); } catch { } @@ -494,7 +486,10 @@ namespace Duplicati.Server catch { } ((RunnerData)data).Controller = controller; - data.UpdateThrottleSpeed(); + data.UpdateThrottleSpeed(FIXMEGlobal.DataConnection.ApplicationSettings.UploadSpeedLimit, FIXMEGlobal.DataConnection.ApplicationSettings.DownloadSpeedLimit); + + // Pass on the provider, will be replaced if configured in the backup + controller.SetSecretProvider(FIXMEGlobal.SecretProvider); if (backup.Metadata.ContainsKey("LastCompactFinished")) controller.LastCompact = Library.Utility.Utility.DeserializeDateTime(backup.Metadata["LastCompactFinished"]); @@ -741,6 +736,7 @@ namespace Duplicati.Server backup.Metadata["TargetFilesSize"] = r.KnownFileSize.ToString(); backup.Metadata["TargetFilesCount"] = r.KnownFileCount.ToString(); + backup.Metadata["TargetFilesetsCount"] = r.KnownFilesets.ToString(); backup.Metadata["TargetSizeString"] = Duplicati.Library.Utility.Utility.FormatSizeString(r.KnownFileSize); } } @@ -924,6 +920,18 @@ namespace Duplicati.Server // The server hangs if the module is enabled as there is no console attached DisableModule("console-password-input", options); + // Patch in additional report urls + var additionalReportUrl = FIXMEGlobal.DataConnection.ApplicationSettings.AdditionalReportUrl; + if (!string.IsNullOrWhiteSpace(additionalReportUrl)) + { + options["send-http-json-urls"] = string.Join(";", + new[] { + options.GetValueOrDefault("send-http-json-urls"), + additionalReportUrl + }.Where(x => !string.IsNullOrWhiteSpace(x)) + ); + } + return options; } diff --git a/Duplicati/Library/RestAPI/Scheduler.cs b/Duplicati/Library/RestAPI/Scheduler.cs index 0363f9248..8940691fd 100644 --- a/Duplicati/Library/RestAPI/Scheduler.cs +++ b/Duplicati/Library/RestAPI/Scheduler.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -94,8 +94,8 @@ namespace Duplicati.Server /// The worker thread public void Init(WorkerThread worker) { - m_thread = new Thread(new ThreadStart(Runner)); m_worker = worker; + m_thread = new Thread(new ThreadStart(Runner)); m_worker.CompletedWork += OnCompleted; m_worker.StartingWork += OnStartingWork; m_schedule = new KeyValuePair[0]; @@ -143,7 +143,7 @@ namespace Duplicati.Server get { lock (m_lock) - return m_schedule.ToList(); + return (m_schedule ?? []).ToList(); } } @@ -152,7 +152,7 @@ namespace Duplicati.Server /// public List WorkerQueue { - get { return (from t in m_worker.CurrentTasks where t != null select t).ToList(); } + get { return m_worker?.CurrentTasks?.Where(t => t != null)?.ToList() ?? []; } } /// @@ -185,25 +185,26 @@ namespace Duplicati.Server /// The days the backup is allowed to run /// The next valid date, or throws an exception if no such date can be found public static DateTime GetNextValidTime(DateTime basetime, DateTime firstdate, string repetition, - DayOfWeek[] allowedDays) + DayOfWeek[] allowedDays, TimeZoneInfo timeZoneInfo) { var res = basetime; + var ts = Timeparser.ParseTimeSpan(repetition); + + // If we move in days or more, we keep the time of day across DST changes + var keepTimeOfDay = ts.Hours == 0 && ts.Minutes == 0 && ts.Seconds == 0; var i = 50000; while (res < firstdate && i-- > 0) - res = Timeparser.ParseTimeInterval(repetition, res); + res = Timeparser.DSTAwareParseTimeInterval(repetition, res, timeZoneInfo, keepTimeOfDay); // If we arrived somewhere after the first allowed date if (res >= firstdate) { - var ts = Timeparser.ParseTimeSpan(repetition); - if (ts.TotalDays >= 1) { // We jump in days, so we pick the first valid day after firstdate - for (var n = 0; n < 8; n++) - if (IsDateAllowed(res, allowedDays)) + if (IsDateAllowed(res, allowedDays, timeZoneInfo)) break; else res = res.AddDays(1); @@ -212,14 +213,13 @@ namespace Duplicati.Server { // We jump less than a day, so we keep adding the repetition until // we hit a valid day - i = 50000; - while (!IsDateAllowed(res, allowedDays) && i-- > 0) - res = Timeparser.ParseTimeInterval(repetition, res); + while (!IsDateAllowed(res, allowedDays, timeZoneInfo) && i-- > 0) + res = Timeparser.DSTAwareParseTimeInterval(repetition, res, timeZoneInfo, keepTimeOfDay); } } - if (!IsDateAllowed(res, allowedDays) || res < firstdate) + if (!IsDateAllowed(res, allowedDays, timeZoneInfo) || res < firstdate) { StringBuilder sb = new StringBuilder(); if (allowedDays != null) @@ -283,6 +283,7 @@ namespace Duplicati.Server // to avoid frequent db lookups //Determine schedule list + var timeZoneInfo = FIXMEGlobal.DataConnection.ApplicationSettings.Timezone; var lst = FIXMEGlobal.DataConnection.Schedules; foreach (var sc in lst) { @@ -313,7 +314,7 @@ namespace Duplicati.Server last = DateTime.UtcNow; } - start = GetNextValidTime(start, last, sc.Repeat, sc.AllowedDays); + start = GetNextValidTime(start, last, sc.Repeat, sc.AllowedDays, timeZoneInfo); } catch (Exception ex) { @@ -356,8 +357,23 @@ namespace Duplicati.Server } else { + Dictionary taskOptions = null; + try + { + var nextRun = GetNextValidTime(start, + new DateTime( + Math.Max(DateTime.UtcNow.AddSeconds(1).Ticks, start.AddSeconds(1).Ticks), + DateTimeKind.Utc), sc.Repeat, sc.AllowedDays, timeZoneInfo); + + taskOptions = new Dictionary() + { { "next-scheduled-run", Utility.SerializeDateTime(nextRun.ToUniversalTime()) } }; + } + catch + { + } + jobsToRun.Add(Server.Runner.CreateTask( - Duplicati.Server.Serialization.DuplicatiOperation.Backup, entry)); + Serialization.DuplicatiOperation.Backup, entry, taskOptions)); } } } @@ -369,7 +385,7 @@ namespace Duplicati.Server start = GetNextValidTime(start, new DateTime( Math.Max(DateTime.UtcNow.AddSeconds(1).Ticks, start.AddSeconds(1).Ticks), - DateTimeKind.Utc), sc.Repeat, sc.AllowedDays); + DateTimeKind.Utc), sc.Repeat, sc.AllowedDays, timeZoneInfo); } catch (Exception ex) { @@ -451,14 +467,15 @@ namespace Duplicati.Server /// /// The time to evaluate /// The allowed days + /// The time zone info /// True if the backup is allowed to run, false otherwise - private static bool IsDateAllowed(DateTime time, DayOfWeek[] allowedDays) + private static bool IsDateAllowed(DateTime time, DayOfWeek[] allowedDays, TimeZoneInfo timeZoneInfo) { - var localTime = time.ToLocalTime(); if (allowedDays == null || allowedDays.Length == 0) return true; - else - return Array.IndexOf(allowedDays, localTime.DayOfWeek) >= 0; + + var localTime = TimeZoneInfo.ConvertTimeFromUtc(time, timeZoneInfo); + return Array.IndexOf(allowedDays, localTime.DayOfWeek) >= 0; } } } \ No newline at end of file diff --git a/Duplicati/Library/RestAPI/Serializable/ImportExportStructure.cs b/Duplicati/Library/RestAPI/Serializable/ImportExportStructure.cs index c33d7225c..a406ee9a0 100644 --- a/Duplicati/Library/RestAPI/Serializable/ImportExportStructure.cs +++ b/Duplicati/Library/RestAPI/Serializable/ImportExportStructure.cs @@ -1,33 +1,35 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using System.Collections.Generic; - -namespace Duplicati.Server.Serializable -{ - public class ImportExportStructure - { - public string CreatedByVersion { get; set; } - public Duplicati.Server.Database.Schedule Schedule { get; set; } - public Duplicati.Server.Database.Backup Backup { get; set; } - public Dictionary DisplayNames { get; set; } - } -} - +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; + +#nullable enable + +namespace Duplicati.Server.Serializable +{ + public class ImportExportStructure + { + public required string CreatedByVersion { get; init; } + public required Duplicati.Server.Database.Schedule? Schedule { get; init; } + public required Duplicati.Server.Database.Backup Backup { get; init; } + public required Dictionary DisplayNames { get; init; } + } +} + diff --git a/Duplicati/Library/RestAPI/Serializable/ServerSettings.cs b/Duplicati/Library/RestAPI/Serializable/ServerSettings.cs index 7d59af99d..180e7f0fd 100644 --- a/Duplicati/Library/RestAPI/Serializable/ServerSettings.cs +++ b/Duplicati/Library/RestAPI/Serializable/ServerSettings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -33,7 +33,7 @@ namespace Duplicati.Server.Serializable /// /// Shared implementation for reporting dynamic modules /// - private class DynamicModule : IDynamicModule + private class DynamicModule : IDynamicModule { /// /// Constructor for backend interface @@ -43,10 +43,9 @@ namespace Duplicati.Server.Serializable this.Key = backend.ProtocolKey; this.Description = backend.Description; this.DisplayName = backend.DisplayName; - if (backend.SupportedCommands != null) - this.Options = backend.SupportedCommands.ToArray(); + this.Options = backend.SupportedCommands?.ToArray() ?? []; } - + /// /// Constructor for compression module interface /// @@ -55,8 +54,7 @@ namespace Duplicati.Server.Serializable this.Key = module.FilenameExtension; this.Description = module.Description; this.DisplayName = module.DisplayName; - if (module.SupportedCommands != null) - this.Options = module.SupportedCommands.ToArray(); + this.Options = module.SupportedCommands?.ToArray() ?? []; } /// @@ -67,8 +65,7 @@ namespace Duplicati.Server.Serializable this.Key = module.FilenameExtension; this.Description = module.Description; this.DisplayName = module.DisplayName; - if (module.SupportedCommands != null) - this.Options = module.SupportedCommands.ToArray(); + this.Options = module.SupportedCommands?.ToArray() ?? []; } /// @@ -79,8 +76,7 @@ namespace Duplicati.Server.Serializable this.Key = module.Key; this.Description = module.Description; this.DisplayName = module.DisplayName; - if (module.SupportedCommands != null) - this.Options = module.SupportedCommands.ToArray(); + this.Options = module.SupportedCommands?.ToArray() ?? []; } /// @@ -91,8 +87,18 @@ namespace Duplicati.Server.Serializable this.Key = module.Key; this.Description = module.Description; this.DisplayName = module.DisplayName; - if (module.SupportedCommands != null) - this.Options = module.SupportedCommands.ToArray(); + this.Options = module.SupportedCommands?.ToArray() ?? []; + } + + /// + /// Constructor for sercretprovider interface + /// + public DynamicModule(Duplicati.Library.Interface.ISecretProvider module) + { + this.Key = module.Key; + this.Description = module.Description; + this.DisplayName = module.DisplayName; + this.Options = module.SupportedCommands?.ToArray() ?? []; } /// /// The module key @@ -112,99 +118,99 @@ namespace Duplicati.Server.Serializable /// public Duplicati.Library.Interface.ICommandLineArgument[] Options { get; private set; } } - + /// /// Gets all supported options /// public static Duplicati.Library.Interface.ICommandLineArgument[] Options - { + { get { return new Duplicati.Library.Main.Options(new System.Collections.Generic.Dictionary()).SupportedCommands.ToArray(); } } - + /// /// The backend modules known by the server /// public static IDynamicModule[] BackendModules - { + { get - { - return + { + return (from n in Library.DynamicLoader.BackendLoader.Backends select new DynamicModule(n)) - .ToArray(); + .ToArray(); } - } + } /// /// The encryption modules known by the server /// public static IDynamicModule[] EncryptionModules { get - { - return + { + return (from n in Library.DynamicLoader.EncryptionLoader.Modules select new DynamicModule(n)) - .ToArray(); + .ToArray(); } } - + /// /// The compression modules known by the server /// public static IDynamicModule[] CompressionModules - { + { get - { - return + { + return (from n in Library.DynamicLoader.CompressionLoader.Modules select new DynamicModule(n)) - .ToArray(); + .ToArray(); } - } + } /// /// The generic modules known by the server /// public static IDynamicModule[] GenericModules - { + { get - { - return + { + return (from n in Library.DynamicLoader.GenericLoader.Modules select new DynamicModule(n)) - .ToArray(); + .ToArray(); } - } - + } + /// /// The web modules known by the server /// public static IDynamicModule[] WebModules - { + { get - { - return + { + return (from n in Library.DynamicLoader.WebLoader.Modules select new DynamicModule(n)) - .ToArray(); + .ToArray(); } - } + } /// /// The web modules known by the server /// public static IDynamicModule[] ConnectionModules - { + { get - { - return + { + return (from n in Library.DynamicLoader.GenericLoader.Modules where n is Library.Interface.IConnectionModule select new DynamicModule(n)) - .ToArray(); + .ToArray(); } } @@ -223,6 +229,20 @@ namespace Duplicati.Server.Serializable } } + /// + /// The web modules known by the server + /// + public static IDynamicModule[] SecretProviderModules + { + get + { + return + (from n in Library.DynamicLoader.SecretProviderLoader.Modules + select new DynamicModule(n)) + .ToArray(); + } + } + /// /// The filters that are applied to all backups /// @@ -230,12 +250,12 @@ namespace Duplicati.Server.Serializable { get { return FIXMEGlobal.DataConnection.Filters; } } - + /// /// The settings applied to all backups by default /// public static ISetting[] Settings - { + { get { return FIXMEGlobal.DataConnection.Settings; } } } diff --git a/Duplicati/Library/RestAPI/Serializable/TreeNode.cs b/Duplicati/Library/RestAPI/Serializable/TreeNode.cs index cf8d3c8e8..41439fe1e 100644 --- a/Duplicati/Library/RestAPI/Serializable/TreeNode.cs +++ b/Duplicati/Library/RestAPI/Serializable/TreeNode.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Duplicati/Library/RestAPI/SpecialFolders.cs b/Duplicati/Library/RestAPI/SpecialFolders.cs index 4a1950684..180810e77 100644 --- a/Duplicati/Library/RestAPI/SpecialFolders.cs +++ b/Duplicati/Library/RestAPI/SpecialFolders.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,7 +23,9 @@ using System; using System.Linq; using System.Collections.Generic; using Duplicati.Library.Common.IO; -using Duplicati.Library.Common; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.IO; namespace Duplicati.Server { @@ -128,6 +130,15 @@ namespace Duplicati.Server TryAdd(lst, Environment.SpecialFolder.ApplicationData, "%APPDATA%", "Application Data"); TryAdd(lst, Environment.SpecialFolder.UserProfile, "%HOME%", "Home"); + try + { + // PInvoke to get the download folder + TryAdd(lst, Library.Utility.SHGetFolder.DownloadFolder, "%MY_DOWNLOADS%", "Downloads"); + } + catch + { + } + try { // In case the UserProfile member points to junk @@ -140,12 +151,19 @@ namespace Duplicati.Server } else { - TryAdd(lst, Environment.SpecialFolder.MyDocuments, "%MY_DOCUMENTS%", "My Documents"); - TryAdd(lst, Environment.SpecialFolder.MyMusic, "%MY_MUSIC%", "My Music"); - TryAdd(lst, Environment.SpecialFolder.MyPictures, "%MY_PICTURES%", "My Pictures"); + var homedir = Environment.GetEnvironmentVariable("HOME"); + if (string.IsNullOrWhiteSpace(homedir)) + homedir = System.Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + TryAdd(lst, Environment.SpecialFolder.MyDocuments, "%MY_DOCUMENTS%", "Documents"); + TryAdd(lst, Environment.SpecialFolder.MyMusic, "%MY_MUSIC%", "Music"); + TryAdd(lst, Environment.SpecialFolder.MyPictures, "%MY_PICTURES%", "Pictures"); TryAdd(lst, Environment.SpecialFolder.DesktopDirectory, "%DESKTOP%", "Desktop"); TryAdd(lst, Environment.GetEnvironmentVariable("HOME"), "%HOME%", "Home"); TryAdd(lst, Environment.SpecialFolder.UserProfile, "%HOME%", "Home"); + TryAdd(lst, Path.Combine(homedir, "Movies"), "%MY_MOVIES%", "Movies"); + TryAdd(lst, Path.Combine(homedir, "Downloads"), "%MY_DOWNLOADS%", "Downloads"); + TryAdd(lst, Path.Combine(homedir, "Public"), "%MY_PUBLIC%", "Public"); } Nodes = lst.ToArray(); diff --git a/Duplicati/Library/RestAPI/Strings.cs b/Duplicati/Library/RestAPI/Strings.cs index fb72417a7..f4e2a2586 100644 --- a/Duplicati/Library/RestAPI/Strings.cs +++ b/Duplicati/Library/RestAPI/Strings.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Collections.Generic; using Duplicati.Library.Localization.Short; using System.Linq; @@ -14,13 +34,14 @@ Error message: {0}", message); } "); } } public static string HelpDisplayFormat(string optionname, string optiontext) { return LC.L(@"--{0}: {1}", optionname, optiontext); } - public static string ParametersFileOptionLong2 { get { return LC.L(@"Use this option to store some or all of the options given to the commandline client. The file must be a plain text file, and UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. Use the special options --{0} and --{1} to override the localpath and the remote destination uri, respectively. The options in this file take precedence over the options provided on the commandline. You cannot specify filters in both the file and on the commandline. Instead, you can use the special --{2}, --{3}, or --{4} options to specify filters inside the parameter file. Each filter must be prefixed with either a + or a -, and multiple filters must be joined with {5} ", "source", "target", "replace-filter", "append-filter", "prepend-filter", System.IO.Path.PathSeparator); } } + public static string ParametersFileOptionLong { get { return LC.L(@"Use this option to store some or all of the options given to the commandline client. The file must be a plain text file, and UTF-8 encoding is preferred. Each line in the file should be of the format --option=value. Use the special options --{0} and --{1} to override the localpath and the remote destination uri, respectively. The options in this file take precedence over the options provided on the commandline. You cannot specify filters in both the file and on the commandline. Instead, you can use the special --{2}, --{3}, or --{4} options to specify filters inside the parameter file. Each filter must be prefixed with either a + or a -, and multiple filters must be joined with {5} ", "source", "target", "replace-filter", "append-filter", "prepend-filter", System.IO.Path.PathSeparator); } } public static string ParametersFileOptionShort { get { return LC.L(@"Path to a file with parameters"); } } public static string FiltersCannotBeUsedWithFileError2 { get { return LC.L(@"Filters cannot be specified on the commandline if filters are also present in the parameter file. Use the special --{0}, --{1}, or --{2} options to specify filters inside the parameter file. Each filter must be prefixed with either a + or a -, and multiple filters must be joined with {3}", "replace-filter", "append-filter", "prepend-filter", System.IO.Path.PathSeparator); } } public static string FailedToParseParametersFileError(string path, string message) { return LC.L(@"Unable to read the parameters file ""{0}"", reason: {1}", path, message); } public static string SkippingSourceArgumentsOnNonBackupOperation { get { return @"The --source argument was specified in the parameter file, but the current operation is not a backup operation, so the argument is ignored"; } } public static string LogfileCommandDescription { get { return LC.L(@"Output log information to the file given"); } } public static string LoglevelCommandDescription { get { return LC.L(@"Determine the amount of information written in the log file"); } } + public static string LogConsoleDescription { get { return LC.L(@"Output log information to the console"); } } public static string PortablemodeCommandDescription { get { return LC.L(@"Activate portable mode where the database is placed below the program executable"); } } public static string SeriousError(string message) { return LC.L(@"A serious error occurred in Duplicati: {0}", message); } public static string TearDownError(string message) { return LC.L(@"An error occurred on server tear down: {0}", message); } @@ -39,6 +60,8 @@ Error message: {0}", error); } public static string WebserverAllowedhostnamesDescription { get { return LC.L(@"The hostnames that are accepted, separated with semicolons. If any of the hostnames are ""*"", all hostnames are allowed and the hostname checking is disabled."); } } public static string PingpongkeepaliveLong { get { return LC.L(@"When running as a server, the service daemon must verify that the process is responding. If this option is enabled, the server reads stdin and writes a reply to each line read."); } } public static string PingpongkeepaliveShort { get { return LC.L(@"Enable the ping-pong responder"); } } + public static string DisableupdatecheckShort { get { return LC.L(@"Disable the automatic update check"); } } + public static string DisableupdatecheckLong { get { return LC.L(@"Use this option to disable the automatic update check. Manual update checks can still be performed."); } } public static string LogretentionLong { get { return LC.L(@"Set the time after which log data will be purged from the database."); } } public static string LogretentionShort { get { return LC.L(@"Clean up old log data"); } } public static string ServerdatafolderLong(string envname) { return LC.L(@"Duplicati needs to store a small database with all settings. Use this option to choose where the settings are stored. This option can also be set with the environment variable {0}.", envname); } @@ -48,27 +71,37 @@ Error message: {0}", error); } public static string TempdirLong { get { return LC.L(@"Use this option to supply an alternative folder for temporary storage. By default the system default temporary folder is used. Note that also SQLite will put temporary files in this temporary folder."); } } public static string TempdirShort { get { return LC.L(@"Temporary storage folder"); } } public static string WebserverResetJwtConfigDescription { get { return LC.L(@"Reset the JWT configuration, invalidating any issued login tokens"); } } + public static string WebserverEnableForeverTokenDescription { get { return LC.L(@"Enable the use of long-lived access tokens"); } } public static string WebserverDisableVisualCaptchaDescription { get { return LC.L(@"Disable the visual captcha"); } } public static string WebserverApiOnlyDescription { get { return LC.L(@"Disable the web interface and only allow API access"); } } public static string WebserverDisableSigninTokensDescription { get { return LC.L(@"Disable the use of signin tokens"); } } public static string WebserverSpaPathsDescription { get { return LC.L(@"The relative paths that should be served as single page applications, separated with semicolons."); } } + public static string WebserverCorsOriginsDescription { get { return LC.L(@"A list of CORS origins to allow, separated with semicolons. Each origin must be a valid URL."); } } + public static string WebserverTimezoneDescription { get { return LC.L(@"The timezone to use for the webserver. The timezone must be a valid timezone identifier, such as ""America/New_York"" or ""UTC"". Common three-letter abbreviations like ""CET"" are supported, but ambiguous in some cases."); } } public static string DisabledbencryptionLong { get { return LC.L(@"Use this option to disable database encryption of sensitive fields"); } } public static string DisabledbencryptionShort { get { return LC.L(@"Disable database encryption"); } } - public static string LogwindowseventlogLong { get { return LC.L(@"Use this option to log to the Windows event log."); } } + public static string LogwindowseventlogLong { get { return LC.L(@"Use this option to log to the Windows event log. The provided name is in the format Log:Source. If no log name is provided, Duplicati is used."); } } public static string LogwindowseventlogShort { get { return LC.L(@"Log to the Windows event log"); } } public static string LogwindowseventloglevelLong { get { return LC.L(@"Use this option to set the log level for the Windows event log."); } } public static string LogwindowseventloglevelShort { get { return LC.L(@"Set the log level for the Windows event log"); } } - public static string WindowsEventLogSourceNotFound(string source) { return LC.L(@"The Windows event log source {0} was not found. The source must be registered before the log can be written.", source); } + public static string WindowsEventLogSourceNotFound(string source) { return LC.L(@"The Windows event log source {0} was not found. Attempting to create.", source); } + public static string WindowsEventLogSourceNotCreated(string source) { return LC.L(@"The Windows Event Log was not created for: {0}, not logging to eventlog.", source); } public static string WindowsEventLogNotSupported { get { return LC.L(@"The Windows event log is not supported on this platform"); } } - public static string ServerStarted(int port) { return LC.L(@"Server has started and is listening on port {0}", port); } + public static string ServerStarted(string @interface, int port) { return LC.L(@"Server has started and is listening on {0}, port {1}", @interface, port); } public static string ServerStartedSignin(string url) { return LC.L(@"Use the following link to sign in: {0}", url); } public static string ServerCrashed(string message) { return LC.L(@"The server crashed: {0}", message); } + public static string ServerStopping { get { return LC.L(@"Server is stopping, tearing down handlers"); } } + public static string ServerStopped { get { return LC.L(@"Server has stopped"); } } public static string RequiredbencryptionLong { get { return LC.L(@"Use this option to require a custom provided key for database encryption of sensitive fields and not rely on the serial number."); } } public static string RequiredbencryptionShort { get { return LC.L(@"Require database encryption"); } } public static string DatabaseEncryptionKeyRequired(string envkey, string disableoptionname) { return LC.L(@"Database encryption key is required. Supply an encryption key via the environment variable {0} or disable database encryption with the option --{1}", envkey, disableoptionname); } public static string BlacklistedEncryptionKey(string envkey, string disableoptionname) { return LC.L(@"The database encryption key is blacklisted and cannot be used. The database has been decrypted. Supply a new encryption key via the environment variable {0} or disable database encryption with the option --{1}", envkey, disableoptionname); } public static string NoEncryptionKeySpecified(string envkey, string disableoptionname) { return LC.L(@"No database encryption key was found. The database will be stored unencrypted. Supply an encryption key via the environment variable {0} or disable database encryption with the option --{1}", envkey, disableoptionname); } public static string EncryptionKeyMissing(string envkey) { return LC.L(@"The database appears to be encrypted, but no key was specified. Opening the database will likely fail. Use the environment variable {0} to specify the key.", envkey); } + public static string InvalidTimezone(string timezone) { return LC.L(@"The timezone {0} is not valid", timezone); } + public static string SettingsencryptionkeyShort { get { return LC.L(@"Set the encryption key for the settings database"); } } + public static string SettingsencryptionkeyLong(string envname) { return LC.L(@"Use this option to set the encryption key for the settings database. This option can also be set with the environment variable {0}.", envname); } + public static string InvalidPauseResumeState(LiveControls.LiveControlState state) { return LC.L(@"Invalid pause/resume state: {0}", state); } } internal static class Scheduler { diff --git a/Duplicati/Library/RestAPI/UpdatePollThread.cs b/Duplicati/Library/RestAPI/UpdatePollThread.cs index 6f0ce0ac7..1d1c41716 100644 --- a/Duplicati/Library/RestAPI/UpdatePollThread.cs +++ b/Duplicati/Library/RestAPI/UpdatePollThread.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,6 +22,7 @@ using System; using System.Linq; using System.Threading; +using Duplicati.Library.AutoUpdater; using Duplicati.Server.Database; using Duplicati.Server.Serialization; @@ -38,6 +39,7 @@ namespace Duplicati.Server private readonly object m_lock = new object(); private AutoResetEvent m_waitSignal; private double m_downloadProgress; + private bool m_disableChecks = false; public bool IsUpdateRequested { get; private set; } = false; @@ -55,8 +57,9 @@ namespace Duplicati.Server } } - public void Init() + public void Init(bool disableChecks) { + m_disableChecks = disableChecks; m_waitSignal = new AutoResetEvent(false); ThreadState = UpdatePollerStates.Waiting; m_thread = new Thread(Run) @@ -114,26 +117,29 @@ namespace Duplicati.Server if (nextCheck < DateTime.UtcNow || m_forceCheck) { + DateTime started = DateTime.UtcNow; + connection.ApplicationSettings.LastUpdateCheck = started; + nextCheck = connection.ApplicationSettings.NextUpdateCheck; + + // If this is not forced, and we have disabled checks, we just update the next check time + if (m_disableChecks && !m_forceCheck) + continue; + lock (m_lock) m_forceCheck = false; ThreadState = UpdatePollerStates.Checking; eventPollNotify.SignalNewEvent(); - DateTime started = DateTime.UtcNow; - connection.ApplicationSettings.LastUpdateCheck = started; - nextCheck = connection.ApplicationSettings.NextUpdateCheck; - - Library.AutoUpdater.ReleaseType rt; - if (!Enum.TryParse(connection.ApplicationSettings.UpdateChannel, true, out rt)) - rt = Duplicati.Library.AutoUpdater.ReleaseType.Unknown; + if (!Enum.TryParse(connection.ApplicationSettings.UpdateChannel, true, out var rt)) + rt = ReleaseType.Unknown; // Choose the default channel in case we have unknown - rt = rt == Duplicati.Library.AutoUpdater.ReleaseType.Unknown ? Duplicati.Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel : rt; + rt = rt == ReleaseType.Unknown ? AutoUpdateSettings.DefaultUpdateChannel : rt; try { - var update = Duplicati.Library.AutoUpdater.UpdaterManager.CheckForUpdate(rt); + var update = UpdaterManager.CheckForUpdate(rt); if (update != null) connection.ApplicationSettings.UpdatedVersion = update; } @@ -161,8 +167,17 @@ namespace Duplicati.Server connection.ApplicationSettings.UpdatedVersion = null; } + // If the update is the same or older than the current version, we discard it + // NOTE: This check is also inside the UpdaterManager.CheckForUpdate method, + // but we may have a stale version recorded, so we force-clear it here + if (connection.ApplicationSettings.UpdatedVersion != null) + { + if (UpdaterManager.TryParseVersion(connection.ApplicationSettings.UpdatedVersion.Version) <= UpdaterManager.TryParseVersion(UpdaterManager.SelfVersion.Version)) + connection.ApplicationSettings.UpdatedVersion = null; + } + var updatedinfo = connection.ApplicationSettings.UpdatedVersion; - if (updatedinfo != null && Duplicati.Library.AutoUpdater.UpdaterManager.TryParseVersion(updatedinfo.Version) > System.Reflection.Assembly.GetExecutingAssembly().GetName().Version) + if (updatedinfo != null && UpdaterManager.TryParseVersion(updatedinfo.Version) > UpdaterManager.TryParseVersion(UpdaterManager.SelfVersion.Version)) { var package = updatedinfo.FindPackage(); diff --git a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs index a34748e13..652dde19d 100644 --- a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs +++ b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/DbUpgradesRegistry.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs index d23d46864..806b738d8 100644 --- a/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs +++ b/Duplicati/Library/SQLiteHelper/DBSchemaUpgrades/IDbSchemaUpgrade.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs index 018aaf322..69c5aa9b5 100644 --- a/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs +++ b/Duplicati/Library/SQLiteHelper/DatabaseUpgrader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj b/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj index 821bda6b5..88fdf5cae 100644 --- a/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj +++ b/Duplicati/Library/SQLiteHelper/Duplicati.Library.SQLiteHelper.csproj @@ -6,7 +6,7 @@ SQLiteHelper SQLiteHelper for Duplicati Duplicati.Library.SQLiteHelper - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/SQLiteHelper/SQLiteLoader.cs b/Duplicati/Library/SQLiteHelper/SQLiteLoader.cs index 07a10aca6..7035816a3 100644 --- a/Duplicati/Library/SQLiteHelper/SQLiteLoader.cs +++ b/Duplicati/Library/SQLiteHelper/SQLiteLoader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,9 +22,6 @@ #nullable enable using System; -using System.IO; -using System.Runtime.Versioning; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; @@ -102,6 +99,34 @@ namespace Duplicati.Library.SQLiteHelper return con ?? throw new InvalidOperationException("Failed to load connection"); } + /// + /// Applies user-supplied custom pragmas to the SQLite connection + /// + /// The connection to apply the pragmas to. + /// The connection with the pragmas applied. + public static System.Data.IDbConnection ApplyCustomPragmas(System.Data.IDbConnection con) + { + var opts = Environment.GetEnvironmentVariable("CUSTOMSQLITEOPTIONS_DUPLICATI"); + if (string.IsNullOrWhiteSpace(opts)) + return con; + + using (var cmd = con.CreateCommand()) + foreach (var opt in opts.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + Logging.Log.WriteVerboseMessage(LOGTAG, "CustomSQLiteOption", @"Setting custom SQLite option '{0}'.", opt); + try + { + cmd.CommandText = string.Format("PRAGMA {0}", opt); + cmd.ExecuteNonQuery(); + } + catch (Exception ex) + { + Logging.Log.WriteWarningMessage(LOGTAG, "CustomSQLiteOption", ex, @"Error setting custom SQLite option '{0}'.", opt); + } + } + return con; + } + /// /// Loads an SQLite connection instance and opening the database /// @@ -112,7 +137,7 @@ namespace Duplicati.Library.SQLiteHelper if (string.IsNullOrWhiteSpace(targetpath)) throw new ArgumentNullException(nameof(targetpath)); - System.Data.IDbConnection con = LoadConnection(); + var con = LoadConnection(); try { @@ -127,32 +152,7 @@ namespace Duplicati.Library.SQLiteHelper } // set custom Sqlite options - var opts = Environment.GetEnvironmentVariable("CUSTOMSQLITEOPTIONS_DUPLICATI"); - if (opts != null) - { - var topts = opts.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - if (topts.Length > 0) - { - using (var cmd = con.CreateCommand()) - { - foreach (var opt in topts) - { - Logging.Log.WriteVerboseMessage(LOGTAG, "CustomSQLiteOption", @"Setting custom SQLite option '{0}'.", opt); - try - { - cmd.CommandText = string.Format("pragma {0}", opt); - cmd.ExecuteNonQuery(); - } - catch (Exception ex) - { - Logging.Log.WriteErrorMessage(LOGTAG, "CustomSQLiteOption", ex, @"Error setting custom SQLite option '{0}'.", opt); - } - } - } - } - } - - return con; + return ApplyCustomPragmas(con); } /// @@ -192,9 +192,12 @@ namespace Duplicati.Library.SQLiteHelper /// private static void SetEnvironmentVariablesForSQLiteTempDir() { - System.Environment.SetEnvironmentVariable("SQLITE_TMPDIR", Library.Utility.TempFolder.SystemTempPath); - System.Environment.SetEnvironmentVariable("TMP", Library.Utility.TempFolder.SystemTempPath); - System.Environment.SetEnvironmentVariable("TEMP", Library.Utility.TempFolder.SystemTempPath); + // Allow the user to override the temp folder for SQLite + if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("SQLITE_TMPDIR"))) + Environment.SetEnvironmentVariable("SQLITE_TMPDIR", Utility.TempFolder.SystemTempPath); + Environment.SetEnvironmentVariable("TMPDIR", Utility.TempFolder.SystemTempPath); + Environment.SetEnvironmentVariable("TMP", Utility.TempFolder.SystemTempPath); + Environment.SetEnvironmentVariable("TEMP", Utility.TempFolder.SystemTempPath); } /// @@ -215,12 +218,6 @@ namespace Duplicati.Library.SQLiteHelper /// Path to the file to open, which may not exist. private static void OpenSQLiteFile(System.Data.IDbConnection con, string path) { - // Check if SQLite database exists before opening a connection to it. - // This information is used to 'fix' permissions on a newly created file. - var fileExists = false; - if (!OperatingSystem.IsWindows()) - fileExists = File.Exists(path); - con.ConnectionString = "Data Source=" + path; con.Open(); if (con is System.Data.SQLite.SQLiteConnection sqlitecon && !OperatingSystem.IsMacOS()) @@ -230,29 +227,10 @@ namespace Duplicati.Library.SQLiteHelper sqlitecon.SetConfigurationOption(System.Data.SQLite.SQLiteConfigDbOpsEnum.SQLITE_DBCONFIG_DQS_DML, false); } - - // If we are non-Windows, make the file only accessible by the current user - if ((OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) && !fileExists) - SetUnixPermissionUserRWOnly(path); - } - - /// - /// Sets the unix permission user read-write Only. - /// - /// The file to set permissions on. - /// Make sure we do not inline this, as we might eventually load Mono.Posix, which is not present on Windows - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] - [SupportedOSPlatform("linux")] - [SupportedOSPlatform("macOS")] - private static void SetUnixPermissionUserRWOnly(string path) - { - var fi = PosixFile.GetUserGroupAndPermissions(path); - PosixFile.SetUserGroupAndPermissions( - path, - fi.UID, - fi.GID, - 0x180 /* FilePermissions.S_IRUSR | FilePermissions.S_IWUSR*/ - ); + // Make the file only accessible by the current user, unless opting out + if (!SystemIO.IO_OS.FileExists(SystemIO.IO_OS.PathCombine(SystemIO.IO_OS.PathGetDirectoryName(path), Util.InsecurePermissionsMarkerFile))) + try { SystemIO.IO_OS.FileSetPermissionUserRWOnly(path); } + catch (Exception ex) { Logging.Log.WriteWarningMessage(LOGTAG, "SQLiteFilePermissionError", ex, "Failed to set permissions on SQLite file '{0}'", path); } } /// diff --git a/Duplicati/Library/SQLiteHelper/SQLiteRC4Decrypter.cs b/Duplicati/Library/SQLiteHelper/SQLiteRC4Decrypter.cs index 9625c1375..61faec3bb 100644 --- a/Duplicati/Library/SQLiteHelper/SQLiteRC4Decrypter.cs +++ b/Duplicati/Library/SQLiteHelper/SQLiteRC4Decrypter.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/SQLiteHelper/Strings.cs b/Duplicati/Library/SQLiteHelper/Strings.cs index 4d72e5633..147412dd3 100644 --- a/Duplicati/Library/SQLiteHelper/Strings.cs +++ b/Duplicati/Library/SQLiteHelper/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/SecretProvider/AWSSecretProvider.cs b/Duplicati/Library/SecretProvider/AWSSecretProvider.cs new file mode 100644 index 000000000..74fef9a60 --- /dev/null +++ b/Duplicati/Library/SecretProvider/AWSSecretProvider.cs @@ -0,0 +1,260 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Reflection; +using System.Text.Json; +using System.Web; +using Amazon.SecretsManager; +using Amazon.SecretsManager.Model; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SecretProvider; + +/// +/// A secret provider that retrieves secrets from AWS Secrets Manager +/// +public class AWSSecretProvider : ISecretProvider +{ + private const string OPTION_PREFIX_EXTRA = "ext-"; + + /// + public string Key => "awssm"; + + /// + public string DisplayName => Strings.AWSSecretProvider.DisplayName; + + /// + public string Description => Strings.AWSSecretProvider.Description; + + /// + /// Constants for environment variables + /// + private static class EnvConstants + { + public const string AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID"; + public const string AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY"; + public const string AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION"; + public const string AWS_ENDPOINT_URL = "AWS_ENDPOINT_URL"; + } + + /// + /// Properties that are present in the specfic configuration and the general configuration + /// + private static readonly HashSet DuplicatedProperties = new[] { + nameof(AmazonSecretsManagerConfig.RegionEndpoint), + nameof(AmazonSecretsManagerConfig.ServiceURL) + }.ToHashSet(); + + /// + /// List of properties that slow down the loading of the AWS Secrets Manager client + /// + /// Changes in this list will likely need to be reflected in S3AwsClient.cs + private static readonly HashSet SlowLoadingProperties = new[] { + nameof(AmazonSecretsManagerConfig.RegionEndpoint), + nameof(AmazonSecretsManagerConfig.ServiceURL), + nameof(AmazonSecretsManagerConfig.MaxErrorRetry), + nameof(AmazonSecretsManagerConfig.DefaultConfigurationMode), + nameof(AmazonSecretsManagerConfig.Timeout), + nameof(AmazonSecretsManagerConfig.RetryMode), + }.ToHashSet(); + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new AWSSettings()) + .Concat(CommandLineArgumentMapper.MapArguments(new AmazonSecretsManagerConfig(), OPTION_PREFIX_EXTRA, exclude: DuplicatedProperties, excludeDefaultValue: SlowLoadingProperties)) + .ToList(); + + /// + /// The AWS Secrets Manager client; null if not initialized + /// + private AmazonSecretsManagerClient? _client; + + /// + /// The secrets to fetch + /// + private string[] _secrets = Array.Empty(); + + /// + /// Whether the secrets are case sensitive + /// + private bool _caseSensitive; + + /// + /// Settings for AWS + /// + private class AWSSettings : ICommandLineArgumentMapper + { + /// + /// The access key + /// + public string? AccessKey { get; set; } + /// + /// The secret key + /// + public string? SecretKey { get; set; } + /// + /// The region endpoint + /// + public string? RegionEndpoint { get; set; } + /// + /// The service URL + /// + public string? ServiceURL { get; set; } + /// + /// The secrets to fetch + /// + public string? Secrets { get; set; } + /// + /// Whether the secrets are case sensitive + /// + public bool CaseSensitive { get; set; } + + /// + /// Gets the description for a command line argument + /// + /// The name of the argument + /// The description for the argument + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(AccessKey) => new CommandLineArgumentDescriptionAttribute() { Name = "access-key", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AWSSecretProvider.AccessKeyDescriptionShort, LongDescription = Strings.AWSSecretProvider.AccessKeyDescriptionLong(EnvConstants.AWS_ACCESS_KEY_ID) }, + nameof(SecretKey) => new CommandLineArgumentDescriptionAttribute() { Name = "secret-key", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.AWSSecretProvider.SecretKeyDescriptionShort, LongDescription = Strings.AWSSecretProvider.SecretKeyDescriptionLong(EnvConstants.AWS_SECRET_ACCESS_KEY) }, + nameof(RegionEndpoint) => new CommandLineArgumentDescriptionAttribute() { Name = "region", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AWSSecretProvider.RegionEndpointDescriptionShort, LongDescription = Strings.AWSSecretProvider.RegionEndpointDescriptionLong(EnvConstants.AWS_DEFAULT_REGION) }, + nameof(ServiceURL) => new CommandLineArgumentDescriptionAttribute() { Name = "service-url", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AWSSecretProvider.ServiceURLDescriptionShort, LongDescription = Strings.AWSSecretProvider.ServiceURLDescriptionLong(EnvConstants.AWS_ENDPOINT_URL) }, + nameof(Secrets) => new CommandLineArgumentDescriptionAttribute() { Name = "secrets", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AWSSecretProvider.SecretsDescriptionShort, LongDescription = Strings.AWSSecretProvider.SecretsDescriptionLong }, + nameof(CaseSensitive) => new CommandLineArgumentDescriptionAttribute() { Name = "case-sensitive", Type = CommandLineArgument.ArgumentType.Boolean, ShortDescription = Strings.AWSSecretProvider.CaseSensitiveDescriptionShort, LongDescription = Strings.AWSSecretProvider.CaseSensitiveDescriptionLong }, + _ => null + }; + + /// + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + /// Gets the name of the argument + /// + /// The name of the property + /// The name of the argument + private string ArgName(string name) => AWSSettings.GetCommandLineArgumentDescription(name)?.Name ?? name; + + /// + public async Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + var args = HttpUtility.ParseQueryString(config.Query); + var cred = CommandLineArgumentMapper.ApplyArguments(new AWSSettings(), args); + + if (string.IsNullOrWhiteSpace(cred.AccessKey)) + cred.AccessKey = Environment.GetEnvironmentVariable(EnvConstants.AWS_ACCESS_KEY_ID); + if (string.IsNullOrWhiteSpace(cred.SecretKey)) + cred.SecretKey = Environment.GetEnvironmentVariable(EnvConstants.AWS_SECRET_ACCESS_KEY); + if (string.IsNullOrWhiteSpace(cred.RegionEndpoint)) + cred.RegionEndpoint = Environment.GetEnvironmentVariable(EnvConstants.AWS_DEFAULT_REGION); + if (string.IsNullOrWhiteSpace(cred.ServiceURL)) + cred.ServiceURL = Environment.GetEnvironmentVariable(EnvConstants.AWS_ENDPOINT_URL); + + if (string.IsNullOrWhiteSpace(cred.AccessKey) || string.IsNullOrWhiteSpace(cred.SecretKey)) + throw new UserInformationException($"{ArgName(nameof(AWSSettings.AccessKey))} and {ArgName(nameof(AWSSettings.AccessKey))} are required for {DisplayName}", "AwssmMissingCredentials"); + + if (string.IsNullOrWhiteSpace(cred.RegionEndpoint) && string.IsNullOrWhiteSpace(cred.ServiceURL)) + throw new UserInformationException($"Either {ArgName(nameof(AWSSettings.RegionEndpoint))} or {ArgName(nameof(AWSSettings.ServiceURL))} is required for {DisplayName}", "AwssmMissingRegionOrUrl"); + + if (string.IsNullOrWhiteSpace(cred.Secrets)) + throw new UserInformationException($"{ArgName(nameof(AWSSettings.Secrets))} is required for {DisplayName}", "AwssmMissingSecrets"); + _secrets = cred.Secrets.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); + _caseSensitive = cred.CaseSensitive; + + var initssconf = new AmazonSecretsManagerConfig(); + if (!string.IsNullOrWhiteSpace(cred.RegionEndpoint)) + initssconf.RegionEndpoint = Amazon.RegionEndpoint.GetBySystemName(cred.RegionEndpoint); + if (!string.IsNullOrWhiteSpace(cred.ServiceURL)) + initssconf.ServiceURL = cred.ServiceURL; + + var scconfig = CommandLineArgumentMapper.ApplyArguments( + initssconf, + args, + OPTION_PREFIX_EXTRA + ); + + var credentials = new Amazon.Runtime.BasicAWSCredentials(cred.AccessKey, cred.SecretKey); + var client = new AmazonSecretsManagerClient(credentials, scconfig); + + // Test the connection + await client.ListSecretsAsync(new ListSecretsRequest() + { + MaxResults = 1 + }, cancellationToken).ConfigureAwait(false); + + _client = client; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_client == null) + throw new InvalidOperationException("The secret provider has not been initialized"); + + var result = new Dictionary(); + var missing = new HashSet(keys); + + foreach (var secret in _secrets) + { + var response = await _client.GetSecretValueAsync(new GetSecretValueRequest() + { + SecretId = secret + }, cancellationToken).ConfigureAwait(false); + + var secretString = response.SecretString; + if (string.IsNullOrWhiteSpace(secretString) && response.SecretBinary != null) + { + using (var ms = response.SecretBinary) + using (var sr = new StreamReader(ms)) + secretString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + } + + var values = string.IsNullOrWhiteSpace(secretString) ? null : JsonSerializer.Deserialize>(response.SecretString); + if (values != null) + { + if (!_caseSensitive) + values = values + .GroupBy(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase); + + + foreach (var key in missing) + { + if (values.TryGetValue(key, out var value) && value is string stringValue) + { + result[key] = stringValue; + missing.Remove(key); + } + } + + if (missing.Count == 0) + return result; + } + } + + throw new KeyNotFoundException("The following keys were not found: " + string.Join(", ", missing)); + + } +} \ No newline at end of file diff --git a/Duplicati/Library/SecretProvider/AzureSecretProvider.cs b/Duplicati/Library/SecretProvider/AzureSecretProvider.cs new file mode 100644 index 000000000..51c96e94c --- /dev/null +++ b/Duplicati/Library/SecretProvider/AzureSecretProvider.cs @@ -0,0 +1,154 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Reflection; +using System.Web; +using Azure.Core; +using Azure.Identity; +using Azure.Security.KeyVault.Secrets; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Secret provider that reads secrets from Azure Key Vault +/// +public class AzureSecretProvider : ISecretProvider +{ + /// + public string Key => "azkv"; + + /// + public string DisplayName => Strings.AzureSecretProvider.DisplayName; + + /// + public string Description => Strings.AzureSecretProvider.Description; + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new AzureSecretProviderConfig()) + .ToList(); + + public enum ConnectionType + { + Https, + Http + } + + public enum AuthenticationType + { + ClientSecret, + ManagedIdentity, + UsernamePassword + } + + private class AzureSecretProviderConfig : ICommandLineArgumentMapper + { + public string? KeyVaultName { get; set; } + public ConnectionType ConnectionType { get; set; } = ConnectionType.Https; + public string? VaultUri { get; set; } + public string? TenantId { get; set; } + public string? ClientId { get; set; } + public string? ClientSecret { get; set; } + public string? Username { get; set; } + public string? Password { get; set; } + public AuthenticationType AuthenticationType { get; set; } = AuthenticationType.ManagedIdentity; + + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(KeyVaultName) => new CommandLineArgumentDescriptionAttribute() { Name = "keyvault-name", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AzureSecretProvider.KeyVaultNameDescriptionShort, LongDescription = Strings.AzureSecretProvider.KeyVaultNameDescriptionLong }, + nameof(ConnectionType) => new CommandLineArgumentDescriptionAttribute() { Name = "connection-type", Type = CommandLineArgument.ArgumentType.Enumeration, ShortDescription = Strings.AzureSecretProvider.ConnectionTypeDescriptionShort, LongDescription = Strings.AzureSecretProvider.ConnectionTypeDescriptionLong }, + nameof(VaultUri) => new CommandLineArgumentDescriptionAttribute() { Name = "vault-uri", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AzureSecretProvider.VaultUriDescriptionShort, LongDescription = Strings.AzureSecretProvider.VaultUriDescriptionLong }, + nameof(TenantId) => new CommandLineArgumentDescriptionAttribute() { Name = "tenant-id", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AzureSecretProvider.TenantIdDescriptionShort, LongDescription = Strings.AzureSecretProvider.TenantIdDescriptionLong }, + nameof(ClientId) => new CommandLineArgumentDescriptionAttribute() { Name = "client-id", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AzureSecretProvider.ClientIdDescriptionShort, LongDescription = Strings.AzureSecretProvider.ClientIdDescriptionLong }, + nameof(ClientSecret) => new CommandLineArgumentDescriptionAttribute() { Name = "client-secret", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.AzureSecretProvider.ClientSecretDescriptionShort, LongDescription = Strings.AzureSecretProvider.ClientSecretDescriptionLong }, + nameof(Username) => new CommandLineArgumentDescriptionAttribute() { Name = "username", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.AzureSecretProvider.UsernameDescriptionShort, LongDescription = Strings.AzureSecretProvider.UsernameDescriptionLong }, + nameof(Password) => new CommandLineArgumentDescriptionAttribute() { Name = "password", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.AzureSecretProvider.PasswordDescriptionShort, LongDescription = Strings.AzureSecretProvider.PasswordDescriptionLong }, + nameof(AuthenticationType) => new CommandLineArgumentDescriptionAttribute() { Name = "auth-type", Type = CommandLineArgument.ArgumentType.Enumeration, ShortDescription = Strings.AzureSecretProvider.AuthenticationTypeDescriptionShort, LongDescription = Strings.AzureSecretProvider.AuthenticationTypeDescriptionLong }, + _ => null + }; + + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + /// The Azure Key Vault client; null if not initialized + /// + private SecretClient? _client; + + /// + /// Gets the name of the argument + /// + /// The name of the argument + /// The name of the argument + private string ArgName(string name) => AzureSecretProviderConfig.GetCommandLineArgumentDescription(name)?.Name ?? name; + + /// + public Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + var args = HttpUtility.ParseQueryString(config.Query); + var cfg = CommandLineArgumentMapper.ApplyArguments(new AzureSecretProviderConfig(), args); + var scheme = cfg.ConnectionType == ConnectionType.Http ? "http" : "https"; + + if (string.IsNullOrWhiteSpace(cfg.KeyVaultName) && string.IsNullOrWhiteSpace(cfg.VaultUri)) + throw new UserInformationException($"Either {ArgName(nameof(AzureSecretProviderConfig.KeyVaultName))} or {ArgName(nameof(AzureSecretProviderConfig.VaultUri))} is required", "MissingVaultNameOrUri"); + else if (!string.IsNullOrWhiteSpace(cfg.KeyVaultName) && !string.IsNullOrWhiteSpace(cfg.VaultUri)) + throw new UserInformationException($"Only one of {ArgName(nameof(AzureSecretProviderConfig.KeyVaultName))} or {ArgName(nameof(AzureSecretProviderConfig.VaultUri))} can be specified", "BothVaultNameAndUriSpecified"); + + var vaultUri = cfg.VaultUri ?? $"{scheme}://{cfg.KeyVaultName}.vault.azure.net"; + + if (cfg.AuthenticationType == AuthenticationType.ClientSecret && (string.IsNullOrWhiteSpace(cfg.TenantId) || string.IsNullOrWhiteSpace(cfg.ClientId) || string.IsNullOrWhiteSpace(cfg.ClientSecret))) + throw new UserInformationException($"The settings {ArgName(nameof(AzureSecretProviderConfig.TenantId))}, {ArgName(nameof(AzureSecretProviderConfig.ClientId))}, and {ArgName(nameof(AzureSecretProviderConfig.ClientSecret))} are required for client secret authentication", "MissingClientSecretSettings"); + else if (cfg.AuthenticationType == AuthenticationType.UsernamePassword && (string.IsNullOrWhiteSpace(cfg.TenantId) || string.IsNullOrWhiteSpace(cfg.ClientId) || string.IsNullOrWhiteSpace(cfg.Username) || string.IsNullOrWhiteSpace(cfg.Password))) + throw new UserInformationException($"The settings {ArgName(nameof(AzureSecretProviderConfig.TenantId))}, {ArgName(nameof(AzureSecretProviderConfig.ClientId))}, {ArgName(nameof(AzureSecretProviderConfig.Username))}, and {ArgName(nameof(AzureSecretProviderConfig.Password))} are required for username/password authentication", "MissingUsernamePasswordSettings"); + + TokenCredential credential = cfg.AuthenticationType switch + { + AuthenticationType.ClientSecret => new ClientSecretCredential(cfg.TenantId, cfg.ClientId, cfg.ClientSecret), + AuthenticationType.ManagedIdentity => new DefaultAzureCredential(), + AuthenticationType.UsernamePassword => new UsernamePasswordCredential(cfg.Username, cfg.Password, cfg.TenantId, cfg.ClientId), + _ => throw new UserInformationException($"Authentication type {cfg.AuthenticationType} is not supported", "UnsupportedAuthenticationType") + }; + + + _client = new SecretClient(new System.Uri(vaultUri), credential); + return Task.CompletedTask; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_client is null) + throw new InvalidOperationException("The secret provider has not been initialized"); + + var secrets = new Dictionary(); + foreach (var key in keys) + { + var secret = await _client.GetSecretAsync(key, cancellationToken: cancellationToken).ConfigureAwait(false); + secrets[key] = secret.Value.Value; + } + + return secrets; + } +} diff --git a/Duplicati/Library/SecretProvider/Duplicati.Library.SecretProvider.csproj b/Duplicati/Library/SecretProvider/Duplicati.Library.SecretProvider.csproj new file mode 100644 index 000000000..fd3e7377d --- /dev/null +++ b/Duplicati/Library/SecretProvider/Duplicati.Library.SecretProvider.csproj @@ -0,0 +1,37 @@ + + + + net8.0 + Library + Duplicati.Library.SecretProvider + Copyright © 2025 Team Duplicati, MIT license + enable + enable + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Duplicati/Library/SecretProvider/EnvironmentSecretProvider.cs b/Duplicati/Library/SecretProvider/EnvironmentSecretProvider.cs new file mode 100644 index 000000000..f19f89347 --- /dev/null +++ b/Duplicati/Library/SecretProvider/EnvironmentSecretProvider.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Secret provider that reads secrets from environment variables +/// +public class EnvironmentSecretProvider : ISecretProvider +{ + /// + public string Key => "env"; + + /// + public string DisplayName => Strings.EnvironmentSecretProvider.DisplayName; + + /// + public string Description => Strings.EnvironmentSecretProvider.Description; + + /// + public IList SupportedCommands => []; + + /// + public Task InitializeAsync(Uri config, CancellationToken cancellationToken) + => Task.CompletedTask; + + /// + public Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + var env = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process); + var envLookup = env.Keys.Cast().ToDictionary(k => k, k => env[k] as string, StringComparer.OrdinalIgnoreCase); + return Task.FromResult( + keys.ToDictionary( + k => k, + k => + { + var value = envLookup.GetValueOrDefault(k); + if (string.IsNullOrWhiteSpace(value)) + throw new KeyNotFoundException($"The key '{k}' was not found"); + return value; + } + ) + ); + } +} diff --git a/Duplicati/Library/SecretProvider/FileSecretProvider.cs b/Duplicati/Library/SecretProvider/FileSecretProvider.cs new file mode 100644 index 000000000..2029a57ba --- /dev/null +++ b/Duplicati/Library/SecretProvider/FileSecretProvider.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Text.Json; +using System.Web; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Secret provider that reads secrets from a file +/// +public class FileSecretProvider : ISecretProvider +{ + private const string PASSPHRASE_OPTION = "passphrase"; + + /// + public string Key => "file-secret"; + + /// + public string DisplayName => Strings.FileSecretProvider.DisplayName; + + /// + public string Description => Strings.FileSecretProvider.Description; + + /// + private IReadOnlyDictionary? _secrets; + + /// + public IList SupportedCommands => [ + new CommandLineArgument(PASSPHRASE_OPTION, CommandLineArgument.ArgumentType.String, Strings.FileSecretProvider.PassphraseDescriptionShort, Strings.FileSecretProvider.PassphraseDescriptionLong, null), + ]; + + /// + public async Task InitializeAsync(Uri config, CancellationToken cancellationToken) + { + var path = config.LocalPath; + if (!File.Exists(path)) + throw new FileNotFoundException($"File not found: {path}"); + + // Get the passphrase from the secrets-passphrase query parameter + var args = HttpUtility.ParseQueryString(config.Query); + var passphrase = args[PASSPHRASE_OPTION]; + using var fs = File.OpenRead(path); + Dictionary secrets; + + if (string.IsNullOrEmpty(passphrase)) + { + secrets = await JsonSerializer.DeserializeAsync>(fs, cancellationToken: cancellationToken).ConfigureAwait(false) + ?? throw new UserInformationException("The file does not contain any secrets", "NoSecrets"); + } + else + { + using var ms = new MemoryStream(); + await SharpAESCrypt.AESCrypt.DecryptAsync(passphrase, fs, ms, SharpAESCrypt.DecryptionOptions.Default with { LeaveOpen = true }, cancellationToken).ConfigureAwait(false); + ms.Position = 0; + secrets = await JsonSerializer.DeserializeAsync>(ms, cancellationToken: cancellationToken).ConfigureAwait(false) + ?? throw new UserInformationException("The file does not contain any secrets", "NoSecrets"); + } + + // Make secrets case-insensitive + _secrets = secrets.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase).AsReadOnly(); + } + + /// + public Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_secrets is null) + throw new InvalidOperationException("The secret provider has not been initialized"); + + return Task.FromResult(keys.ToDictionary(k => k, k => _secrets.TryGetValue(k, out var value) ? value : throw new KeyNotFoundException($"The key '{k}' was not found"))); + } +} diff --git a/Duplicati/Library/SecretProvider/GCSSecretProvider.cs b/Duplicati/Library/SecretProvider/GCSSecretProvider.cs new file mode 100644 index 000000000..d535f1d45 --- /dev/null +++ b/Duplicati/Library/SecretProvider/GCSSecretProvider.cs @@ -0,0 +1,154 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Reflection; +using System.Web; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using Google.Api.Gax.Grpc; +using Google.Api.Gax.Grpc.Rest; +using Google.Apis.Auth.OAuth2; +using Google.Cloud.SecretManager.V1; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Implementation of a secret provider that reads secrets from Google Cloud Secret Manager +/// +public class GCSSecretProvider : ISecretProvider +{ + /// + /// The prefix for extra options + /// + private const string OPTION_PREFIX_EXTRA = "ext-"; + + /// + public string Key => "gcsm"; + + /// + public string DisplayName => Strings.GCSSecretProvider.DisplayName; + + /// + public string Description => Strings.GCSSecretProvider.Description; + + /// + /// The API types supported + /// + private enum ApiType + { + Grpc, + Rest + } + + /// + /// The configuration for the secret provider + /// + private GCSSecretProviderOptions? _cfg; + + /// + /// The options for the secret provider + /// + private class GCSSecretProviderOptions : ICommandLineArgumentMapper + { + public ApiType? ApiType { get; set; } + public string? ProjectId { get; set; } + public string? AccessToken { get; set; } + public string Version { get; set; } = "latest"; + + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(ApiType) => new CommandLineArgumentDescriptionAttribute() { Name = "api-type", Type = CommandLineArgument.ArgumentType.Enumeration, ShortDescription = Strings.GCSSecretProvider.ApiTypeDescriptionShort, LongDescription = Strings.GCSSecretProvider.ApiTypeDescriptionLong }, + nameof(ProjectId) => new CommandLineArgumentDescriptionAttribute() { Name = "project-id", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.GCSSecretProvider.ProjectIdDescriptionShort, LongDescription = Strings.GCSSecretProvider.ProjectIdDescriptionLong }, + nameof(AccessToken) => new CommandLineArgumentDescriptionAttribute() { Name = "access-token", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.GCSSecretProvider.AccessTokenDescriptionShort, LongDescription = Strings.GCSSecretProvider.AccessTokenDescriptionLong }, + nameof(Version) => new CommandLineArgumentDescriptionAttribute() { Name = "version", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.GCSSecretProvider.VersionDescriptionShort, LongDescription = Strings.GCSSecretProvider.VersionDescriptionLong }, + _ => null + }; + + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + public IList SupportedCommands => + CommandLineArgumentMapper.MapArguments(new GCSSecretProviderOptions()) + .Concat(CommandLineArgumentMapper.MapArguments(new SecretManagerServiceClientBuilder(), OPTION_PREFIX_EXTRA)) + .ToList(); + + /// + /// The client for the secret manager + /// + private SecretManagerServiceClient? _client; + + /// + /// Gets the name of the argument + /// + /// The name of the argument + /// The name of the argument + private string ArgName(string name) => GCSSecretProviderOptions.GetCommandLineArgumentDescription(name)?.Name ?? name; + + /// + public async Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + var args = HttpUtility.ParseQueryString(config.Query); + var builder = CommandLineArgumentMapper.ApplyArguments(new SecretManagerServiceClientBuilder(), args, OPTION_PREFIX_EXTRA); + + var cfg = CommandLineArgumentMapper.ApplyArguments(new GCSSecretProviderOptions(), args); + if (string.IsNullOrWhiteSpace(cfg.ProjectId)) + throw new UserInformationException($"{ArgName(nameof(GCSSecretProviderOptions.ProjectId))} is required", "MissingProjectId"); + if (string.IsNullOrWhiteSpace(cfg.Version)) + throw new UserInformationException($"{ArgName(nameof(GCSSecretProviderOptions.Version))} is required", "MissingVersion"); + if (cfg.ApiType != null) + builder.GrpcAdapter = cfg.ApiType switch + { + ApiType.Grpc => GrpcCoreAdapter.Instance, + ApiType.Rest => RestGrpcAdapter.Default, + _ => throw new UserInformationException($"Unknown API type: {cfg.ApiType}", "UnknownApiType") + }; + + if (!string.IsNullOrWhiteSpace(cfg.AccessToken)) + builder.Credential = GoogleCredential.FromAccessToken(cfg.AccessToken); + else + builder.Credential = await GoogleCredential.GetApplicationDefaultAsync(cancellationToken).ConfigureAwait(false); + + var client = builder.Build(); + var res = client.ListSecretsAsync(new ListSecretsRequest() { ParentAsProjectName = new Google.Api.Gax.ResourceNames.ProjectName(cfg.ProjectId) }, CallSettings.FromCancellationToken(cancellationToken)); + await res.ReadPageAsync(1, cancellationToken).ConfigureAwait(false); + _cfg = cfg; + _client = client; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_client is null || _cfg is null) + throw new InvalidOperationException("The secret provider has not been initialized"); + + var res = new Dictionary(); + foreach (var key in keys) + { + var sec = await _client.AccessSecretVersionAsync(new AccessSecretVersionRequest() { SecretVersionName = new SecretVersionName(_cfg.ProjectId, key, "latest") }, callSettings: CallSettings.FromCancellationToken(cancellationToken)).ConfigureAwait(false); + res[key] = sec.Payload.Data.ToStringUtf8(); + } + + return res; + } +} diff --git a/Duplicati/Library/SecretProvider/HCVaultSecretProvider.cs b/Duplicati/Library/SecretProvider/HCVaultSecretProvider.cs new file mode 100644 index 000000000..999bd6af2 --- /dev/null +++ b/Duplicati/Library/SecretProvider/HCVaultSecretProvider.cs @@ -0,0 +1,251 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Reflection; +using System.Web; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; +using VaultSharp; +using VaultSharp.V1.AuthMethods; +using VaultSharp.V1.AuthMethods.AppRole; +using VaultSharp.V1.AuthMethods.Token; + +namespace Duplicati.Library.SecretProvider; + +/// +/// A secret provider that retrieves secrets from HashiCorp Vault +/// +public class HCVaultSecretProvider : ISecretProvider +{ + /// + public string Key => "hcv"; + + /// + public string DisplayName => Strings.HCVaultSecretProvider.DisplayName; + + /// + public string Description => Strings.HCVaultSecretProvider.Description; + + /// + /// The configuration for the secret provider; null if not initialized + /// + private IVaultClient? _client; + + /// + /// The list of secrets to fetch + /// + private IReadOnlyList? _secrets; + + /// + /// Whether the secrets are case sensitive + /// + private bool _caseSensitive; + + /// + /// The mount point to use + /// + private string? _mountPoint; + + /// + /// Constants for environment variables + /// + private static class EnvConstants + { + /// + /// The client ID for the HashiCorp Vault + /// + public const string HCP_CLIENT_ID = "HCP_CLIENT_ID"; + /// + /// The client secret for the HashiCorp Vault + /// + public const string HCP_CLIENT_SECRET = "HCP_CLIENT_SECRET"; + } + + /// + /// The connection types + /// + public enum ConnectionType + { + /// + /// Use HTTPS + /// + Https, + /// + /// Use HTTP + /// + Http + }; + + /// + /// Mapper for the command line arguments + /// + private class HCVaultSettings : ICommandLineArgumentMapper + { + /// + /// The token to use for authentication + /// + public string? Token { get; set; } + /// + /// The connection type to use + /// + public ConnectionType ConnectionType { get; set; } = ConnectionType.Https; + /// + /// The secrets to probe for values + /// + public string? Secrets { get; set; } + /// + /// The mount point for the secrets + /// + public string? MountPoint { get; set; } = "secret"; + /// + /// The client ID to use for authentication + /// + public string? ClientId { get; set; } + /// + /// The client secret to use for authentication + /// + public string? ClientSecret { get; set; } + /// + /// Whether the secrets are case sensitive + /// + public bool CaseSensitive { get; set; } + + /// + /// Gets the description for a command line argument + /// + /// The name of the argument + /// The description for the argument + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(Token) => new CommandLineArgumentDescriptionAttribute() { Name = "token", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.HCVaultSecretProvider.TokenDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.TokenDescriptionLong }, + nameof(ConnectionType) => new CommandLineArgumentDescriptionAttribute() { Name = "connection-type", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.HCVaultSecretProvider.ProtocolDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.ProtocolDescriptionLong }, + nameof(Secrets) => new CommandLineArgumentDescriptionAttribute() { Name = "secrets", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.HCVaultSecretProvider.SecretsDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.SecretsDescriptionLong }, + nameof(ClientId) => new CommandLineArgumentDescriptionAttribute() { Name = "client-id", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.HCVaultSecretProvider.ClientIdDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.ClientIdDescriptionLong(EnvConstants.HCP_CLIENT_ID) }, + nameof(ClientSecret) => new CommandLineArgumentDescriptionAttribute() { Name = "client-secret", Type = CommandLineArgument.ArgumentType.Password, ShortDescription = Strings.HCVaultSecretProvider.ClientSecretDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.ClientSecretDescriptionLong(EnvConstants.HCP_CLIENT_SECRET) }, + nameof(MountPoint) => new CommandLineArgumentDescriptionAttribute() { Name = "mount", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.HCVaultSecretProvider.MountPointDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.MountPointDescriptionLong }, + nameof(CaseSensitive) => new CommandLineArgumentDescriptionAttribute() { Name = "case-sensitive", Type = CommandLineArgument.ArgumentType.Boolean, ShortDescription = Strings.HCVaultSecretProvider.CaseSensitiveDescriptionShort, LongDescription = Strings.HCVaultSecretProvider.CaseSensitiveDescriptionLong }, + _ => null + }; + + /// + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new HCVaultSettings()) + .Concat(CommandLineArgumentMapper.MapArguments(typeof(VaultClientSettings))) + .ToList(); + + /// + /// Gets the name of the argument + /// + /// The name of the argument + /// The name of the argument + private string ArgName(string name) => HCVaultSettings.GetCommandLineArgumentDescription(name)?.Name ?? name; + + /// + public async Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + var args = HttpUtility.ParseQueryString(config.Query); + var cfg = CommandLineArgumentMapper.ApplyArguments(new HCVaultSettings(), args); + + if (string.IsNullOrWhiteSpace(cfg.ClientId)) + cfg.ClientId = Environment.GetEnvironmentVariable(EnvConstants.HCP_CLIENT_ID); + if (string.IsNullOrWhiteSpace(cfg.ClientSecret)) + cfg.ClientSecret = Environment.GetEnvironmentVariable(EnvConstants.HCP_CLIENT_SECRET); + + if (string.IsNullOrWhiteSpace(cfg.ClientSecret) && !string.IsNullOrWhiteSpace(cfg.ClientId)) + throw new UserInformationException($"{ArgName(nameof(HCVaultSettings.ClientSecret))} is required when {ArgName(nameof(HCVaultSettings.ClientId))} is specified", "MissingClientSecret"); + if (string.IsNullOrWhiteSpace(cfg.Token) && string.IsNullOrWhiteSpace(cfg.ClientId)) + throw new UserInformationException($"Either {ArgName(nameof(HCVaultSettings.Token))} or {ArgName(nameof(HCVaultSettings.ClientId))} is required", "MissingTokenOrClient"); + if (string.IsNullOrWhiteSpace(cfg.Secrets)) + throw new UserInformationException($"{ArgName(nameof(HCVaultSettings.Secrets))} is required", "MissingSecrets"); + + var secrets = cfg.Secrets?.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? Array.Empty(); + + var builder = new UriBuilder(config) + { + Scheme = cfg.ConnectionType == ConnectionType.Http ? "http" : "https", + Query = null + }; + + IAuthMethodInfo authMethod = string.IsNullOrWhiteSpace(cfg.Token) + ? new AppRoleAuthMethodInfo(cfg.ClientId, cfg.ClientSecret) + : new TokenAuthMethodInfo(cfg.Token); + + var vaultConfig = CommandLineArgumentMapper.ApplyArguments( + new VaultClientSettings(builder.Uri.ToString(), authMethod), + args + ); + + var client = new VaultClient(vaultConfig); + // Check if the connection works + await client.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: secrets.First(), mountPoint: cfg.MountPoint).ConfigureAwait(false); //missing cancellationToken + + _secrets = secrets; + _mountPoint = cfg.MountPoint; + _caseSensitive = cfg.CaseSensitive; + _client = client; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_client is null || _secrets is null) + throw new InvalidOperationException("The secret provider has not been initialized"); + + using var client = new HttpClient(); + var result = new Dictionary(); + var missing = new HashSet(keys); + + // Keep trying to get the secrets from each URL until all keys are found + foreach (var secret in _secrets) + { + var data = await _client.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: secret, mountPoint: _mountPoint).ConfigureAwait(false); //mssing cancellationToken + if (data is null || data.Data is null) + continue; + + var lookupDict = data.Data.Data; + + if (!_caseSensitive) + lookupDict = lookupDict + .GroupBy(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x.Key, x => x.First(), StringComparer.OrdinalIgnoreCase); + + foreach (var key in missing) + { + if (lookupDict.TryGetValue(key, out var value) && value is string stringValue) + { + result[key] = stringValue; + missing.Remove(key); + } + } + + if (missing.Count == 0) + return result; + } + + throw new KeyNotFoundException("The following keys were not found: " + string.Join(", ", missing)); + } +} diff --git a/Duplicati/Library/SecretProvider/LibSecret/SecretCollection.cs b/Duplicati/Library/SecretProvider/LibSecret/SecretCollection.cs new file mode 100644 index 000000000..d3d6f5603 --- /dev/null +++ b/Duplicati/Library/SecretProvider/LibSecret/SecretCollection.cs @@ -0,0 +1,193 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Runtime.Versioning; +using System.Text; +using Duplicati.Library.Interface; +using Duplicati.Library.Logging; +using secrets.DBus; +using Tmds.DBus.Protocol; + +namespace Duplicati.Library.SecretProvider.LibSecret; + +/// +/// Implementation of the secret collection for libsecret +/// +[SupportedOSPlatform("Linux")] +public class SecretCollection : IDisposable +{ + private static readonly string LogTag = Log.LogTagFromType(); + /// + /// The secrets service + /// + private readonly secretsService _secretsService; + /// + /// The service instance + /// + private readonly Service _service; + /// + /// The session instance + /// + private readonly Session _session; + /// + /// The collection instance + /// + private readonly Collection _collection; + /// + /// Whether the collection is locked + /// + private bool _locked; + /// + /// Creates a new secret collection + /// + /// The secrets service + /// The service instance + /// The session instance + /// The collection instance + /// Whether the collection is locked + private SecretCollection(secretsService secretsService, Service service, Session session, Collection collection, bool locked) + { + _secretsService = secretsService; + _service = service; + _session = session; + _collection = collection; + _locked = locked; + } + + /// + /// Creates a new secret collection + /// + /// The collection name + /// The cancellation token + /// The created secret collection + public static async Task CreateAsync(string collectionName, CancellationToken cancellationToken) + { + var connection = Connection.Session; + var secretsService = new secretsService(connection, "org.freedesktop.secrets"); + var service = secretsService.CreateService("/org/freedesktop/secrets"); + var (_, sessionPath) = await service.OpenSessionAsync("plain", "").ConfigureAwait(false); + collectionName ??= ""; + + var collectionPath = (await service.GetCollectionsAsync().ConfigureAwait(false)) + .FirstOrDefault(c => c.ToString().EndsWith(collectionName, StringComparison.OrdinalIgnoreCase)); + + if (!collectionPath.ToString().EndsWith(collectionName, StringComparison.OrdinalIgnoreCase)) + throw new UserInformationException($"Collection {collectionName} not found", "CollectionNotFound"); + + var session = secretsService.CreateSession(sessionPath); + var collection = secretsService.CreateCollection(collectionPath.ToString()); + var locked = await collection.GetLockedAsync().ConfigureAwait(false); + + return new SecretCollection(secretsService, service, session, collection, locked); + } + + /// + /// Unlocks the collection + /// + /// The task to await + public async Task UnlockAsync() + { + if (!_locked) + return; + + var (unlocked, prompt) = await _service.UnlockAsync([_collection.Path]); + if (prompt != null && prompt != "/") + { + var promptInstance = _secretsService.CreatePrompt(prompt); + var completedTask = new TaskCompletionSource(); + + // Set up callback + using var result = await promptInstance.WatchCompletedAsync((exception, result) => + { + if (exception != null) + completedTask.TrySetException(exception); + else if (result.Dismissed) + completedTask.TrySetResult(false); + else + completedTask.TrySetResult(true); + }).ConfigureAwait(false); + + // Prompt + await promptInstance.PromptAsync(Guid.NewGuid().ToString()).ConfigureAwait(false); + + // Wait for prompt to be dismissed or completed + var done = await completedTask.Task.ConfigureAwait(false); + if (!done) + throw new UserInformationException("Dimissed collection unlock prompt", "UnlockDismissed"); + + // Unlock again, so we have the handles + (unlocked, prompt) = await _service.UnlockAsync([_collection.Path]); + } + + if (unlocked.Length == 0) + throw new UserInformationException("Failed to unlock collection", "UnlockFailed"); + } + + /// + /// Obtains the secrets from the collection + /// + /// The labels to look for + /// The string comparer + /// The dictionary of secrets + public async Task> GetSecretsAsync(IEnumerable labels, StringComparer comparer) + { + var attributes = new Dictionary(); + var collection = _secretsService.CreateCollection(_collection.Path); + var entries = await collection.SearchItemsAsync(attributes).ConfigureAwait(false); + var result = new Dictionary(comparer); + var missing = labels.ToHashSet(comparer); + if (missing.Count == 0) + return result; + + // Enumerate all items in the collection + foreach (var r in entries) + { + var item = _secretsService.CreateItem(r); + try + { + var label = await item.GetLabelAsync().ConfigureAwait(false); + if (missing.Contains(label)) + { + var (sessionPath, _, secret, contentType) = await item.GetSecretAsync(_session.Path).ConfigureAwait(false); + result[label] = Encoding.Default.GetString(secret); + missing.Remove(label); + + if (missing.Count == 0) + break; + } + } + catch (Exception ex) + { + Log.WriteWarningMessage(LogTag, "SecretLookupError", ex, "Failed to get returned secret"); + } + } + + if (missing.Count > 0) + throw new UserInformationException($"Missing secrets: {string.Join(", ", missing)}", "MissingSecrets"); + + return result; + } + + /// + public void Dispose() + { + try { _session.CloseAsync().Wait(); } catch { } + } +} diff --git a/Duplicati/Library/SecretProvider/LibSecret/secrets.DBus.cs b/Duplicati/Library/SecretProvider/LibSecret/secrets.DBus.cs new file mode 100644 index 000000000..5052c2ef8 --- /dev/null +++ b/Duplicati/Library/SecretProvider/LibSecret/secrets.DBus.cs @@ -0,0 +1,1319 @@ +// Autogenerated with command: +// dotnet dbus codegen --protocol-api --service org.freedesktop.secrets + +namespace secrets.DBus +{ + using System; + using Tmds.DBus.Protocol; + using System.Collections.Generic; + using System.Threading.Tasks; + + partial class Daemon : secretsObject + { + private const string __Interface = "org.gnome.keyring.Daemon"; + public Daemon(secretsService service, ObjectPath path) : base(service, path) + { } + public Task> GetEnvironmentAsync() + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_aess(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "GetEnvironment"); + return writer.CreateMessage(); + } + } + public Task GetControlDirectoryAsync() + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_s(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "GetControlDirectory"); + return writer.CreateMessage(); + } + } + } + record SecretProperties + { + public uint Version { get; set; } = default!; + } + partial class Secret : secretsObject + { + private const string __Interface = "org.freedesktop.impl.portal.Secret"; + public Secret(secretsService service, ObjectPath path) : base(service, path) + { } + public Task<(uint Response, Dictionary Results)> RetrieveSecretAsync(ObjectPath handle, string appId, System.Runtime.InteropServices.SafeHandle fd, Dictionary options) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_uaesv(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "osha{sv}", + member: "RetrieveSecret"); + writer.WriteObjectPath(handle); + writer.WriteString(appId); + writer.WriteHandle(fd); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + public Task SetVersionAsync(uint value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + public Task GetVersionAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "version"), (Message m, object? s) => ReadMessage_v_u(m, (secretsObject)s!), this); + public Task GetPropertiesAsync() + { + return this.Connection.CallMethodAsync(CreateGetAllPropertiesMessage(__Interface), (Message m, object? s) => ReadMessage(m, (secretsObject)s!), this); + static SecretProperties ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + { + return base.WatchPropertiesChangedAsync(__Interface, (Message m, object? s) => ReadMessage(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + static PropertyChanges ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(), invalidated = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(arrayEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "version": invalidated.Add("Version"); break; + } + } + return invalidated?.ToArray() ?? Array.Empty(); + } + } + private static SecretProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new SecretProperties(); + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(arrayEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + default: + reader.ReadVariantValue(); + break; + } + } + return props; + } + } + partial class InternalUnsupportedGuiltRiddenInterface : secretsObject + { + private const string __Interface = "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface"; + public InternalUnsupportedGuiltRiddenInterface(secretsService service, ObjectPath path) : base(service, path) + { } + public Task ChangeWithMasterPasswordAsync(ObjectPath collection, (ObjectPath, byte[], byte[], string) original, (ObjectPath, byte[], byte[], string) master) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "o(oayays)(oayays)", + member: "ChangeWithMasterPassword"); + writer.WriteObjectPath(collection); + WriteType_roayaysz(ref writer, original); + WriteType_roayaysz(ref writer, master); + return writer.CreateMessage(); + } + } + public Task ChangeWithPromptAsync(ObjectPath collection) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "o", + member: "ChangeWithPrompt"); + writer.WriteObjectPath(collection); + return writer.CreateMessage(); + } + } + public Task CreateWithMasterPasswordAsync(Dictionary attributes, (ObjectPath, byte[], byte[], string) master) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "a{sv}(oayays)", + member: "CreateWithMasterPassword"); + writer.WriteDictionary(attributes); + WriteType_roayaysz(ref writer, master); + return writer.CreateMessage(); + } + } + public Task UnlockWithMasterPasswordAsync(ObjectPath collection, (ObjectPath, byte[], byte[], string) master) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "o(oayays)", + member: "UnlockWithMasterPassword"); + writer.WriteObjectPath(collection); + WriteType_roayaysz(ref writer, master); + return writer.CreateMessage(); + } + } + } + record ServiceProperties + { + public ObjectPath[] Collections { get; set; } = default!; + } + partial class Service : secretsObject + { + private const string __Interface = "org.freedesktop.Secret.Service"; + public Service(secretsService service, ObjectPath path) : base(service, path) + { } + public Task<(VariantValue Output, ObjectPath Result)> OpenSessionAsync(string algorithm, Variant input) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_vo(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "sv", + member: "OpenSession"); + writer.WriteString(algorithm); + writer.WriteVariant(input); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath Collection, ObjectPath Prompt)> CreateCollectionAsync(Dictionary properties, string @alias) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_oo(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "a{sv}s", + member: "CreateCollection"); + writer.WriteDictionary(properties); + writer.WriteString(@alias); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath[] Unlocked, ObjectPath[] Locked)> SearchItemsAsync(Dictionary attributes) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_aoao(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "a{ss}", + member: "SearchItems"); + WriteType_aess(ref writer, attributes); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath[] Unlocked, ObjectPath Prompt)> UnlockAsync(ObjectPath[] objects) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_aoo(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "ao", + member: "Unlock"); + writer.WriteArray(objects); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath[] Locked, ObjectPath Prompt)> LockAsync(ObjectPath[] objects) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_aoo(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "ao", + member: "Lock"); + writer.WriteArray(objects); + return writer.CreateMessage(); + } + } + public Task LockServiceAsync() + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "LockService"); + return writer.CreateMessage(); + } + } + public Task ChangeLockAsync(ObjectPath collection) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "o", + member: "ChangeLock"); + writer.WriteObjectPath(collection); + return writer.CreateMessage(); + } + } + public Task> GetSecretsAsync(ObjectPath[] items, ObjectPath session) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_aeoroayaysz(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "aoo", + member: "GetSecrets"); + writer.WriteArray(items); + writer.WriteObjectPath(session); + return writer.CreateMessage(); + } + } + public Task ReadAliasAsync(string name) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "s", + member: "ReadAlias"); + writer.WriteString(name); + return writer.CreateMessage(); + } + } + public Task SetAliasAsync(string name, ObjectPath collection) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "so", + member: "SetAlias"); + writer.WriteString(name); + writer.WriteObjectPath(collection); + return writer.CreateMessage(); + } + } + public ValueTask WatchCollectionCreatedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "CollectionCreated", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public ValueTask WatchCollectionDeletedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "CollectionDeleted", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public ValueTask WatchCollectionChangedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "CollectionChanged", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public Task SetCollectionsAsync(ObjectPath[] value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Collections"); + writer.WriteSignature("ao"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + public Task GetCollectionsAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Collections"), (Message m, object? s) => ReadMessage_v_ao(m, (secretsObject)s!), this); + public Task GetPropertiesAsync() + { + return this.Connection.CallMethodAsync(CreateGetAllPropertiesMessage(__Interface), (Message m, object? s) => ReadMessage(m, (secretsObject)s!), this); + static ServiceProperties ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + { + return base.WatchPropertiesChangedAsync(__Interface, (Message m, object? s) => ReadMessage(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + static PropertyChanges ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(), invalidated = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(arrayEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "Collections": invalidated.Add("Collections"); break; + } + } + return invalidated?.ToArray() ?? Array.Empty(); + } + } + private static ServiceProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new ServiceProperties(); + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(arrayEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "Collections": + reader.ReadSignature("ao"); + props.Collections = reader.ReadArrayOfObjectPath(); + changedList?.Add("Collections"); + break; + default: + reader.ReadVariantValue(); + break; + } + } + return props; + } + } + partial class Prompt : secretsObject + { + private const string __Interface = "org.freedesktop.Secret.Prompt"; + public Prompt(secretsService service, ObjectPath path) : base(service, path) + { } + public Task PromptAsync(string windowId) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "s", + member: "Prompt"); + writer.WriteString(windowId); + return writer.CreateMessage(); + } + } + public Task DismissAsync() + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "Dismiss"); + return writer.CreateMessage(); + } + } + public ValueTask WatchCompletedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "Completed", (Message m, object? s) => ReadMessage_bv(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + } + record CollectionProperties + { + public ObjectPath[] Items { get; set; } = default!; + public string Label { get; set; } = default!; + public bool Locked { get; set; } = default!; + public ulong Created { get; set; } = default!; + public ulong Modified { get; set; } = default!; + } + partial class Collection : secretsObject + { + private const string __Interface = "org.freedesktop.Secret.Collection"; + public Collection(secretsService service, ObjectPath path) : base(service, path) + { } + public Task DeleteAsync() + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "Delete"); + return writer.CreateMessage(); + } + } + public Task SearchItemsAsync(Dictionary attributes) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_ao(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "a{ss}", + member: "SearchItems"); + WriteType_aess(ref writer, attributes); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath Item, ObjectPath Prompt)> CreateItemAsync(Dictionary properties, (ObjectPath, byte[], byte[], string) secret, bool replace) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_oo(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "a{sv}(oayays)b", + member: "CreateItem"); + writer.WriteDictionary(properties); + WriteType_roayaysz(ref writer, secret); + writer.WriteBool(replace); + return writer.CreateMessage(); + } + } + public ValueTask WatchItemCreatedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "ItemCreated", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public ValueTask WatchItemDeletedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "ItemDeleted", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public ValueTask WatchItemChangedAsync(Action handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + => base.WatchSignalAsync(Service.Destination, __Interface, Path, "ItemChanged", (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + public Task SetItemsAsync(ObjectPath[] value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Items"); + writer.WriteSignature("ao"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + public Task SetLabelAsync(string value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Label"); + writer.WriteSignature("s"); + writer.WriteString(value); + return writer.CreateMessage(); + } + } + public Task SetLockedAsync(bool value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Locked"); + writer.WriteSignature("b"); + writer.WriteBool(value); + return writer.CreateMessage(); + } + } + public Task SetCreatedAsync(ulong value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Created"); + writer.WriteSignature("t"); + writer.WriteUInt64(value); + return writer.CreateMessage(); + } + } + public Task SetModifiedAsync(ulong value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Modified"); + writer.WriteSignature("t"); + writer.WriteUInt64(value); + return writer.CreateMessage(); + } + } + public Task GetItemsAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Items"), (Message m, object? s) => ReadMessage_v_ao(m, (secretsObject)s!), this); + public Task GetLabelAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Label"), (Message m, object? s) => ReadMessage_v_s(m, (secretsObject)s!), this); + public Task GetLockedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Locked"), (Message m, object? s) => ReadMessage_v_b(m, (secretsObject)s!), this); + public Task GetCreatedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Created"), (Message m, object? s) => ReadMessage_v_t(m, (secretsObject)s!), this); + public Task GetModifiedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Modified"), (Message m, object? s) => ReadMessage_v_t(m, (secretsObject)s!), this); + public Task GetPropertiesAsync() + { + return this.Connection.CallMethodAsync(CreateGetAllPropertiesMessage(__Interface), (Message m, object? s) => ReadMessage(m, (secretsObject)s!), this); + static CollectionProperties ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + { + return base.WatchPropertiesChangedAsync(__Interface, (Message m, object? s) => ReadMessage(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + static PropertyChanges ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(), invalidated = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(arrayEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "Items": invalidated.Add("Items"); break; + case "Label": invalidated.Add("Label"); break; + case "Locked": invalidated.Add("Locked"); break; + case "Created": invalidated.Add("Created"); break; + case "Modified": invalidated.Add("Modified"); break; + } + } + return invalidated?.ToArray() ?? Array.Empty(); + } + } + private static CollectionProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new CollectionProperties(); + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(arrayEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "Items": + reader.ReadSignature("ao"); + props.Items = reader.ReadArrayOfObjectPath(); + changedList?.Add("Items"); + break; + case "Label": + reader.ReadSignature("s"); + props.Label = reader.ReadString(); + changedList?.Add("Label"); + break; + case "Locked": + reader.ReadSignature("b"); + props.Locked = reader.ReadBool(); + changedList?.Add("Locked"); + break; + case "Created": + reader.ReadSignature("t"); + props.Created = reader.ReadUInt64(); + changedList?.Add("Created"); + break; + case "Modified": + reader.ReadSignature("t"); + props.Modified = reader.ReadUInt64(); + changedList?.Add("Modified"); + break; + default: + reader.ReadVariantValue(); + break; + } + } + return props; + } + } + record ItemProperties + { + public bool Locked { get; set; } = default!; + public Dictionary Attributes { get; set; } = default!; + public string Label { get; set; } = default!; + public string Type { get; set; } = default!; + public ulong Created { get; set; } = default!; + public ulong Modified { get; set; } = default!; + } + partial class Item : secretsObject + { + private const string __Interface = "org.freedesktop.Secret.Item"; + public Item(secretsService service, ObjectPath path) : base(service, path) + { } + public Task DeleteAsync() + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_o(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "Delete"); + return writer.CreateMessage(); + } + } + public Task<(ObjectPath, byte[], byte[], string)> GetSecretAsync(ObjectPath session) + { + return this.Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_roayaysz(m, (secretsObject)s!), this); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "o", + member: "GetSecret"); + writer.WriteObjectPath(session); + return writer.CreateMessage(); + } + } + public Task SetSecretAsync((ObjectPath, byte[], byte[], string) secret) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + signature: "(oayays)", + member: "SetSecret"); + WriteType_roayaysz(ref writer, secret); + return writer.CreateMessage(); + } + } + public Task SetLockedAsync(bool value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Locked"); + writer.WriteSignature("b"); + writer.WriteBool(value); + return writer.CreateMessage(); + } + } + public Task SetAttributesAsync(Dictionary value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Attributes"); + writer.WriteSignature("a{ss}"); + WriteType_aess(ref writer, value); + return writer.CreateMessage(); + } + } + public Task SetLabelAsync(string value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Label"); + writer.WriteSignature("s"); + writer.WriteString(value); + return writer.CreateMessage(); + } + } + public Task SetTypeAsync(string value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Type"); + writer.WriteSignature("s"); + writer.WriteString(value); + return writer.CreateMessage(); + } + } + public Task SetCreatedAsync(ulong value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Created"); + writer.WriteSignature("t"); + writer.WriteUInt64(value); + return writer.CreateMessage(); + } + } + public Task SetModifiedAsync(ulong value) + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(__Interface); + writer.WriteString("Modified"); + writer.WriteSignature("t"); + writer.WriteUInt64(value); + return writer.CreateMessage(); + } + } + public Task GetLockedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Locked"), (Message m, object? s) => ReadMessage_v_b(m, (secretsObject)s!), this); + public Task> GetAttributesAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Attributes"), (Message m, object? s) => ReadMessage_v_aess(m, (secretsObject)s!), this); + public Task GetLabelAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Label"), (Message m, object? s) => ReadMessage_v_s(m, (secretsObject)s!), this); + public Task GetTypeAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Type"), (Message m, object? s) => ReadMessage_v_s(m, (secretsObject)s!), this); + public Task GetCreatedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Created"), (Message m, object? s) => ReadMessage_v_t(m, (secretsObject)s!), this); + public Task GetModifiedAsync() + => this.Connection.CallMethodAsync(CreateGetPropertyMessage(__Interface, "Modified"), (Message m, object? s) => ReadMessage_v_t(m, (secretsObject)s!), this); + public Task GetPropertiesAsync() + { + return this.Connection.CallMethodAsync(CreateGetAllPropertiesMessage(__Interface), (Message m, object? s) => ReadMessage(m, (secretsObject)s!), this); + static ItemProperties ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true, ObserverFlags flags = ObserverFlags.None) + { + return base.WatchPropertiesChangedAsync(__Interface, (Message m, object? s) => ReadMessage(m, (secretsObject)s!), handler, emitOnCapturedContext, flags); + static PropertyChanges ReadMessage(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(), invalidated = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(arrayEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "Locked": invalidated.Add("Locked"); break; + case "Attributes": invalidated.Add("Attributes"); break; + case "Label": invalidated.Add("Label"); break; + case "Type": invalidated.Add("Type"); break; + case "Created": invalidated.Add("Created"); break; + case "Modified": invalidated.Add("Modified"); break; + } + } + return invalidated?.ToArray() ?? Array.Empty(); + } + } + private static ItemProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new ItemProperties(); + ArrayEnd arrayEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(arrayEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "Locked": + reader.ReadSignature("b"); + props.Locked = reader.ReadBool(); + changedList?.Add("Locked"); + break; + case "Attributes": + reader.ReadSignature("a{ss}"); + props.Attributes = ReadType_aess(ref reader); + changedList?.Add("Attributes"); + break; + case "Label": + reader.ReadSignature("s"); + props.Label = reader.ReadString(); + changedList?.Add("Label"); + break; + case "Type": + reader.ReadSignature("s"); + props.Type = reader.ReadString(); + changedList?.Add("Type"); + break; + case "Created": + reader.ReadSignature("t"); + props.Created = reader.ReadUInt64(); + changedList?.Add("Created"); + break; + case "Modified": + reader.ReadSignature("t"); + props.Modified = reader.ReadUInt64(); + changedList?.Add("Modified"); + break; + default: + reader.ReadVariantValue(); + break; + } + } + return props; + } + } + partial class Session : secretsObject + { + private const string __Interface = "org.freedesktop.Secret.Session"; + public Session(secretsService service, ObjectPath path) : base(service, path) + { } + public Task CloseAsync() + { + return this.Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: __Interface, + member: "Close"); + return writer.CreateMessage(); + } + } + } + partial class secretsService + { + public Tmds.DBus.Protocol.Connection Connection { get; } + public string Destination { get; } + public secretsService(Tmds.DBus.Protocol.Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + public Daemon CreateDaemon(string path) => new Daemon(this, path); + public Secret CreateSecret(string path) => new Secret(this, path); + public InternalUnsupportedGuiltRiddenInterface CreateInternalUnsupportedGuiltRiddenInterface(string path) => new InternalUnsupportedGuiltRiddenInterface(this, path); + public Service CreateService(string path) => new Service(this, path); + public Prompt CreatePrompt(string path) => new Prompt(this, path); + public Collection CreateCollection(string path) => new Collection(this, path); + public Item CreateItem(string path) => new Item(this, path); + public Session CreateSession(string path) => new Session(this, path); + } + class secretsObject + { + public secretsService Service { get; } + public ObjectPath Path { get; } + protected Tmds.DBus.Protocol.Connection Connection => Service.Connection; + protected secretsObject(secretsService service, ObjectPath path) + => (Service, Path) = (service, path); + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + var writer = this.Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + destination: Service.Destination, + path: Path, + @interface: "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext, ObserverFlags flags) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return this.Connection.AddMatchAsync(rule, reader, + (Exception? ex, PropertyChanges changes, object? rs, object? hs) => ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext, flags); + } + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext, ObserverFlags flags) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return this.Connection.AddMatchAsync(rule, reader, + (Exception? ex, TArg arg, object? rs, object? hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext, flags); + } + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext, ObserverFlags flags) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return this.Connection.AddMatchAsync(rule, (Message message, object? state) => null!, + (Exception? ex, object v, object? rs, object? hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext, flags); + } + protected static Dictionary ReadMessage_aess(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadType_aess(ref reader); + } + protected static string ReadMessage_s(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadString(); + } + protected static (uint, Dictionary) ReadMessage_uaesv(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadDictionaryOfStringToVariantValue(); + return (arg0, arg1); + } + protected static uint ReadMessage_v_u(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("u"); + return reader.ReadUInt32(); + } + protected static ObjectPath ReadMessage_o(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadObjectPath(); + } + protected static (VariantValue, ObjectPath) ReadMessage_vo(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadVariantValue(); + var arg1 = reader.ReadObjectPath(); + return (arg0, arg1); + } + protected static (ObjectPath, ObjectPath) ReadMessage_oo(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadObjectPath(); + var arg1 = reader.ReadObjectPath(); + return (arg0, arg1); + } + protected static (ObjectPath[], ObjectPath[]) ReadMessage_aoao(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadArrayOfObjectPath(); + var arg1 = reader.ReadArrayOfObjectPath(); + return (arg0, arg1); + } + protected static (ObjectPath[], ObjectPath) ReadMessage_aoo(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadArrayOfObjectPath(); + var arg1 = reader.ReadObjectPath(); + return (arg0, arg1); + } + protected static Dictionary ReadMessage_aeoroayaysz(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadType_aeoroayaysz(ref reader); + } + protected static ObjectPath[] ReadMessage_v_ao(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("ao"); + return reader.ReadArrayOfObjectPath(); + } + protected static (bool, VariantValue) ReadMessage_bv(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadBool(); + var arg1 = reader.ReadVariantValue(); + return (arg0, arg1); + } + protected static ObjectPath[] ReadMessage_ao(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadArrayOfObjectPath(); + } + protected static string ReadMessage_v_s(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("s"); + return reader.ReadString(); + } + protected static bool ReadMessage_v_b(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("b"); + return reader.ReadBool(); + } + protected static ulong ReadMessage_v_t(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("t"); + return reader.ReadUInt64(); + } + protected static (ObjectPath, byte[], byte[], string) ReadMessage_roayaysz(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + return ReadType_roayaysz(ref reader); + } + protected static Dictionary ReadMessage_v_aess(Message message, secretsObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("a{ss}"); + return ReadType_aess(ref reader); + } + protected static Dictionary ReadType_aess(ref Reader reader) + { + Dictionary dictionary = new(); + ArrayEnd dictEnd = reader.ReadDictionaryStart(); + while (reader.HasNext(dictEnd)) + { + var key = reader.ReadString(); + var value = reader.ReadString(); + dictionary[key] = value; + } + return dictionary; + } + protected static Dictionary ReadType_aeoroayaysz(ref Reader reader) + { + Dictionary dictionary = new(); + ArrayEnd dictEnd = reader.ReadDictionaryStart(); + while (reader.HasNext(dictEnd)) + { + var key = reader.ReadObjectPath(); + var value = ReadType_roayaysz(ref reader); + dictionary[key] = value; + } + return dictionary; + } + protected static (ObjectPath, byte[], byte[], string) ReadType_roayaysz(ref Reader reader) + { + return (reader.ReadObjectPath(), reader.ReadArrayOfByte(), reader.ReadArrayOfByte(), reader.ReadString()); + } + protected static void WriteType_roayaysz(ref MessageWriter writer, (ObjectPath, byte[], byte[], string) value) + { + writer.WriteStructureStart(); + writer.WriteObjectPath(value.Item1); + writer.WriteArray(value.Item2); + writer.WriteArray(value.Item3); + writer.WriteString(value.Item4); + } + protected static void WriteType_aess(ref MessageWriter writer, Dictionary value) + { + ArrayStart arrayStart = writer.WriteDictionaryStart(); + foreach (var item in value) + { + writer.WriteDictionaryEntryStart(); + writer.WriteString(item.Key); + writer.WriteString(item.Value); + } + writer.WriteDictionaryEnd(arrayStart); + } + } + class PropertyChanges + { + public PropertyChanges(TProperties properties, string[] invalidated, string[] changed) + => (Properties, Invalidated, Changed) = (properties, invalidated, changed); + public TProperties Properties { get; } + public string[] Invalidated { get; } + public string[] Changed { get; } + public bool HasChanged(string property) => Array.IndexOf(Changed, property) != -1; + public bool IsInvalidated(string property) => Array.IndexOf(Invalidated, property) != -1; + } +} diff --git a/Duplicati/Library/SecretProvider/LibSecretLinuxProvider.cs b/Duplicati/Library/SecretProvider/LibSecretLinuxProvider.cs new file mode 100644 index 000000000..16cfb664a --- /dev/null +++ b/Duplicati/Library/SecretProvider/LibSecretLinuxProvider.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Reflection; +using System.Web; +using Duplicati.Library.Interface; +using Duplicati.Library.SecretProvider.LibSecret; +using Duplicati.Library.Utility; +namespace Duplicati.Library.SecretProvider; + +/// +/// Secret provider that reads secrets from libsecret on Linux +/// +public class LibSecretLinuxProvider : ISecretProvider +{ + /// + public string Key => "libsecret"; + + /// + public string DisplayName => Strings.LibSecretLinuxProvider.DisplayName; + + /// + public string Description => Strings.LibSecretLinuxProvider.Description; + + /// + /// The configuration for the secret provider + /// + private class LibSecretConfig : ICommandLineArgumentMapper + { + /// + /// The collection to use + /// + public string Collection { get; set; } = "default"; + + /// + /// Whether the collection name is case sensitive + /// + public bool CaseSensitive { get; set; } + + /// + /// Gets the command line argument description for the given name + /// + /// The name of the argument + /// The description or null if not found + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(Collection) => new CommandLineArgumentDescriptionAttribute() { Name = "collection", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.LibSecretLinuxProvider.CollectionDescriptionShort, LongDescription = Strings.LibSecretLinuxProvider.CollectionDescriptionLong }, + nameof(CaseSensitive) => new CommandLineArgumentDescriptionAttribute() { Name = "case-sensitive", Type = CommandLineArgument.ArgumentType.Boolean, ShortDescription = Strings.LibSecretLinuxProvider.CaseSensitiveDescriptionShort, LongDescription = Strings.LibSecretLinuxProvider.CaseSensitiveDescriptionLong }, + _ => null, + }; + + /// + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + /// The configuration for the secret provider; null if not initialized + /// + private LibSecretConfig? _cfg; + + /// + /// The secret collection + /// + private SecretCollection? _collection; + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new LibSecretConfig()) + .ToList(); + + /// + public async Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + if (!OperatingSystem.IsLinux()) + throw new PlatformNotSupportedException("LibSecret is only supported on Linux"); + + var args = HttpUtility.ParseQueryString(config.Query); + _cfg = CommandLineArgumentMapper.ApplyArguments(new LibSecretConfig(), args); + if (string.IsNullOrWhiteSpace(_cfg.Collection)) + throw new UserInformationException("The collection must be specified", "CollectionRequired"); + + _collection = await SecretCollection.CreateAsync(_cfg.Collection, cancellationToken).ConfigureAwait(false); + await _collection.UnlockAsync().ConfigureAwait(false); + } + + /// + public Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_cfg is null || _collection is null) + throw new InvalidOperationException("The secret provider has not been initialized"); + if (!OperatingSystem.IsLinux()) + throw new PlatformNotSupportedException("LibSecret is only supported on Linux"); + + return _collection.GetSecretsAsync(keys, _cfg.CaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); + } +} diff --git a/Duplicati/Library/SecretProvider/MacOSKeyChainProvider.cs b/Duplicati/Library/SecretProvider/MacOSKeyChainProvider.cs new file mode 100644 index 000000000..6e7b477ce --- /dev/null +++ b/Duplicati/Library/SecretProvider/MacOSKeyChainProvider.cs @@ -0,0 +1,191 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Diagnostics; +using System.Reflection; +using System.Web; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Implementation of a secret provider that reads secrets from the MacOS keychain +/// +public class MacOSKeyChainProvider : ISecretProvider +{ + /// + public string Key => "keychain"; + + /// + public string DisplayName => Strings.MacOSKeyChainProvider.DisplayName; + + /// + public string Description => Strings.MacOSKeyChainProvider.Description; + + /// + /// The type of password to get + /// + private enum PasswordType + { + /// + /// A generic password + /// + Generic, + /// + /// An internet password + /// + Internet + } + + /// + /// The settings for the keychain + /// + private class KeyChainSettings : ICommandLineArgumentMapper + { + /// + /// The service name + /// + public string? Service { get; set; } + /// + /// The account name + /// + public string? Account { get; set; } + + /// + /// Gets or sets a value indicating whether to use internet passwords as opposed to generic passwords + /// + public PasswordType Type { get; set; } = PasswordType.Generic; + + /// + /// Gets the command line argument description for a member + /// + /// The name of the member + /// The command line argument description + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(Service) => new CommandLineArgumentDescriptionAttribute() { Name = "service", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.MacOSKeyChainProvider.ServiceDescriptionShort, LongDescription = Strings.MacOSKeyChainProvider.ServiceDescriptionLong }, + nameof(Account) => new CommandLineArgumentDescriptionAttribute() { Name = "account", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.MacOSKeyChainProvider.AccountDescriptionShort, LongDescription = Strings.MacOSKeyChainProvider.AccountDescriptionLong }, + nameof(Type) => new CommandLineArgumentDescriptionAttribute() { Name = "type", Type = CommandLineArgument.ArgumentType.Enumeration, ShortDescription = Strings.MacOSKeyChainProvider.TypeDescriptionShort, LongDescription = Strings.MacOSKeyChainProvider.TypeDescriptionLong }, + _ => null + }; + + /// + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + /// The settings for the keychain; null if not initialized + /// + private KeyChainSettings? _settings; + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new KeyChainSettings()) + .ToList(); + + + /// + public Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + if (!OperatingSystem.IsMacOS()) + throw new PlatformNotSupportedException("The MacOSKeyChainProvider is only supported on MacOS"); + + var args = HttpUtility.ParseQueryString(config.Query); + _settings = CommandLineArgumentMapper.ApplyArguments(new KeyChainSettings(), args); + + return Task.CompletedTask; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_settings is null) + throw new InvalidOperationException("The MacOSKeyChainProvider has not been initialized"); + + var result = new Dictionary(); + foreach (var key in keys) + { + var value = await GetStringAsync(key, _settings, cancellationToken).ConfigureAwait(false); + result[key] = value; + } + + return result; + } + + /// + /// Gets a string from the keychain using the security command + /// + /// The name of the key to get + /// The settings for the keychain + /// The cancellation token + /// The value of the key + private static async Task GetStringAsync(string name, KeyChainSettings settings, CancellationToken cancellationToken) + { + var psi = new ProcessStartInfo("security") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + if (settings.Type == PasswordType.Internet) + psi.ArgumentList.Add("find-internet-password"); + else + psi.ArgumentList.Add("find-generic-password"); + + if (!string.IsNullOrWhiteSpace(settings.Service)) + { + psi.ArgumentList.Add("-s"); + psi.ArgumentList.Add(settings.Service); + } + + if (!string.IsNullOrWhiteSpace(settings.Account)) + { + psi.ArgumentList.Add("-a"); + psi.ArgumentList.Add(settings.Account); + } + + psi.ArgumentList.Add("-w"); + psi.ArgumentList.Add("-l"); + psi.ArgumentList.Add(name); + + using var process = Process.Start(psi); + if (process is null) + throw new InvalidOperationException("Failed to start the security process"); + + var output = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false); + if (process.ExitCode != 0) + throw new UserInformationException($"Item not found in keychain: {name}", "KeyChainItemMissing"); + + output = output.Trim(); + if (string.IsNullOrWhiteSpace(output)) + throw new UserInformationException($"The key '{name}' returned an empty value", "KeyChainItemEmpty"); + + if (output.IndexOfAny(['\r', '\n']) >= 0) + throw new UserInformationException($"The key '{name}' returned a multi-line value", "KeyChainItemMultiLine"); + + return output; + } +} diff --git a/Duplicati/Library/SecretProvider/SecretProviderModules.cs b/Duplicati/Library/SecretProvider/SecretProviderModules.cs new file mode 100644 index 000000000..4b1f18e5e --- /dev/null +++ b/Duplicati/Library/SecretProvider/SecretProviderModules.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SecretProvider; + +public static class SecretProviderModules +{ + /// + /// The list of all built-in secret provider modules + /// + public static IReadOnlyList Modules => new ISecretProvider?[] { + new EnvironmentSecretProvider(), + new FileSecretProvider(), + new AWSSecretProvider(), + new HCVaultSecretProvider(), + new GCSSecretProvider(), + new AzureSecretProvider(), + OperatingSystem.IsWindows() ? new WindowsCredentialManagerProvider() : null, + OperatingSystem.IsMacOS() ? new MacOSKeyChainProvider() : null, + OperatingSystem.IsLinux() ? new UnixPassProvider() : null, + OperatingSystem.IsLinux() ? new LibSecretLinuxProvider() : null, + } + .WhereNotNull() + .ToList(); +} diff --git a/Duplicati/Library/SecretProvider/Strings.cs b/Duplicati/Library/SecretProvider/Strings.cs new file mode 100644 index 000000000..710f62416 --- /dev/null +++ b/Duplicati/Library/SecretProvider/Strings.cs @@ -0,0 +1,201 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Localization.Short; + +namespace Duplicati.Library.SecretProvider.Strings; + +internal static class EnvironmentSecretProvider +{ + public static string DisplayName => LC.L("Secrets from environment variables"); + public static string Description => LC.L( +@"Secret provider that reads secrets from environment variables. +Example use: + env: + +For environment variables, the lookup is case-insensitive. +"); +} + +internal static class FileSecretProvider +{ + public static string DisplayName => LC.L("Secrets from a file"); + public static string Description => LC.L( +@"Secret provider that reads secrets from a file +Example use: + file://path/to/file?passphrase=secret + +The file should be a JSON-encoded object with the secrets as key-value pairs. +If the file is not encrypted with a passphrase, the passphrase parameter can be omitted. +The file must be encrypted with AESCrypt if encryption is desired. +For file-based secrets, the lookup is case-insensitive. +"); + + public static string PassphraseDescriptionShort => LC.L(@"The decryption passphrase"); + public static string PassphraseDescriptionLong => LC.L(@"The passphrase to use for decrypting the file with secrets"); +} + +internal static class AWSSecretProvider +{ + public static string DisplayName => LC.L("Secrets from AWS Secrets Manager"); + public static string Description => LC.L( +@"Secret provider that reads secrets from AWS Secrets Manager +Example use: + aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2 + +Either the region endpoint or the service URL must be provided. +Each secret is retrieved in the order specified until all requested keys are found. +Each secret must be a key-value pair type secret. +"); + public static string AccessKeyDescriptionShort => LC.L(@"The AWS access key"); + public static string AccessKeyDescriptionLong(string envname) => LC.L(@$"The access key to use for authentication with AWS. Can also be supplied via the environment variable {envname}"); + public static string SecretKeyDescriptionShort => LC.L(@"The AWS secret key"); + public static string SecretKeyDescriptionLong(string envname) => LC.L(@$"The secret key to use for authentication with AWS. Can also be supplied via the environment variable {envname}"); + public static string RegionEndpointDescriptionShort => LC.L(@"The AWS region endpoint"); + public static string RegionEndpointDescriptionLong(string envname) => LC.L(@$"The region endpoint to use for communication with AWS. Can also be supplied via the environment variable {envname}"); + public static string ServiceURLDescriptionShort => LC.L(@"The AWS service URL"); + public static string ServiceURLDescriptionLong(string envname) => LC.L(@$"The service URL to use for communication with AWS. Can also be supplied via the environment variable {envname}"); + public static string SecretsDescriptionShort => LC.L(@"The secret names to retrieve"); + public static string SecretsDescriptionLong => LC.L(@"The names of the secrets to retrieve from AWS Secrets Manager, separated by semicolons or commas. Secrets are retrieved in the order specified until all requested keys are found."); + public static string CaseSensitiveDescriptionShort => LC.L(@"Case sensitivity"); + public static string CaseSensitiveDescriptionLong => LC.L(@"Whether to use case-sensitive matching for secret names"); +} + +internal static class HCVaultSecretProvider +{ + public static string DisplayName => LC.L("Secrets from HashiCorp Vault"); + public static string Description => LC.L( +@"Secret provider that reads secrets from HashiCorp Vault +Example use: + hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2 + +The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault. +Each vault secret is retrieved in the order specified until all requested keys are found. +"); + public static string TokenDescriptionShort => LC.L(@"The Vault token"); + public static string TokenDescriptionLong => LC.L(@"The token to use for authentication with HashiCorp Vault"); + public static string ProtocolDescriptionShort => LC.L(@"The vault server protocol"); + public static string ProtocolDescriptionLong => LC.L(@"The protocol to use for communication with the Vault server"); + public static string SecretsDescriptionShort => LC.L(@"Secret entries"); + public static string SecretsDescriptionLong => LC.L(@"The names of the secrets (aka Vault Apps) to retrieve from the Vault, separated by semicolons or commas. Secrets are retrieved in the order specified until all requested keys are found."); + public static string ClientIdDescriptionShort => LC.L(@"The client ID"); + public static string ClientIdDescriptionLong(string envname) => LC.L(@$"The client ID to use for authentication with HashiCorp Vault. Can also be supplied via the environment variable {envname}"); + public static string ClientSecretDescriptionShort => LC.L(@"The client secret"); + public static string ClientSecretDescriptionLong(string envname) => LC.L(@$"The client secret to use for authentication with HashiCorp Vault. Can also be supplied via the environment variable {envname}"); + public static string MountPointDescriptionShort => LC.L(@"The mount point"); + public static string MountPointDescriptionLong => LC.L(@"The mount point for the secrets in the Vault"); + public static string CaseSensitiveDescriptionShort => LC.L(@"Case sensitivity"); + public static string CaseSensitiveDescriptionLong => LC.L(@"Whether to use case-sensitive matching for secret names"); +} + +internal static class GCSSecretProvider +{ + public static string DisplayName => LC.L("Secrets from Google Cloud Storage Secret Manager"); + public static string Description => LC.L( +@"Secret provider that reads secrets from Google Cloud Storage Secret Manager +Example use: + gcs://?project-id=...&version=latest + +If the access token is not supplied, the default GCS credentials from the machine will be used. +Use the accesstoken property to specify a custom access token. + +"); + public static string ApiTypeDescriptionShort => LC.L(@"The API type to use"); + public static string ApiTypeDescriptionLong => LC.L(@"The type of API to use for communication with Google Cloud Storage"); + public static string ProjectIdDescriptionShort => LC.L(@"The GCP project ID"); + public static string ProjectIdDescriptionLong => LC.L(@"The ID of the Google Cloud Platform project to use for authentication"); + public static string AccessTokenDescriptionShort => LC.L(@"The access token"); + public static string AccessTokenDescriptionLong => LC.L(@"The access token to use for authentication with Google Cloud Storage. If not supplied, the default GCS credentials from the machine will be used."); + public static string VersionDescriptionShort => LC.L(@"The secret version to get (or alias)"); + public static string VersionDescriptionLong => LC.L(@"The version of the secret to retrieve from Google Cloud Storage. If not supplied, the latest version will be used."); +} + +internal static class AzureSecretProvider +{ + public static string DisplayName => LC.L("Secrets from Azure Key Vault"); + public static string Description => LC.L( +@"Secret provider that reads secrets from Azure Key Vault +Example use: + az://?keyvault-name=...&auth-type=ManagedIdentity + +If a client ID and secret are provided, use the auth type ClientSecret: + az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=... + +If a username and password are provided, use the auth type UsernamePassword: + az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=... + +If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI. +"); + public static string KeyVaultNameDescriptionShort => LC.L(@"The name of the Azure Key Vault"); + public static string KeyVaultNameDescriptionLong => LC.L(@"The name of the Azure Key Vault to use for retrieving secrets"); + public static string ConnectionTypeDescriptionShort => LC.L(@"The connection type to use"); + public static string ConnectionTypeDescriptionLong => LC.L(@"The type of connection to use for communication with Azure Key Vault"); + public static string VaultUriDescriptionShort => LC.L(@"The URI of the Azure Key Vault"); + public static string VaultUriDescriptionLong => LC.L(@"The URI of the Azure Key Vault to use for retrieving secrets"); + public static string TenantIdDescriptionShort => LC.L(@"The Azure tenant ID"); + public static string TenantIdDescriptionLong => LC.L(@"The ID of the Azure tenant to use for authentication"); + public static string ClientIdDescriptionShort => LC.L(@"The Azure client ID"); + public static string ClientIdDescriptionLong => LC.L(@"The ID of the Azure client to use for authentication"); + public static string ClientSecretDescriptionShort => LC.L(@"The Azure client secret"); + public static string ClientSecretDescriptionLong => LC.L(@"The secret to use for authentication with Azure"); + public static string UsernameDescriptionShort => LC.L(@"The Azure username"); + public static string UsernameDescriptionLong => LC.L(@"The username to use for authentication with Azure"); + public static string PasswordDescriptionShort => LC.L(@"The Azure password"); + public static string PasswordDescriptionLong => LC.L(@"The password to use for authentication with Azure"); + public static string AuthenticationTypeDescriptionShort => LC.L(@"The authentication type to use"); + public static string AuthenticationTypeDescriptionLong => LC.L(@"The type of authentication to use for communication with Azure Key Vault"); +} + +internal static class MacOSKeyChainProvider +{ + public static string DisplayName => LC.L("Secrets from macOS Keychain"); + public static string Description => LC.L("Secret provider that reads secrets from the macOS Keychain"); + public static string ServiceDescriptionShort => LC.L("The service name"); + public static string ServiceDescriptionLong => LC.L("The service name to use for retrieving secrets from the macOS Keychain"); + public static string AccountDescriptionShort => LC.L("The account name"); + public static string AccountDescriptionLong => LC.L("The account name to use for retrieving secrets from the macOS Keychain"); + public static string TypeDescriptionShort => LC.L("The type of password to get"); + public static string TypeDescriptionLong => LC.L("The type of password to get from the macOS Keychain"); +} + +internal static class UnixPassProvider +{ + public static string DisplayName => LC.L("Secrets from Unix pass"); + public static string Description => LC.L("Secret provider that reads secrets from the Unix pass password manager"); + public static string PassCommandDescriptionShort => LC.L("The pass command"); + public static string PassCommandDescriptionLong => LC.L("The command to use for retrieving secrets from the Unix pass password manager"); +} + +internal static class WindowsCredentialManagerProvider +{ + public static string DisplayName => LC.L("Secrets from Windows Credential Manager"); + public static string Description => LC.L("Secret provider that reads secrets from the Windows Credential Manager"); +} + +internal static class LibSecretLinuxProvider +{ + public static string DisplayName => LC.L("Secrets from libsecret"); + public static string Description => LC.L("Secret provider that reads secrets from the libsecret password manager"); + public static string CollectionDescriptionShort => LC.L("The collection name"); + public static string CollectionDescriptionLong => LC.L("The collection name to use for retrieving secrets from the libsecret password manager"); + public static string CaseSensitiveDescriptionShort => LC.L("Case sensitivity"); + public static string CaseSensitiveDescriptionLong => LC.L("Whether to use case-sensitive matching for secret names"); +} + diff --git a/Duplicati/Library/SecretProvider/UnixPassProvider.cs b/Duplicati/Library/SecretProvider/UnixPassProvider.cs new file mode 100644 index 000000000..8f38b2e02 --- /dev/null +++ b/Duplicati/Library/SecretProvider/UnixPassProvider.cs @@ -0,0 +1,139 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Diagnostics; +using System.Reflection; +using System.Web; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Implementation of a secret provider that reads secrets from the Unix pass utility +/// +public class UnixPassProvider : ISecretProvider +{ + /// + public string Key => "pass"; + + /// + public string DisplayName => Strings.UnixPassProvider.DisplayName; + + /// + public string Description => Strings.UnixPassProvider.Description; + + /// + public IList SupportedCommands + => CommandLineArgumentMapper.MapArguments(new UnixPassProviderConfig()) + .ToList(); + + /// + /// The configuration for the secret provider; null if not initialized + /// + private UnixPassProviderConfig? _config; + + /// + /// The configuration for the Unix pass provider + /// + private class UnixPassProviderConfig : ICommandLineArgumentMapper + { + /// + /// The command to run to get a password + /// + public string PassCommand { get; set; } = "pass"; + + /// + /// Gets the command line argument description for a member + /// + /// The name of the member + /// The command line argument description, or null if the member is not a command line argument + public static CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(string name) + => name switch + { + nameof(PassCommand) => new CommandLineArgumentDescriptionAttribute() { Name = "pass-command", Type = CommandLineArgument.ArgumentType.String, ShortDescription = Strings.UnixPassProvider.PassCommandDescriptionShort, LongDescription = Strings.UnixPassProvider.PassCommandDescriptionLong }, + _ => null + }; + + /// + CommandLineArgumentDescriptionAttribute? ICommandLineArgumentMapper.GetCommandLineArgumentDescription(MemberInfo mi) + => GetCommandLineArgumentDescription(mi.Name); + } + + /// + public Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + var args = HttpUtility.ParseQueryString(config.Query); + _config = CommandLineArgumentMapper.ApplyArguments(new UnixPassProviderConfig(), args); + return Task.CompletedTask; + } + + /// + public async Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (_config is null) + throw new InvalidOperationException("The UnixPassProvider has not been initialized"); + + var result = new Dictionary(); + foreach (var key in keys) + { + var value = await GetString(key, _config, cancellationToken).ConfigureAwait(false); + result[key] = value; + } + + return result; + } + + /// + /// Gets a string from the pass utility + /// + /// The name of the secret + /// The configuration for the Unix pass provider + /// The cancellation token + /// The secret + private static async Task GetString(string name, UnixPassProviderConfig config, CancellationToken cancellationToken) + { + var psi = new ProcessStartInfo(config.PassCommand) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + psi.ArgumentList.Add("show"); + psi.ArgumentList.Add(name); + + using var process = Process.Start(psi); + if (process is null) + throw new InvalidOperationException("Failed to start pass"); + + var output = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false); + var error = await process.StandardError.ReadToEndAsync().ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(error)) + throw new UserInformationException($"Error running pass: {error}", "PassError"); + if (process.ExitCode != 0) + throw new UserInformationException($"pass failed with exit code {process.ExitCode}", "PassFailed"); + if (string.IsNullOrWhiteSpace(output)) + throw new UserInformationException("pass returned no output", "PassNoOutput"); + + return output.Trim(); + } +} diff --git a/Duplicati/Library/SecretProvider/WindowsCredentialManagerProvider.cs b/Duplicati/Library/SecretProvider/WindowsCredentialManagerProvider.cs new file mode 100644 index 000000000..379ee089f --- /dev/null +++ b/Duplicati/Library/SecretProvider/WindowsCredentialManagerProvider.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using Meziantou.Framework.Win32; + +namespace Duplicati.Library.SecretProvider; + +/// +/// Secret provider that reads secrets from Windows Credential Manager +/// +public class WindowsCredentialManagerProvider : ISecretProvider +{ + /// + public string Key => "wincred"; + + /// + public string DisplayName => Strings.WindowsCredentialManagerProvider.DisplayName; + + /// + public string Description => Strings.WindowsCredentialManagerProvider.Description; + + /// + public IList SupportedCommands => []; + + /// + public Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + if (!OperatingSystem.IsWindows()) + throw new PlatformNotSupportedException("Windows Credential Manager is only supported on Windows"); + if (!OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600)) + throw new PlatformNotSupportedException("Windows Credential Manager is only supported on Windows XP or later"); + + return Task.CompletedTask; + } + + /// + public Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + if (!OperatingSystem.IsWindowsVersionAtLeast(5, 1, 2600)) + throw new PlatformNotSupportedException("Windows Credential Manager is only supported on Windows"); + + var result = new Dictionary(); + foreach (var key in keys) + { + var cred = CredentialManager.ReadCredential(key); + var value = cred?.Password; + if (string.IsNullOrWhiteSpace(value)) + throw new KeyNotFoundException($"The key '{key}' was not found"); + + result[key] = value; + } + + return Task.FromResult(result); + } +} diff --git a/Duplicati/Library/Snapshots/Duplicati.Library.Snapshots.csproj b/Duplicati/Library/Snapshots/Duplicati.Library.Snapshots.csproj index 14c86421c..a166310db 100644 --- a/Duplicati/Library/Snapshots/Duplicati.Library.Snapshots.csproj +++ b/Duplicati/Library/Snapshots/Duplicati.Library.Snapshots.csproj @@ -2,11 +2,13 @@ net8.0 - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + Duplicati.Library.Snapshots + @@ -14,7 +16,7 @@ - + diff --git a/Duplicati/Library/Snapshots/ISnapshotService.cs b/Duplicati/Library/Snapshots/ISnapshotService.cs index be6d7af44..bebf5c92a 100644 --- a/Duplicati/Library/Snapshots/ISnapshotService.cs +++ b/Duplicati/Library/Snapshots/ISnapshotService.cs @@ -1,27 +1,32 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable using System; using System.Collections.Generic; using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; namespace Duplicati.Library.Snapshots { @@ -31,12 +36,28 @@ namespace Duplicati.Library.Snapshots public interface ISnapshotService : IDisposable { /// - /// Enumerates all files and folders in the snapshot, restricted to sources + /// Gets the source entries /// - /// Sources to enumerate - /// The callback to invoke with each found path - /// The callback used to report errors - IEnumerable EnumerateFilesAndFolders(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate callback, Utility.Utility.ReportAccessError errorCallback); + IEnumerable SourceEntries { get; } + + /// + /// Enumerates the root source files and folders + /// + /// The source files and folders + IEnumerable EnumerateFilesystemEntries(); + /// + /// Enumerates the files and folders in a given folder + /// + /// The parent folder to enumerate + /// The files and folders in the parent folder + IEnumerable EnumerateFilesystemEntries(ISourceProviderEntry parent); + /// + /// Gets a filesystem entry for a given path + /// + /// The path to get + /// True if the path is a folder + /// The filesystem entry + ISourceProviderEntry? GetFilesystemEntry(string path, bool isFolder); /// /// Gets the last write time of a given file in UTC @@ -59,6 +80,14 @@ namespace Duplicati.Library.Snapshots /// An open filestream that can be read and seeked Stream OpenRead(string localPath); + /// + /// Opens a file for reading + /// + /// The full path to the file in non-snapshot format + /// A token that can be used to cancel the operation + /// An open filestream that can be read and seeked + Task OpenReadAsync(string localPath, CancellationToken cancellationToken); + /// /// Returns the size of a file /// @@ -79,29 +108,28 @@ namespace Duplicati.Library.Snapshots /// The file attributes /// The file or folder to examine FileAttributes GetAttributes(string localPath); - + /// /// Gets the metadata for the given file or folder /// /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink); - + Dictionary GetMetadata(string localPath, bool isSymlink); + /// /// Gets a value indicating if the path points to a block device /// /// true if this instance is a block device; otherwise, false. /// The file or folder to examine bool IsBlockDevice(string localPath); - + /// /// Gets a unique hardlink target ID /// /// The hardlink ID /// The file or folder to examine - string HardlinkTargetID(string localPath); + string? HardlinkTargetID(string localPath); /// /// Converts a snapshot path to a local path @@ -130,17 +158,5 @@ namespace Duplicati.Library.Snapshots /// The local path /// True if folder exists, false otherwise bool DirectoryExists(string localFolderPath); - - /// - /// Tests if a file or folder exists in the snapshot - /// - /// The local path - /// True if file or folder exists, false otherwise - bool PathExists(string localFileOrFolderPath); - - /// - /// Is true if the actual service is a real snapshot service - /// - bool IsSnapshot { get; } } } diff --git a/Duplicati/Library/Snapshots/LinuxSnapshot.cs b/Duplicati/Library/Snapshots/LinuxSnapshot.cs index 90ad78390..83b786ddc 100644 --- a/Duplicati/Library/Snapshots/LinuxSnapshot.cs +++ b/Duplicati/Library/Snapshots/LinuxSnapshot.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,9 +23,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.Versioning; using System.Text; using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; namespace Duplicati.Library.Snapshots { @@ -40,7 +42,7 @@ namespace Duplicati.Library.Snapshots [SupportedOSPlatform("macOS")] public sealed class LinuxSnapshot : SnapshotBase { - /// + /// /// The tag used for logging messages /// public static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(WindowsSnapshot)); @@ -59,7 +61,9 @@ namespace Duplicati.Library.Snapshots /// Constructs a new snapshot module using LVM /// /// The list of folders to create snapshots for - public LinuxSnapshot(IEnumerable sources) + /// A flag indicating if symlinks should be followed + public LinuxSnapshot(IEnumerable sources, bool followSymlinks) + : base(followSymlinks) { try { @@ -79,7 +83,7 @@ namespace Duplicati.Library.Snapshots m_entries.Add(new KeyValuePair(path, snap)); } - m_snapShots = new List(snaps.Values); + m_snapShots = [.. snaps.Values]; // We have all the snapshots that we need, lets activate them foreach (var snap in m_snapShots) @@ -94,15 +98,35 @@ namespace Duplicati.Library.Snapshots { Dispose(); } - catch (Exception ex) + catch (Exception ex) { - Logging.Log.WriteVerboseMessage(LOGTAG, "SnapshotCleanupError", ex, "Failed to clean up after error"); + Logging.Log.WriteVerboseMessage(LOGTAG, "SnapshotCleanupError", ex, "Failed to clean up after error"); } throw; } } + /// + /// Gets the source folders + /// + public override IEnumerable SourceEntries => m_entries.Select(x => x.Key); + + /// + /// Enumerates the root source files and folders + /// + /// The source files and folders + public override IEnumerable EnumerateFilesystemEntries() + { + foreach (var sourceEntry in SourceEntries) + { + if (DirectoryExists(sourceEntry) || sourceEntry.EndsWith(System.IO.Path.DirectorySeparatorChar)) + yield return new SnapshotSourceFileEntry(this, Util.AppendDirSeparator(sourceEntry), true, true); + else + yield return new SnapshotSourceFileEntry(this, sourceEntry, false, true); + } + } + /// protected override void Dispose(bool disposing) { @@ -111,10 +135,10 @@ namespace Duplicati.Library.Snapshots if (disposing) { // Attempt to clean out as many as possible - foreach(var s in m_snapShots) + foreach (var s in m_snapShots) { try { s.Dispose(); } - catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "SnapshotCloseError", ex, "Failed to close a snapshot"); } + catch (Exception ex) { Logging.Log.WriteVerboseMessage(LOGTAG, "SnapshotCloseError", ex, "Failed to close a snapshot"); } } } @@ -466,7 +490,7 @@ namespace Duplicati.Library.Snapshots /// The symlink target public override string GetSymlinkTarget(string localPath) { - return SystemIO.IO_SYS.GetSymlinkTarget(ConvertToSnapshotPath(localPath)); + return SystemIO.IO_OS.GetSymlinkTarget(ConvertToSnapshotPath(localPath)); } /// @@ -475,10 +499,9 @@ namespace Duplicati.Library.Snapshots /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - public override Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink) + public override Dictionary GetMetadata(string localPath, bool isSymlink) { - return SystemIO.IO_SYS.GetMetadata(ConvertToSnapshotPath(localPath), isSymlink, followSymlink); + return SystemIO.IO_OS.GetMetadata(ConvertToSnapshotPath(localPath), isSymlink, FollowSymlinks); } /// @@ -500,8 +523,8 @@ namespace Duplicati.Library.Snapshots default: return true; } - } - catch + } + catch { if (!System.IO.File.Exists(SystemIOLinux.NormalizePath(localPath))) return false; @@ -509,7 +532,7 @@ namespace Duplicati.Library.Snapshots throw; } } - + /// /// Gets a unique hardlink target ID /// @@ -518,10 +541,10 @@ namespace Duplicati.Library.Snapshots public override string HardlinkTargetID(string localPath) { var snapshotPath = ConvertToSnapshotPath(localPath); - + if (PosixFile.GetHardlinkCount(snapshotPath) <= 1) return null; - + return PosixFile.GetInodeTargetID(snapshotPath); } @@ -537,9 +560,6 @@ namespace Duplicati.Library.Snapshots return FindSnapshotByLocalPath(localPath).ConvertToSnapshotPath(localPath); } - /// - public override bool IsSnapshot => true; - #endregion } } diff --git a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs index 2408dea40..de21cec98 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotLinux.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotLinux.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,6 +25,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; using Microsoft.Win32.SafeHandles; namespace Duplicati.Library.Snapshots @@ -65,15 +66,43 @@ namespace Duplicati.Library.Snapshots /// private readonly bool m_ignoreAdvisoryLocks; + /// + /// The list of folders to create snapshots of + /// + private readonly IEnumerable m_sources; + /// /// Initializes a new instance of the class. /// + /// The list of sources to create snapshots of /// A flag indicating if advisory locks should be ignored - public NoSnapshotLinux(bool ignoreAdvisoryLocks) + /// A flag indicating if symlinks should be followed + public NoSnapshotLinux(IEnumerable sources, bool ignoreAdvisoryLocks, bool followSymlinks) + : base(followSymlinks) { + m_sources = sources; m_ignoreAdvisoryLocks = ignoreAdvisoryLocks; } + /// + /// Gets the source folders + /// + public override IEnumerable SourceEntries => m_sources; + + /// + /// Enumerates the root source files and folders + /// + /// The source files and folders + public override IEnumerable EnumerateFilesystemEntries() + { + foreach (var folder in m_sources) + { + if (DirectoryExists(folder) || folder.EndsWith(Path.DirectorySeparatorChar)) + yield return new SnapshotSourceFileEntry(this, Util.AppendDirSeparator(folder), true, true); + else + yield return new SnapshotSourceFileEntry(this, folder, false, true); + } + } /// /// Returns the symlink target if the entry is a symlink, and null otherwise @@ -82,7 +111,7 @@ namespace Duplicati.Library.Snapshots /// The symlink target public override string GetSymlinkTarget(string localPath) { - return SystemIO.IO_SYS.GetSymlinkTarget(localPath); + return SystemIO.IO_OS.GetSymlinkTarget(localPath); } /// @@ -91,10 +120,9 @@ namespace Duplicati.Library.Snapshots /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - public override Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink) + public override Dictionary GetMetadata(string localPath, bool isSymlink) { - return SystemIO.IO_SYS.GetMetadata(localPath, isSymlink, followSymlink); + return SystemIO.IO_OS.GetMetadata(localPath, isSymlink, FollowSymlinks); } /// diff --git a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs index 44e81a849..aa8c9a5ac 100644 --- a/Duplicati/Library/Snapshots/NoSnapshotWindows.cs +++ b/Duplicati/Library/Snapshots/NoSnapshotWindows.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,6 +24,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Versioning; using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; namespace Duplicati.Library.Snapshots { @@ -33,102 +34,116 @@ namespace Duplicati.Library.Snapshots [SupportedOSPlatform("windows")] public sealed class NoSnapshotWindows : SnapshotBase { + /// + /// The list of folders to create snapshots of + /// + private readonly IEnumerable m_sources; + + /// + /// Initializes a new instance of the class. + /// + /// The list of entries to create snapshots of + /// A flag indicating if symlinks should be followed + public NoSnapshotWindows(IEnumerable sources, bool followSymlinks) + : base(followSymlinks) + { + m_sources = sources; + } + /// /// Returns the symlink target if the entry is a symlink, and null otherwise /// /// The file or folder to examine /// The symlink target public override string GetSymlinkTarget(string localPath) - { - return SystemIO.IO_WIN.GetSymlinkTarget(localPath); - } + => SystemIO.IO_OS.GetSymlinkTarget(localPath); /// /// Gets the attributes for the given file or folder /// /// The file attributes /// The file or folder to examine - public override System.IO.FileAttributes GetAttributes (string localPath) - { - return SystemIO.IO_WIN.GetFileAttributes(localPath); - } + public override System.IO.FileAttributes GetAttributes(string localPath) + => SystemIO.IO_OS.GetFileAttributes(localPath); /// /// Returns the size of a file /// /// The full path to the file in non-snapshot format /// The length of the file - public override long GetFileSize (string localPath) - { - return SystemIO.IO_WIN.FileLength(localPath); - } + public override long GetFileSize(string localPath) + => SystemIO.IO_OS.FileLength(localPath); /// - /// Enumerates all files and folders in the snapshot, restricted to sources + /// Gets the source folders /// - /// Sources to enumerate - /// The callback to invoke with each found path - /// The callback used to report errors - public override IEnumerable EnumerateFilesAndFolders(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate callback, Utility.Utility.ReportAccessError errorCallback) + public override IEnumerable SourceEntries + => m_sources; + + /// + /// Enumerates the root source files and folders + /// + /// The source files and folders + public override IEnumerable EnumerateFilesystemEntries() { - // For Windows, ensure we don't store paths with extended device path prefixes (i.e., @"\\?\" or @"\\?\UNC\") - return base.EnumerateFilesAndFolders(sources.Select(SystemIOWindows.RemoveExtendedDevicePathPrefix), callback, errorCallback); + foreach (var folder in m_sources.Select(SystemIOWindows.RemoveExtendedDevicePathPrefix)) + { + if (DirectoryExists(folder) || folder.EndsWith(System.IO.Path.DirectorySeparatorChar)) + yield return new SnapshotSourceFileEntry(this, Util.AppendDirSeparator(folder), true, true); + else + yield return new SnapshotSourceFileEntry(this, folder, false, true); + } } + /// /// Gets the last write time of a given file in UTC /// /// The full path to the file in non-snapshot format /// The last write time of the file - public override DateTime GetLastWriteTimeUtc (string localPath) - { - return SystemIO.IO_WIN.FileGetLastWriteTimeUtc(localPath); - } + public override DateTime GetLastWriteTimeUtc(string localPath) + => SystemIO.IO_OS.FileGetLastWriteTimeUtc(localPath); /// /// Gets the last write time of a given file in UTC /// /// The full path to the file in non-snapshot format /// The last write time of the file - public override DateTime GetCreationTimeUtc (string localPath) - { - return SystemIO.IO_WIN.FileGetCreationTimeUtc(localPath); - } + public override DateTime GetCreationTimeUtc(string localPath) + => SystemIO.IO_OS.FileGetCreationTimeUtc(localPath); /// /// Opens a file for reading /// /// The full path to the file in non-snapshot format /// An open filestream that can be read - public override System.IO.Stream OpenRead (string localPath) - { - return SystemIO.IO_WIN.FileOpenRead(localPath); - } + public override System.IO.Stream OpenRead(string localPath) + => SystemIO.IO_OS.FileOpenRead(localPath); /// /// Lists all files in the given folder /// /// All folders found in the folder /// The folder to examinate - protected override string[] ListFiles (string localFolderPath) + protected override string[] ListFiles(string localFolderPath) { - string[] tmp = SystemIO.IO_WIN.GetFiles(localFolderPath); + string[] tmp = SystemIO.IO_OS.GetFiles(localFolderPath); string[] res = new string[tmp.Length]; - for(int i = 0; i < tmp.Length; i++) + for (int i = 0; i < tmp.Length; i++) res[i] = SystemIOWindows.RemoveExtendedDevicePathPrefix(tmp[i]); return res; } - + /// /// Lists all folders in the given folder /// /// All folders found in the folder /// The folder to examinate - protected override string[] ListFolders (string localFolderPath) + protected override string[] ListFolders(string localFolderPath) { - string[] tmp = SystemIO.IO_WIN.GetDirectories(SystemIOWindows.AddExtendedDevicePathPrefix(localFolderPath)); + string[] tmp = SystemIO.IO_OS.GetDirectories(SystemIOWindows.AddExtendedDevicePathPrefix(localFolderPath)); string[] res = new string[tmp.Length]; for (int i = 0; i < tmp.Length; i++) res[i] = SystemIOWindows.RemoveExtendedDevicePathPrefix(tmp[i]); @@ -142,11 +157,8 @@ namespace Duplicati.Library.Snapshots /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - public override Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink) - { - return SystemIO.IO_WIN.GetMetadata(localPath, isSymlink, followSymlink); - } + public override Dictionary GetMetadata(string localPath, bool isSymlink) + => SystemIO.IO_OS.GetMetadata(localPath, isSymlink, FollowSymlinks); /// public override string ConvertToLocalPath(string snapshotPath) @@ -155,11 +167,9 @@ namespace Duplicati.Library.Snapshots } /// - public override string ConvertToSnapshotPath(string localPath) - { + public override string ConvertToSnapshotPath(string localPath) => // For Windows, ensure we don't store paths with extended device path prefixes (i.e., @"\\?\" or @"\\?\UNC\") - return SystemIOWindows.RemoveExtendedDevicePathPrefix(localPath); - } + SystemIOWindows.RemoveExtendedDevicePathPrefix(localPath); } } diff --git a/Duplicati/Library/Snapshots/Program.cs b/Duplicati/Library/Snapshots/Program.cs index 57194cd22..8e48df343 100644 --- a/Duplicati/Library/Snapshots/Program.cs +++ b/Duplicati/Library/Snapshots/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,7 +22,7 @@ using System; using System.Collections.Generic; using Duplicati.Library.AutoUpdater; -using Duplicati.Library.Common; +using Duplicati.Library.Utility; namespace Duplicati.Library.Snapshots { @@ -62,7 +62,7 @@ namespace Duplicati.Library.Snapshots return options; } - public static void Main(string[] _args) + public static int Main(string[] _args) { try { @@ -72,13 +72,13 @@ namespace Duplicati.Library.Snapshots if (args.Count == 0) args = new List { System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) }; - if (args.Count != 1) + if (args.Count != 1 || HelpOptionExtensions.IsArgumentAnyHelpString(args)) { Console.WriteLine(@$"Usage: {PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.Snapshots)} [test-folder] Where is the folder where files will be locked/created etc"); - return; + return 1; } if (!System.IO.Directory.Exists(args[0])) @@ -99,7 +99,7 @@ Where is the folder where files will be locked/created etc"); Console.WriteLine("Could open locked file {0}, cannot test", filename); Console.WriteLine("* Test failed"); - return; + return 1; } catch (Exception ex) { @@ -108,7 +108,7 @@ Where is the folder where files will be locked/created etc"); Console.WriteLine("Creating snapshot for folder: {0}", args[0]); Console.WriteLine("If this fails, try to run as " + ((OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) ? "root" : "Administrator")); - using (ISnapshotService snapshot = SnapshotUtility.CreateSnapshot(new[] { args[0] }, options)) + using (ISnapshotService snapshot = SnapshotUtility.CreateSnapshot(new[] { args[0] }, options, false)) { Console.WriteLine("Attempting to read locked file via snapshot"); try @@ -122,17 +122,19 @@ Where is the folder where files will be locked/created etc"); { Console.WriteLine("The file {0} was locked even through snapshot, message: {1}", filename, ex); Console.WriteLine("* Test failed"); - return; + return 2; } } } Console.WriteLine("* Test passed"); + return 0; } catch (Exception ex) { Console.WriteLine("The snapshot tester failed: {0}", ex); Console.WriteLine("* Test failed"); + return 3; } } diff --git a/Duplicati/Library/Snapshots/SnapshotBase.cs b/Duplicati/Library/Snapshots/SnapshotBase.cs index 7d786e16d..0bacef4af 100644 --- a/Duplicati/Library/Snapshots/SnapshotBase.cs +++ b/Duplicati/Library/Snapshots/SnapshotBase.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,9 +20,11 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Linq; using System.Collections.Generic; using System.IO; +using Duplicati.Library.Interface; +using System.Threading.Tasks; +using System.Threading; using Duplicati.Library.Common.IO; namespace Duplicati.Library.Snapshots @@ -40,19 +42,63 @@ namespace Duplicati.Library.Snapshots Dispose(false); } - #region ISnapshotService - /// - /// Enumerates all files and folders in the shadow copy + /// Initializes a new instance of the class. /// - /// Source paths to enumerate - /// The callback to invoke with each found path - /// The callback used to report errors - public virtual IEnumerable EnumerateFilesAndFolders(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate callback, Utility.Utility.ReportAccessError errorCallback) + /// A flag indicating if symlinks should be followed + protected SnapshotBase(bool followSymlinks) { - return sources.SelectMany(s => Utility.Utility.EnumerateFileSystemEntries(s, callback, ListFolders, ListFiles, GetAttributes, errorCallback)); + FollowSymlinks = followSymlinks; } - + + #region ISnapshotService + + protected bool FollowSymlinks { get; } + + /// + /// Gets the source entries + /// + public abstract IEnumerable SourceEntries { get; } + + /// + /// Enuemrates the root source files and folders + /// + /// The source files and folders + public abstract IEnumerable EnumerateFilesystemEntries(); + + /// + /// Gets a filesystem entry for a given path + /// + /// The path to get the entry for + /// A flag indicating if the path is a folder + /// The filesystem entry + public virtual ISourceProviderEntry GetFilesystemEntry(string path, bool isFolder) + { + if (isFolder && !DirectoryExists(path)) + return null; + else if (!isFolder && !FileExists(path)) + return null; + + return new SnapshotSourceFileEntry(this, isFolder ? Util.AppendDirSeparator(path) : path, isFolder, false); + } + + /// + /// Enumerates files and folders in the given folder + /// + /// Source to enumerate + /// The files and folders in the given folder + public virtual IEnumerable EnumerateFilesystemEntries(ISourceProviderEntry source) + { + if (source.IsFolder) + { + foreach (var f in ListFiles(source.Path)) + yield return new SnapshotSourceFileEntry(this, f, false, false); + + foreach (var d in ListFolders(source.Path)) + yield return new SnapshotSourceFileEntry(this, Util.AppendDirSeparator(d), true, false); + } + } + /// /// Gets the last write time of a given file in UTC /// @@ -72,7 +118,7 @@ namespace Duplicati.Library.Snapshots { return File.GetCreationTimeUtc(localPath); } - + /// /// Opens a file for reading /// @@ -83,6 +129,15 @@ namespace Duplicati.Library.Snapshots return File.Open(localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); } + /// + /// Opens a file for reading + /// + /// The full path to the file in non-snapshot format + /// A token that can be used to cancel the operation + /// An open filestream that can be read + public virtual Task OpenReadAsync(string localPath, CancellationToken cancellationToken) + => Task.FromResult(OpenRead(localPath)); + /// /// Returns the size of a file /// @@ -116,9 +171,8 @@ namespace Duplicati.Library.Snapshots /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - public abstract Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink); - + public abstract Dictionary GetMetadata(string localPath, bool isSymlink); + /// /// Gets a value indicating if the path points to a block device /// @@ -128,7 +182,7 @@ namespace Duplicati.Library.Snapshots { return false; } - + /// /// Gets a unique hardlink target ID /// @@ -157,15 +211,6 @@ namespace Duplicati.Library.Snapshots return Directory.Exists(ConvertToSnapshotPath(localFolderPath)); } - /// - public virtual bool PathExists(string localFileOrFolderPath) - { - return FileExists(localFileOrFolderPath) || DirectoryExists(localFileOrFolderPath); - } - - /// - public virtual bool IsSnapshot => false; - #endregion /// @@ -198,7 +243,7 @@ namespace Duplicati.Library.Snapshots // Now suppress finalization for this object, since we've already handled our resource cleanup tasks GC.SuppressFinalize(this); - } + } #endregion diff --git a/Duplicati/Library/Snapshots/SnapshotProvider.cs b/Duplicati/Library/Snapshots/SnapshotProvider.cs new file mode 100644 index 000000000..c1e8984fa --- /dev/null +++ b/Duplicati/Library/Snapshots/SnapshotProvider.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Library.Snapshots +{ + public enum SnapshotProvider + { + AlphaVSS, + Wmic, + LVM + } +} diff --git a/Duplicati/Library/Snapshots/SnapshotSourceFileEntry.cs b/Duplicati/Library/Snapshots/SnapshotSourceFileEntry.cs new file mode 100644 index 000000000..7fc9cae22 --- /dev/null +++ b/Duplicati/Library/Snapshots/SnapshotSourceFileEntry.cs @@ -0,0 +1,143 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Snapshots; + + +/// +/// A snapshot-based source file entry +/// +/// The snapshot service +/// The path of the file +/// True if the entry is a folder, false otherwise +/// True if the entry is the root entry, false otherwise +public class SnapshotSourceFileEntry(ISnapshotService service, string path, bool isFolder, bool isRoot) : ISourceProviderEntry +{ + /// + /// The symlink target + /// + private string? symlinkTarget; + /// + /// The file attributes + /// + private FileAttributes? attributes; + /// + /// Gets the symlink target or null if the entry is not a symlink + /// + public string? SymlinkTarget + { + get + { + if (symlinkTarget != null) + return symlinkTarget; + + if (!service.IsSymlink(path, Attributes)) + return null; + + return symlinkTarget = service.GetSymlinkTarget(path); + } + } + + /// + /// True if the entry is a symlink, false otherwise + /// + public bool IsSymlink => SymlinkTarget != null; + + /// + /// Gets the entry attributes + /// + public FileAttributes Attributes + { + get + { + if (attributes != null) + return attributes.Value; + + attributes = service.GetAttributes(path); + return attributes.Value; + } + } + + /// + /// Gets the minor metadata for the file + /// + public Dictionary MinorMetadata => service.GetMetadata(path, IsSymlink); + + /// + /// Gets a value indicating if the entry is a block device + /// + public bool IsBlockDevice => service.IsBlockDevice(path); + + /// + /// Gets a value indicating if the entry is a character device + /// + public bool IsCharacterDevice => false; + + /// + /// Gets a value indicating if the entry is an alternate stream + /// + public bool IsAlternateStream => false; + + /// + /// Gets the hardlink target ID, or null if the entry is not a hardlink + /// + public string? HardlinkTargetId => service.HardlinkTargetID(path); + + /// + public bool IsFolder => isFolder; + + /// + public bool IsMetaEntry => false; + /// + public bool IsRootEntry => isRoot; + + /// + public DateTime CreatedUtc => service.GetCreationTimeUtc(path); + + /// + public DateTime LastModificationUtc => service.GetLastWriteTimeUtc(path); + + /// + public string Path => path; + + /// + public long Size => IsFolder ? -1 : service.GetFileSize(path); + + /// + public Task OpenRead(CancellationToken cancellationToken) => service.OpenReadAsync(path, cancellationToken); + + /// + public Task OpenMetadataRead(CancellationToken cancellationToken) => Task.FromResult(new MemoryStream(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(MinorMetadata))); + /// + public IAsyncEnumerable Enumerate(CancellationToken cancellationToken) => service.EnumerateFilesystemEntries(this).ToAsyncEnumerable(); + /// + public Task FileExists(string filename, CancellationToken cancellationToken) => Task.FromResult(service.FileExists(System.IO.Path.Combine(path, filename))); +} \ No newline at end of file diff --git a/Duplicati/Library/Snapshots/SnapshotUtility.cs b/Duplicati/Library/Snapshots/SnapshotUtility.cs index e116e1ae7..10e9d6c6e 100644 --- a/Duplicati/Library/Snapshots/SnapshotUtility.cs +++ b/Duplicati/Library/Snapshots/SnapshotUtility.cs @@ -1,30 +1,29 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Runtime.Versioning; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; namespace Duplicati.Library.Snapshots @@ -37,24 +36,42 @@ namespace Duplicati.Library.Snapshots /// /// Loads a snapshot implementation for the current OS /// - /// The list of folders to create snapshots of + /// The list of paths to create snapshots of /// A set of commandline options + /// Whether to follow symlinks /// The ISnapshotService implementation - public static ISnapshotService CreateSnapshot(IEnumerable folders, Dictionary options) + public static ISnapshotService CreateSnapshot(IEnumerable paths, Dictionary options, bool followSymlinks) { - if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + if (OperatingSystem.IsLinux()) { - return CreateLinuxSnapshot(folders); + return CreateLinuxSnapshot(paths, followSymlinks); } else if (OperatingSystem.IsWindows()) { - return CreateWindowsSnapshot(folders, options); + return CreateWindowsSnapshot(paths, options, followSymlinks); } else { throw new NotSupportedException("Unsupported Operating System"); } - + } + + /// + /// Creates a snapshot implementation that does not use snapshots (i.e., regular file access) + /// + /// The list of paths to create snapshots of + /// Flag to ignore advisory locking + /// Whether to follow symlinks + /// The ISnapshotService implementation + public static ISnapshotService CreateNoSnapshot(IEnumerable paths, bool ignoreAdvisoryLocking, bool followSymlinks) + { + if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux()) + return new NoSnapshotLinux(paths, ignoreAdvisoryLocking, followSymlinks); + else if (OperatingSystem.IsWindows()) + return new NoSnapshotWindows(paths, followSymlinks); + else + throw new NotSupportedException("Unsupported Operating System"); + } // The two loader methods below guard against the type system attempting to load types @@ -65,13 +82,14 @@ namespace Duplicati.Library.Snapshots /// Loads a snapshot implementation for Linux /// /// The list of folders to create snapshots of + /// Whether to follow symlinks /// The ISnapshotService implementation [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] [SupportedOSPlatform("linux")] [SupportedOSPlatform("macOS")] - private static ISnapshotService CreateLinuxSnapshot(IEnumerable folders) + private static ISnapshotService CreateLinuxSnapshot(IEnumerable folders, bool followSymlinks) { - return new LinuxSnapshot(folders); + return new LinuxSnapshot(folders, followSymlinks); } /// @@ -79,23 +97,13 @@ namespace Duplicati.Library.Snapshots /// /// The list of folders to create snapshots of /// A set of commandline options + /// Whether to follow symlinks /// The ISnapshotService implementation [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] [SupportedOSPlatform("windows")] - private static ISnapshotService CreateWindowsSnapshot(IEnumerable folders, Dictionary options) + private static ISnapshotService CreateWindowsSnapshot(IEnumerable folders, Dictionary options, bool followSymlinks) { - return new WindowsSnapshot(folders, options); - } - - /// - /// Extension method for ISnapshotService which determines whether the given path is a symlink. - /// - /// ISnapshotService implementation - /// File or folder path - /// Whether the path is a symlink - public static bool IsSymlink(this ISnapshotService snapshot, string path) - { - return snapshot.IsSymlink(path, snapshot.GetAttributes(path)); + return new WindowsSnapshot(folders, options, followSymlinks); } /// diff --git a/Duplicati/Library/Snapshots/Strings.cs b/Duplicati/Library/Snapshots/Strings.cs index 22e5ebf19..e8c470b9c 100644 --- a/Duplicati/Library/Snapshots/Strings.cs +++ b/Duplicati/Library/Snapshots/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Snapshots/USNJournal.cs b/Duplicati/Library/Snapshots/USN/USNJournal.cs similarity index 96% rename from Duplicati/Library/Snapshots/USNJournal.cs rename to Duplicati/Library/Snapshots/USN/USNJournal.cs index b213a82ac..73346ae59 100644 --- a/Duplicati/Library/Snapshots/USNJournal.cs +++ b/Duplicati/Library/Snapshots/USN/USNJournal.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -31,7 +31,7 @@ using System.Runtime.Versioning; using Duplicati.Library.Common.IO; using Microsoft.Win32.SafeHandles; -namespace Duplicati.Library.Snapshots +namespace Duplicati.Library.Snapshots.USN { /// /// Class that encapsulates USN journal access to a single volume @@ -178,7 +178,7 @@ namespace Duplicati.Library.Snapshots if (path == null) throw new Exception(Strings.USNHelper.UnexpectedPathFormat); - return SystemIO.IO_WIN.GetPathRoot(path); + return SystemIO.IO_OS.GetPathRoot(path); } public static string GetDeviceNameFromPath(string path) @@ -509,7 +509,7 @@ namespace Duplicati.Library.Snapshots var path = m_volume; foreach (var r in pathList) { - path = SystemIO.IO_WIN.PathCombine(path, r.FileName); + path = SystemIO.IO_OS.PathCombine(path, r.FileName); } if (rec.UsnRecord.FileAttributes.HasFlag(Win32USN.FileAttributes.Directory)) diff --git a/Duplicati/Library/Snapshots/UsnJournalService.cs b/Duplicati/Library/Snapshots/USN/UsnJournalService.cs similarity index 65% rename from Duplicati/Library/Snapshots/UsnJournalService.cs rename to Duplicati/Library/Snapshots/USN/UsnJournalService.cs index 83e91ddad..cc5963199 100644 --- a/Duplicati/Library/Snapshots/UsnJournalService.cs +++ b/Duplicati/Library/Snapshots/USN/UsnJournalService.cs @@ -1,37 +1,41 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#nullable enable using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Versioning; using System.Threading; using Duplicati.Library.Interface; using Duplicati.Library.Common.IO; using Duplicati.Library.Utility; -using Duplicati.Library.Common; +using System.Threading.Tasks; +using System.Runtime.CompilerServices; -namespace Duplicati.Library.Snapshots +namespace Duplicati.Library.Snapshots.USN { + [SupportedOSPlatform("windows")] public class UsnJournalService { /// @@ -39,30 +43,39 @@ namespace Duplicati.Library.Snapshots /// private static readonly string FILTER_LOGTAG = Logging.Log.LogTagFromType(typeof(UsnJournalService)); + /// + /// The snapshot service + /// private readonly ISnapshotService m_snapshot; - private readonly IEnumerable m_sources; + /// + /// The volume data dictionary + /// private readonly Dictionary m_volumeDataDict; + /// + /// The cancellation token + /// private readonly CancellationToken m_token; /// /// Constructor. /// - /// Sources to filter /// /// Emit filter /// /// /// Journal-data of previous fileset /// - public UsnJournalService(IEnumerable sources, ISnapshotService snapshot, IFilter emitFilter, FileAttributes fileAttributeFilter, + public UsnJournalService(ISnapshotService snapshot, IFilter emitFilter, FileAttributes fileAttributeFilter, long skipFilesLargerThan, IEnumerable prevJournalData, CancellationToken token) { - m_sources = sources; m_snapshot = snapshot; m_volumeDataDict = Initialize(emitFilter, fileAttributeFilter, skipFilesLargerThan, prevJournalData); m_token = token; } + /// + /// The volume data lists for the mapped root drives + /// public IEnumerable VolumeDataList => m_volumeDataDict.Select(e => e.Value); /// @@ -83,8 +96,8 @@ namespace Duplicati.Library.Snapshots // get hash identifying current source filter / sources configuration var configHash = Utility.Utility.ByteArrayAsHexString(MD5HashHelper.GetHash(new string[] { - emitFilter == null ? string.Empty : emitFilter.ToString(), - string.Join("; ", m_sources), + emitFilter?.ToString() ?? string.Empty, + string.Join("; ", m_snapshot.SourceEntries), fileAttributeFilter.ToString(), skipFilesLargerThan.ToString() })); @@ -93,7 +106,7 @@ namespace Duplicati.Library.Snapshots var journalDataDict = prevJournalData.ToDictionary(data => data.Volume); // iterate over volumes - foreach (var sourcesPerVolume in SortByVolume(m_sources)) + foreach (var sourcesPerVolume in SortByVolume(m_snapshot.SourceEntries)) { Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "UsnInitialize", "Reading USN journal for volume: {0}", sourcesPerVolume.Key); @@ -138,7 +151,7 @@ namespace Duplicati.Library.Snapshots if (prevData.NextUsn == 0) throw new UsnJournalSoftFailureException(Strings.USNHelper.NextUsnZero); - + if (prevData.ConfigHash != nextData.ConfigHash) throw new UsnJournalSoftFailureException(Strings.USNHelper.ConfigHashChanged); @@ -179,8 +192,7 @@ namespace Duplicati.Library.Snapshots // Since that folder is going to be fully scanned, those files can be removed. // Note: it would be wrong to use the result from step 2. as the folder list! The entries removed // between 1. and 2. are *excluded* folders, and files below them are to be *excluded*, too. - volumeData.Files = - new HashSet(Utility.Utility.GetFilesNotInFolders(changedFiles, volumeData.Folders)); + volumeData.Files = [.. Utility.Utility.GetFilesNotInFolders(changedFiles, volumeData.Folders)]; // Record success for volume volumeData.IsFullScan = false; @@ -212,26 +224,69 @@ namespace Duplicati.Library.Snapshots return result; } + /// + /// Returns all sources that should be fully scanned + /// + /// Cancellation token + /// Filtered sources + public async IAsyncEnumerable GetFullScanSources([EnumeratorCancellation] CancellationToken token) + { + // Make the method async enumerable + await Task.CompletedTask; + + foreach (var volumeData in m_volumeDataDict.Values.Where(x => x.IsFullScan)) + { + if (volumeData.Folders != null) + { + foreach (var folderPath in volumeData.Folders) + { + if (token.IsCancellationRequested) + yield break; + + var folder = m_snapshot.GetFilesystemEntry(folderPath, true); + if (folder != null) + yield return folder; + else + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FolderLookupError", null, "No entry while locating source folder {0}", folderPath); + } + } + + if (volumeData.Files != null) + { + foreach (var filePath in volumeData.Files) + { + if (token.IsCancellationRequested) + yield break; + + var file = m_snapshot.GetFilesystemEntry(filePath, false); + if (file != null) + yield return file; + else + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FolderLookupError", null, "No entry while locating source file {0}", filePath); + } + } + } + } + + /// /// Filters sources, returning sub-set having been modified since last /// change, as specified by journalData. /// /// Filter callback to exclude filtered items + /// Cancellation token /// Filtered sources - public IEnumerable GetModifiedSources(Utility.Utility.EnumerationFilterDelegate filter) + public async IAsyncEnumerable GetModifiedSources(Func> filter, [EnumeratorCancellation] CancellationToken token) { - // iterate over volumes - foreach (var volumeData in m_volumeDataDict) + foreach (var volumeData in m_volumeDataDict.Values.Where(x => !x.IsFullScan)) { // prepare cache for includes (value = true) and excludes (value = false, will be populated // on-demand) var cache = new Dictionary(); - foreach (var source in m_sources) + foreach (var source in m_snapshot.SourceEntries) { - if (m_token.IsCancellationRequested) - { - break; - } + if (token.IsCancellationRequested) + yield break; cache[source] = true; } @@ -243,17 +298,15 @@ namespace Duplicati.Library.Snapshots // FilterHandler.EnumerateFilesAndFolders() works top-down, and won't even enumerate child // folders. // The sources are needed to stop evaluating parent folders above the specified source folders - if (volumeData.Value.Folders != null) + await foreach (var folder in FilterExcludedFolders(volumeData.Folders, m_snapshot, filter, cache, token).ConfigureAwait(false)) { - foreach (var folder in FilterExcludedFolders(volumeData.Value.Folders, filter, cache).Where(m_snapshot.DirectoryExists)) - { - if (m_token.IsCancellationRequested) - { - break; - } + if (token.IsCancellationRequested) + yield break; - yield return folder; - } + if (!m_snapshot.DirectoryExists(folder.Path)) + continue; + + yield return folder; } // The simplified file list also needs to be checked against the exclusion filter, as it @@ -262,17 +315,15 @@ namespace Duplicati.Library.Snapshots // Note that the simplified file list may contain entries that have been deleted! They need to // be kept in the list (unless excluded by the filter) in order for the backup handler to record their // deletion. - if (volumeData.Value.Files != null) + await foreach (var files in FilterExcludedFiles(volumeData.Files, m_snapshot, filter, cache, token).ConfigureAwait(false)) { - foreach (var files in FilterExcludedFiles(volumeData.Value.Files, filter, cache).Where(m_snapshot.FileExists)) - { - if (m_token.IsCancellationRequested) - { - break; - } + if (token.IsCancellationRequested) + yield break; - yield return files; - } + if (!m_snapshot.FileExists(files.Path)) + continue; + + yield return files; } } } @@ -282,45 +333,53 @@ namespace Duplicati.Library.Snapshots /// of its parent folders, is excluded by the filter. /// /// Files to filter + /// Snapshot service /// Exclusion filter /// Cache of included and excluded files / folders - /// + /// Cancellation token /// Filtered files - private IEnumerable FilterExcludedFiles(IEnumerable files, - Utility.Utility.EnumerationFilterDelegate filter, IDictionary cache, Utility.Utility.ReportAccessError errorCallback = null) + private static async IAsyncEnumerable FilterExcludedFiles( + IEnumerable? files, + ISnapshotService snapshot, + Func> filter, + IDictionary cache, + [EnumeratorCancellation] CancellationToken token) { - var result = new List(); + if (files == null) + yield break; - foreach (var file in files) + foreach (var filePath in files) { - if (m_token.IsCancellationRequested) - { - break; - } - - var attr = m_snapshot.FileExists(file) ? m_snapshot.GetAttributes(file) : FileAttributes.Normal; + ISourceProviderEntry? file; try { - if (!filter(file, file, attr)) + file = snapshot.GetFilesystemEntry(filePath, false); + if (file == null) continue; - if (!IsFolderOrAncestorsExcluded(Utility.Utility.GetParent(file, true), filter, cache)) + if (!await filter(file)) + continue; + + var parentPath = Utility.Utility.GetParent(file.Path, true); + if (!string.IsNullOrWhiteSpace(parentPath)) { - result.Add(file); + var parent = snapshot.GetFilesystemEntry(parentPath, true); + if (parent == null) + continue; + + if (await IsFolderOrAncestorsExcluded(parent, snapshot, filter, cache, token)) + continue; } } - catch (System.Threading.ThreadAbortException) - { - throw; - } catch (Exception ex) { - errorCallback?.Invoke(file, file, ex); - filter(file, file, attr | Utility.Utility.ATTRIBUTE_ERROR); + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FilterExcludedFilesError", ex, "Error while filtering file: {0}", ex.Message); + continue; } - } - return result; + if (file != null) + yield return file; + } } /// @@ -328,41 +387,40 @@ namespace Duplicati.Library.Snapshots /// of its ancestors, is excluded by the filter. /// /// Folder to filter + /// Snapshot service /// Exclusion filter - /// Cache of excluded folders (optional) - /// + /// Cache of excluded folders + /// Cancellation token /// Filtered folders - private IEnumerable FilterExcludedFolders(IEnumerable folders, - Utility.Utility.EnumerationFilterDelegate filter, IDictionary cache, Utility.Utility.ReportAccessError errorCallback = null) + private static async IAsyncEnumerable FilterExcludedFolders( + IEnumerable? folders, + ISnapshotService snapshot, + Func> filter, + IDictionary cache, + [EnumeratorCancellation] CancellationToken token) { - var result = new List(); + if (folders == null) + yield break; - foreach (var folder in folders) + foreach (var folderPath in folders) { - if (m_token.IsCancellationRequested) - { - break; - } - + ISourceProviderEntry? folder; try { - if (!IsFolderOrAncestorsExcluded(folder, filter, cache)) - { - result.Add(folder); - } - } - catch (System.Threading.ThreadAbortException) - { - throw; + folder = snapshot.GetFilesystemEntry(folderPath, true); + if (folder == null) + continue; + if (await IsFolderOrAncestorsExcluded(folder, snapshot, filter, cache, token)) + continue; } catch (Exception ex) { - errorCallback?.Invoke(folder, folder, ex); - filter(folder, folder, FileAttributes.Directory | Utility.Utility.ATTRIBUTE_ERROR); + Logging.Log.WriteWarningMessage(FILTER_LOGTAG, "FilterExcludedFoldersError", ex, "Error while filtering folder: {0}", ex.Message); + continue; } - } - return result; + yield return folder; + } } /// @@ -372,18 +430,23 @@ namespace Duplicati.Library.Snapshots /// Filter /// Cache of excluded folders (optional) /// True if excluded, false otherwise - private bool IsFolderOrAncestorsExcluded(string folder, Utility.Utility.EnumerationFilterDelegate filter, IDictionary cache) + private static async ValueTask IsFolderOrAncestorsExcluded( + ISourceProviderEntry inputFolder, + ISnapshotService snapshot, + Func> filter, + IDictionary cache, + CancellationToken token) { - List parents = null; + List? parents = null; + ISourceProviderEntry? folder = inputFolder; + while (folder != null) { - if (m_token.IsCancellationRequested) - { + if (token.IsCancellationRequested) break; - } // first check cache - if (cache.TryGetValue(folder, out var include)) + if (cache.TryGetValue(folder.Path, out var include)) { if (include) return false; @@ -391,20 +454,20 @@ namespace Duplicati.Library.Snapshots break; // hit! } + parents ??= []; // create on-demand + // remember folder for cache - if (parents == null) - { - parents = new List(); // create on-demand - } - parents.Add(folder); + parents.Add(folder.Path); - var attr = m_snapshot.DirectoryExists(folder) ? m_snapshot.GetAttributes(folder) : FileAttributes.Directory; - - if (!filter(folder, folder, attr)) + if (!await filter(folder)) break; // excluded - folder = Utility.Utility.GetParent(folder, true); + var parentPath = Utility.Utility.GetParent(folder.Path, true); + if (string.IsNullOrWhiteSpace(parentPath)) + break; + + folder = snapshot.GetFilesystemEntry(parentPath, true); } if (folder != null) @@ -412,7 +475,7 @@ namespace Duplicati.Library.Snapshots // update cache parents?.ForEach(p => cache[p] = false); } - + return folder != null; } @@ -458,9 +521,12 @@ namespace Duplicati.Library.Snapshots if (volumeData.IsFullScan) return true; // do not append from previous set, already scanned - if (volumeData.Files.Contains(path)) + if (volumeData.Files != null && volumeData.Files.Contains(path)) return true; // do not append from previous set, already scanned + if (volumeData.Folders == null) + return false; + foreach (var folder in volumeData.Folders) { if (m_token.IsCancellationRequested) @@ -487,23 +553,23 @@ namespace Duplicati.Library.Snapshots /// /// Volume /// - public string Volume { get; set; } + public required string Volume { get; set; } /// /// Set of potentially modified files /// - public HashSet Files { get; internal set; } + public HashSet? Files { get; internal set; } /// /// Set of folders that are potentially modified, or whose children /// are potentially modified /// - public List Folders { get; internal set; } + public List? Folders { get; internal set; } /// /// Journal data to use for next backup /// - public USNJournalDataEntry JournalData { get; internal set; } + public USNJournalDataEntry? JournalData { get; internal set; } /// /// If true, a full scan for this volume was required @@ -513,6 +579,6 @@ namespace Duplicati.Library.Snapshots /// /// Optional exception message for volume /// - public Exception Exception { get; internal set; } + public Exception? Exception { get; internal set; } } } diff --git a/Duplicati/Library/Snapshots/Win32USN.cs b/Duplicati/Library/Snapshots/USN/Win32USN.cs similarity index 97% rename from Duplicati/Library/Snapshots/Win32USN.cs rename to Duplicati/Library/Snapshots/USN/Win32USN.cs index f6bf56a8b..3a9ebf7c2 100644 --- a/Duplicati/Library/Snapshots/Win32USN.cs +++ b/Duplicati/Library/Snapshots/USN/Win32USN.cs @@ -1,30 +1,30 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Win32.SafeHandles; -namespace Duplicati.Library.Snapshots +namespace Duplicati.Library.Snapshots.USN { //The signatures in this file are from http://pinvoke.net diff --git a/Duplicati/Library/Snapshots/Windows/AlphaVssBackup.cs b/Duplicati/Library/Snapshots/Windows/AlphaVssBackup.cs new file mode 100644 index 000000000..85d65f49b --- /dev/null +++ b/Duplicati/Library/Snapshots/Windows/AlphaVssBackup.cs @@ -0,0 +1,200 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Alphaleonis.Win32.Vss; +using Duplicati.Library.Common.IO; + +namespace Duplicati.Library.Snapshots.Windows; + +/// +/// Implementation of a snapshot, using AlphaVSS +/// +internal class AlphaVssBackup : ISnapshotProvider +{ + /// + /// The implementation that is being wrapped + /// + private IVssBackupComponents _components; + /// + /// Creates a new snapshot implementation + /// + /// The components to use + private AlphaVssBackup(IVssBackupComponents components) + { + _components = components; + } + + /// + public void EnableWriterClasses(Guid[] guids) + => _components.EnableWriterClasses(guids); + + /// + public void DisableWriterClasses(Guid[] guids) + => _components.DisableWriterClasses(guids); + + /// + public void GatherWriterMetadata() + => _components.GatherWriterMetadata(); + /// + public void FreeWriterMetadata() + => _components.FreeWriterMetadata(); + + /// + public void VerifyWriters(Guid[] guids) + { + foreach (var writerGUID in guids) + { + if (!_components.WriterMetadata.Any(o => o.WriterId.Equals(writerGUID))) + { + throw new Exception(string.Format("Writer with GUID {0} was not added to VSS writer set.", writerGUID.ToString())); + } + } + } + + /// + /// Stub for returning the snapshot info + /// + /// The path where the snapshot is located + private sealed record SnapshotInfo(string SnapshotDeviceObject) : ISnapshotInfo; + + /// + public ISnapshotInfo GetSnapshotProperties(Guid shadowId) + => new SnapshotInfo(_components.GetSnapshotProperties(shadowId).SnapshotDeviceObject); + + /// + public void StartSnapshotSet() + => _components.StartSnapshotSet(); + + /// + public void PrepareForBackup() + => _components.PrepareForBackup(); + + /// + public void DoSnapshotSet() + => _components.DoSnapshotSet(); + + /// + public bool IsVolumeSupported(string drive) + => _components.IsVolumeSupported(drive); + + /// + public Guid AddToSnapshotSet(string drive) + => _components.AddToSnapshotSet(drive); + + /// + public void BackupComplete() + => _components.BackupComplete(); + + /// + public void DeleteSnapshot(Guid shadowId, bool forceDelete) + => _components.DeleteSnapshot(shadowId, forceDelete); + + /// + public static ISnapshotProvider Create() + => new AlphaVssBackup(GetVssBackupComponents()); + + /// + /// Creates a new AlphaVssBackupComponents instance and adds the context + /// + /// The configured instance + private static IVssBackupComponents GetVssBackupComponents() + { + //Prepare the backup + IVssBackupComponents vssBackupComponents = CreateVssBackupComponents(); + vssBackupComponents.InitializeForBackup(null); + vssBackupComponents.SetContext(VssSnapshotContext.Backup); + vssBackupComponents.SetBackupState(false, true, VssBackupType.Full, false); + + return vssBackupComponents; + } + + /// + /// Loads the AlphaVSS implementation for the current OS + /// + /// The implementation + private static IVssBackupComponents CreateVssBackupComponents() + { + var vss = VssFactoryProvider.Default.GetVssFactory(); + if (vss == null) + throw new InvalidOperationException(); + + return vss.CreateVssBackupComponents(); + } + + /// + public IEnumerable ParseWriterMetaData(Guid[] writers) + { + // check if writers got enabled + foreach (var writerGUID in writers) + { + var writer = _components.WriterMetadata.First(o => o.WriterId.Equals(writerGUID)); + foreach (var component in writer.Components) + { + yield return new WriterMetaData + { + Guid = writerGUID, + Name = component.ComponentName, + LogicalPath = component.LogicalPath, + Paths = GetPathsFromComponent(component) + }; + } + } + } + + /// + /// Returns the paths from the writer metadata + /// + /// The component to get the paths from + /// The list of paths + private List GetPathsFromComponent(IVssWMComponent component) + { + var paths = new List(); + + foreach (var file in component.Files) + { + if (file.FileSpecification.Contains("*")) + { + if (Directory.Exists(Util.AppendDirSeparator(file.Path))) + paths.Add(Util.AppendDirSeparator(file.Path)); + } + else + { + var fileWithSpec = SystemIO.IO_OS.PathCombine(file.Path, file.FileSpecification); + if (File.Exists(fileWithSpec)) + paths.Add(fileWithSpec); + } + } + return paths; + + } + + /// + public void Dispose() + { + _components.Dispose(); + } +} \ No newline at end of file diff --git a/Duplicati/Library/Snapshots/HyperVUtility.cs b/Duplicati/Library/Snapshots/Windows/HyperVUtility.cs similarity index 88% rename from Duplicati/Library/Snapshots/HyperVUtility.cs rename to Duplicati/Library/Snapshots/Windows/HyperVUtility.cs index 3e40125e8..9e0f16524 100644 --- a/Duplicati/Library/Snapshots/HyperVUtility.cs +++ b/Duplicati/Library/Snapshots/Windows/HyperVUtility.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,10 +24,9 @@ using System.IO; using System.Linq; using System.Management; using System.Runtime.Versioning; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; -namespace Duplicati.Library.Snapshots +namespace Duplicati.Library.Snapshots.Windows { public class HyperVGuest : IEquatable { @@ -90,6 +89,11 @@ namespace Duplicati.Library.Snapshots /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + /// + /// The System.IO abstraction for the Windows platform + /// + private static readonly ISystemIO IO_WIN = SystemIO.IO_OS; + private readonly ManagementScope _wmiScope; private readonly string _vmIdField; private readonly string _wmiHost = "localhost"; @@ -156,8 +160,9 @@ namespace Duplicati.Library.Snapshots /// Query Hyper-V for all Virtual Machines info /// /// Specify if returned data should contain VM paths + /// The provider to use for VSS /// List of Hyper-V Machines - public void QueryHyperVGuestsInfo(bool bIncludePaths = false) + public void QueryHyperVGuestsInfo(SnapshotProvider provider, bool bIncludePaths = false) { if (!IsHyperVInstalled) return; @@ -174,7 +179,7 @@ namespace Duplicati.Library.Snapshots if (bIncludePaths) { - foreach (var o in GetAllVMsPathsVSS()) + foreach (var o in GetAllVMsPathsVSS(provider)) { foreach (var mObject in moCollection) { @@ -214,9 +219,9 @@ namespace Duplicati.Library.Snapshots /// For all Hyper-V guests it enumerate all associated paths using VSS data /// /// A collection of VMs and paths - private static IEnumerable GetAllVMsPathsVSS() + private static IEnumerable GetAllVMsPathsVSS(SnapshotProvider provider) { - using (var vssBackupComponents = new VssBackupComponents()) + using (var vssBackupComponents = new SnapshotManager(provider)) { var writerGUIDS = new[] { HyperVWriterGuid }; @@ -251,7 +256,7 @@ namespace Duplicati.Library.Snapshots using (var mObject1 = new ManagementObjectSearcher(_wmiScope, new ObjectQuery(wmiQuery)).Get().Cast().First()) if (_wmiv2Namespace) { - path = SystemIO.IO_WIN.PathCombine((string)mObject1["ConfigurationDataRoot"], (string)mObject1["ConfigurationFile"]); + path = IO_WIN.PathCombine((string)mObject1["ConfigurationDataRoot"], (string)mObject1["ConfigurationFile"]); if (File.Exists(path)) result.Add(path); @@ -261,10 +266,10 @@ namespace Duplicati.Library.Snapshots { foreach (var snap in snaps) { - path = SystemIO.IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["ConfigurationFile"]); + path = IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["ConfigurationFile"]); if (File.Exists(path)) result.Add(path); - path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["SuspendDataRoot"])); + path = Util.AppendDirSeparator(IO_WIN.PathCombine((string)snap["ConfigurationDataRoot"], (string)snap["SuspendDataRoot"])); if (Directory.Exists(path)) result.Add(path); } @@ -272,10 +277,10 @@ namespace Duplicati.Library.Snapshots } else { - path = SystemIO.IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID + ".xml"); + path = IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID + ".xml"); if (File.Exists(path)) result.Add(path); - path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID)); + path = Util.AppendDirSeparator(IO_WIN.PathCombine((string)mObject1["ExternalDataRoot"], "Virtual Machines", vmID)); if (Directory.Exists(path)) result.Add(path); @@ -285,10 +290,10 @@ namespace Duplicati.Library.Snapshots foreach (var snapID in snapsIDs) { - path = SystemIO.IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", "") + ".xml"); + path = IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", "") + ".xml"); if (File.Exists(path)) result.Add(path); - path = Util.AppendDirSeparator(SystemIO.IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", ""))); + path = Util.AppendDirSeparator(IO_WIN.PathCombine((string)mObject1["SnapshotDataRoot"], "Snapshots", snapID.Replace("Microsoft:", ""))); if (Directory.Exists(path)) result.Add(path); } diff --git a/Duplicati/Library/Snapshots/Windows/ISnapshotProvider.cs b/Duplicati/Library/Snapshots/Windows/ISnapshotProvider.cs new file mode 100644 index 000000000..db6a0965a --- /dev/null +++ b/Duplicati/Library/Snapshots/Windows/ISnapshotProvider.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace Duplicati.Library.Snapshots.Windows; + +/// +/// Interface for the snapshot information +/// +internal interface ISnapshotInfo +{ + /// + /// The path where the snapshot is located + /// + string SnapshotDeviceObject { get; } +} + +/// +/// Interface for a snapshot provider +/// +internal interface ISnapshotProvider : IDisposable +{ + /// + /// Explicitly enables the selected writer GUIDs + /// + /// The GUIDs to enable + void EnableWriterClasses(Guid[] guids); + /// + /// Explicitly disable the selected writer GUIDs + /// + /// The GUIDs to disable + void DisableWriterClasses(Guid[] guids); + /// + /// Gather writer metadata prior to the snapshot + /// + void GatherWriterMetadata(); + /// + /// Release any allocated metadata + /// + void FreeWriterMetadata(); + /// + /// Verify that the writes are enabled on the snapshot + /// + /// The writer GUIDs to verify as enabled + void VerifyWriters(Guid[] guids); + + /// + /// Gets information of a snapshot + /// + /// The shadow copy id + /// The snapshot information + ISnapshotInfo GetSnapshotProperties(Guid shadowId); + + /// + /// Creates a snapshot + /// + void StartSnapshotSet(); + /// + /// Starts the snapshot as a backup snapshot + /// + void PrepareForBackup(); + /// + /// Finalizes the snapshot creation + /// + void DoSnapshotSet(); + /// + /// Checks if the volume is supported by the snapshot + /// + /// The drive to check + /// true if the volume is supported; false otherwise + bool IsVolumeSupported(string drive); + /// + /// Adds a drive to the snapshot set + /// + /// The drive to add + /// The shadow id + Guid AddToSnapshotSet(string drive); + + /// + /// Completes the snapshot after the backup has completed + /// + void BackupComplete(); + /// + /// Releases all resources held by the snapshot + /// + /// The snapshot shadow id + /// Flag to choose force deletion + void DeleteSnapshot(Guid shadowId, bool forceDelete); + + /// + /// Parses write metadata + /// + /// The writers to return information from + /// The writer metadata + IEnumerable ParseWriterMetaData(Guid[] writers); +} \ No newline at end of file diff --git a/Duplicati/Library/Snapshots/MSSQLUtility.cs b/Duplicati/Library/Snapshots/Windows/MSSQLUtility.cs similarity index 94% rename from Duplicati/Library/Snapshots/MSSQLUtility.cs rename to Duplicati/Library/Snapshots/Windows/MSSQLUtility.cs index 416fe2bd8..7cbf8444e 100644 --- a/Duplicati/Library/Snapshots/MSSQLUtility.cs +++ b/Duplicati/Library/Snapshots/Windows/MSSQLUtility.cs @@ -1,31 +1,30 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Common; -using Duplicati.Library.Common.IO; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Versioning; -namespace Duplicati.Library.Snapshots +namespace Duplicati.Library.Snapshots.Windows { public class MSSQLDB : IEquatable { @@ -152,14 +151,14 @@ namespace Duplicati.Library.Snapshots /// For all MS SQL databases it enumerate all associated paths using VSS data /// /// A collection of DBs and paths - public void QueryDBsInfo() + public void QueryDBsInfo(SnapshotProvider provider) { if (!IsMSSQLInstalled) return; m_DBs.Clear(); - using (var vssBackupComponents = new VssBackupComponents()) + using (var vssBackupComponents = new SnapshotManager(provider)) { var writerGUIDS = new [] { MSSQLWriterGuid }; try diff --git a/Duplicati/Library/Common/IO/VssBackupComponents.cs b/Duplicati/Library/Snapshots/Windows/SnapshotManager.cs similarity index 61% rename from Duplicati/Library/Common/IO/VssBackupComponents.cs rename to Duplicati/Library/Snapshots/Windows/SnapshotManager.cs index af050a7d9..78d21fa91 100644 --- a/Duplicati/Library/Common/IO/VssBackupComponents.cs +++ b/Duplicati/Library/Snapshots/Windows/SnapshotManager.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,13 +21,16 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.Versioning; using Alphaleonis.Win32.Vss; +using Duplicati.Library.Common.IO; -namespace Duplicati.Library.Common.IO +namespace Duplicati.Library.Snapshots.Windows { + /// + /// Metadata from a writer instance + /// public class WriterMetaData { public string Name { get; set; } @@ -36,16 +39,21 @@ namespace Duplicati.Library.Common.IO public List Paths { get; set; } } + /// + /// The manager for the Windows snapshot + /// [SupportedOSPlatform("windows")] - public class VssBackupComponents : IDisposable + public class SnapshotManager : IDisposable { /// /// The tag used for logging messages /// - public static readonly string LOGTAG = Logging.Log.LogTagFromType(); + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); - - private IVssBackupComponents _vssBackupComponents; + /// + /// The snapshot implementation + /// + private ISnapshotProvider _snapshotProvider; /// /// The list of snapshot ids for each volume, key is the path root, e.g. C:\. @@ -69,26 +77,39 @@ namespace Duplicati.Library.Common.IO /// private List _mappedDrives; - public VssBackupComponents() + /// + /// Creates a new snapshot manager + /// + /// The provider to use + public SnapshotManager(SnapshotProvider provider) { - _vssBackupComponents = VssBackupComponentsHelper.GetVssBackupComponents(); + _snapshotProvider = provider switch { + SnapshotProvider.AlphaVSS => AlphaVssBackup.Create(), + SnapshotProvider.Wmic => new WmicVssBackup(), + _ => throw new ArgumentException($"Invalid provider: {provider}") + }; } + /// + /// Setup writers on the snapshot + /// + /// The explicitly included writers + /// The explicitly excluded writers public void SetupWriters(Guid[] includedWriters, Guid[] excludedWriters) { if (includedWriters != null && includedWriters.Length > 0) - _vssBackupComponents.EnableWriterClasses(includedWriters); + _snapshotProvider.EnableWriterClasses(includedWriters); if (excludedWriters != null && excludedWriters.Length > 0) - _vssBackupComponents.DisableWriterClasses(excludedWriters); + _snapshotProvider.DisableWriterClasses(excludedWriters); try { - _vssBackupComponents.GatherWriterMetadata(); + _snapshotProvider.GatherWriterMetadata(); } finally { - _vssBackupComponents.FreeWriterMetadata(); + _snapshotProvider.FreeWriterMetadata(); } if (includedWriters == null) @@ -97,15 +118,12 @@ namespace Duplicati.Library.Common.IO } // check if writers got enabled - foreach (var writerGUID in includedWriters) - { - if (!_vssBackupComponents.WriterMetadata.Any(o => o.WriterId.Equals(writerGUID))) - { - throw new Exception(string.Format("Writer with GUID {0} was not added to VSS writer set.", writerGUID.ToString())); - } - } + _snapshotProvider.VerifyWriters(includedWriters); } + /// + /// Maps drives from snapshot paths + /// public void MapDrives() { _mappedDrives = new List(); @@ -124,6 +142,9 @@ namespace Duplicati.Library.Common.IO } } + /// + /// Makes a map of the snapshots for easy lookup + /// public void MapVolumesToSnapShots() { _volumeMap = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -133,41 +154,17 @@ namespace Duplicati.Library.Common.IO // Attempting to get attributes on this path will fail, so we need to append a double backslash // Strangely, the double backslash is not needed when acessing a subfolder, here a single backslash is enough, // but double backslash works for both cases due to path normalization. - _volumeMap.Add(kvp.Key, Util.AppendDirSeparator(_vssBackupComponents.GetSnapshotProperties(kvp.Value).SnapshotDeviceObject) + "\\"); + _volumeMap.Add(kvp.Key, Util.AppendDirSeparator(_snapshotProvider.GetSnapshotProperties(kvp.Value).SnapshotDeviceObject) + "\\"); } _volumeReverseMap = _volumeMap.ToDictionary(x => x.Value, x => x.Key); } - public Dictionary SnapshotDeviceAndVolumes - { - get - { - return _volumeReverseMap; - } - } - - private List GetPathsFromComponent(IVssWMComponent component) - { - var paths = new List(); - - foreach (var file in component.Files) - { - if (file.FileSpecification.Contains("*")) - { - if (Directory.Exists(Util.AppendDirSeparator(file.Path))) - paths.Add(Util.AppendDirSeparator(file.Path)); - } - else - { - var fileWithSpec = SystemIO.IO_WIN.PathCombine(file.Path, file.FileSpecification); - if (File.Exists(fileWithSpec)) - paths.Add(fileWithSpec); - } - } - return paths; - - } + /// + /// Returns the map of snapshot path and logical path + /// + public Dictionary SnapshotDeviceAndVolumes + => _volumeReverseMap; /// /// Enumerable to iterate over components of the given writers. @@ -176,38 +173,30 @@ namespace Duplicati.Library.Common.IO /// Anonymous object containing GUID, component name, logical path and paths of the component. /// Writers. public IEnumerable ParseWriterMetaData(Guid[] writers) - { - // check if writers got enabled - foreach (var writerGUID in writers) - { - var writer = _vssBackupComponents.WriterMetadata.First(o => o.WriterId.Equals(writerGUID)); - foreach (var component in writer.Components) - { - yield return new WriterMetaData - { - Guid = writerGUID, - Name = component.ComponentName, - LogicalPath = component.LogicalPath, - Paths = GetPathsFromComponent(component) - }; - } - } - } - + => _snapshotProvider.ParseWriterMetaData(writers); + /// + /// Prepares the snapshot and notifies all writers + /// + /// The soruces to include public void InitShadowVolumes(IEnumerable sources) { - _vssBackupComponents.StartSnapshotSet(); + _snapshotProvider.StartSnapshotSet(); - CheckSupportedVolumes(sources); + CheckAndAddSupportedVolumes(sources); //Make all writers aware that we are going to do the backup - _vssBackupComponents.PrepareForBackup(); + _snapshotProvider.PrepareForBackup(); //Create the shadow volumes - _vssBackupComponents.DoSnapshotSet(); + _snapshotProvider.DoSnapshotSet(); } + /// + /// Gets the snapshot path from the local path + /// + /// The local path + /// The snapshot path public string GetVolumeFromCache(string path) { if (!_volumeMap.TryGetValue(path, out var volumePath)) @@ -216,25 +205,30 @@ namespace Duplicati.Library.Common.IO return volumePath; } - public void CheckSupportedVolumes(IEnumerable sources) + /// + /// Checks if the volumes can be added and creates snapshots as needed + /// + /// The sources to create snapshots for + private void CheckAndAddSupportedVolumes(IEnumerable sources) { //Figure out which volumes are in the set _volumes = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var s in sources) { - var drive = SystemIO.IO_WIN.GetPathRoot(s); + var drive = SystemIO.IO_OS.GetPathRoot(s); if (!_volumes.ContainsKey(drive)) { - if (!_vssBackupComponents.IsVolumeSupported(drive)) + if (!_snapshotProvider.IsVolumeSupported(drive)) { throw new VssVolumeNotSupportedException(drive); } - _volumes.Add(drive, _vssBackupComponents.AddToSnapshotSet(drive)); + _volumes.Add(drive, _snapshotProvider.AddToSnapshotSet(drive)); } } } + /// public void Dispose() { try @@ -256,7 +250,7 @@ namespace Duplicati.Library.Common.IO try { - _vssBackupComponents?.BackupComplete(); + _snapshotProvider?.BackupComplete(); } catch (Exception ex) { @@ -265,13 +259,13 @@ namespace Duplicati.Library.Common.IO try { - if (_vssBackupComponents != null) + if (_snapshotProvider != null) { foreach (var g in _volumes.Values) { try { - _vssBackupComponents.DeleteSnapshot(g, false); + _snapshotProvider.DeleteSnapshot(g, false); } catch (Exception ex) { @@ -285,35 +279,11 @@ namespace Duplicati.Library.Common.IO Logging.Log.WriteVerboseMessage(LOGTAG, "VSSSnapShotDeleteCleanError", ex, "Failed during VSS esnapshot closing"); } - if (_vssBackupComponents != null) + if (_snapshotProvider != null) { - _vssBackupComponents.Dispose(); - _vssBackupComponents = null; + _snapshotProvider.Dispose(); + _snapshotProvider = null; } } } - - - public static class VssBackupComponentsHelper - { - public static IVssBackupComponents GetVssBackupComponents() - { - //Prepare the backup - IVssBackupComponents vssBackupComponents = CreateVssBackupComponents(); - vssBackupComponents.InitializeForBackup(null); - vssBackupComponents.SetContext(VssSnapshotContext.Backup); - vssBackupComponents.SetBackupState(false, true, VssBackupType.Full, false); - - return vssBackupComponents; - } - - public static IVssBackupComponents CreateVssBackupComponents() - { - var vss = VssFactoryProvider.Default.GetVssFactory(); - if (vss == null) - throw new InvalidOperationException(); - - return vss.CreateVssBackupComponents(); - } - } } diff --git a/Duplicati/Library/Snapshots/Windows/WmicShadowCopyManager.cs b/Duplicati/Library/Snapshots/Windows/WmicShadowCopyManager.cs new file mode 100644 index 000000000..ab54530b8 --- /dev/null +++ b/Duplicati/Library/Snapshots/Windows/WmicShadowCopyManager.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.Versioning; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +#nullable enable + +namespace Duplicati.Library.Snapshots.Windows; + +/// +/// A shadow copy manager using the wmic commandline tool +/// +[SupportedOSPlatform("windows")] +internal class WmicShadowCopyManager : IDisposable +{ + /// + /// The tag used for logging messages + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + /// + /// A single shadow copy + /// + /// The shadow ID as a string + /// The shadow ID as a GUID + /// The drive that the snapshot is for + /// The path that contains the snapshot + public class WmicShadowCopy(string shadowId, Guid parsedId, string originalDrive, string mappedPath) : IDisposable + { + /// + /// Gets the shadow ID + /// + public string ShadowID { get; } = shadowId; + /// + /// Gets the shadow ID + /// + public Guid ParsedId { get; } = parsedId; + /// + /// Gets the drive that was originally mapped + /// + public string OriginalDrive { get; } = originalDrive; + /// + /// Gets the path where the snapshot is found + /// + public string MappedPath { get; } = mappedPath; + + /// + /// Flag keeping track of the snapshot deletion state + /// + private bool _snapshotDeleted; + + /// + public void Dispose() + { + DeleteShadowCopy(); + } + + /// + /// Deletes the shadow copy + /// + private void DeleteShadowCopy() + { + if (_snapshotDeleted) + return; + if (!string.IsNullOrEmpty(ShadowID)) + { + _snapshotDeleted = true; + Logging.Log.WriteVerboseMessage(LOGTAG, "DeleteShadowCopy", $"Deleting Shadow Copy: {ShadowID}"); + DeleteShadow(ShadowID); + } + } + } + + /// + /// The list of the currently registered shadow copies + /// + private List _shadowCopies = new List(); + + /// + /// Gets the list of the currently registered shadow copies + /// + public IEnumerable ShadowCopies => _shadowCopies; + + /// + /// Creates a new snapshot for the given drive + /// + /// The drive to create the snapshot for + /// The created snapshot + public WmicShadowCopy Add(string drive) + { + var shadowId = CreateShadowCopy(drive); + if (string.IsNullOrEmpty(shadowId)) + throw new InvalidOperationException("Failed to create shadow copy"); + + var shadowPath = GetShadowPath(shadowId); + if (string.IsNullOrEmpty(shadowPath)) + { + DeleteShadow(shadowId); + throw new InvalidOperationException("Failed to get shadow copy path"); + } + + var snapshot = new WmicShadowCopy(shadowId, Guid.Parse(shadowId), drive, shadowPath); + _shadowCopies.Add(snapshot); + + return snapshot; + } + + /// + public void Dispose() + { + foreach (var shadow in ShadowCopies) + { + shadow.Dispose(); + } + _shadowCopies.Clear(); + } + + /// + /// Creates a shadow copy + /// + /// The drive to create the snapshot for + /// The shadow id + private static string? CreateShadowCopy(string drive) + { + string output = ExecuteCommand("wmic", $"shadowcopy call create Volume='{drive}'", 10000); + + // Extract ShadowID using regex + Match match = Regex.Match(output, @"ShadowID\s*=\s*""({[0-9A-F\-]+})"""); + if (match.Success) + { + return match.Groups[1].Value; + } + + Logging.Log.WriteErrorMessage(LOGTAG, "ShadowCopyFailed", null, "Failed to create shadow copy for {0}: {1}", drive, output); + return null; + } + + /// + /// Gets the path where the shadow copy is mounted + /// + /// The shadow copy id + /// The path where the copy is mounted + private static string? GetShadowPath(string shadowId) + { + string output = ExecuteCommand("wmic", "shadowcopy get ID, DeviceObject", 5000); + + // Extract DeviceObject corresponding to the ShadowID + string pattern = $@"(\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy\d+)\s+{shadowId}"; + Match match = Regex.Match(output, pattern); + + if (match.Success) + { + return match.Groups[1].Value; + } + + Logging.Log.WriteErrorMessage(LOGTAG, "ShadowCopyFailed", null, "Failed to get shadow copy path for {0}: {1}", shadowId, output); + return null; + } + + /// + /// Returns the drives that are vss enabled + /// + /// + public static HashSet GetVssCapableDrivesViaVssadmin() + { + var vssDrives = new HashSet(StringComparer.OrdinalIgnoreCase); + + try + { + var output = ExecuteCommand("vssadmin", "list volumes", 5000); + + // Regex to match drive letters in output + var matches = Regex.Matches(output, @"Volume\s+path:\s*([A-Z]:)\\?\s", RegexOptions.IgnoreCase); + foreach (Match match in matches) + vssDrives.Add(match.Groups[1].Value.Substring(0, 1)); + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "ShadowCopyListFailed", ex, "Failed to list volumes"); + } + + return vssDrives; + } + + /// + /// Delete a shadow copy + /// + /// The shadow id + private static void DeleteShadow(string shadowId) + => ExecuteCommand("wmic", $"shadowcopy where ID=\"{shadowId}\" delete", 5000); + + /// + /// Executes a command + /// + /// The binary to execute + /// The arguments to use + /// The timeout + /// The output of the command + private static string ExecuteCommand(string fileName, string arguments, int timeoutMs) + { + try + { + using (var process = new Process()) + { + process.StartInfo.FileName = fileName; + process.StartInfo.Arguments = arguments; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + + process.Start(); + + var outputTask = process.StandardOutput.ReadToEndAsync(); + var errorTask = process.StandardError.ReadToEndAsync(); + + if (!process.WaitForExit(timeoutMs)) + { + process.Kill(); + throw new TimeoutException($"Command '{fileName} {arguments}' timed out after {timeoutMs}ms."); + } + + var output = outputTask.Result; + var error = errorTask.Result; + + if (!string.IsNullOrWhiteSpace(error)) + Logging.Log.WriteWarningMessage(LOGTAG, "ShadowCopyFailed", null, "Failed to execute command: {0} {1}: {2}", fileName, arguments, error); + + return output; + } + } + catch (Exception ex) + { + Logging.Log.WriteErrorMessage(LOGTAG, "ShadowCopyFailed", ex, "Failed to execute command: {0} {1}", fileName, arguments); + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Snapshots/Windows/WmicVssBackup.cs b/Duplicati/Library/Snapshots/Windows/WmicVssBackup.cs new file mode 100644 index 000000000..bf7568ed0 --- /dev/null +++ b/Duplicati/Library/Snapshots/Windows/WmicVssBackup.cs @@ -0,0 +1,131 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Versioning; + +namespace Duplicati.Library.Snapshots.Windows; + +/// +/// Snapshot provider based on WMIC +/// +[SupportedOSPlatform("windows")] +internal class WmicVssBackup : ISnapshotProvider +{ + /// + /// The manager that contains the actual interface to WMIC + /// + private WmicShadowCopyManager _manager = new WmicShadowCopyManager(); + + /// + /// Gets the VSS enabled drives + /// + private Lazy> _vssEnabledDrives = new Lazy>(() => WmicShadowCopyManager.GetVssCapableDrivesViaVssadmin()); + + /// + public Guid AddToSnapshotSet(string drive) + => _manager.Add(drive).ParsedId; + + /// + public void BackupComplete() + { + } + + /// + public void DeleteSnapshot(Guid shadowId, bool forceDelete) + { + foreach (var x in _manager.ShadowCopies.Where(x => x.ParsedId == shadowId)) + x.Dispose(); + } + + /// + public void DisableWriterClasses(Guid[] guids) + { + } + + /// + public void Dispose() + { + _manager.Dispose(); + } + + /// + public void DoSnapshotSet() + { + } + + /// + public void EnableWriterClasses(Guid[] guids) + { + } + + /// + public void FreeWriterMetadata() + { + } + + /// + public void GatherWriterMetadata() + { + } + + /// + /// Stub for returning the snapshot info + /// + /// The path where the snapshot is located + private sealed record SnapshotInfo (string SnapshotDeviceObject) : ISnapshotInfo; + + /// + public ISnapshotInfo GetSnapshotProperties(Guid shadowId) + => new SnapshotInfo(_manager.ShadowCopies.First(x => x.ParsedId == shadowId).MappedPath); + + /// + public bool IsVolumeSupported(string drive) + { + if (string.IsNullOrWhiteSpace(drive)) + return false; + + return _vssEnabledDrives.Value.Contains(drive.Substring(0, 1)); + } + + /// + public IEnumerable ParseWriterMetaData(Guid[] writers) + => Enumerable.Empty(); + + /// + public void PrepareForBackup() + { + } + + /// + public void StartSnapshotSet() + { + } + + /// + public void VerifyWriters(Guid[] guids) + { + } +} \ No newline at end of file diff --git a/Duplicati/Library/Snapshots/WindowsSnapshot.cs b/Duplicati/Library/Snapshots/WindowsSnapshot.cs index c1067961a..bbe4154c3 100644 --- a/Duplicati/Library/Snapshots/WindowsSnapshot.cs +++ b/Duplicati/Library/Snapshots/WindowsSnapshot.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,13 +19,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. - using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Versioning; using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using Duplicati.Library.Snapshots.Windows; namespace Duplicati.Library.Snapshots { @@ -44,23 +45,36 @@ namespace Duplicati.Library.Snapshots /// public static readonly string LOGTAG = Logging.Log.LogTagFromType(); + /// + /// The system IO for the current platform + /// + private static readonly ISystemIO IO_WIN = SystemIO.IO_OS; + /// /// The main reference to the backup controller /// - private readonly VssBackupComponents _vssBackupComponents; + private readonly SnapshotManager _snapshotManager; + + /// + /// The source folders included in the snapshot + /// + private readonly IReadOnlyList _sourceEntries; /// /// Constructs a new backup snapshot, using all the required disks /// /// Sources to determine which volumes to include in snapshot /// A set of commandline options - public WindowsSnapshot(IEnumerable sources, IDictionary options) + /// A flag indicating if symlinks should be followed + public WindowsSnapshot(IEnumerable sources, IDictionary options, bool followSymlinks) + : base(followSymlinks) { // For Windows, ensure we don't store paths with extended device path prefixes (i.e., @"\\?\" or @"\\?\UNC\") - sources = sources.Select(SystemIOWindows.RemoveExtendedDevicePathPrefix); + _sourceEntries = sources.Select(SystemIOWindows.RemoveExtendedDevicePathPrefix).ToList(); try { - _vssBackupComponents = new VssBackupComponents(); + var provider = Utility.Utility.ParseEnumOption(options.AsReadOnly(), "snapshot-provider", SnapshotProvider.AlphaVSS); + _snapshotManager = new SnapshotManager(provider); // Default to exclude the System State writer var excludedWriters = new Guid[] { new Guid("{e8132975-6f93-4464-a53e-1050253ae220}") }; @@ -72,16 +86,16 @@ namespace Duplicati.Library.Snapshots .Select(x => new Guid(x)) .ToArray(); } - _vssBackupComponents.SetupWriters(null, excludedWriters); + _snapshotManager.SetupWriters(null, excludedWriters); - _vssBackupComponents.InitShadowVolumes(sources); + _snapshotManager.InitShadowVolumes(_sourceEntries); - _vssBackupComponents.MapVolumesToSnapShots(); + _snapshotManager.MapVolumesToSnapShots(); //If we should map the drives, we do that now and update the volumeMap if (Utility.Utility.ParseBoolOption(options.AsReadOnly(), "vss-use-mapping")) { - _vssBackupComponents.MapDrives(); + _snapshotManager.MapDrives(); } } catch (Exception ex1) @@ -103,6 +117,26 @@ namespace Duplicati.Library.Snapshots } } + /// + /// Gets the source folders + /// + public override IEnumerable SourceEntries => _sourceEntries; + + /// + /// Enumerates the root source files and folders + /// + /// The source files and folders + public override IEnumerable EnumerateFilesystemEntries() + { + foreach (var folder in _sourceEntries) + { + if (folder.EndsWith(Path.DirectorySeparatorChar) || DirectoryExists(folder)) + yield return new SnapshotSourceFileEntry(this, Util.AppendDirSeparator(folder), true, true); + else + yield return new SnapshotSourceFileEntry(this, folder, false, true); + } + } + #region Private functions /// @@ -115,8 +149,8 @@ namespace Duplicati.Library.Snapshots { string[] tmp = null; var spath = ConvertToSnapshotPath(localFolderPath); - tmp = SystemIO.IO_WIN.GetDirectories(spath); - var root = Util.AppendDirSeparator(SystemIO.IO_WIN.GetPathRoot(localFolderPath)); + tmp = IO_WIN.GetDirectories(spath); + var root = Util.AppendDirSeparator(IO_WIN.GetPathRoot(localFolderPath)); var volumePath = Util.AppendDirSeparator(ConvertToSnapshotPath(root)); volumePath = SystemIOWindows.AddExtendedDevicePathPrefix(volumePath); @@ -140,10 +174,10 @@ namespace Duplicati.Library.Snapshots string[] files = null; var spath = ConvertToSnapshotPath(localFolderPath); - files = SystemIO.IO_WIN.GetFiles(spath); + files = IO_WIN.GetFiles(spath); // convert back to non-shadow, i.e., non-vss version - var root = Util.AppendDirSeparator(SystemIO.IO_WIN.GetPathRoot(localFolderPath)); + var root = Util.AppendDirSeparator(IO_WIN.GetPathRoot(localFolderPath)); var volumePath = Util.AppendDirSeparator(ConvertToSnapshotPath(root)); volumePath = SystemIOWindows.AddExtendedDevicePathPrefix(volumePath); @@ -158,18 +192,6 @@ namespace Duplicati.Library.Snapshots #region ISnapshotService Members - /// - /// Enumerates all files and folders in the snapshot, restricted to sources - /// - /// Sources to enumerate - /// The callback to invoke with each found path - /// The callback used to report errors - public override IEnumerable EnumerateFilesAndFolders(IEnumerable sources, Utility.Utility.EnumerationFilterDelegate callback, Utility.Utility.ReportAccessError errorCallback) - { - // For Windows, ensure we don't store paths with extended device path prefixes (i.e., @"\\?\" or @"\\?\UNC\") - return base.EnumerateFilesAndFolders(sources.Select(SystemIOWindows.RemoveExtendedDevicePathPrefix), callback, errorCallback); - } - /// /// Gets the last write time of a given file in UTC /// @@ -179,7 +201,7 @@ namespace Duplicati.Library.Snapshots { var spath = ConvertToSnapshotPath(localPath); - return SystemIO.IO_WIN.GetLastWriteTimeUtc(SystemIOWindows.AddExtendedDevicePathPrefix(spath)); + return IO_WIN.GetLastWriteTimeUtc(SystemIOWindows.AddExtendedDevicePathPrefix(spath)); } /// @@ -191,7 +213,7 @@ namespace Duplicati.Library.Snapshots { var spath = ConvertToSnapshotPath(localPath); - return SystemIO.IO_WIN.GetCreationTimeUtc(SystemIOWindows.AddExtendedDevicePathPrefix(spath)); + return IO_WIN.GetCreationTimeUtc(SystemIOWindows.AddExtendedDevicePathPrefix(spath)); } /// @@ -201,7 +223,7 @@ namespace Duplicati.Library.Snapshots /// An open filestream that can be read public override Stream OpenRead(string localPath) { - return SystemIO.IO_WIN.FileOpenRead(ConvertToSnapshotPath(localPath)); + return IO_WIN.FileOpenRead(ConvertToSnapshotPath(localPath)); } /// @@ -211,7 +233,7 @@ namespace Duplicati.Library.Snapshots /// The length of the file public override long GetFileSize(string localPath) { - return SystemIO.IO_WIN.FileLength(ConvertToSnapshotPath(localPath)); + return IO_WIN.FileLength(ConvertToSnapshotPath(localPath)); } /// @@ -221,7 +243,7 @@ namespace Duplicati.Library.Snapshots /// The file or folder to examine public override FileAttributes GetAttributes(string localPath) { - return SystemIO.IO_WIN.GetFileAttributes(ConvertToSnapshotPath(localPath)); + return IO_WIN.GetFileAttributes(ConvertToSnapshotPath(localPath)); } /// @@ -232,7 +254,7 @@ namespace Duplicati.Library.Snapshots public override string GetSymlinkTarget(string localPath) { var spath = ConvertToSnapshotPath(localPath); - return SystemIO.IO_WIN.GetSymlinkTarget(spath); + return IO_WIN.GetSymlinkTarget(spath); } /// @@ -241,23 +263,16 @@ namespace Duplicati.Library.Snapshots /// The metadata for the given file or folder /// The file or folder to examine /// A flag indicating if the target is a symlink - /// A flag indicating if a symlink should be followed - public override Dictionary GetMetadata(string localPath, bool isSymlink, bool followSymlink) + public override Dictionary GetMetadata(string localPath, bool isSymlink) { - return SystemIO.IO_WIN.GetMetadata(ConvertToSnapshotPath(localPath), isSymlink, followSymlink); + return SystemIO.IO_OS.GetMetadata(ConvertToSnapshotPath(localPath), isSymlink, FollowSymlinks); } /// - public override bool IsBlockDevice(string localPath) - { - return false; - } + public override bool IsBlockDevice(string localPath) => false; /// - public override string HardlinkTargetID(string localPath) - { - return null; - } + public override string HardlinkTargetID(string localPath) => null; /// public override string ConvertToLocalPath(string snapshotPath) @@ -265,10 +280,10 @@ namespace Duplicati.Library.Snapshots if (!Path.IsPathRooted(snapshotPath)) throw new InvalidOperationException(); - foreach (var kvp in _vssBackupComponents.SnapshotDeviceAndVolumes) + foreach (var kvp in _snapshotManager.SnapshotDeviceAndVolumes) { if (snapshotPath.StartsWith(kvp.Key, Utility.Utility.ClientFilenameStringComparison)) - return SystemIO.IO_WIN.PathCombine(kvp.Value, snapshotPath.Substring(kvp.Key.Length)); + return IO_WIN.PathCombine(kvp.Value, snapshotPath.Substring(kvp.Key.Length)); } throw new InvalidOperationException(); @@ -283,8 +298,8 @@ namespace Duplicati.Library.Snapshots if (!Path.IsPathRooted(localPath)) throw new InvalidOperationException(); - var root = SystemIO.IO_WIN.GetPathRoot(localPath); - var volumePath = _vssBackupComponents.GetVolumeFromCache(root); + var root = IO_WIN.GetPathRoot(localPath); + var volumePath = _snapshotManager.GetVolumeFromCache(root); // Note that using a simple Path.Combine() for the following code // can result in invalid snapshot paths; e.g., if localPath is @@ -303,18 +318,15 @@ namespace Duplicati.Library.Snapshots /// public override bool FileExists(string localFilePath) { - return SystemIO.IO_WIN.FileExists(ConvertToSnapshotPath(localFilePath)); + return IO_WIN.FileExists(ConvertToSnapshotPath(localFilePath)); } /// public override bool DirectoryExists(string localFolderPath) { - return SystemIO.IO_WIN.DirectoryExists(ConvertToSnapshotPath(localFolderPath)); + return IO_WIN.DirectoryExists(ConvertToSnapshotPath(localFolderPath)); } - /// - public override bool IsSnapshot => true; - #endregion /// @@ -322,7 +334,7 @@ namespace Duplicati.Library.Snapshots { if (disposing) { - _vssBackupComponents?.Dispose(); + _snapshotManager?.Dispose(); } base.Dispose(disposing); diff --git a/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs b/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs new file mode 100644 index 000000000..da1a31ef0 --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/BackendSourceFileEntry.cs @@ -0,0 +1,347 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Diagnostics.CodeAnalysis; +using System.IO.Pipelines; +using System.Runtime.CompilerServices; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using Duplicati.Library.Utility; + +namespace Duplicati.Library.SourceProvider; + +/// +/// Creates a new backend source entry +/// +/// The parent backend +/// The path of the entry +/// True if the entry is a folder +/// True if the entry is a meta entry +/// The creation time of the entry +/// The last modification time of the entry +/// The size of the entry +public class BackendSourceFileEntry(BackendSourceProvider parent, string path, bool isFolder, bool isRootEntry, DateTime createdUtc, DateTime lastModificationUtc, long size) + : ISourceProviderEntry +{ + /// + /// The log tag for this instance + /// + private static string LOGTAG = Logging.Log.LogTagFromType(); + + /// + public bool IsFolder => isFolder; + + /// + public bool IsMetaEntry => false; + + /// + public bool IsRootEntry => isRootEntry; + + /// + public DateTime CreatedUtc => createdUtc; + + /// + public DateTime LastModificationUtc => lastModificationUtc; + + /// + public string Path => SystemIO.IO_OS.PathCombine(parent?.MountedPath, NormalizePathToLocalSystem(path)); + + /// + public long Size => size; + /// + public bool IsSymlink => false; + + /// + public string? SymlinkTarget => null; + + /// + public FileAttributes Attributes => IsFolder + ? FileAttributes.Directory + : FileAttributes.Normal; + + /// + public Dictionary MinorMetadata => new Dictionary(); + + /// + public bool IsBlockDevice => false; + + /// + public bool IsCharacterDevice => false; + + /// + public bool IsAlternateStream => false; + + /// + public string? HardlinkTargetId => null; + + /// + /// An async enumerator for the entries + /// + private IAsyncEnumerator? preparedEnumerator = null; + + /// + /// A flag to indicate if the prepared enumerator has any entries + /// + private bool preparedEnumeratorAny = false; + + /// + /// Prepares the enumerator for this entry + /// + /// The cancellation token + /// The prepared enumerator + public async Task PrepareEnumerator(CancellationToken cancellationToken) + { + if (!isFolder || !isRootEntry) + throw new InvalidOperationException("PrepareEnumerator can only be called on root folders"); + + if (preparedEnumerator != null) + throw new InvalidOperationException("PrepareEnumerator can only be called once"); + + var result = EnumerateInternal(cancellationToken).GetAsyncEnumerator(cancellationToken); + var prev = Interlocked.Exchange(ref preparedEnumerator, result); + if (prev != null) + throw new InvalidOperationException("PrepareEnumerator can only be called once"); + + // Advance to the first entry, so we are sure it does not throw exceptions + preparedEnumeratorAny = await result.MoveNextAsync().ConfigureAwait(false); + } + + /// + public async IAsyncEnumerable Enumerate([EnumeratorCancellation] CancellationToken cancellationToken) + { + if (!isFolder) + throw new InvalidOperationException("Enumerate can only be called on folders"); + + if (isRootEntry) + { + // If we have a prepared enumerator, consume and use it + var enumerator = Interlocked.Exchange(ref preparedEnumerator, null); + if (enumerator != null) + { + if (preparedEnumeratorAny) + { + // It has already been advanced, so return the current value + yield return enumerator.Current; + + while (await enumerator.MoveNextAsync()) + yield return enumerator.Current; + } + + yield break; + } + } + + // Otherwise, enumerate the entries + await foreach (var entry in EnumerateInternal(cancellationToken).ConfigureAwait(false)) + yield return entry; + + } + + private IAsyncEnumerable EnumerateInternal(CancellationToken cancellationToken) + => parent.WrappedBackend.ListAsync(NormalizePathTo(path, '/'), cancellationToken) + // Remove the current and parent folder entries + .Where(x => !string.IsNullOrWhiteSpace(x.Name) && x.Name != "." && x.Name != "..") + // Remove sub-folder entries + .Where(x => !x.Name[0..^1].Contains('\\') && !x.Name[0..^1].Contains('/')) + // Convert to source file entries + .Select(x => + { + var localPath = SystemIO.IO_OS.PathCombine(path, NormalizePathToLocalSystem(x.Name)); + if (x.IsFolder) + localPath = Util.AppendDirSeparator(localPath); + + return new BackendSourceFileEntry( + parent, + localPath, + x.IsFolder, + false, + x.Created, + x.LastModification, + x.Size + ); + }); + + /// + public async Task FileExists(string path, CancellationToken cancellationToken) + { + if (!isFolder) + throw new InvalidOperationException("FileExists cannot be called on folders"); + + try + { + var entry = await parent.WrappedBackend.GetEntryAsync(NormalizePathTo(path, '/'), cancellationToken); + return entry != null && !entry.IsFolder; + } + catch (FileNotFoundException) + { + return false; + } + } + + /// + public Task OpenMetadataRead(CancellationToken cancellationToken) + => Task.FromResult(null); + + /// + /// Helper class for reporting th length of a stream + /// + /// The stream to wrap + /// The length to report + private class LengthReportingStream(Stream stream, long reportedLength) : OverrideableStream(stream) + { + /// + /// Track the position + /// + private long position = 0; + + /// + public override long Length => reportedLength; + + /// + public override long Position + { + get => position; + set => position = base.Position = value; + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + base.Write(buffer, offset, count); + position += count; + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + var read = base.Read(buffer, offset, count); + position += read; + return read; + } + + /// + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var read = await base.ReadAsync(buffer, offset, count, cancellationToken); + position += read; + return read; + } + + /// + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + await base.WriteAsync(buffer, offset, count, cancellationToken); + position += count; + } + } + + /// + public async Task OpenRead(CancellationToken cancellationToken) + { + if (isFolder) + throw new InvalidOperationException("OpenRead can only be called on files"); + + if (parent.WrappedBackend is IStreamingBackend streamingBackend) + { + var pipe = new Pipe(); + + // Start writing data to the pipe asynchronously + _ = Task.Run(async () => + { + try + { + await streamingBackend.GetAsync(NormalizePathTo(path, '/'), pipe.Writer.AsStream(), cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + await pipe.Writer.CompleteAsync(ex).ConfigureAwait(false); + } + finally + { + await pipe.Writer.CompleteAsync().ConfigureAwait(false); + } + }) + .ContinueWith(t => + { + if (t.IsFaulted) + Logging.Log.WriteWarningMessage(LOGTAG, "ErrorPipingStream", t.Exception, "Error piping stream for {0}", this.Path); + }); + + // Return the readable stream so the caller can read data as it's produced + return new LengthReportingStream(pipe.Reader.AsStream(), size); + } + else + { + TempFile? tempFile = null; + TempFileStream? file = null; + try + { + tempFile = new TempFile(); + await parent.WrappedBackend.GetAsync(path, tempFile, cancellationToken); + file = TempFileStream.Create(tempFile); + return file; + } + catch + { + file?.Dispose(); + tempFile?.Dispose(); + throw; + } + } + } + + /// + /// Normalizes the path, turning backslashes into forward slashes, + /// or vice versa, depending on the platform + /// + /// The path to normalize + /// The normalized path + public static string NormalizePathToLocalSystem(string path) + => NormalizePathTo(path, System.IO.Path.DirectorySeparatorChar); + + /// + /// Normalizes the path, turning backslashes into forward slashes, + /// or vice versa, depending on the platform + /// + /// The path to normalize + /// The normalized path + [return: NotNullIfNotNull("path")] + public static string? NormalizePathTo(string? path, char separator) + { + if (string.IsNullOrEmpty(path)) + return path; + + return path + .Replace('/', separator) + .Replace('\\', separator); + } + + /// + /// Creates a new backend source entry from a file entry + /// + /// The parent backend + /// The file entry + /// The prefix to add to the path + /// The new backend source entry + public static BackendSourceFileEntry FromFileEntry(BackendSourceProvider parent, string prefix, IFileEntry entry) + => new BackendSourceFileEntry(parent, SystemIO.IO_OS.PathCombine(prefix, entry.Name), entry.IsFolder, false, entry.Created, entry.LastModification, entry.Size); +} + diff --git a/Duplicati/Library/SourceProvider/Builtin/BackendSourceProvider.cs b/Duplicati/Library/SourceProvider/Builtin/BackendSourceProvider.cs new file mode 100644 index 000000000..8e9c39853 --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/BackendSourceProvider.cs @@ -0,0 +1,99 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; + +namespace Duplicati.Library.SourceProvider; + +/// +/// A source provider that wraps a backend +/// +/// The backend to wrap +/// The path to mount the backend +public class BackendSourceProvider(IFolderEnabledBackend backend, string mountedPath) : ISourceProvider, ISourceProviderModule +{ + /// + /// The wrapped backend + /// + public IFolderEnabledBackend WrappedBackend => backend; + + /// + /// The path where the provider is logically mounted + /// + public string MountedPath => mountedPath; + + /// + public string Key => backend.ProtocolKey; + + /// + public string DisplayName => backend.DisplayName; + + /// + public IList SupportedCommands => backend.SupportedCommands; + + /// + /// The prepared root entry, if any + /// + private BackendSourceFileEntry? preparedRoot; + + /// + /// Flags if the provider has been initialized + /// + private int isInitialized = 0; + + /// + /// Creates a root entry + /// + /// The root entry + private BackendSourceFileEntry CreateRoot() + => new BackendSourceFileEntry(this, "", true, true, new DateTime(0), new DateTime(0), 0); + + /// + public async Task Initialize(CancellationToken cancellationToken) + { + // Only allow a single intiiialization call + if (Interlocked.Exchange(ref isInitialized, 1) != 0) + return; + + // Prepare the root entry + var root = CreateRoot(); + await root.PrepareEnumerator(cancellationToken).ConfigureAwait(false); + preparedRoot = root; + } + + /// + public IAsyncEnumerable Enumerate(CancellationToken cancellationToken) + => new[] { Interlocked.Exchange(ref preparedRoot, null) ?? CreateRoot() }.ToAsyncEnumerable(); + + /// + public async Task GetEntry(string path, bool isFolder, CancellationToken cancellationToken) + { + var entry = await backend.GetEntryAsync(BackendSourceFileEntry.NormalizePathTo(path, '/'), cancellationToken).ConfigureAwait(false); + return entry == null ? null : BackendSourceFileEntry.FromFileEntry(this, path, entry); + } + + /// + public void Dispose() + { + WrappedBackend.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/Duplicati/Library/SourceProvider/Builtin/Combiner.cs b/Duplicati/Library/SourceProvider/Builtin/Combiner.cs new file mode 100644 index 000000000..5205c049e --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/Combiner.cs @@ -0,0 +1,102 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Runtime.CompilerServices; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.SourceProvider; + +/// +/// Helper class that wraps multiple source providers into a single source provider +/// +public class Combiner(IEnumerable providers) : ISourceProvider +{ + /// + public string MountedPath => string.Empty; + + /// + /// The providers to combine + /// + private readonly List providers = providers.SelectMany(x => x is Combiner c ? c.providers.AsEnumerable() : [x]).ToList(); + + /// + public Task Initialize(CancellationToken cancellationToken) + => Task.WhenAll(providers.Select(x => x.Initialize(cancellationToken))); + + /// + public async IAsyncEnumerable Enumerate([EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var provider in providers) + { + await foreach (var entry in provider.Enumerate(cancellationToken).ConfigureAwait(false)) + yield return entry; + } + } + + /// + /// Provides access to the combined providers + /// + public IEnumerable Providers => providers; + + /// + /// Gets a filesystem entry for a given path + /// + /// The path to get + /// True if the path is a folder + /// The cancellation token + /// The filesystem entry + public async Task GetEntry(string path, bool isFolder, CancellationToken cancellationToken) + { + foreach (var provider in providers) + if (!string.IsNullOrWhiteSpace(MountedPath) && provider.MountedPath.StartsWith(path)) + { + var subpath = provider.MountedPath.Substring(path.Length); + return await provider.GetEntry(subpath, isFolder, cancellationToken).ConfigureAwait(false); + } + + foreach (var provider in providers.Where(x => string.IsNullOrWhiteSpace(x.MountedPath))) + { + var res = await provider.GetEntry(path, isFolder, cancellationToken).ConfigureAwait(false); + if (res != null) + return res; + } + + return null; + } + + /// + /// Creates a combined source provider from a list of providers + /// + /// The providers to combine + /// The combined provider + public static ISourceProvider Combine(IEnumerable providers) + { + var providerTotal = providers.SelectMany(x => x is Combiner c ? c.providers.AsEnumerable() : [x]).ToList(); + return providerTotal.Count == 1 ? providerTotal[0] : new Combiner(providers); + } + + /// + public void Dispose() + { + foreach (var provider in providers) + provider.Dispose(); + } +} diff --git a/Duplicati/Library/SourceProvider/Builtin/Duplicati.Library.SourceProvider.Builtin.csproj b/Duplicati/Library/SourceProvider/Builtin/Duplicati.Library.SourceProvider.Builtin.csproj new file mode 100644 index 000000000..e48c06ee4 --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/Duplicati.Library.SourceProvider.Builtin.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + Library + Duplicati.Library.SourceProvider + Copyright © 2024 Team Duplicati, MIT license + enable + enable + Duplicati.Library.SourceProvider + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Duplicati/Library/SourceProvider/Builtin/LocalFileSource.cs b/Duplicati/Library/SourceProvider/Builtin/LocalFileSource.cs new file mode 100644 index 000000000..2568b4474 --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/LocalFileSource.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using Duplicati.Library.Snapshots; + +namespace Duplicati.Library.SourceProvider; + +/// +/// A source provider that wraps a file system through a snapshot service +/// +/// The snapshot service to use +public class LocalFileSource(ISnapshotService snapshotService) : ISourceProvider +{ + /// + public string MountedPath => string.Empty; + + /// + public Task Initialize(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + public IAsyncEnumerable Enumerate(CancellationToken cancellationToken) + => snapshotService.EnumerateFilesystemEntries().ToAsyncEnumerable(); + + /// + /// The snapshot service being used + /// + public ISnapshotService SnapshotService => snapshotService; + + /// + public Task GetEntry(string path, bool isFolder, CancellationToken cancellationToken) + => Task.FromResult(snapshotService.GetFilesystemEntry(path, isFolder)); + + /// + public void Dispose() + { + snapshotService.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/Duplicati/Library/SourceProvider/Builtin/Strings.cs b/Duplicati/Library/SourceProvider/Builtin/Strings.cs new file mode 100644 index 000000000..64cc9e118 --- /dev/null +++ b/Duplicati/Library/SourceProvider/Builtin/Strings.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Localization.Short; +namespace Duplicati.Library.SourceProvider.Strings +{ + internal static class File + { + public static string Description { get { return LC.L(@"This backend can read and write data to an file based backend. Allowed formats are ""file://hostname/folder"" and ""file://username:password@hostname/folder"". You may supply UNC paths (e.g.: ""file://\\server\folder"") or local paths (e.g.: (win) ""file://c:\folder"", (linux) ""file:///usr/pub/files""). Use the prefix ""vss://"" or ""lvm://"" to create snapshot based folders."); } } + public static string DisplayName { get { return LC.L(@"Local folder or drive"); } } + } +} diff --git a/Duplicati/Library/SourceProviders/Duplicati.Library.SourceProviders.csproj b/Duplicati/Library/SourceProviders/Duplicati.Library.SourceProviders.csproj new file mode 100644 index 000000000..64dae938d --- /dev/null +++ b/Duplicati/Library/SourceProviders/Duplicati.Library.SourceProviders.csproj @@ -0,0 +1,14 @@ + + + net8.0 + Central reference to all source providers + Duplicati.Library.Backend + Copyright © 2024 Team Duplicati, MIT license + enable + enable + + + + + + diff --git a/Duplicati/Library/SourceProviders/SourceProviderModules.cs b/Duplicati/Library/SourceProviders/SourceProviderModules.cs new file mode 100644 index 000000000..7bc0b612c --- /dev/null +++ b/Duplicati/Library/SourceProviders/SourceProviderModules.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; + +namespace Duplicati.Library.SourceProviders; + +/// +/// Shared list of statically linked source-provider modules +/// +public static class SourceProviderModules +{ + /// + /// The list of all built-in source-provider modules + /// + public static IReadOnlyList BuiltInSourceProviderModules => SupportedSourceProviders; + + /// + /// Calculate list once and cache it + /// + private static readonly IReadOnlyList SupportedSourceProviders = new ISourceProviderModule[] { + // ... No providers currently implemented + } + .Where(x => x != null) + .ToList(); +} + diff --git a/Duplicati/Library/UsageReporter/Duplicati.Library.UsageReporter.csproj b/Duplicati/Library/UsageReporter/Duplicati.Library.UsageReporter.csproj index 558c75704..2c77a7215 100644 --- a/Duplicati/Library/UsageReporter/Duplicati.Library.UsageReporter.csproj +++ b/Duplicati/Library/UsageReporter/Duplicati.Library.UsageReporter.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Library/UsageReporter/EventProcessor.cs b/Duplicati/Library/UsageReporter/EventProcessor.cs index 61ce20b08..04d1fc810 100644 --- a/Duplicati/Library/UsageReporter/EventProcessor.cs +++ b/Duplicati/Library/UsageReporter/EventProcessor.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/UsageReporter/OSInfoHelper.cs b/Duplicati/Library/UsageReporter/OSInfoHelper.cs index bf0d26e3b..4100c7703 100644 --- a/Duplicati/Library/UsageReporter/OSInfoHelper.cs +++ b/Duplicati/Library/UsageReporter/OSInfoHelper.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/UsageReporter/ReportItem.cs b/Duplicati/Library/UsageReporter/ReportItem.cs index 78db893e8..534e70806 100644 --- a/Duplicati/Library/UsageReporter/ReportItem.cs +++ b/Duplicati/Library/UsageReporter/ReportItem.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/UsageReporter/ReportSet.cs b/Duplicati/Library/UsageReporter/ReportSet.cs index 85955bd5c..55ac80dcb 100644 --- a/Duplicati/Library/UsageReporter/ReportSet.cs +++ b/Duplicati/Library/UsageReporter/ReportSet.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -38,18 +38,19 @@ namespace Duplicati.Library.UsageReporter private static string Cached_AppName; private static string Cached_AppVersion; private static string Cached_Assembly; + private static string Cached_PackageTypeId; [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void DoInitUID() { - Cached_UserID = Library.AutoUpdater.UpdaterManager.InstallID; + Cached_UserID = Library.AutoUpdater.DataFolderManager.InstallID; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void DoInitOS() { if (OperatingSystem.IsMacOS()) - Cached_OSType = "OSX"; + Cached_OSType = "MacOS"; else if (OperatingSystem.IsLinux()) Cached_OSType = "Linux"; else if (OperatingSystem.IsWindows()) @@ -60,6 +61,12 @@ namespace Duplicati.Library.UsageReporter Cached_OSVersion = OSInfoHelper.PlatformString; } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private static void DoInitPackageType() + { + Cached_PackageTypeId = Library.AutoUpdater.UpdaterManager.PackageTypeId; + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] private static void DoInitCLR() { @@ -90,6 +97,9 @@ namespace Duplicati.Library.UsageReporter try { DoInitOS(); } catch { } + try { DoInitPackageType(); } + catch { } + try { DoInitCLR(); } catch { } @@ -109,6 +119,8 @@ namespace Duplicati.Library.UsageReporter public string OSVersion { get; set; } [JsonProperty("clrversion")] public string CLRVersion { get; set; } + [JsonProperty("pkgid")] + public string PackageTypeId { get; set; } [JsonProperty("appname")] public string AppName { get; set; } [JsonProperty("appversion")] @@ -126,6 +138,7 @@ namespace Duplicati.Library.UsageReporter this.OSType = Cached_OSType; this.OSVersion = Cached_OSVersion; this.CLRVersion = Cached_CLRVersion; + this.PackageTypeId = Cached_PackageTypeId; this.AppName = Cached_AppName; this.AppVersion = Cached_AppVersion; this.Assembly = Cached_Assembly; diff --git a/Duplicati/Library/UsageReporter/ReportSetUploader.cs b/Duplicati/Library/UsageReporter/ReportSetUploader.cs index b65a86fb3..9c396f760 100644 --- a/Duplicati/Library/UsageReporter/ReportSetUploader.cs +++ b/Duplicati/Library/UsageReporter/ReportSetUploader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,10 +22,7 @@ using System; using CoCoL; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Linq; using System.IO; -using System.Net; using System.Net.Http; using Duplicati.Library.Utility; using System.Threading; @@ -62,7 +59,7 @@ namespace Duplicati.Library.UsageReporter public static Tuple> Run() { var channel = ChannelManager.CreateChannel( - buffersize: MAX_PENDING_UPLOADS, + buffersize: MAX_PENDING_UPLOADS, pendingWritersOverflowStrategy: QueueOverflowStrategy.LIFO ); @@ -85,13 +82,14 @@ namespace Duplicati.Library.UsageReporter if (fs.Length > 0) { using var request = new HttpRequestMessage(HttpMethod.Post, UPLOAD_URL); - + request.Content = new StreamContent(fs); using var timeoutToken = new CancellationTokenSource(); timeoutToken.CancelAfter(TimeSpan.FromSeconds(UPLOAD_OPERATION_TIMEOUT_SECONDS)); - - var response = await HttpClientHelper.DefaultClient.UploadStream(request, timeoutToken.Token); + + using var client = HttpClientHelper.CreateClient(); + using var response = await client.UploadStream(request, timeoutToken.Token); rc = (int)response.StatusCode; } else diff --git a/Duplicati/Library/UsageReporter/ReportType.cs b/Duplicati/Library/UsageReporter/ReportType.cs index c863392bb..a8aead69f 100644 --- a/Duplicati/Library/UsageReporter/ReportType.cs +++ b/Duplicati/Library/UsageReporter/ReportType.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/UsageReporter/Reporter.cs b/Duplicati/Library/UsageReporter/Reporter.cs index cf9ac5f96..35ce6e684 100644 --- a/Duplicati/Library/UsageReporter/Reporter.cs +++ b/Duplicati/Library/UsageReporter/Reporter.cs @@ -1,27 +1,28 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Threading.Tasks; using CoCoL; +using Duplicati.Library.Utility; namespace Duplicati.Library.UsageReporter { @@ -34,7 +35,7 @@ namespace Duplicati.Library.UsageReporter /// The tag used for logging /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(Reporter)); - + /// /// The primary input channel for new report messages /// @@ -126,7 +127,7 @@ namespace Duplicati.Library.UsageReporter public static void ShutDown() { if (_eventChannel != null && !_eventChannel.IsRetiredAsync.Result) - _eventChannel.Retire(); + _eventChannel.RetireAsync().Await(); if (ShutdownTask != null) { @@ -194,7 +195,7 @@ namespace Duplicati.Library.UsageReporter /// true if is disabled; otherwise, false. private static bool IsDisabled { - get + get { if (Forced_Disabled) return true; diff --git a/Duplicati/Library/Utility/AsyncHttpRequest.cs b/Duplicati/Library/Utility/AsyncHttpRequest.cs index 94444c2cd..e9615717a 100644 --- a/Duplicati/Library/Utility/AsyncHttpRequest.cs +++ b/Duplicati/Library/Utility/AsyncHttpRequest.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; diff --git a/Duplicati/Library/Utility/BlockingQueue.cs b/Duplicati/Library/Utility/BlockingQueue.cs index b9e696179..c2b9f2a8b 100644 --- a/Duplicati/Library/Utility/BlockingQueue.cs +++ b/Duplicati/Library/Utility/BlockingQueue.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/CallContextSettings.cs b/Duplicati/Library/Utility/CallContextSettings.cs index 7a0ddfe46..66a1824cf 100644 --- a/Duplicati/Library/Utility/CallContextSettings.cs +++ b/Duplicati/Library/Utility/CallContextSettings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,13 +20,13 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Security; using System.Security.Cryptography.X509Certificates; - +using System.Threading; using Duplicati.Library.Interface; -using Duplicati.Library.Logging; namespace Duplicati.Library.Utility { @@ -81,12 +81,9 @@ namespace Duplicati.Library.Utility } set { - lock (CallContextSettings._lock) - { - var st = CallContextSettings.Settings; - st.Tempdir = value; - CallContextSettings.Settings = st; - } + var st = CallContextSettings.Settings; + st.Tempdir = value; + CallContextSettings.Settings = st; } } @@ -228,20 +225,58 @@ namespace Duplicati.Library.Utility public static class CallContextSettings { /// - /// The settings that are stored in the call context, which are not serializable + /// The call context settings for OAuth /// - private static readonly Dictionary _settings = new Dictionary(); + private static readonly AsyncLocal _settings = new AsyncLocal(); /// - /// The context setting types that are used and need their private setting key - /// to prevent overwriting each others settings. + /// The instances that are currently active /// - private static string contextSettingsType = null; + private static readonly ConcurrentDictionary _instances = new ConcurrentDictionary(); /// - /// Lock for protecting the dictionary + /// Disposable class for setting call context settings /// - public static readonly object _lock = new object(); + /// The type of the settings + private sealed class Disposer : IDisposable + { + /// + /// A flag indicating if the object has been disposed + /// + private bool m_disposed = false; + /// + /// The previous value + /// + private T m_prev; + /// + /// The current value + /// + private T m_current; + + /// + /// Initializes a new instance of the class. + /// + /// The current server URL + /// The previous server URL + public Disposer(T current, T prev) + { + _settings.Value = m_current = current; + this.m_prev = prev; + _instances.TryAdd(current, null); + } + + /// + public void Dispose() + { + if (m_disposed) + return; + GC.SuppressFinalize(this); + m_disposed = true; + if (_settings.Value.Equals(m_current)) + _settings.Value = m_prev; + _instances.TryRemove(m_current, out _); + } + } /// /// Gets or sets the values in the current call context @@ -249,40 +284,8 @@ namespace Duplicati.Library.Utility /// The settings. public static T Settings { - get - { - var key = ContextID; - if (string.IsNullOrWhiteSpace(key)) - return default(T); - - - if (!_settings.TryGetValue(key, out T res)) - lock (_lock) // if not present but context id is not null, wait - if (!_settings.TryGetValue(key, out res)) - return default(T); - - return res; - } - set - { - var key = ContextID; - if (!string.IsNullOrWhiteSpace(key)) - lock (_lock) - _settings[key] = value; - } - } - - /// - /// Starts the context wit a default value. - /// - /// The disposable handle for the context. - /// The initial value. - public static IDisposable StartContext(string settingsKey, T initial = default(T)) - { - contextSettingsType = settingsKey; - var res = new ContextGuard(); - Settings = initial; - return res; + get => _settings.Value; + set => _settings.Value = value; } /// @@ -291,72 +294,10 @@ namespace Duplicati.Library.Utility /// The disposable handle for the context. /// The initial value. public static IDisposable StartContext(T initial = default(T)) - { - return StartContext(typeof(T).ToString(), initial); - } + => new Disposer(initial, _settings.Value); - /// - /// Gets all instances currently registered - /// - internal static T[] GetAllInstances() - { - lock (_lock) - return _settings.Values.ToArray(); - } + public static IEnumerable GetAllInstances() + => _instances.Keys.ToList(); - /// - /// Help class for providing a way to release resources associated with a call context - /// - private class ContextGuard : IDisposable - { - /// - /// Randomly generated identifier - /// - private readonly string ID = Guid.NewGuid().ToString(); - - /// - /// Initializes a new instance of the - /// class, - /// and sets up the call context - /// - public ContextGuard() - { - CallContext.SetData(contextSettingsType, ID); - } - - /// - /// Releases all resource used by the - /// object. - /// - /// Call when you are finished using the - /// . The - /// method leaves the in an - /// unusable state. After calling , you must release all references to the - /// so the garbage collector - /// can reclaim the memory that the - /// was occupying. - public void Dispose() - { - lock (_lock) - { - // Release the resources, if any - _settings.Remove(ID); - } - - CallContext.SetData(contextSettingsType, null); - } - } - - /// - /// Gets the context ID for this call. - /// - /// The context identifier. - private static string ContextID - { - get - { - return contextSettingsType != null ? CallContext.GetData(contextSettingsType) as string : null; - } - } } } diff --git a/Duplicati/Library/Utility/CombinedStream.cs b/Duplicati/Library/Utility/CombinedStream.cs index 573c1e72d..b801049e1 100644 --- a/Duplicati/Library/Utility/CombinedStream.cs +++ b/Duplicati/Library/Utility/CombinedStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/CommandLineArgumentMapper.cs b/Duplicati/Library/Utility/CommandLineArgumentMapper.cs new file mode 100644 index 000000000..54a6cf55e --- /dev/null +++ b/Duplicati/Library/Utility/CommandLineArgumentMapper.cs @@ -0,0 +1,222 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reflection; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility; + +/// +/// Helper to get the command line argument type attribute +/// +public interface ICommandLineArgumentMapper +{ + /// + /// Get the command line argument type attribute + /// + /// The member info to get the attribute from + /// The command line argument type attribute + CommandLineArgumentDescriptionAttribute? GetCommandLineArgumentDescription(MemberInfo mi); +} + +/// +/// Attribute to define command line argument types +/// +public class CommandLineArgumentDescriptionAttribute +{ + /// + /// The type of the argument, using the property type as default + /// + public CommandLineArgument.ArgumentType? Type { get; init; } = null; + /// + /// The name of the argument, using the property name as default + /// + public string? Name { get; init; } = null; + /// + /// A short description of the argument, using the property name as default + /// + public string? ShortDescription { get; init; } = null; + /// + /// A long description of the argument, using the property name as default + /// + public string? LongDescription { get; init; } = null; + /// + /// The default value for the argument, using the property value as default + /// + public string? DefaultValue { get; init; } = null; + /// + /// The list of valid values for the argument + /// + public string[]? ValueList { get; init; } = null; + /// + /// The converter to use for the argument + /// + public Func? Converter { get; init; } = null; +} + +/// +/// Implementation of commandline argument mapping +/// +public static class CommandLineArgumentMapper +{ + /// + /// Helper method to detect slow-access properties + /// + /// The property to read + /// The parent object to read from + /// A list of properties to exclude + /// The value of the property + private static object? GetValueGuarded(PropertyInfo p, object parent, HashSet? excludeDefaultValue) + { + object? value = null; + if (excludeDefaultValue != null && excludeDefaultValue.Contains(p.Name)) + return value; +#if DEBUG + var start = DateTime.Now; + value = p.GetValue(parent); + var duration = DateTime.Now - start; + if (duration.TotalMilliseconds > 100) + Console.WriteLine($"Warning: {parent.GetType().FullName}.{p.Name} took {duration.TotalMilliseconds}ms to get value"); +#else + value = p.GetValue(parent); +#endif + return value; + } + + /// + /// Extracts all primitive types from a class and maps them to command line arguments + /// + /// The type to extract arguments from + /// The prefix to use for the arguments + /// A list of properties to exclude + /// A list of command line arguments + public static IEnumerable MapArguments(object obj, string prefix = "", HashSet? exclude = null, HashSet? excludeDefaultValue = null) + { + exclude ??= new HashSet(); + foreach (var p in obj.GetType().GetProperties().Where(p => p.CanWrite && p.CanRead)) + { + if (exclude.Contains(p.Name)) + continue; + + var customAttr = (obj as ICommandLineArgumentMapper)?.GetCommandLineArgumentDescription(p); + + var name = (prefix + (customAttr?.Name ?? p.Name)).ToLowerInvariant(); + var shortDescription = customAttr?.ShortDescription ?? p.Name; + var longDescription = customAttr?.LongDescription ?? p.Name; + var defaultValue = customAttr?.DefaultValue ?? GetValueGuarded(p, obj, excludeDefaultValue)?.ToString(); + var argumentType = customAttr?.Type; + + // Fully custom argument + if (customAttr != null && customAttr.Converter != null && argumentType != null) + { + yield return new CommandLineArgument(name, argumentType.Value, shortDescription, longDescription, defaultValue, null, customAttr.ValueList); + continue; + } + + var propType = p.PropertyType; + if (Nullable.GetUnderlyingType(propType) != null) + propType = Nullable.GetUnderlyingType(propType); + + if (propType == null) + continue; + + if (propType == typeof(string)) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.String, shortDescription, longDescription, defaultValue); + else if (propType == typeof(int)) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.Integer, shortDescription, longDescription, defaultValue); + else if (propType == typeof(bool)) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.Boolean, shortDescription, longDescription, defaultValue); + else if (propType.IsEnum) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.String, shortDescription, longDescription, defaultValue, null, customAttr?.ValueList ?? Enum.GetNames(propType)); + else if (propType == typeof(Uri)) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.String, shortDescription, longDescription, defaultValue); + else if (propType == typeof(TimeSpan)) + yield return new CommandLineArgument(name, argumentType ?? CommandLineArgument.ArgumentType.Timespan, shortDescription, longDescription, defaultValue); + } + } + + /// + /// Applies the arguments to the object + /// + /// The type of object to apply the arguments to + /// The object to apply the arguments to + /// The arguments to apply + /// The prefix to use for the arguments + /// The object with the arguments applied + public static T ApplyArguments(T obj, IReadOnlyDictionary args, string prefix = "") + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + + foreach (var p in obj.GetType().GetProperties().Where(p => p.CanWrite && p.CanRead)) + { + var customAttr = (obj as ICommandLineArgumentMapper)?.GetCommandLineArgumentDescription(p); + var name = (prefix + (customAttr?.Name ?? p.Name)).ToLowerInvariant(); + + var propType = p.PropertyType; + if (Nullable.GetUnderlyingType(propType) != null) + propType = Nullable.GetUnderlyingType(propType); + + if (propType == null) + continue; + + // Ignore case-sensitive properties of input dictionary + var value = args.Keys.FirstOrDefault(x => string.Equals(x, name, StringComparison.OrdinalIgnoreCase)) is string key ? args[key] : null; + if (value != null) + { + if (customAttr?.Converter != null) + p.SetValue(obj, customAttr.Converter(value)); + else if (propType == typeof(string)) + p.SetValue(obj, value); + else if (propType == typeof(int)) + p.SetValue(obj, int.Parse(value)); + else if (propType == typeof(bool)) + p.SetValue(obj, bool.Parse(value)); + else if (propType.IsEnum) + p.SetValue(obj, Enum.Parse(propType, value, true)); + else if (propType == typeof(Uri)) + p.SetValue(obj, new Uri(value)); + else if (propType == typeof(TimeSpan)) + p.SetValue(obj, Timeparser.ParseTimeSpan(value)); + } + } + + return obj; + } + + /// + /// Applies the arguments to the object + /// + /// The type of object to apply the arguments to + /// The object to apply the arguments to + /// The arguments to apply + /// The prefix to use for the arguments + /// The object with the arguments applied + public static T ApplyArguments(T obj, NameValueCollection nameValueCollection, string prefix = "") + => ApplyArguments(obj, nameValueCollection.AllKeys.Where(k => !string.IsNullOrWhiteSpace(k)).ToDictionary(k => k!, k => nameValueCollection[k]), prefix); + +} diff --git a/Duplicati/Library/Utility/CommandLineArgumentValidator.cs b/Duplicati/Library/Utility/CommandLineArgumentValidator.cs new file mode 100644 index 000000000..cd7136faa --- /dev/null +++ b/Duplicati/Library/Utility/CommandLineArgumentValidator.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility; + +/// +/// Helper class for validating command line arguments +/// +public static class CommandLineArgumentValidator +{ + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(CommandLineArgumentValidator)); + + /// + /// Validates arguments against the supported commands + /// + /// The arguments supported< + public static void ValidateArguments(IEnumerable arguments, IReadOnlyDictionary options, IReadOnlySet knownDuplicateOptions, IReadOnlySet IgnoredOptions) + { + var allOptionsGrouped = arguments + .SelectMany(option => (option.Aliases ?? []).Prepend(option.Name).Select(name => (Name: name, Option: option))) + .GroupBy(x => x.Name, StringComparer.OrdinalIgnoreCase); + +#if DEBUG + foreach (var n in allOptionsGrouped.Where(x => !knownDuplicateOptions.Contains(x.Key) && x.Count() > 1)) + Logging.Log.WriteErrorMessage(LOGTAG, "DuplicateOption", null, $"Duplicate option: {n.Key}"); +#endif + + var allOptions = allOptionsGrouped + .ToDictionary(x => x.Key, x => x.First().Option, StringComparer.OrdinalIgnoreCase); + + foreach (var (key, value) in options.Where(x => !IgnoredOptions.Contains(x.Key))) + { + if (!allOptions.TryGetValue(key, out var option) || option == null) + { + Logging.Log.WriteWarningMessage(LOGTAG, "UnknownOption", null, $"Unknown option supplied: {key}"); + continue; + } + + var validationMessage = ValidateOptionValue(option, key, value); + if (!string.IsNullOrWhiteSpace(validationMessage)) + Logging.Log.WriteWarningMessage(LOGTAG, "OptionValidationError", null, validationMessage); + } + } + + /// + /// Checks if the value passed to an option is actually valid. + /// + /// The argument being validated + /// The name of the option to validate + /// The value to check + /// Null if no errors are found, an error message otherwise + public static string? ValidateOptionValue(ICommandLineArgument arg, string optionname, string? value) + { + if (arg.Type == CommandLineArgument.ArgumentType.Enumeration) + { + var found = false; + foreach (var v in arg.ValidValues ?? []) + if (string.Equals(v, value, StringComparison.OrdinalIgnoreCase)) + { + found = true; + break; + } + + if (!found) + return Strings.CommandLineArgumentValidator.UnsupportedEnumerationValue(optionname, value, arg.ValidValues ?? new string[0]); + + } + else if (arg.Type == CommandLineArgument.ArgumentType.Flags) + { + var validatedAllFlags = false; + var flags = (value ?? string.Empty).ToLowerInvariant().Split(new[] { "," }, StringSplitOptions.None).Select(flag => flag.Trim()).Distinct(); + var validFlags = arg.ValidValues ?? []; + + foreach (var flag in flags) + { + if (!validFlags.Any(validFlag => string.Equals(validFlag, flag, StringComparison.OrdinalIgnoreCase))) + { + validatedAllFlags = false; + break; + } + + validatedAllFlags = true; + } + + if (!validatedAllFlags) + return Strings.CommandLineArgumentValidator.UnsupportedFlagsValue(optionname, value, validFlags); + } + else if (arg.Type == CommandLineArgument.ArgumentType.Boolean) + { + if (!string.IsNullOrEmpty(value) && Utility.ParseBool(value, true) != Utility.ParseBool(value, false)) + return Strings.CommandLineArgumentValidator.UnsupportedBooleanValue(optionname, value); + } + else if (arg.Type == CommandLineArgument.ArgumentType.Integer) + { + if (!long.TryParse(value, out _)) + return Strings.CommandLineArgumentValidator.UnsupportedIntegerValue(optionname, value); + } + else if (arg.Type == CommandLineArgument.ArgumentType.Path) + { + if (string.IsNullOrWhiteSpace(value)) + return Strings.CommandLineArgumentValidator.UnsupportedPathValue(optionname, value); + + foreach (var p in value.Split(Path.DirectorySeparatorChar)) + if (p.IndexOfAny(Path.GetInvalidPathChars()) >= 0) + return Strings.CommandLineArgumentValidator.UnsupportedPathValue(optionname, p); + } + else if (arg.Type == CommandLineArgument.ArgumentType.Size) + { + try + { + Sizeparser.ParseSize(value); + } + catch + { + return Strings.CommandLineArgumentValidator.UnsupportedSizeValue(optionname, value); + } + + if (!string.IsNullOrWhiteSpace(value) && char.IsDigit(value.Last())) + return Strings.CommandLineArgumentValidator.NonQualifiedSizeValue(optionname, value); + } + else if (arg.Type == CommandLineArgument.ArgumentType.Timespan) + { + try + { + Timeparser.ParseTimeSpan(value); + } + catch + { + return Strings.CommandLineArgumentValidator.UnsupportedTimeValue(optionname, value); + } + } + + return null; + } + +} \ No newline at end of file diff --git a/Duplicati/Library/Utility/CommandLineParser.cs b/Duplicati/Library/Utility/CommandLineParser.cs index 95686ae0e..347570cd5 100644 --- a/Duplicati/Library/Utility/CommandLineParser.cs +++ b/Duplicati/Library/Utility/CommandLineParser.cs @@ -1,27 +1,26 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; -using System.Text; namespace Duplicati.Library.Utility { @@ -39,7 +38,7 @@ namespace Duplicati.Library.Utility /// The commandline arguments public static Dictionary ExtractOptions(List args, Func parserCallback = null) { - Dictionary options = new Dictionary(); + var options = new Dictionary(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < args.Count; i++) { diff --git a/Duplicati/Library/Utility/DeviceIDHelper.cs b/Duplicati/Library/Utility/DeviceIDHelper.cs index a30e5b8f7..64f896b06 100644 --- a/Duplicati/Library/Utility/DeviceIDHelper.cs +++ b/Duplicati/Library/Utility/DeviceIDHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj index f95031649..d5bdf8b5f 100644 --- a/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj +++ b/Duplicati/Library/Utility/Duplicati.Library.Utility.csproj @@ -4,11 +4,13 @@ net8.0 Library Duplicati.Library.Utility - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + + diff --git a/Duplicati/Library/Utility/EnumerableExtensions.cs b/Duplicati/Library/Utility/EnumerableExtensions.cs new file mode 100644 index 000000000..97417d278 --- /dev/null +++ b/Duplicati/Library/Utility/EnumerableExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System.Collections.Generic; +using System.Linq; + +namespace Duplicati.Library.Utility; + +/// +/// Contains extension methods for . +/// +public static class EnumerableExtensions +{ + /// + /// Filters a sequence of values to exclude null elements. + /// + /// The sequence to filter. + /// A sequence that contains only the non-null elements from the input sequence. + public static IEnumerable WhereNotNull(this IEnumerable source) + => source.Where(x => x != null) + .Select(x => x!); + + /// + /// Filters a sequence of values to exclude null or whitespace elements. + /// + /// The sequence to filter. + /// A sequence that contains only the non-null and non-whitespace elements from the input sequence. + public static IEnumerable WhereNotNullOrWhiteSpace(this IEnumerable source) + => source.Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x!); +} diff --git a/Duplicati/Library/Utility/FileBackedList.cs b/Duplicati/Library/Utility/FileBackedList.cs index fc99feb64..ab0a7c848 100644 --- a/Duplicati/Library/Utility/FileBackedList.cs +++ b/Duplicati/Library/Utility/FileBackedList.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/FilterCollector.cs b/Duplicati/Library/Utility/FilterCollector.cs index aa85f5d08..7c6b48255 100644 --- a/Duplicati/Library/Utility/FilterCollector.cs +++ b/Duplicati/Library/Utility/FilterCollector.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -27,23 +27,23 @@ namespace Duplicati.Library.Utility { public class FilterCollector { - private readonly List m_filters = new List(); - private Library.Utility.IFilter Filter + private readonly List m_filters = new List(); + private IFilter Filter { get { if (m_filters.Count == 0) - return new Library.Utility.FilterExpression(); + return new FilterExpression(); else if (m_filters.Count == 1) return m_filters[0]; else - return m_filters.Aggregate(Library.Utility.JoinedFilterExpression.Join); + return m_filters.Aggregate(JoinedFilterExpression.Join); } } private Dictionary DoExtractOptions(List args, Func callbackHandler = null) { - return Library.Utility.CommandLineParser.ExtractOptions(args, (key, value) => + return CommandLineParser.ExtractOptions(args, (key, value) => { if (!string.IsNullOrEmpty(value)) { @@ -52,7 +52,7 @@ namespace Duplicati.Library.Utility if (include || exclude) { - m_filters.Add(new Library.Utility.FilterExpression(Environment.ExpandEnvironmentVariables(value), include)); + m_filters.Add(new FilterExpression(Environment.ExpandEnvironmentVariables(value), include)); return false; } } @@ -64,11 +64,11 @@ namespace Duplicati.Library.Utility }); } - public static Tuple, Library.Utility.IFilter> ExtractOptions(List args, Func callbackHandler = null) + public static Tuple, IFilter> ExtractOptions(List args, Func callbackHandler = null) { var fc = new FilterCollector(); var opts = fc.DoExtractOptions(args, callbackHandler); - return new Tuple, Library.Utility.IFilter>(opts, fc.Filter); + return new Tuple, IFilter>(opts, fc.Filter); } } } diff --git a/Duplicati/Library/Utility/FilterExpression.cs b/Duplicati/Library/Utility/FilterExpression.cs index bec2f5d93..1570df6a2 100644 --- a/Duplicati/Library/Utility/FilterExpression.cs +++ b/Duplicati/Library/Utility/FilterExpression.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/FilterGroups.cs b/Duplicati/Library/Utility/FilterGroups.cs index 8b8288284..136f2f471 100644 --- a/Duplicati/Library/Utility/FilterGroups.cs +++ b/Duplicati/Library/Utility/FilterGroups.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/HashCalculatingStream.cs b/Duplicati/Library/Utility/HashCalculatingStream.cs index c38ab627e..b73a7303d 100644 --- a/Duplicati/Library/Utility/HashCalculatingStream.cs +++ b/Duplicati/Library/Utility/HashCalculatingStream.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/HashExtentions.cs b/Duplicati/Library/Utility/HashExtentions.cs index 72347bfbf..1bc0250e0 100644 --- a/Duplicati/Library/Utility/HashExtentions.cs +++ b/Duplicati/Library/Utility/HashExtentions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/HashFactory.cs b/Duplicati/Library/Utility/HashFactory.cs index 043b37c67..06cf8d3af 100644 --- a/Duplicati/Library/Utility/HashFactory.cs +++ b/Duplicati/Library/Utility/HashFactory.cs @@ -1,4 +1,24 @@ -using System; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; using System.Collections.Generic; using Crypto = System.Security.Cryptography; using System.Text; diff --git a/Duplicati/Library/Utility/HelpOptionExtensions.cs b/Duplicati/Library/Utility/HelpOptionExtensions.cs new file mode 100644 index 000000000..209adfab3 --- /dev/null +++ b/Duplicati/Library/Utility/HelpOptionExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.CommandLine.Builder; +using System.Linq; + +namespace Duplicati.Library.Utility; + +public static class HelpOptionExtensions +{ + /// + /// Aliases to support for help command + /// + private static readonly string[] AlternativeHelpStrings = ["help", "/help","--help", "usage", "/usage", "--usage", "/h", "-h"]; + + /// + /// Adds the following aliases: "help", "/help", "--usage", "/usage". + /// These aliases will work the same way as the default help options (-h, --help). + /// + /// The command line builder to extend. + /// The command line builder with additional help aliases. + public static CommandLineBuilder UseAdditionalHelpAliases(this CommandLineBuilder builder) + { + var rootHelpOption = builder.Command.Options.FirstOrDefault(o => o.GetType().Name == "HelpOption"); + if (rootHelpOption == null) return builder; + foreach (var alias in AlternativeHelpStrings) + if (!rootHelpOption.Aliases.Contains(alias)) + rootHelpOption.AddAlias(alias); + + return builder; + } + + /// + /// Returns true if any of the arguments are any of the help strings. + /// + /// Arguments + public static bool IsArgumentAnyHelpString(IEnumerable args) + => args != null && AlternativeHelpStrings.Any(x => args.Contains(x, StringComparer.OrdinalIgnoreCase)); +} \ No newline at end of file diff --git a/Duplicati/Library/Utility/HostKeyException.cs b/Duplicati/Library/Utility/HostKeyException.cs index 3bb87efb2..4b555f6ae 100644 --- a/Duplicati/Library/Utility/HostKeyException.cs +++ b/Duplicati/Library/Utility/HostKeyException.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/HttpClientExtensions.cs b/Duplicati/Library/Utility/HttpClientExtensions.cs index 5f0ff1949..1247818b1 100644 --- a/Duplicati/Library/Utility/HttpClientExtensions.cs +++ b/Duplicati/Library/Utility/HttpClientExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,6 +25,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +#nullable enable + namespace Duplicati.Library.Utility; /// @@ -43,20 +45,20 @@ public static class HttpClientExtensions /// Action for progress reporting /// Cancelation token /// - public static async Task DownloadFile(this HttpClient client, HttpRequestMessage request, string filename, Action progressReportingAction = null, CancellationToken cancellationToken = default) + public static async Task DownloadFile(this HttpClient client, HttpRequestMessage request, string filename, Action? progressReportingAction = null, CancellationToken cancellationToken = default) { - using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); - using var fileStream = System.IO.File.Create(filename); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using var fileStream = System.IO.File.Create(filename); if (progressReportingAction != null) { using var ProgressReportingStream = new ProgressReportingStream(stream, progressReportingAction); - await ProgressReportingStream.CopyToAsync(fileStream,cancellationToken); + await ProgressReportingStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } else { - await stream.CopyToAsync(fileStream, cancellationToken); + await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } } @@ -69,20 +71,19 @@ public static class HttpClientExtensions /// Action for progress reporting /// Cancelation token /// - public static async Task DownloadFile(this HttpClient client, HttpRequestMessage request, Stream fileStream, Action progressReportingAction = null, CancellationToken cancellationToken = default) + public static async Task DownloadFile(this HttpClient client, HttpRequestMessage request, Stream fileStream, Action? progressReportingAction = null, CancellationToken cancellationToken = default) { - - using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); - using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); + await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); if (progressReportingAction != null) { using var ProgressReportingStream = new ProgressReportingStream(stream, progressReportingAction); - await ProgressReportingStream.CopyToAsync(fileStream, cancellationToken); + await ProgressReportingStream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } else { - await stream.CopyToAsync(fileStream, cancellationToken); + await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); } } @@ -92,8 +93,6 @@ public static class HttpClientExtensions /// The Http client reference /// A prepared HttpRequestMessage (Presumably with a stream) /// Cancelation token - public static async Task UploadStream(this HttpClient client, HttpRequestMessage request,CancellationToken cancellationToken = default) - { - return await client.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken); - } + public static Task UploadStream(this HttpClient client, HttpRequestMessage request, CancellationToken cancellationToken = default) + => client.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken); } diff --git a/Duplicati/Library/Utility/HttpClientHelper.cs b/Duplicati/Library/Utility/HttpClientHelper.cs index 82ac118c7..40cdef66a 100644 --- a/Duplicati/Library/Utility/HttpClientHelper.cs +++ b/Duplicati/Library/Utility/HttpClientHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,10 +19,9 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; -using System.Collections.Generic; +#nullable enable + using System.Net.Http; -using Duplicati.Library.Interface; namespace Duplicati.Library.Utility; @@ -33,25 +32,11 @@ namespace Duplicati.Library.Utility; /// public static class HttpClientHelper { - - /// - /// Globally configured certificate validator to be used across the application. - /// Each backend can choose when creating the httpclient/requesthandler if it will - /// use this or the default (null) validator. - /// - public static SslCertificateValidator CertificateValidator { get; set; } - - /// - /// Central HttpClient singleton instance to be used across the application as - /// per the HttpClientFactory recommended pattern. - /// - public static HttpClient DefaultClient { get; private set; } - /// /// Reference to the HttpClientFactory instance so we can create specific clients /// when the singleton pattnern is not desirable. /// - private static IHttpClientFactory _factory { get; set; } + private static IHttpClientFactory? _factory { get; set; } /// /// Sets the factory reference. @@ -60,7 +45,6 @@ public static class HttpClientHelper public static void Configure(IHttpClientFactory factory) { _factory = factory; - DefaultClient = factory.CreateClient(); } /// @@ -82,7 +66,7 @@ public static class HttpClientHelper { return new HttpClient(handler); } - + /// /// Configures the httphandler with the certificate validator, if needed. /// @@ -92,7 +76,7 @@ public static class HttpClientHelper /// The HttpClientHandler to be configured /// If all certificates will be considered valid /// List of hashes of certificates that will be accepted - public static void ConfigureHandlerCertificateValidator(HttpClientHandler handler, bool acceptAllCertificates, string[] acceptSpecificCertificateHashes) + public static void ConfigureHandlerCertificateValidator(HttpClientHandler handler, bool acceptAllCertificates, string[]? acceptSpecificCertificateHashes) { var validator = new SslCertificateValidator(acceptAllCertificates, acceptSpecificCertificateHashes); handler.ServerCertificateCustomValidationCallback = validator.ValidateServerCertificate; diff --git a/Duplicati/Library/Utility/IFilter.cs b/Duplicati/Library/Utility/IFilter.cs index d7722ef7e..28e83ea7f 100644 --- a/Duplicati/Library/Utility/IFilter.cs +++ b/Duplicati/Library/Utility/IFilter.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,8 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; - namespace Duplicati.Library.Utility { /// diff --git a/Duplicati/Library/Utility/JSONSignature.cs b/Duplicati/Library/Utility/JSONSignature.cs index 1df08d75f..ed6f97f2f 100644 --- a/Duplicati/Library/Utility/JSONSignature.cs +++ b/Duplicati/Library/Utility/JSONSignature.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/JoinedFilterExpression.cs b/Duplicati/Library/Utility/JoinedFilterExpression.cs index 1bcec17c3..a823804d3 100644 --- a/Duplicati/Library/Utility/JoinedFilterExpression.cs +++ b/Duplicati/Library/Utility/JoinedFilterExpression.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.Utility diff --git a/Duplicati/Library/Utility/MD5HashHelper.cs b/Duplicati/Library/Utility/MD5HashHelper.cs index bb7dbfcd2..4063c0ce6 100644 --- a/Duplicati/Library/Utility/MD5HashHelper.cs +++ b/Duplicati/Library/Utility/MD5HashHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/MachineNameReader.cs b/Duplicati/Library/Utility/MachineNameReader.cs new file mode 100644 index 000000000..370e24e80 --- /dev/null +++ b/Duplicati/Library/Utility/MachineNameReader.cs @@ -0,0 +1,95 @@ +using System; +using System.Management; +using System.Runtime.Versioning; + +#nullable enable + +namespace Duplicati.Library.Utility; + +/// +/// Class for reading the machine name +/// +public static class MachineNameReader +{ + /// + /// Makes a best effort to get the machine name + /// + /// The machine name + public static string GetMachineName() + { + string? machineName = null; + if (OperatingSystem.IsWindows()) + machineName = GetMachineNameWindows(); + else if (OperatingSystem.IsMacOS()) + machineName = GetMachineNameMacOS(); + else if (OperatingSystem.IsLinux()) + machineName = GetMachineNameLinux(); + + return string.IsNullOrWhiteSpace(machineName) + ? Environment.MachineName + : machineName; + } + /// + /// Executes a command and reads the output + /// + /// The command to execute + /// The arguments to pass to the command + /// The output of the command + private static string ExecuteAndReadOutput(string command, string arguments) + { + try + { + var process = new System.Diagnostics.Process + { + StartInfo = new System.Diagnostics.ProcessStartInfo + { + FileName = command, + Arguments = arguments, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + process.Start(); + process.WaitForExit(TimeSpan.FromSeconds(1)); + if (!process.HasExited) + { + process.Kill(); + return string.Empty; + } + + if (process.ExitCode != 0) + return string.Empty; + + return process.StandardOutput.ReadToEnd().Trim(); + } + catch + { + return string.Empty; + } + } + + /// + /// Gets the machine name if running MacOS + /// + /// The machine name + [SupportedOSPlatform("macos")] + private static string? GetMachineNameMacOS() + => ExecuteAndReadOutput("scutil", "--get ComputerName"); + + /// + /// Gets the machine name if running Windows + /// + /// The machine name + [SupportedOSPlatform("windows")] + private static string? GetMachineNameWindows() + => null; // No special handling for Windows, always uses NetBIOS name + + /// + /// Gets the machine name if running Linux + /// + /// The machine name + [SupportedOSPlatform("linux")] + private static string? GetMachineNameLinux() + => null; // No special handling for Linux, always uses hostname +} diff --git a/Duplicati/Library/Utility/Options/AuthIdOptionsHelper.cs b/Duplicati/Library/Utility/Options/AuthIdOptionsHelper.cs new file mode 100644 index 000000000..2689d19d0 --- /dev/null +++ b/Duplicati/Library/Utility/Options/AuthIdOptionsHelper.cs @@ -0,0 +1,82 @@ + + +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable +using System.Collections.Generic; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility.Options; + +/// +/// Helper class for unified use of authentication settings +/// +public static class AuthIdOptionsHelper +{ + /// + /// The authentication ID option, without a prefix + /// + public const string AuthIdOption = "authid"; + /// + /// Gets the authentication options + /// + /// The URL to use for the token + /// An optional prefix for the options + /// The authentication options + public static CommandLineArgument[] GetOptions(string tokenurl, string? prefix = null) => + [ + new CommandLineArgument($"{prefix}{AuthIdOption}", CommandLineArgument.ArgumentType.String, Strings.AuthIdSettingsHelper.AuthidShort, Strings.AuthIdSettingsHelper.AuthidLong(tokenurl)) + ]; + + /// + /// Parses the authentication options from a dictionary + /// + /// The dictionary to parse + /// An optional prefix for the options + /// The parsed authentication options + public static AuthIdOptions Parse(IReadOnlyDictionary options, string? prefix = null) + => new AuthIdOptions(options.GetValueOrDefault($"{prefix}{AuthIdOption}")); + + /// + /// Structure to hold the authentication options + /// + /// The Auth ID + public sealed record AuthIdOptions(string? AuthId) + { + /// + /// Checks if the authid is set + /// + public bool IsValid() => !string.IsNullOrEmpty(AuthId); + + /// + /// Throws an exception if the username and password are not set + /// + /// The URL to use for the token + public AuthIdOptions RequireCredentials(string tokenurl) + { + if (!IsValid()) + throw new UserInformationException(Strings.AuthIdSettingsHelper.MissingAuthID(tokenurl), "MissingAuthID"); + return this; + } + } + +} diff --git a/Duplicati/Library/Utility/Options/AuthOptionsHelper.cs b/Duplicati/Library/Utility/Options/AuthOptionsHelper.cs new file mode 100644 index 000000000..a812244f7 --- /dev/null +++ b/Duplicati/Library/Utility/Options/AuthOptionsHelper.cs @@ -0,0 +1,158 @@ + + +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable +using System.Collections.Generic; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility.Options; + +/// +/// Helper class for unified use of authentication settings +/// +public static class AuthOptionsHelper +{ + /// + /// The authentication username option, without a prefix + /// + public const string AuthUsernameOption = "auth-username"; + /// + /// The authentication password option, without a prefix + /// + public const string AuthPasswordOption = "auth-password"; + + /// + /// Gets the authentication options + /// + /// An optional prefix for the options + /// The authentication options + public static CommandLineArgument[] GetOptions(string? prefix = null) => + [ + new CommandLineArgument($"{prefix}{AuthUsernameOption}", CommandLineArgument.ArgumentType.String, Strings.AuthSettingsHelper.DescriptionAuthUsernameShort, Strings.AuthSettingsHelper.DescriptionAuthUsernameLong), + new CommandLineArgument($"{prefix}{AuthPasswordOption}", CommandLineArgument.ArgumentType.Password, Strings.AuthSettingsHelper.DescriptionAuthPasswordShort, Strings.AuthSettingsHelper.DescriptionAuthPasswordLong) + ]; + + /// + /// Parses the authentication options from a dictionary + /// + /// The dictionary to parse + /// The URI to get the default values from + /// The name of the username options + /// The name of the password options + /// The parsed authentication options + public static AuthOptions ParseWithAlias(IReadOnlyDictionary options, Uri uri, string username, string password) + { + // Prefer the primary name, if set + var optionUsername = options.GetValueOrDefault(username); + var optionPassword = options.GetValueOrDefault(password); + var parsedOptions = Parse(options, uri); + + if (string.IsNullOrWhiteSpace(optionUsername)) + optionUsername = parsedOptions.Username; + if (string.IsNullOrWhiteSpace(optionPassword)) + optionPassword = parsedOptions.Password; + + return new AuthOptions(optionUsername, optionPassword); + } + + /// + /// Parses the authentication options from a dictionary + /// + /// The dictionary to parse + /// The URI to get the default values from + /// An optional prefix for the options + /// The parsed authentication options + public static AuthOptions Parse(IReadOnlyDictionary options, Uri uri, string? prefix = null) + { + var optionUsername = options.GetValueOrDefault($"{prefix}{AuthUsernameOption}"); + var optionPassword = options.GetValueOrDefault($"{prefix}{AuthPasswordOption}"); + + // Prefer the URL values, if set + string? username = null; + string? password = null; + if (!string.IsNullOrEmpty(uri.Username)) + { + username = uri.Username; + if (!string.IsNullOrEmpty(uri.Password)) + password = uri.Password; + else if (!string.IsNullOrWhiteSpace(optionPassword)) + password = optionPassword; + } + else + { + if (!string.IsNullOrWhiteSpace(optionUsername)) + { + username = optionUsername; + if (!string.IsNullOrWhiteSpace(optionPassword)) + password = optionPassword; + } + } + + return new AuthOptions(username, password); + } + + /// + /// Structure to hold the authentication options + /// + /// The username + /// The password + public sealed record AuthOptions(string? Username, string? Password) + { + /// + /// Checks if the username has a value + /// + public bool HasUsername => !string.IsNullOrWhiteSpace(Username); + /// + /// Checks if the password has a value + /// + public bool HasPassword => !string.IsNullOrWhiteSpace(Password); + + /// + /// Checks if the username and password are set + /// + /// True if the username and password are set + public bool IsValid() => HasUsername && HasPassword; + + /// + /// Throws an exception if the username and password are not set + /// + public AuthOptions RequireCredentials() + { + if (!IsValid()) + throw new UserInformationException(Strings.AuthSettingsHelper.UsernameAndPasswordRequired, "UsernameAndPasswordRequired"); + + return this; + } + + /// + /// Returns the username and password after checking that they are set + /// + /// The username and password + public (string Username, string Password) GetCredentials() + { + RequireCredentials(); + return (Username!, Password!); + } + } + +} diff --git a/Duplicati/Library/Utility/Options/SslOptionsHelper.cs b/Duplicati/Library/Utility/Options/SslOptionsHelper.cs new file mode 100644 index 000000000..d4d568faf --- /dev/null +++ b/Duplicati/Library/Utility/Options/SslOptionsHelper.cs @@ -0,0 +1,126 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Net.Http; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility.Options; + +/// +/// Helper class for unified use of SSL certificate options +/// +public static class SslOptionsHelper +{ + /// + /// The name of the ssl option without a prefix + /// + public const string UseSSLOption = "use-ssl"; + /// + /// The name of the accept any ssl certificate option without a prefix + /// + public const string AcceptAnySslCertificateOption = "accept-any-ssl-certificate"; + /// + /// The name of the accept specified ssl hash option without a prefix + /// + public const string AcceptSpecifiedSslHashOption = "accept-specified-ssl-hash"; + + /// + /// Gets the SSL certificate options for certificate only + /// + /// An optional prefix for the options + /// The SSL certificate options + public static CommandLineArgument[] GetCertOnlyOptions(string? prefix = null) => + [ + new CommandLineArgument($"{prefix}{AcceptAnySslCertificateOption}", CommandLineArgument.ArgumentType.Boolean, Strings.SslOptionsHelper.DescriptionAcceptAnyCertificateShort, Strings.SslOptionsHelper.DescriptionAcceptAnyCertificateLong, "false"), + new CommandLineArgument($"{prefix}{AcceptSpecifiedSslHashOption}", CommandLineArgument.ArgumentType.String, Strings.SslOptionsHelper.DescriptionAcceptHashShort, Strings.SslOptionsHelper.DescriptionAcceptHashLong) + ]; + + /// + /// Gets the SSL certificate options for SSL only + /// + /// An optional prefix for the options + /// The SSL certificate options + public static CommandLineArgument[] GetSslOnlyOption(string? prefix = null) => + [ + new CommandLineArgument($"{prefix}{UseSSLOption}", CommandLineArgument.ArgumentType.Boolean, Strings.SslOptionsHelper.DescriptionUseSSLShort, Strings.SslOptionsHelper.DescriptionUseSSLLong, "false") + ]; + + /// + /// Gets the SSL certificate options + /// + /// An optional prefix for the options + /// The SSL certificate options + public static CommandLineArgument[] GetOptions(string? prefix = null) => + [ + .. GetSslOnlyOption(prefix), + .. GetCertOnlyOptions(prefix) + ]; + + /// + /// Parses the SSL certificate options from a dictionary + /// + /// The dictionary to parse + /// An optional prefix for the options + /// The parsed SSL certificate options + public static SslCertificateOptions Parse(IReadOnlyDictionary options, string? prefix = null) + { + var acceptSpecificCertificatesString = options.TryGetValue($"{prefix}{AcceptSpecifiedSslHashOption}", out var value) ? value : null; + return new SslCertificateOptions( + Utility.ParseBoolOption(options, $"{prefix}{UseSSLOption}"), + Utility.ParseBoolOption(options, $"{prefix}{AcceptAnySslCertificateOption}"), + string.IsNullOrWhiteSpace(acceptSpecificCertificatesString) ? [] : acceptSpecificCertificatesString.Split([",", ";"], StringSplitOptions.RemoveEmptyEntries) + ); + } + + /// + /// Structure to hold the SSL certificate options + /// + /// Flag to indicate if SSL is used + /// Flag to accept all certificates + /// Array of specific certificate hashes to accept + public sealed record SslCertificateOptions(bool UseSSL, bool AcceptAllCertificates, string[] AcceptSpecificCertificateHashes) + { + /// + /// Creates a handler with the SSL certificate options + /// + /// The created handler + public HttpClientHandler CreateHandler() + { + var handler = new HttpClientHandler(); + return ConfigureHandler(handler); + } + + /// + /// Configures an existing handler with the SSL certificate options + /// + /// The handler to configure + /// The configured handler + public HttpClientHandler ConfigureHandler(HttpClientHandler handler) + { + HttpClientHelper.ConfigureHandlerCertificateValidator(handler, AcceptAllCertificates, AcceptSpecificCertificateHashes); + return handler; + } + } +} \ No newline at end of file diff --git a/Duplicati/Library/Utility/Options/TimeoutOptionsHelper.cs b/Duplicati/Library/Utility/Options/TimeoutOptionsHelper.cs new file mode 100644 index 000000000..1864df23e --- /dev/null +++ b/Duplicati/Library/Utility/Options/TimeoutOptionsHelper.cs @@ -0,0 +1,96 @@ + +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using Duplicati.Library.Interface; + +namespace Duplicati.Library.Utility.Options; + +/// +/// Helper class for unified use of timeout settings +/// +public static class TimeoutOptionsHelper +{ + /// + /// The default timeout in seconds for List operations + /// + public const string DefaultListOperationTimeout = "10m"; + + /// + /// The default timeout in seconds for Delete/CreateFolder operations + /// + public const string DefaultShortOperationTimeout = "30s"; + + /// + /// The timeout for no activity during transfers + /// + public const string DefaultReadWriteTimeout = "30s"; + + /// + /// The name of the short timeout option, without a prefix + /// + public const string ShortTimeoutOption = "short-timeout"; + /// + /// The name of the list timeout option, without a prefix + /// + public const string ListTimeoutOption = "list-timeout"; + /// + /// The name of the read-write timeout option, without a prefix + /// + public const string ReadWriteTimeoutOption = "read-write-timeout"; + + /// + /// Gets the timeout options + /// + /// An optional prefix for the options + /// The timeout options + public static CommandLineArgument[] GetOptions(string? prefix = null) => + [ + new CommandLineArgument($"{prefix}{ShortTimeoutOption}", CommandLineArgument.ArgumentType.Timespan, Strings.TimeoutSettingsHelper.DescriptionShortTimeoutShort, Strings.TimeoutSettingsHelper.DescriptionShortTimeoutLong, DefaultShortOperationTimeout), + new CommandLineArgument($"{prefix}{ListTimeoutOption}", CommandLineArgument.ArgumentType.Timespan, Strings.TimeoutSettingsHelper.DescriptionListTimeoutShort, Strings.TimeoutSettingsHelper.DescriptionListTimeoutLong, DefaultListOperationTimeout), + new CommandLineArgument($"{prefix}{ReadWriteTimeoutOption}", CommandLineArgument.ArgumentType.Timespan, Strings.TimeoutSettingsHelper.DescriptionReadWriteTimeoutShort, Strings.TimeoutSettingsHelper.DescriptionReadWriteTimeoutLong, DefaultReadWriteTimeout) + ]; + + /// + /// Parses the timeout options from an options dictionary + /// + /// The options dictionary + /// An optional prefix for the options + /// The timeout configuration + public static Timeouts Parse(IReadOnlyDictionary options, string? prefix = null) + => new Timeouts( + Utility.ParseTimespanOption(options, $"{prefix}{ShortTimeoutOption}", DefaultShortOperationTimeout), + Utility.ParseTimespanOption(options, $"{prefix}{ListTimeoutOption}", DefaultListOperationTimeout), + Utility.ParseTimespanOption(options, $"{prefix}{ReadWriteTimeoutOption}", DefaultReadWriteTimeout) + ); + + /// + /// Structure for timeout configuration + /// + /// The timeout in seconds for short operations like delete and create folder + /// The timeout in seconds for listing files and folders + /// The timeout in seconds for read and write operations. + public sealed record Timeouts(TimeSpan ShortTimeout, TimeSpan ListTimeout, TimeSpan ReadWriteTimeout); +} \ No newline at end of file diff --git a/Duplicati/Library/Utility/OverrideableStream.cs b/Duplicati/Library/Utility/OverrideableStream.cs index a878e4a55..35e8a5273 100644 --- a/Duplicati/Library/Utility/OverrideableStream.cs +++ b/Duplicati/Library/Utility/OverrideableStream.cs @@ -1,28 +1,27 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Text; using System.IO; +using System.Threading.Tasks; namespace Duplicati.Library.Utility { @@ -33,79 +32,78 @@ namespace Duplicati.Library.Utility /// public class OverrideableStream : Stream { - protected System.IO.Stream m_basestream; + /// + /// The base stream that is wrapped + /// + protected Stream m_basestream; + /// + /// Creates a new instance + /// + /// The stream to wrap + /// public OverrideableStream(Stream basestream) { - if (basestream == null) - throw new ArgumentNullException(nameof(basestream)); - m_basestream = basestream; + m_basestream = basestream ?? throw new ArgumentNullException(nameof(basestream)); } - public override bool CanRead - { - get { return m_basestream.CanRead; } - } + /// + public override bool CanRead => m_basestream.CanRead; - public override bool CanSeek - { - get { return m_basestream.CanSeek; } - } + /// + public override bool CanSeek => m_basestream.CanSeek; - public override bool CanWrite - { - get { return m_basestream.CanWrite; } - } + /// + public override bool CanWrite => m_basestream.CanWrite; - public override void Flush() - { - m_basestream.Flush(); - } + /// + public override void Flush() => m_basestream.Flush(); - public override long Length - { - get { return m_basestream.Length; } - } + /// + public override long Length => m_basestream.Length; + /// public override long Position { - get - { - return m_basestream.Position; - } - set - { - m_basestream.Position = value; - } + get => m_basestream.Position; + set => m_basestream.Position = value; } + /// public override int Read(byte[] buffer, int offset, int count) - { - return m_basestream.Read(buffer, offset, count); - } + => m_basestream.Read(buffer, offset, count); + /// public override long Seek(long offset, System.IO.SeekOrigin origin) - { - return m_basestream.Seek(offset, origin); - } + => m_basestream.Seek(offset, origin); + /// public override void SetLength(long value) - { - m_basestream.SetLength(value); - } + => m_basestream.SetLength(value); + /// public override void Write(byte[] buffer, int offset, int count) - { - m_basestream.Write(buffer, offset, count); - } + => m_basestream.Write(buffer, offset, count); + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) + => m_basestream.ReadAsync(buffer, offset, count, cancellationToken); + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) + => m_basestream.WriteAsync(buffer, offset, count, cancellationToken); + + /// + public override Task FlushAsync(System.Threading.CancellationToken cancellationToken) + => m_basestream.FlushAsync(cancellationToken); + + /// protected override void Dispose(bool disposing) { - if (m_basestream != null) - m_basestream.Dispose(); + m_basestream?.Dispose(); m_basestream = null; base.Dispose(disposing); } - + } } diff --git a/Duplicati/Library/Utility/Power/DefaultPowerSupplyState.cs b/Duplicati/Library/Utility/Power/DefaultPowerSupplyState.cs index b1db7ebc5..b87922010 100644 --- a/Duplicati/Library/Utility/Power/DefaultPowerSupplyState.cs +++ b/Duplicati/Library/Utility/Power/DefaultPowerSupplyState.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.Utility.Power diff --git a/Duplicati/Library/Utility/Power/IPowerSupplyState.cs b/Duplicati/Library/Utility/Power/IPowerSupplyState.cs index 9e201774b..d3ec9756d 100644 --- a/Duplicati/Library/Utility/Power/IPowerSupplyState.cs +++ b/Duplicati/Library/Utility/Power/IPowerSupplyState.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Library.Utility.Power diff --git a/Duplicati/Library/Utility/Power/LinuxPowerSupplyState.cs b/Duplicati/Library/Utility/Power/LinuxPowerSupplyState.cs index 7db0dec91..b6d063b6c 100644 --- a/Duplicati/Library/Utility/Power/LinuxPowerSupplyState.cs +++ b/Duplicati/Library/Utility/Power/LinuxPowerSupplyState.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/Power/MacOSPowerSupplyState.cs b/Duplicati/Library/Utility/Power/MacOSPowerSupplyState.cs index 0d53c1419..0627d3263 100644 --- a/Duplicati/Library/Utility/Power/MacOSPowerSupplyState.cs +++ b/Duplicati/Library/Utility/Power/MacOSPowerSupplyState.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/Power/PowerSupply.cs b/Duplicati/Library/Utility/Power/PowerSupply.cs index edc96dd95..681c3dc1f 100644 --- a/Duplicati/Library/Utility/Power/PowerSupply.cs +++ b/Duplicati/Library/Utility/Power/PowerSupply.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common; diff --git a/Duplicati/Library/Utility/Power/WindowsPowerSupplyState.cs b/Duplicati/Library/Utility/Power/WindowsPowerSupplyState.cs index be41261ca..4a2f809f9 100644 --- a/Duplicati/Library/Utility/Power/WindowsPowerSupplyState.cs +++ b/Duplicati/Library/Utility/Power/WindowsPowerSupplyState.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/ProgressReportingStream.cs b/Duplicati/Library/Utility/ProgressReportingStream.cs index 941c8e83d..7a77df83b 100644 --- a/Duplicati/Library/Utility/ProgressReportingStream.cs +++ b/Duplicati/Library/Utility/ProgressReportingStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/ReadLimitLengthStream.cs b/Duplicati/Library/Utility/ReadLimitLengthStream.cs index 7696331ab..184b87096 100644 --- a/Duplicati/Library/Utility/ReadLimitLengthStream.cs +++ b/Duplicati/Library/Utility/ReadLimitLengthStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/RegistryUtility.cs b/Duplicati/Library/Utility/RegistryUtility.cs index fbeee23d9..1ebfc96aa 100644 --- a/Duplicati/Library/Utility/RegistryUtility.cs +++ b/Duplicati/Library/Utility/RegistryUtility.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/RetryHelper.cs b/Duplicati/Library/Utility/RetryHelper.cs index 5c32db3e5..1bbcaae3e 100644 --- a/Duplicati/Library/Utility/RetryHelper.cs +++ b/Duplicati/Library/Utility/RetryHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,6 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Threading; using System.Threading.Tasks; @@ -27,7 +29,7 @@ namespace Duplicati.Library.Utility; public static class RetryHelper { - public static async Task Retry(Func> action, int maxRetries, TimeSpan delay, CancellationToken token) + public static async Task Retry(Func> action, Action? errorCallback, int maxRetries, TimeSpan delay, CancellationToken token) { var attempt = 0; @@ -37,21 +39,26 @@ public static class RetryHelper { return await action(); } - catch + catch (Exception ex) { + attempt++; + errorCallback?.Invoke(ex, attempt); + if (token.IsCancellationRequested) throw; if (attempt >= maxRetries) throw; - - attempt++; } await Task.Delay(delay, token); } } + public static async Task Retry(Func> action, int maxRetries, TimeSpan delay, CancellationToken token) + => await Retry(action, null, maxRetries, delay, token); + public static async Task Retry(Func action, Action? errorCallback, int maxRetries, TimeSpan delay, CancellationToken token) + => await Retry(async () => { await action(); return true; }, errorCallback, maxRetries, delay, token); public static async Task Retry(Func action, int maxRetries, TimeSpan delay, CancellationToken token) - => await Retry(async () => { await action(); return true; }, maxRetries, delay, token); + => await Retry(async () => { await action(); return true; }, null, maxRetries, delay, token); } diff --git a/Duplicati/Library/Utility/SHGetFolder.cs b/Duplicati/Library/Utility/SHGetFolder.cs new file mode 100644 index 000000000..fd1df513b --- /dev/null +++ b/Duplicati/Library/Utility/SHGetFolder.cs @@ -0,0 +1,104 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +#nullable enable + +namespace Duplicati.Library.Utility; +/// +/// Wrapper for SHGetKnownFolderPath to various system folders on Windows +/// +/// List of GUIDs: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid +[SupportedOSPlatform("windows")] +public static class SHGetFolder +{ + /// + /// Gets the download folder path + /// + public static string? DownloadFolder => GetKnownFolderPath(new Guid("374DE290-123F-4565-9164-39C4925E467B")); + + /// + /// Gets the user profiles folder path + /// + public static string? UserProfilesFolder + { + get + { + try + { + // Prefered way, works on Windows Vista and later + var shPath = GetKnownFolderPath(new Guid("0762D272-C50A-4BB0-A382-697DCD729B80")); + if (!string.IsNullOrWhiteSpace(shPath) && Path.IsPathRooted(shPath) && Directory.Exists(shPath)) + return shPath; + } + catch + { + } + + try + { + // Fallback to "{systemDrive}\\Users" + var envPath = Environment.ExpandEnvironmentVariables("%SystemDrive%\\Users"); + if (!string.IsNullOrWhiteSpace(envPath) && Path.IsPathRooted(envPath) && Directory.Exists(envPath)) + return envPath; + } + catch + { + } + + try + { + // Fallback to "{systemDrive}\\Users", method 2 + var sysPath = Environment.GetFolderPath(Environment.SpecialFolder.System); + if (!string.IsNullOrWhiteSpace(sysPath) && Path.IsPathRooted(sysPath) && Directory.Exists(sysPath)) + { + var sysDir = Path.GetPathRoot(sysPath); + if (!string.IsNullOrWhiteSpace(sysDir)) + return Path.Combine(sysDir, "Users"); + } + } + catch + { + } + + // Most likely, this is the correct path + var path = "C:\\Users"; + if (Directory.Exists(path)) + return path; + + return null; + } + } + + /// + /// SHGetKnownFolderPath function to get the folder path + /// + /// The folder GUID + /// Get folder flags + /// The access token + /// The folder path + /// The HRESULT error code + [DllImport("Shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int SHGetKnownFolderPath( + [MarshalAs(UnmanagedType.LPStruct)] Guid rfid, + uint dwFlags, + IntPtr hToken, + out IntPtr ppszPath); + + /// + /// Gets the folder path using SHGetKnownFolderPath + /// + /// The folder GUID + /// The folder path + private static string? GetKnownFolderPath(Guid folderGuid) + { + var result = SHGetKnownFolderPath(folderGuid, 0, IntPtr.Zero, out var outPath); + if (result != 0) + Marshal.ThrowExceptionForHR(result); // Throws an exception for the HRESULT error code + + var path = Marshal.PtrToStringUni(outPath); + Marshal.FreeCoTaskMem(outPath); // Free the memory allocated by SHGetKnownFolderPath + return path; + } +} diff --git a/Duplicati/Library/Utility/ShaderStream.cs b/Duplicati/Library/Utility/ShaderStream.cs index 9ab276a54..3bd89b3b1 100644 --- a/Duplicati/Library/Utility/ShaderStream.cs +++ b/Duplicati/Library/Utility/ShaderStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/Sizeparser.cs b/Duplicati/Library/Utility/Sizeparser.cs index 902badfad..ef7872f9c 100644 --- a/Duplicati/Library/Utility/Sizeparser.cs +++ b/Duplicati/Library/Utility/Sizeparser.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -17,7 +17,7 @@ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; @@ -35,9 +35,13 @@ namespace Duplicati.Library.Utility size = size.Trim(); if (size.EndsWith("tb", StringComparison.OrdinalIgnoreCase) || + size.EndsWith("tib", StringComparison.OrdinalIgnoreCase) || size.EndsWith("gb", StringComparison.OrdinalIgnoreCase) || + size.EndsWith("gib", StringComparison.OrdinalIgnoreCase) || size.EndsWith("mb", StringComparison.OrdinalIgnoreCase) || + size.EndsWith("mib", StringComparison.OrdinalIgnoreCase) || size.EndsWith("kb", StringComparison.OrdinalIgnoreCase) || + size.EndsWith("kib", StringComparison.OrdinalIgnoreCase) || size.EndsWith("b", StringComparison.OrdinalIgnoreCase)) return ParseSize(size); else @@ -52,7 +56,7 @@ namespace Duplicati.Library.Utility string origsize = size; size = size.Trim(); - + long factor = 1; if (size.EndsWith("tb", StringComparison.OrdinalIgnoreCase)) @@ -60,21 +64,41 @@ namespace Duplicati.Library.Utility factor = 1024L * 1024 * 1024 * 1024; size = size.Substring(0, size.Length - 2).Trim(); } + if (size.EndsWith("tib", StringComparison.OrdinalIgnoreCase)) + { + factor = 1024L * 1024 * 1024 * 1024; + size = size.Substring(0, size.Length - 3).Trim(); + } else if (size.EndsWith("gb", StringComparison.OrdinalIgnoreCase)) { factor = 1024 * 1024 * 1024; size = size.Substring(0, size.Length - 2).Trim(); } + else if (size.EndsWith("gib", StringComparison.OrdinalIgnoreCase)) + { + factor = 1024 * 1024 * 1024; + size = size.Substring(0, size.Length - 3).Trim(); + } else if (size.EndsWith("mb", StringComparison.OrdinalIgnoreCase)) { factor = 1024 * 1024; size = size.Substring(0, size.Length - 2).Trim(); } + else if (size.EndsWith("mib", StringComparison.OrdinalIgnoreCase)) + { + factor = 1024 * 1024; + size = size.Substring(0, size.Length - 3).Trim(); + } else if (size.EndsWith("kb", StringComparison.OrdinalIgnoreCase)) { factor = 1024; size = size.Substring(0, size.Length - 2).Trim(); } + else if (size.EndsWith("kib", StringComparison.OrdinalIgnoreCase)) + { + factor = 1024; + size = size.Substring(0, size.Length - 3).Trim(); + } else if (size.EndsWith("b", StringComparison.OrdinalIgnoreCase)) size = size.Substring(0, size.Length - 1).Trim(); diff --git a/Duplicati/Library/Utility/SslCertificateValidator.cs b/Duplicati/Library/Utility/SslCertificateValidator.cs index e206e7586..85c4cd7c5 100644 --- a/Duplicati/Library/Utility/SslCertificateValidator.cs +++ b/Duplicati/Library/Utility/SslCertificateValidator.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,61 +19,68 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; +using System.Linq; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace Duplicati.Library.Utility; -public class SslCertificateValidator(bool acceptAll, string[] validHashes) +public class SslCertificateValidator(bool acceptAll, string[]? validHashes) { [Serializable] - public class InvalidCertificateException : Exception + public class InvalidCertificateException(string certificate, SslPolicyErrors error) + : Exception(Strings.SslCertificateValidator.VerifyCertificateException(error, certificate)) { - private readonly string m_certificate; - private readonly SslPolicyErrors m_errors = SslPolicyErrors.None; + private readonly string _mCertificate = certificate; + private readonly SslPolicyErrors _mErrors = error; - public string Certificate => m_certificate; - public SslPolicyErrors SslError => m_errors; - - public InvalidCertificateException(string certificate, SslPolicyErrors error) - : base(Strings.SslCertificateValidator.VerifyCertificateException(error, certificate)) - { - m_certificate = certificate; - m_errors = error; - } + public string Certificate => _mCertificate; + public SslPolicyErrors SslError => _mErrors; } - public bool ValidateServerCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors) + public bool ValidateServerCertificate(object sender, X509Certificate? cert, X509Chain? chain, SslPolicyErrors sslPolicyErrors) { - - if (sslPolicyErrors == SslPolicyErrors.None) - return true; - if (acceptAll) return true; - string certHash; - try { - certHash = Utility.ByteArrayAsHexString(cert.GetCertHash()); - if (certHash != null && validHashes != null) + DateTime now = DateTime.Now; + + using var certificate = cert as X509Certificate2 ?? new X509Certificate2(cert ?? throw new ArgumentNullException(nameof(cert))); + + if (!IsDateValid(certificate, now)) + return false; + + if (validHashes != null) { - foreach (var hash in validHashes) - { - if (!string.IsNullOrEmpty(hash) && certHash.Equals(hash, StringComparison.OrdinalIgnoreCase)) - { + // Check main certificate hash + if (IsTrustedHash(Utility.ByteArrayAsHexString(certificate.GetCertHash()))) + return true; + + // Check chain certificate from root for the hash (this allows custom CA certificates hashes to be added) + if (chain?.ChainElements != null) + if (chain.ChainElements.Any(element => IsTrustedHash(Utility.ByteArrayAsHexString(element.Certificate.GetCertHash())) && IsDateValid(element.Certificate, now))) return true; - } - } } + + // If no hash is found, perform the standard validations + return sslPolicyErrors == SslPolicyErrors.None && certificate.Verify(); } catch (Exception ex) { throw new Exception(Strings.SslCertificateValidator.VerifyCertificateHashError(ex, sslPolicyErrors), ex); } - - return false; } + + private bool IsTrustedHash(string hash) => + !string.IsNullOrWhiteSpace(hash) && + validHashes != null && + validHashes.Any(validHash => !string.IsNullOrEmpty(validHash) && + hash.Equals(validHash, StringComparison.OrdinalIgnoreCase)); + private bool IsDateValid(X509Certificate2 cert, DateTime now) => now <= cert.NotAfter && now >= cert.NotBefore; + } \ No newline at end of file diff --git a/Duplicati/Library/Utility/Strings.cs b/Duplicati/Library/Utility/Strings.cs index 0491d8cfe..36c9659dc 100644 --- a/Duplicati/Library/Utility/Strings.cs +++ b/Duplicati/Library/Utility/Strings.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -52,10 +52,10 @@ namespace Duplicati.Library.Utility.Strings internal static class Utility { public static string FormatStringB(long size) { return LC.L(@"{0} bytes", size); } - public static string FormatStringGB(double size) { return LC.L(@"{0:N} GB", size); } - public static string FormatStringKB(double size) { return LC.L(@"{0:N} KB", size); } - public static string FormatStringMB(double size) { return LC.L(@"{0:N} MB", size); } - public static string FormatStringTB(double size) { return LC.L(@"{0:N} TB", size); } + public static string FormatStringGB(double size) { return LC.L(@"{0:N} GiB", size); } + public static string FormatStringKB(double size) { return LC.L(@"{0:N} KiB", size); } + public static string FormatStringMB(double size) { return LC.L(@"{0:N} MiB", size); } + public static string FormatStringTB(double size) { return LC.L(@"{0:N} TiB", size); } public static string InvalidDateError(string data) { return LC.L(@"The string ""{0}"" could not be parsed into a date", data); } } internal static class HashCalculatingStream @@ -66,4 +66,53 @@ namespace Duplicati.Library.Utility.Strings { public static string UnknownFilterGroup(string filterSet) { return LC.L(@"The string {0} does not represent a known filter group name. Valid values are: {1}", filterSet, string.Join(", ", Enum.GetNames(typeof(FilterGroup)))); } } + + internal static class TimeoutSettingsHelper + { + public static string DescriptionShortTimeoutLong { get { return LC.L(@"The timeout in seconds for short operations like delete and create folder"); } } + public static string DescriptionShortTimeoutShort { get { return LC.L(@"Short operation timeout"); } } + public static string DescriptionListTimeoutLong { get { return LC.L(@"The timeout in seconds for listing files and folders"); } } + public static string DescriptionListTimeoutShort { get { return LC.L(@"List operation timeout"); } } + public static string DescriptionReadWriteTimeoutLong { get { return LC.L(@"The timeout in seconds for read and write operations. If no activity is detected in this interval, a timeout error is raised"); } } + public static string DescriptionReadWriteTimeoutShort { get { return LC.L(@"Read/write operation timeout"); } } + } + + internal static class SslOptionsHelper + { + public static string DescriptionAcceptAnyCertificateLong { get { return LC.L(@"Use this option to accept any server certificate, regardless of what errors it may have. Please use --{0} instead, whenever possible.", "accept-specified-ssl-hash"); } } + public static string DescriptionAcceptAnyCertificateShort { get { return LC.L(@"Accept any server certificate"); } } + public static string DescriptionAcceptHashLong { get { return LC.L(@"If your server certificate is reported as invalid (e.g. with self-signed certificates), you can supply the certificate hash (SHA1) to approve it anyway. The hash value must be entered in hex format without spaces or colons. You can enter multiple hashes separated by commas."); } } + public static string DescriptionAcceptHashShort { get { return LC.L(@"Optionally accept a known SSL certificate"); } } + public static string DescriptionUseSSLLong { get { return LC.L(@"Use this option to communicate using Secure Socket Layer (SSL) over http (https)."); } } + public static string DescriptionUseSSLShort { get { return LC.L(@"Instruct Duplicati to use an SSL (https) connection"); } } + } + + internal static class AuthSettingsHelper + { + public static string DescriptionAuthPasswordLong { get { return LC.L(@"The password used to connect to the server. This may also be supplied as the environment variable ""AUTH_PASSWORD""."); } } + public static string DescriptionAuthPasswordShort { get { return LC.L(@"Supply the password used to connect to the server"); } } + public static string DescriptionAuthUsernameLong { get { return LC.L(@"The username used to connect to the server. This may also be supplied as the environment variable ""AUTH_USERNAME""."); } } + public static string DescriptionAuthUsernameShort { get { return LC.L(@"Supply the username used to connect to the server"); } } + public static string UsernameAndPasswordRequired { get { return LC.L(@"Authentication requires both a username and a password"); } } + } + + internal static class AuthIdSettingsHelper + { + public static string AuthidLong(string url) { return LC.L(@"The authorization token retrieved from {0}", url); } + public static string AuthidShort { get { return LC.L(@"The authorization code"); } } + public static string MissingAuthID(string url) { return LC.L(@"You need an AuthID to use this destination. You can get it from: {0}", url); } + + } + + internal static class CommandLineArgumentValidator + { + public static string UnsupportedBooleanValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not parse into a valid boolean. This will be treated as if it was set to ""true""", optionname, value); } + public static string UnsupportedEnumerationValue(string optionname, string value, string[] values) { return LC.L(@"The option --{0} does not support the value ""{1}"". Supported values are: {2}", optionname, value, string.Join(", ", values)); } + public static string UnsupportedFlagsValue(string optionname, string value, string[] values) { return LC.L(@"The option --{0} does not support the value ""{1}"". Supported flag values are: {2}", optionname, value, string.Join(", ", values)); } + public static string UnsupportedIntegerValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid integer", optionname, value); } + public static string UnsupportedPathValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid path", optionname, value); } + public static string UnsupportedSizeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid size", optionname, value); } + public static string UnsupportedTimeValue(string optionname, string value) { return LC.L(@"The value ""{1}"" supplied to --{0} does not represent a valid time", optionname, value); } + public static string NonQualifiedSizeValue(string optionname, string value) { return LC.L(@"The size ""{1}"" supplied to --{0} does not have a multiplier (b, kb, mb, etc). A multiplier is recommended to avoid unexpected changes if the program is updated.", optionname, value); } + } } diff --git a/Duplicati/Library/Utility/TempFile.cs b/Duplicati/Library/Utility/TempFile.cs index b0835e825..aa9853ea0 100644 --- a/Duplicati/Library/Utility/TempFile.cs +++ b/Duplicati/Library/Utility/TempFile.cs @@ -1,23 +1,23 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; @@ -37,6 +37,7 @@ namespace Duplicati.Library.Utility private string m_path; private bool m_protect; + private bool m_disposed = false; #if DEBUG //In debug mode, we track the creation of temporary files, and encode the generating method into the name @@ -179,6 +180,8 @@ namespace Duplicati.Library.Utility protected void Dispose(bool disposing) { + m_disposed = true; + if (disposing) GC.SuppressFinalize(this); @@ -204,6 +207,9 @@ namespace Duplicati.Library.Utility ~TempFile() { + if (!m_disposed) + Logging.Log.WriteWarningMessage("TempFile", "Finalizer", null, "TempFile was not disposed, cleaning up"); + Dispose(false); } diff --git a/Duplicati/Library/Utility/TempFileStream.cs b/Duplicati/Library/Utility/TempFileStream.cs new file mode 100644 index 000000000..85b8efcd2 --- /dev/null +++ b/Duplicati/Library/Utility/TempFileStream.cs @@ -0,0 +1,97 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Utility; + +/// +/// A stream that wraps a temporary file. +/// The temporary file is automatically deleted when the stream is disposed. +/// +public class TempFileStream : StreamUtil.WrappingAsyncStream +{ + /// + /// The temporary file instance. + /// + private readonly TempFile tempFile; + /// + /// The stream for the temporary file. + /// + private readonly Stream stream; + + /// + /// The path to the temporary file. + /// + public string Path => tempFile.Name; + + /// + /// Creates a new TempFileStream instance. + /// + /// The temporary file instance. + /// The stream for the temporary file. + private TempFileStream(TempFile tempFile, Stream stream) + : base(stream) + { + this.tempFile = tempFile; + this.stream = stream; + } + + /// + /// Creates a new TempFileStream instance. + /// + /// The temporary file instance. + /// The new TempFileStream instance. + public static TempFileStream Create(TempFile tempFile) + { + return new TempFileStream(tempFile, File.Open(tempFile.Name, FileMode.Open, FileAccess.ReadWrite, FileShare.None)); + } + + /// + /// Creates a new TempFileStream instance. + /// + /// The new TempFileStream instance. + public static TempFileStream Create() + { + var tempFile = new TempFile(); + return new TempFileStream(tempFile, File.Open(tempFile.Name, FileMode.Open, FileAccess.ReadWrite, FileShare.None)); + } + + /// + protected override Task ReadImplAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => stream.ReadAsync(buffer, offset, count, cancellationToken); + + /// + protected override Task WriteImplAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + => stream.WriteAsync(buffer, offset, count, cancellationToken); + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + stream.Dispose(); + tempFile.Dispose(); + } +} diff --git a/Duplicati/Library/Utility/TempFolder.cs b/Duplicati/Library/Utility/TempFolder.cs index fd1f93fe0..d22d33bb4 100644 --- a/Duplicati/Library/Utility/TempFolder.cs +++ b/Duplicati/Library/Utility/TempFolder.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/ThrottledStream.cs b/Duplicati/Library/Utility/ThrottledStream.cs index 45dfea212..2780cbac1 100644 --- a/Duplicati/Library/Utility/ThrottledStream.cs +++ b/Duplicati/Library/Utility/ThrottledStream.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/TimeZoneHelper.cs b/Duplicati/Library/Utility/TimeZoneHelper.cs new file mode 100644 index 000000000..892dadb63 --- /dev/null +++ b/Duplicati/Library/Utility/TimeZoneHelper.cs @@ -0,0 +1,171 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Duplicati.Library.Utility; + +/// +/// Adds the specified number of minutes to the given DateTime, taking into account the time zone's daylight saving time rules. +/// A helper class to get the time zone information +/// +public static class TimeZoneHelper +{ + /// + /// A record to display the time zone + /// + /// The time zone id + /// The display name of the time zone + /// The base UTC offset of the time zone + public record TimeZoneDisplay(string Id, string DisplayName, TimeSpan CurrentUtcOffset); + + /// + /// Get the local (system) time zone + /// + /// The local time zone + public static string GetLocalTimeZone() + => TimeZoneInfo.Local.Id; + + /// + /// Get the time zones on this system + /// + /// The time zones + public static IEnumerable GetTimeZones() + { + foreach (var tz in TimeZoneInfo.GetSystemTimeZones()) + yield return new TimeZoneDisplay(tz.Id, tz.DisplayName, tz.GetUtcOffset(DateTime.Now)); + } + + /// + /// Get the time zone by id + /// + /// The time zone id + /// The time zone + public static TimeZoneInfo? GetTimeZoneById(string id) + { + try + { + return TimeZoneInfo.FindSystemTimeZoneById(id); + } + catch + { + } + return null; + } + + /// + /// Search for a time zone + /// + /// The search string + /// The time zone + public static TimeZoneInfo? FindTimeZone(string search) + { + var tzi = GetTimeZoneById(search); + if (tzi != null) + return tzi; + + return TimeZoneInfo.GetSystemTimeZones() + .FirstOrDefault(tz => tz.Id.Equals(search, StringComparison.OrdinalIgnoreCase) || tz.DisplayName.Contains(search, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Corrects the time for daylight saving time, by checking if the offset has changed + /// + /// The timezone to use for the correction + /// The time before in UTC + /// The time after in UTC + /// The corrected time in UTC + public static DateTime DSTAwareTimeAdjust(this TimeZoneInfo timeZoneInfo, DateTime before, DateTime after) + { + var beforeLocal = TimeZoneInfo.ConvertTime(new DateTimeOffset(before.Kind is DateTimeKind.Local ? before.ToUniversalTime() : before, TimeSpan.Zero), timeZoneInfo); + var afterLocal = TimeZoneInfo.ConvertTime(new DateTimeOffset(after.Kind is DateTimeKind.Local ? after.ToUniversalTime() : after, TimeSpan.Zero), timeZoneInfo); + + if (beforeLocal.Offset == afterLocal.Offset) + return after; + + var diff = beforeLocal.Offset - afterLocal.Offset; + return after.Add(diff); + } + + /// + /// Adds seconds to a time in a specific time zone + /// + /// The time zone to use for the calculation + /// The time to add seconds to + /// The number of seconds to add + /// The new time + public static DateTime DSTAwareAddSeconds(this TimeZoneInfo timeZoneInfo, DateTime dt, long seconds) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddSeconds(seconds)); + + /// + /// Adds the specified number of minutes to the given DateTime, taking into account the time zone's daylight saving time rules. + /// + /// The time zone information. + /// The DateTime to which minutes will be added. + /// The number of minutes to add. + /// A DateTime that is the result of adding the specified number of minutes to the given DateTime, adjusted for daylight saving time. + public static DateTime DSTAwareAddMinutes(this TimeZoneInfo timeZoneInfo, DateTime dt, int minutes) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddMinutes(minutes)); + + /// + /// Adds the specified number of hours to the given DateTime, taking into account the time zone's daylight saving time rules. + /// + /// The time zone information. + /// The DateTime to which hours will be added. + /// The number of hours to add. + /// A DateTime that is the result of adding the specified number of hours to the given DateTime, adjusted for daylight saving time. + public static DateTime DSTAwareAddHours(this TimeZoneInfo timeZoneInfo, DateTime dt, int hours) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddHours(hours)); + + /// + /// Adds the specified number of days to the given DateTime, taking into account the time zone's daylight saving time rules. + /// + /// The time zone information. + /// The DateTime to which days will be added. + /// The number of days to add. + /// A DateTime that is the result of adding the specified number of days to the given DateTime, adjusted for daylight saving time. + public static DateTime DSTAwareAddDays(this TimeZoneInfo timeZoneInfo, DateTime dt, int days) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddDays(days)); + + /// + /// Adds the specified number of months to the given DateTime, taking into account the time zone's daylight saving time rules. + /// + /// The time zone information. + /// The DateTime to which months will be added. + /// The number of months to add. + /// A DateTime that is the result of adding the specified number of months to the given DateTime, adjusted for daylight saving time. + public static DateTime DSTAwareAddMonths(this TimeZoneInfo timeZoneInfo, DateTime dt, int months) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddMonths(months)); + + /// + /// Adds the specified number of years to the given DateTime, taking into account the time zone's daylight saving time rules. + /// + /// The time zone information. + /// The DateTime to which years will be added. + /// The number of years to add. + /// A DateTime that is the result of adding the specified number of years to the given DateTime, adjusted for daylight saving time. + public static DateTime DSTAwareAddYears(this TimeZoneInfo timeZoneInfo, DateTime dt, int years) + => DSTAwareTimeAdjust(timeZoneInfo, dt, dt.AddYears(years)); +} diff --git a/Duplicati/Library/Utility/Timeparser.cs b/Duplicati/Library/Utility/Timeparser.cs index b73c7b9a6..ff248a130 100644 --- a/Duplicati/Library/Utility/Timeparser.cs +++ b/Duplicati/Library/Utility/Timeparser.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -20,8 +20,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Text; namespace Duplicati.Library.Utility { @@ -33,9 +31,50 @@ namespace Duplicati.Library.Utility public static class Timeparser { public static TimeSpan ParseTimeSpan(string datestring) + => ParseTimeSpan(datestring, false); + + public static TimeSpan ParseTimeSpan(string datestring, bool negate) { - DateTime dt = new DateTime(0, DateTimeKind.Local); - return ParseTimeInterval(datestring, dt) - dt; + var multiplier = negate ? -1 : 1; + var offset = TimeSpan.Zero; + + if (string.IsNullOrEmpty(datestring)) + return offset; + + if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l)) + return offset.Add(TimeSpan.FromSeconds(l * multiplier)); + + var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' }; + + int index; + var previndex = 0; + + while ((index = datestring.IndexOfAny(separators, previndex)) > 0) + { + var partial = datestring.Substring(previndex, index - previndex).Trim(); + if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor)) + throw new Exception(Strings.Timeparser.InvalidIntegerError(partial)); + + factor *= multiplier; + + offset += datestring[index] switch + { + 's' => TimeSpan.FromSeconds(factor), + 'm' => TimeSpan.FromMinutes(factor), + 'h' => TimeSpan.FromHours(factor), + 'D' => TimeSpan.FromDays(factor), + 'W' => TimeSpan.FromDays(factor * 7), + 'M' => TimeSpan.FromDays(factor * 30), + 'Y' => TimeSpan.FromDays(factor * 365), + _ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])), + }; + previndex = index + 1; + } + + if (datestring.Substring(previndex).Trim().Length > 0) + throw new Exception(Strings.Timeparser.UnparsedDataFragmentError(datestring.Substring(previndex))); + + return offset; } public static DateTime ParseTimeInterval(string datestring, DateTime offset) @@ -48,16 +87,15 @@ namespace Duplicati.Library.Utility if (offset.Kind == DateTimeKind.Unspecified) offset = new DateTime(offset.Ticks, DateTimeKind.Local); - int multiplier = negate ? -1 : 1; + var multiplier = negate ? -1 : 1; if (string.IsNullOrEmpty(datestring)) return offset; - if (String.Equals(datestring.Trim(), "now", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(datestring.Trim(), "now", StringComparison.OrdinalIgnoreCase)) return DateTime.Now; - long l; - if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out l)) + if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l)) return offset.AddSeconds(l * multiplier); if (DateTime.TryParse(datestring, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var t)) @@ -66,46 +104,98 @@ namespace Duplicati.Library.Utility if (Utility.TryDeserializeDateTime(datestring, out t)) return t; - char[] separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' }; + var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' }; - int index = 0; - int previndex = 0; + int index; + var previndex = 0; while ((index = datestring.IndexOfAny(separators, previndex)) > 0) { - string partial = datestring.Substring(previndex, index - previndex).Trim(); - int factor; - if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out factor)) + var partial = datestring.Substring(previndex, index - previndex).Trim(); + if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor)) throw new Exception(Strings.Timeparser.InvalidIntegerError(partial)); factor *= multiplier; - switch (datestring[index]) + offset = datestring[index] switch { - case 's': - offset = offset.AddSeconds(factor); - break; - case 'm': - offset = offset.AddMinutes(factor); - break; - case 'h': - offset = offset.AddHours(factor); - break; - case 'D': - offset = offset.AddDays(factor); - break; - case 'W': - offset = offset.AddDays(factor * 7); - break; - case 'M': - offset = offset.AddMonths(factor); - break; - case 'Y': - offset = offset.AddYears(factor); - break; - default: - throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])); - } + 's' => offset.AddSeconds(factor), + 'm' => offset.AddMinutes(factor), + 'h' => offset.AddHours(factor), + 'D' => offset.AddDays(factor), + 'W' => offset.AddDays(factor * 7), + 'M' => offset.AddMonths(factor), + 'Y' => offset.AddYears(factor), + _ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])), + }; + previndex = index + 1; + } + + if (datestring.Substring(previndex).Trim().Length > 0) + throw new Exception(Strings.Timeparser.UnparsedDataFragmentError(datestring.Substring(previndex))); + + return offset; + } + + /// + /// Parses a time interval string with a timezone offset, retaining the local time + /// + /// The repeating interval string + /// The base time to add the interval to + /// The timezone to use for the calculation + /// True if the time of day should be kept across DST changes + /// True if the interval should be subtracted + /// The calculated time + public static DateTime DSTAwareParseTimeInterval(string datestring, DateTime offset, TimeZoneInfo timeZoneInfo, bool keepTimeOfDay, bool negate = false) + { + // Use non-DST-aware version if we are not fixing this to the current time-of-day + if (!keepTimeOfDay) + return ParseTimeInterval(datestring, offset, negate); + + if (offset.Kind == DateTimeKind.Unspecified) + offset = new DateTime(offset.Ticks, DateTimeKind.Utc); + + int multiplier = negate ? -1 : 1; + + if (string.IsNullOrEmpty(datestring)) + return offset; + + if (string.Equals(datestring.Trim(), "now", StringComparison.OrdinalIgnoreCase)) + return DateTime.UtcNow; + + if (long.TryParse(datestring, System.Globalization.NumberStyles.Integer, null, out var l)) + return timeZoneInfo.DSTAwareAddSeconds(offset, l * multiplier); + + if (DateTime.TryParse(datestring, System.Globalization.CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var t)) + return t; + + if (Utility.TryDeserializeDateTime(datestring, out t)) + return t; + + var separators = new char[] { 's', 'm', 'h', 'D', 'W', 'M', 'Y' }; + + int index; + var previndex = 0; + + while ((index = datestring.IndexOfAny(separators, previndex)) > 0) + { + var partial = datestring.Substring(previndex, index - previndex).Trim(); + if (!int.TryParse(partial, System.Globalization.NumberStyles.Integer, null, out var factor)) + throw new Exception(Strings.Timeparser.InvalidIntegerError(partial)); + + factor *= multiplier; + + offset = datestring[index] switch + { + 's' => timeZoneInfo.DSTAwareAddSeconds(offset, factor), + 'm' => timeZoneInfo.DSTAwareAddMinutes(offset, factor), + 'h' => timeZoneInfo.DSTAwareAddHours(offset, factor), + 'D' => timeZoneInfo.DSTAwareAddDays(offset, factor), + 'W' => timeZoneInfo.DSTAwareAddDays(offset, factor * 7), + 'M' => timeZoneInfo.DSTAwareAddMonths(offset, factor), + 'Y' => timeZoneInfo.DSTAwareAddYears(offset, factor), + _ => throw new Exception(Strings.Timeparser.InvalidSpecifierError(datestring[index])), + }; previndex = index + 1; } diff --git a/Duplicati/Library/Utility/Uri.cs b/Duplicati/Library/Utility/Uri.cs index c05ecaab8..51b4f278d 100644 --- a/Duplicati/Library/Utility/Uri.cs +++ b/Duplicati/Library/Utility/Uri.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -26,6 +26,14 @@ using System.Text; namespace Duplicati.Library.Utility { + // TODO: This class should be deleted. + + // It was introduced to make it simpler to give the backend url on the commandline, + // and because the Mono implementation of System.Uri had some issues. + // Since Mono is no longer used, the only problem is the commandline, + // but it does not make sense to support "invalid" urls as that increases the complexity + // of the code and potentially introduces ambiguity for the user. + /// /// Represents a relaxed parsing of a URL. /// The goal is to cover as many types of url's as possible, @@ -49,7 +57,8 @@ namespace Duplicati.Library.Utility /// public readonly string Host; /// - /// The server path, e.g. index.html + /// The server path, e.g. index.html. + /// Note that the path does NOT have a leading /. /// public readonly string Path; /// @@ -176,8 +185,11 @@ namespace Duplicati.Library.Utility // file://c:\test support if (h.Length == 1 && p.StartsWith(":", StringComparison.Ordinal)) { - h = h + p; - p = ""; + p = h + p; + if (p.IndexOfAny(System.IO.Path.GetInvalidPathChars()) >= 0) + throw new ArgumentException(Strings.Uri.UriParseError(url), nameof(url)); + p = System.IO.Path.GetFullPath(p); + h = null; } this.Host = h; @@ -265,7 +277,7 @@ namespace Duplicati.Library.Utility if (!string.IsNullOrEmpty(path)) { - if (!string.IsNullOrEmpty(host) && !path.StartsWith("/", StringComparison.Ordinal)) + if (!string.IsNullOrEmpty(host)) s += "/"; s += path; } @@ -449,7 +461,7 @@ namespace Duplicati.Library.Utility /// The query to parse public static NameValueCollection ParseQueryString(string query) { - return Library.Utility.Uri.ParseQueryString(query, true); + return ParseQueryString(query, true); } /// @@ -529,7 +541,7 @@ namespace Duplicati.Library.Utility { var builder = new UriBuilder(url) { - Path = (new UrlPath(ExtractPath(url)).Append(path)).ToString(), + Path = new UrlPath(ExtractPath(url)).Append(path).ToString(), Query = query != null ? BuildUriQuery(query) : null }; return builder.Uri.AbsoluteUri; @@ -543,7 +555,7 @@ namespace Duplicati.Library.Utility /// URL. public static string ExtractPath(string url) { - return (new Uri(url)).Path; + return new Uri(url).Path; } /// diff --git a/Duplicati/Library/Utility/UrlPath.cs b/Duplicati/Library/Utility/UrlPath.cs index c32870cac..db1370a16 100644 --- a/Duplicati/Library/Utility/UrlPath.cs +++ b/Duplicati/Library/Utility/UrlPath.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Text; diff --git a/Duplicati/Library/Utility/UrlUtility.cs b/Duplicati/Library/Utility/UrlUtility.cs index 7d1c82204..caa0bf102 100644 --- a/Duplicati/Library/Utility/UrlUtility.cs +++ b/Duplicati/Library/Utility/UrlUtility.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index a27a7d844..e1a56a9fc 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,18 +19,25 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.Versioning; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; using Duplicati.Library.Common.IO; -using System.Globalization; -using System.Runtime.Versioning; -using System.Security.Cryptography.X509Certificates; +using Duplicati.StreamUtil; namespace Duplicati.Library.Utility { @@ -82,7 +89,7 @@ namespace Duplicati.Library.Utility /// The stream to write to /// True if an attempt should be made to rewind the source stream, false otherwise /// Temporary buffer to use (optional) - public static long CopyStream(Stream source, Stream target, bool tryRewindSource, byte[] buf = null) + public static long CopyStream(Stream source, Stream target, bool tryRewindSource, byte[]? buf = null) { if (tryRewindSource && source.CanSeek) try { source.Position = 0; } @@ -123,7 +130,7 @@ namespace Duplicati.Library.Utility /// True if an attempt should be made to rewind the source stream, false otherwise /// Token to cancel the operation. /// Temporary buffer to use (optional) - public static async Task CopyStreamAsync(Stream source, Stream target, bool tryRewindSource, CancellationToken cancelToken, byte[] buf = null) + public static async Task CopyStreamAsync(Stream source, Stream target, bool tryRewindSource, CancellationToken cancelToken, byte[]? buf = null) { if (tryRewindSource && source.CanSeek) try { source.Position = 0; } @@ -243,7 +250,7 @@ namespace Duplicati.Library.Utility /// A list of the full filenames and foldernames. Foldernames ends with the directoryseparator char public static IEnumerable EnumerateFileSystemEntries(string basepath) { - return EnumerateFileSystemEntries(basepath, (rootpath, path, attributes) => true, SystemIO.IO_OS.GetDirectories, Directory.GetFiles, null); + return EnumerateFileSystemEntries(basepath, SystemIO.IO_OS.GetDirectories, Directory.GetFiles, null); } /// @@ -273,13 +280,12 @@ namespace Duplicati.Library.Utility /// The search is recursive. /// /// The folder to look in - /// The function to call with the filenames /// A function to call that lists all folders in the supplied folder /// A function to call that lists all files in the supplied folder /// A function to call that obtains the attributes for an element, set to null to avoid reading attributes /// An optional function to call with error messages. /// A list of the full filenames - public static IEnumerable EnumerateFileSystemEntries(string rootpath, EnumerationFilterDelegate callback, FileSystemInteraction folderList, FileSystemInteraction fileList, ExtractFileAttributes attributeReader, ReportAccessError errorCallback = null) + public static IEnumerable EnumerateFileSystemEntries(string rootpath, FileSystemInteraction folderList, FileSystemInteraction fileList, ExtractFileAttributes? attributeReader, ReportAccessError? errorCallback = null) { var lst = new Stack(); @@ -289,17 +295,11 @@ namespace Duplicati.Library.Utility try { var attr = attributeReader?.Invoke(rootpath) ?? FileAttributes.Directory; - if (callback(rootpath, rootpath, attr)) - lst.Push(rootpath); + lst.Push(rootpath); } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) + catch (Exception ex) when (!ex.IsAbortException()) { errorCallback?.Invoke(rootpath, rootpath, ex); - callback(rootpath, rootpath, FileAttributes.Directory | ATTRIBUTE_ERROR); } while (lst.Count > 0) @@ -316,92 +316,41 @@ namespace Duplicati.Library.Utility try { var attr = attributeReader?.Invoke(sf) ?? FileAttributes.Directory; - if (callback(rootpath, sf, attr)) - lst.Push(sf); + lst.Push(sf); } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) + catch (Exception ex) when (!ex.IsAbortException()) { errorCallback?.Invoke(rootpath, sf, ex); - callback(rootpath, sf, FileAttributes.Directory | ATTRIBUTE_ERROR); } } } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) + catch (Exception ex) when (!ex.IsAbortException()) { errorCallback?.Invoke(rootpath, f, ex); - callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR); } - string[] files = null; + string[]? files = null; if (fileList != null) { try { files = fileList(f); } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) + catch (Exception ex) when (!ex.IsAbortException()) { errorCallback?.Invoke(rootpath, f, ex); - callback(rootpath, f, FileAttributes.Directory | ATTRIBUTE_ERROR); } } if (files != null) { foreach (var s in files) - { - try - { - var attr = attributeReader?.Invoke(s) ?? FileAttributes.Normal; - if (!callback(rootpath, s, attr)) - continue; - } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) - { - errorCallback?.Invoke(rootpath, s, ex); - callback(rootpath, s, ATTRIBUTE_ERROR); - continue; - } yield return s; - } } } } else { - try - { - var attr = attributeReader?.Invoke(rootpath) ?? FileAttributes.Normal; - if (!callback(rootpath, rootpath, attr)) - yield break; - } - catch (System.Threading.ThreadAbortException) - { - throw; - } - catch (Exception ex) - { - errorCallback?.Invoke(rootpath, rootpath, ex); - callback(rootpath, rootpath, ATTRIBUTE_ERROR); - yield break; - } - yield return rootpath; } } @@ -412,7 +361,7 @@ namespace Duplicati.Library.Utility /// Path to test /// Function to use for testing path /// True if path is refers to a folder - public static bool IsFolder(string path, ExtractFileAttributes attributeReader) + public static bool IsFolder(string path, ExtractFileAttributes? attributeReader) { if (attributeReader == null) return true; @@ -447,7 +396,7 @@ namespace Duplicati.Library.Utility /// Full file or folder path /// If true, return value always has trailing separator /// Parent folder of path (containing folder for file paths, parent folder for folder paths) - public static string GetParent(string path, bool forceTrailingDirectorySeparator) + public static string? GetParent(string path, bool forceTrailingDirectorySeparator) { var len = path.Length - 1; if (len > 1 && path[len] == Path.DirectorySeparatorChar) @@ -491,7 +440,7 @@ namespace Duplicati.Library.Utility foreach (var folder1 in folders) { bool addFolder = true; - LinkedListNode next; + LinkedListNode? next; for (var node = result.First; node != null; node = next) { next = node.Next; @@ -692,44 +641,13 @@ namespace Duplicati.Library.Utility return Strings.Utility.FormatStringB((long)size); // safe to cast because lower than 1024 and thus well within range of long } - public static System.Threading.ThreadPriority ParsePriority(string value) - { - if (string.IsNullOrEmpty(value) || value.Trim().Length == 0) - return System.Threading.ThreadPriority.Normal; - - switch (value.ToLower(CultureInfo.InvariantCulture).Trim()) - { - case "+2": - case "high": - case "highest": - return System.Threading.ThreadPriority.Highest; - case "+1": - case "abovenormal": - case "above normal": - return System.Threading.ThreadPriority.AboveNormal; - - case "-1": - case "belownormal": - case "below normal": - return System.Threading.ThreadPriority.BelowNormal; - case "-2": - case "low": - case "lowest": - case "idle": - return System.Threading.ThreadPriority.Lowest; - - default: - return System.Threading.ThreadPriority.Normal; - } - } - /// /// Parses a string into a boolean value. /// /// The value to parse. /// A delegate that returns the default value if is not a valid boolean value. /// The parsed value, or the value returned by . - public static bool ParseBool(string value, Func defaultFunc) + public static bool ParseBool(string? value, Func defaultFunc) { if (String.IsNullOrWhiteSpace(value)) { @@ -759,7 +677,7 @@ namespace Duplicati.Library.Utility /// The value to parse. /// The default value, in case is not a valid boolean value. /// The parsed value, or the default value. - public static bool ParseBool(string value, bool @default) + public static bool ParseBool(string? value, bool @default) { return ParseBool(value, () => @default); } @@ -769,16 +687,31 @@ namespace Duplicati.Library.Utility /// /// The set of options to look for the setting in /// The value to look for in the settings - /// - public static bool ParseBoolOption(IReadOnlyDictionary options, string value) + /// The parsed value, or the default value (false). + public static bool ParseBoolOption(IReadOnlyDictionary options, string value) { - string opt; - if (options.TryGetValue(value, out opt)) + if (options.TryGetValue(value, out var opt)) return ParseBool(opt, true); else return false; } + /// + /// Parses an integer option from the option set, returning the default value if the option is not found or cannot be parsed + /// + /// The set of options to look for the setting in + /// The value to look for in the settings + /// The default value to return if there are no matches. + /// The parsed or default integer value. + public static TimeSpan ParseTimespanOption(IReadOnlyDictionary options, string value, string @default) + { + var opt = options.GetValueOrDefault(value); + if (string.IsNullOrWhiteSpace(opt)) + opt = @default; + + return Timeparser.ParseTimeSpan(opt); + } + /// /// Parses an enum found in the options dictionary /// @@ -787,7 +720,20 @@ namespace Duplicati.Library.Utility /// The value to look for in the settings /// The default value to return if there are no matches. /// The enum type parameter. - public static T ParseEnumOption(IReadOnlyDictionary options, string value, T @default) + public static T ParseEnumOption(IReadOnlyDictionary options, string value, T @default) where T : struct, Enum + { + return options.TryGetValue(value, out var opt) ? ParseEnum(opt, @default) : @default; + } + + /// + /// Parses a flags-type enum found in the options dictionary + /// + /// The parsed or default enum value. + /// The set of options to look for the setting in + /// The value to look for in the settings + /// The default value to return if there are no matches. + /// The enum type parameter. + public static T ParseFlagsOption(IReadOnlyDictionary options, string value, T @default) where T : struct, Enum { return options.TryGetValue(value, out var opt) ? ParseEnum(opt, @default) : @default; } @@ -799,8 +745,10 @@ namespace Duplicati.Library.Utility /// The string to parse. /// The default value to return if there are no matches. /// The enum type parameter. - public static T ParseEnum(string value, T @default) + public static T ParseEnum(string? value, T @default) where T : struct, Enum { + if (string.IsNullOrWhiteSpace(value)) + return @default; foreach (var s in Enum.GetNames(typeof(T))) if (s.Equals(value, StringComparison.OrdinalIgnoreCase)) return (T)Enum.Parse(typeof(T), s); @@ -808,6 +756,41 @@ namespace Duplicati.Library.Utility return @default; } + /// + /// Parses a string into a flags enum value. + /// + /// The enum type to parse. + /// The value to parse. + /// The default value to return if there are no matches. + /// + public static T ParseFlags(string? value, T @default) where T : struct, Enum + { + if (string.IsNullOrWhiteSpace(value)) + return @default; + + var flags = 0; + foreach (var s in value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) + { + var trimmed = s.Trim(); + if (Enum.TryParse(trimmed, true, out T flag)) + flags = flags | (int)(object)flag; + } + + return (T)(object)flags; + } + + /// + /// Parses an option with int value, returning the default value if the option is not found or cannot be parsed + /// + /// The set of options to look for the setting in + /// The value to look for in the settings + /// default value + /// + public static int ParseIntOption(IReadOnlyDictionary options, string value, int @default) + { + return options.TryGetValue(value, out var opt) && int.TryParse(opt ?? string.Empty, out var result) ? result : @default; + } + /// /// Converts a sequence of bytes to a hex string /// @@ -863,7 +846,8 @@ namespace Duplicati.Library.Utility UseShellExecute = false }; - var pi = System.Diagnostics.Process.Start(psi); + var pi = System.Diagnostics.Process.Start(psi) + ?? throw new Exception("Unexpected failure to start process"); pi.WaitForExit(5000); if (pi.HasExited) return pi.ExitCode == 0; @@ -924,17 +908,20 @@ namespace Duplicati.Library.Utility /// The expanded string. /// The string to expand. /// A lookup method that converts an environment key to an expanded string - public static string ExpandEnvironmentVariablesRegexp(string str, Func lookup = null) + public static string? ExpandEnvironmentVariablesRegexp(string? str, Func? lookup = null) { + if (string.IsNullOrWhiteSpace(str)) + return str; + if (lookup == null) - lookup = Environment.GetEnvironmentVariable; + lookup = x => Environment.GetEnvironmentVariable(x ?? string.Empty); return // TODO: Should we switch to using the native format ($VAR or ${VAR}), instead of following the Windows scheme? // IsClientLinux ? new Regex(@"\$(?\w+)|(\{(?[^\}]+)\})") : ENVIRONMENT_VARIABLE_MATCHER_WINDOWS - ENVIRONMENT_VARIABLE_MATCHER_WINDOWS.Replace(str, m => Regex.Escape(lookup(m.Groups["name"].Value))); + ENVIRONMENT_VARIABLE_MATCHER_WINDOWS.Replace(str, m => Regex.Escape(lookup(m.Groups["name"].Value) ?? string.Empty)); } /// @@ -1010,7 +997,7 @@ namespace Duplicati.Library.Utility /// The collection to remove duplicate items from. /// The duplicate items in . /// The unique items from . - public static ISet GetUniqueItems(IEnumerable collection, out ISet duplicateItems) + public static IList GetUniqueItems(IEnumerable collection, out ISet duplicateItems) { return GetUniqueItems(collection, EqualityComparer.Default, out duplicateItems); } @@ -1023,18 +1010,21 @@ namespace Duplicati.Library.Utility /// The implementation to use when comparing values in the collection. /// The duplicate items in . /// The unique items from . - public static ISet GetUniqueItems(IEnumerable collection, IEqualityComparer comparer, out ISet duplicateItems) + public static IList GetUniqueItems(IEnumerable collection, IEqualityComparer comparer, out ISet duplicateItems) { var uniqueItems = new HashSet(comparer); + var results = new List(); duplicateItems = new HashSet(comparer); foreach (var item in collection) { - if (!uniqueItems.Add(item)) + if (uniqueItems.Add(item)) + results.Add(item); + else duplicateItems.Add(item); } - return uniqueItems; + return results; } // @@ -1112,7 +1102,7 @@ namespace Duplicati.Library.Utility /// true, the item was printed, false otherwise. /// The item to write. /// The target writer. - private static bool PrintSerializeIfPrimitive(object item, TextWriter writer) + private static bool PrintSerializeIfPrimitive(object? item, TextWriter writer) { if (item == null) { @@ -1148,14 +1138,13 @@ namespace Duplicati.Library.Utility /// The string indentation /// A lookup table with visited objects, used to avoid infinite recursion /// The maximum number of items to report from an IEnumerable instance - public static void PrintSerializeObject(object item, TextWriter writer, Func filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 0, Dictionary visited = null) + public static void PrintSerializeObject(object? item, TextWriter writer, Func? filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 0, Dictionary? visited = null) { - visited = visited ?? new Dictionary(); + visited = visited ?? new Dictionary(); var indentstring = new string(' ', indentation); var first = true; - if (item == null || IsPrimitiveTypeForSerialization(item.GetType())) { writer.Write(indentstring); @@ -1163,6 +1152,9 @@ namespace Duplicati.Library.Utility return; } + if (item == null) + return; + foreach (var p in item.GetType().GetProperties()) { if (filter != null && !filter(p, item)) @@ -1185,7 +1177,7 @@ namespace Duplicati.Library.Utility } else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(p.PropertyType)) { - var enumerable = (System.Collections.IEnumerable)p.GetValue(item, null); + var enumerable = p.GetValue(item, null) as System.Collections.IEnumerable; var any = false; if (enumerable != null) { @@ -1269,7 +1261,7 @@ namespace Duplicati.Library.Utility /// A value indicating if non-primitive values are recursed /// The string indentation /// The maximum number of items to report from an IEnumerable instance, set to zero or less for reporting all - public static StringBuilder PrintSerializeObject(object item, StringBuilder sb = null, Func filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 10) + public static StringBuilder PrintSerializeObject(object? item, StringBuilder? sb = null, Func? filter = null, bool recurseobjects = false, int indentation = 0, int collectionlimit = 10) { sb = sb ?? new StringBuilder(); using (var sw = new StringWriter(sb)) @@ -1311,7 +1303,7 @@ namespace Duplicati.Library.Utility h.Initialize(); h.TransformBlock(salt, 0, salt.Length, salt, 0); h.TransformFinalBlock(data, 0, data.Length); - var buf = h.Hash; + var buf = h.Hash ?? throw new CryptographicUnexpectedOperationException("Computed hash was null?"); for (var i = 0; i < repeats; i++) { @@ -1333,14 +1325,14 @@ namespace Duplicati.Library.Utility /// Drive letter, as a single character, or null if the volume wasn't found [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] [SupportedOSPlatform("windows")] - public static string GetDriveLetterFromVolumeGuid(Guid volumeGuid) + public static string? GetDriveLetterFromVolumeGuid(Guid volumeGuid) { // Based on this answer: // https://stackoverflow.com/questions/10186277/how-to-get-drive-information-by-volume-id - using (System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("Select * from Win32_Volume")) + using (var searcher = new System.Management.ManagementObjectSearcher("Select * from Win32_Volume")) { string targetId = string.Format(@"\\?\Volume{{{0}}}\", volumeGuid); - foreach (System.Management.ManagementObject obj in searcher.Get()) + foreach (var obj in searcher.Get()) { if (string.Equals(obj["DeviceID"].ToString(), targetId, StringComparison.OrdinalIgnoreCase)) { @@ -1527,7 +1519,7 @@ namespace Duplicati.Library.Utility /// The password used to protect the PFX file /// A flag indicating if unsafe certificate loading is allowed /// The loaded certificate - public static X509Certificate2 LoadPfxCertificate(ReadOnlySpan pfxcertificate, string? password, bool allowUnsafeCertificateLoad = false) + public static X509Certificate2Collection LoadPfxCertificate(ReadOnlySpan pfxcertificate, string? password, bool allowUnsafeCertificateLoad = false) { if (string.IsNullOrWhiteSpace(password) && !allowUnsafeCertificateLoad) throw new ArgumentException("Refusing to write unencryped certificate to disk"); @@ -1543,7 +1535,7 @@ namespace Duplicati.Library.Utility /// The path to the file /// The password used to protect the PFX file /// The loaded certificate - public static X509Certificate2 LoadPfxCertificate(string pfxPath, string? password) + public static X509Certificate2Collection LoadPfxCertificate(string pfxPath, string? password) { if (string.IsNullOrEmpty(pfxPath)) throw new ArgumentNullException(nameof(pfxPath)); @@ -1551,7 +1543,267 @@ namespace Duplicati.Library.Utility if (!File.Exists(pfxPath)) throw new FileNotFoundException("The specified PFX file does not exist.", pfxPath); - return new X509Certificate2(pfxPath, password, X509KeyStorageFlags.Exportable); + var collection = new X509Certificate2Collection(); + collection.Import(pfxPath, password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); + return collection; + } + + /// + /// Probes the system for the presence of a loopback address on IPv4 + /// + public static bool HasIPv4Loopback => + NetworkInterface.GetAllNetworkInterfaces() + .Where(ni => ni.OperationalStatus == OperationalStatus.Up) + .SelectMany(ni => ni.GetIPProperties().UnicastAddresses) + .Any(addr => addr.Address.AddressFamily == AddressFamily.InterNetwork + && addr.Address.Equals(IPAddress.Loopback)); + + /// + /// On systems that have IPV4 and IPV6, the method will return the default loopback ( 127, 0, 0, 1) + /// On systems with IPV6 only, the method will return the IPV6 loopback (::1) + /// + /// + public static string IpVersionCompatibleLoopback => + HasIPv4Loopback ? IPAddress.Loopback.ToString() : $"[{IPAddress.IPv6Loopback.ToString()}]"; + + /// + /// Flattens an exception and its inner exceptions + /// + /// The exception to flatten + /// An enumerable of exceptions + public static IEnumerable FlattenException(Exception? ex) + { + if (ex == null) + yield break; + + yield return ex; + + if (ex is AggregateException aex) + foreach (var iex in aex.Flatten().InnerExceptions) + foreach (var iex2 in FlattenException(iex)) + yield return iex2; + + foreach (var iex in FlattenException(ex.InnerException)) + yield return iex; + } + + /// + /// Guesses the URL scheme and returns it + /// + /// The URL to guess the scheme for + /// The guessed scheme, or null if no scheme was found + public static string? GuessScheme(string url) + { + if (string.IsNullOrWhiteSpace(url)) + return null; + + var idx = url.IndexOf("://"); + if (idx < 0 && idx < 15 && idx + "://".Length < url.Length) + return null; + + return url.Substring(0, idx); + } + + /// + /// Returns a url that is safe to display, by removing any credentials + /// + /// The url to sanitize + /// The sanitized url + public static string GetUrlWithoutCredentials(string url) + { + // Assumed safe part of the url to show + const int maxShown = 25; + if (string.IsNullOrWhiteSpace(url)) + return url; + + // Use a reportable url without credentials + var sepIndex = Math.Max(0, url.IndexOf('|')) + 1; + var length = url.Length - sepIndex; + var shown = Math.Min(length, maxShown); + var hidden = length - maxShown; + var sanitizedUrl = $"{url[sepIndex..(sepIndex + shown)]}{new string('*', hidden)}"; + + // If we can parse it, this result is better + try + { + var uri = new Uri(url[sepIndex..]); + sanitizedUrl = new Uri($"{uri.Scheme}://{uri.Host}").SetPath(uri.Path).ToString(); + } + catch + { + } + + return sanitizedUrl; + } + + /// + /// Checks if an exception is a stop, cancel or timeout exception + /// + /// The operation to check + /// true if the exception is a stop or cancel exception, false otherwise + public static bool IsAbortOrCancelException(this Exception ex) + { + return ex is OperationCanceledException || ex is ThreadAbortException || ex is TaskCanceledException || ex is TimeoutException; + } + + /// + /// Checks if an exception is a stop exception + /// + /// The operation to check + /// true if the exception is a stop exception, false otherwise + public static bool IsAbortException(this Exception ex) + { + return ex is OperationCanceledException || ex is ThreadAbortException; + } + + /// + /// Formats the string using the invariant culture + /// + /// The format string + /// The arguments to format + /// The formatted string + public static string FormatInvariant(this string format, params object?[] args) + => string.Format(CultureInfo.InvariantCulture, format, args); + + /// + /// Formats the string using the invariant culture + /// + /// The formattable string + /// The formatted string + public static string FormatInvariant(this FormattableString formattable) + => formattable.ToString(CultureInfo.InvariantCulture); + + /// + /// Performs the function with an additional timeout + /// + /// The timeout to observe + /// The cancellation token + /// The function to invoke + /// The task + public static async Task WithTimeout(TimeSpan timeout, CancellationToken token, Action func) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + cts.CancelAfter(timeout); + try + { + await Task.Run(() => func(cts.Token), cts.Token).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + if (cts.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + + /// + /// Performs the function with an additional timeout + /// + /// The timeout to observe + /// The cancellation token + /// The function to invoke + /// The task + public static async Task WithTimeout(TimeSpan timeout, CancellationToken token, Func func) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + cts.CancelAfter(timeout); + try + { + return await Task.Run(() => func(cts.Token), cts.Token).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + if (cts.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + + /// + /// Performs the function with an additional timeout + /// + /// The timeout to observe + /// The cancellation token + /// The function to invoke + /// The task + public static async Task WithTimeout(TimeSpan timeout, CancellationToken token, Func func) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + cts.CancelAfter(timeout); + try + { + await func(cts.Token); + } + catch (TaskCanceledException) + { + if (cts.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + + /// + /// Performs the function with an additional timeout + /// + /// The return type + /// The timeout to observe + /// The cancellation token + /// The function to invoke + /// The task + public static async Task WithTimeout(TimeSpan timeout, CancellationToken token, Func> func) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(token); + cts.CancelAfter(timeout); + try + { + return await func(cts.Token); + } + catch (TaskCanceledException) + { + if (cts.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + + /// + /// Wraps the stream in a timeout observing stream + /// + /// The stream to wrap + /// The timeout to observe + /// A flag indicating if the base stream should be disposed + /// The wrapped stream + public static Stream ObserveReadTimeout(this Stream stream, TimeSpan timeout, bool disposeBaseStream = true) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + return new TimeoutObservingStream(stream, disposeBaseStream) + { + ReadTimeout = timeout.Ticks > 0 && timeout != Timeout.InfiniteTimeSpan + ? (int)timeout.TotalMilliseconds + : Timeout.Infinite + }; + } + + /// + /// Wraps the stream in a timeout observing stream + /// + /// The stream to wrap + /// The timeout to observe + /// A flag indicating if the base stream should be disposed + /// The wrapped stream + public static Stream ObserveWriteTimeout(this Stream stream, TimeSpan timeout, bool disposeBaseStream = true) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + return new TimeoutObservingStream(stream, disposeBaseStream) + { + WriteTimeout = timeout.Ticks > 0 && timeout != Timeout.InfiniteTimeSpan + ? (int)timeout.TotalMilliseconds + : Timeout.Infinite + }; } } } diff --git a/Duplicati/Library/Utility/Win32.cs b/Duplicati/Library/Utility/Win32.cs index 68ff58205..9e2c5e0f6 100644 --- a/Duplicati/Library/Utility/Win32.cs +++ b/Duplicati/Library/Utility/Win32.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Library/Utility/WorkerThread.cs b/Duplicati/Library/Utility/WorkerThread.cs index eaf49d5a1..285aac182 100644 --- a/Duplicati/Library/Utility/WorkerThread.cs +++ b/Duplicati/Library/Utility/WorkerThread.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -333,18 +333,17 @@ namespace Duplicati.Library.Utility } catch (Exception ex) { - try { System.Threading.Thread.ResetAbort(); } - catch { } - + //TODO: Here where Thread.ResetAbort() was called we shall integrate the CancelationToken pattern. if (OnError != null) try { OnError(this, m_currentTask, ex); } - catch { } + catch + { + // ignored + } } finally { - try { System.Threading.Thread.ResetAbort(); } - catch { } - + //TODO: Here where Thread.ResetAbort() was called we shall integrate the CancelationToken pattern. m_active = false; } @@ -356,7 +355,10 @@ namespace Duplicati.Library.Utility catch (Exception ex) { try { OnError(this, task, ex); } - catch { } + catch + { + // ignored + } } } } diff --git a/Duplicati/License/Duplicati.License.csproj b/Duplicati/License/Duplicati.License.csproj index 9716b0140..d32900c23 100644 --- a/Duplicati/License/Duplicati.License.csproj +++ b/Duplicati/License/Duplicati.License.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -154,18 +154,6 @@ licenses\MailKit\licensedata.json PreserveNewest - - licenses\ManagedLZMA\download.txt - PreserveNewest - - - licenses\ManagedLZMA\license.txt - PreserveNewest - - - licenses\ManagedLZMA\licensedata.json - PreserveNewest - licenses\MegaApi\Homepage.txt PreserveNewest diff --git a/Duplicati/License/LicenseEntry.cs b/Duplicati/License/LicenseEntry.cs index 613e319b2..05981b855 100644 --- a/Duplicati/License/LicenseEntry.cs +++ b/Duplicati/License/LicenseEntry.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/License/LicenseReader.cs b/Duplicati/License/LicenseReader.cs index b96f67f54..55fd6f513 100644 --- a/Duplicati/License/LicenseReader.cs +++ b/Duplicati/License/LicenseReader.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/License/VersionNumbers.cs b/Duplicati/License/VersionNumbers.cs index 724104cd4..98a4bb71a 100644 --- a/Duplicati/License/VersionNumbers.cs +++ b/Duplicati/License/VersionNumbers.cs @@ -1,35 +1,43 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Text; +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.License { + /// + /// The version numbers for the application, read from the assembly + /// public static class VersionNumbers { + /// + /// The Version Tag read from the embedded resource VersionTag.txt + /// public static readonly string TAG; + /// + /// The version number of the assembly + /// public static readonly string VERSION_NAME; - + + /// + /// Static constructor to read the version numbers from the assembly + /// static VersionNumbers() { string tag = ""; @@ -41,13 +49,13 @@ namespace Duplicati.License catch { } - + if (string.IsNullOrWhiteSpace(tag)) tag = ""; - + TAG = tag.Trim(); - var v = VersionNumber; + var v = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); if (!string.IsNullOrWhiteSpace(TAG)) v = " - " + TAG; #if DEBUG @@ -56,21 +64,5 @@ namespace Duplicati.License VERSION_NAME = v; } - - public static string Version - { - get - { - return VERSION_NAME; - } - } - - private static string VersionNumber - { - get - { - return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); - } - } } } diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Duplicati.Server.Serialization.csproj b/Duplicati/Server/Duplicati.Server.Serialization/Duplicati.Server.Serialization.csproj index 185bc1d01..c9e196c15 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Duplicati.Server.Serialization.csproj +++ b/Duplicati/Server/Duplicati.Server.Serialization/Duplicati.Server.Serialization.csproj @@ -3,7 +3,7 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Enums.cs b/Duplicati/Server/Duplicati.Server.Serialization/Enums.cs index 19915ed9f..714e450d3 100755 --- a/Duplicati/Server/Duplicati.Server.Serialization/Enums.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Enums.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -53,7 +53,8 @@ namespace Duplicati.Server.Serialization ReadyError, Paused, Active, - ActivePaused + ActivePaused, + Disconnected } diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Filter.cs b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Filter.cs index 0e8ac6290..cabb59433 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Filter.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Filter.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Notification.cs b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Notification.cs index 9aa1f57c2..db0dee811 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Notification.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Notification.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Setting.cs b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Setting.cs index 0985b11e8..a32a1bb49 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Setting.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Implementations/Setting.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs index de6f3aaaf..3b4e66535 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IBackup.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IDynamicModule.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IDynamicModule.cs index 5cf0fc9d5..72fd28601 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IDynamicModule.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IDynamicModule.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Server.Serialization.Interface diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IFilter.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IFilter.cs index 671b47f3f..817f0478d 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IFilter.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IFilter.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Server.Serialization.Interface diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/INotification.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/INotification.cs index 29511cd3b..ae8a447b0 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/INotification.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/INotification.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IProgressEventData.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IProgressEventData.cs index 1b04aa5ea..66c8b0934 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IProgressEventData.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IProgressEventData.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Server.Serialization.Interface diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs index e49739d39..385287292 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Server.Serialization.Interface diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISchedule.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISchedule.cs index a68dcafc0..fba036f01 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISchedule.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISchedule.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISetting.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISetting.cs index fdcf2ef42..7c7ab1014 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISetting.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/ISetting.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. namespace Duplicati.Server.Serialization.Interface diff --git a/Duplicati/Server/Duplicati.Server.Serialization/InterfaceResolver.cs b/Duplicati/Server/Duplicati.Server.Serialization/InterfaceResolver.cs index 1f6f7f66a..872be6cb0 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/InterfaceResolver.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/InterfaceResolver.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using Newtonsoft.Json.Converters; diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Serializer.cs b/Duplicati/Server/Duplicati.Server.Serialization/Serializer.cs index 7d8d36551..c5a5c62f8 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Serializer.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Serializer.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System.Linq; using Newtonsoft.Json; diff --git a/Duplicati/Server/Duplicati.Server.csproj b/Duplicati/Server/Duplicati.Server.csproj index fe5751430..c36cb8d1b 100644 --- a/Duplicati/Server/Duplicati.Server.csproj +++ b/Duplicati/Server/Duplicati.Server.csproj @@ -3,7 +3,7 @@ net8.0 Duplicati.Server.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -37,6 +37,7 @@ + diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index 596e8aa59..c8d8705f1 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -21,15 +21,19 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; +using System.Threading; using System.Threading.Tasks; +using Duplicati.Library.AutoUpdater; using Duplicati.Library.Common.IO; +using Duplicati.Library.Crashlog; using Duplicati.Library.Encryption; using Duplicati.Library.Interface; +using Duplicati.Library.Logging; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; using Duplicati.Library.RestAPI; +using Duplicati.Library.Utility; using Duplicati.Server.Database; using Duplicati.WebserverCore; using Duplicati.WebserverCore.Abstractions; @@ -39,16 +43,22 @@ namespace Duplicati.Server { public class Program { - - private static readonly string[] AlternativeHelpStrings = ["help", "/help", "usage", "/usage", "--help"]; - - private static readonly string[] ParameterFileOptionStrings = ["parameters-file", "parameterfile"]; - + private const string PARAMETERS_FILE_OPTION = "parameters-file"; + private static readonly string[] PARAMETERS_FILE_OPTION_EXTRAS = ["parameterfile"]; private const string PING_PONG_KEEPALIVE_OPTION = "ping-pong-keepalive"; private const string WINDOWS_EVENTLOG_OPTION = "windows-eventlog"; private const string WINDOWS_EVENTLOG_LEVEL_OPTION = "windows-eventlog-level"; private const string DISABLE_DB_ENCRYPTION_OPTION = "disable-db-encryption"; private const string REQUIRE_DB_ENCRYPTION_KEY_OPTION = "require-db-encryption-key"; + private const string SETTINGS_ENCRYPTION_KEY_OPTION = "settings-encryption-key"; + private const string DISABLE_UPDATE_CHECK_OPTION = "disable-update-check"; + private const string LOG_FILE_OPTION = "log-file"; + private const string LOG_LEVEL_OPTION = "log-level"; + private const string LOG_CONSOLE_OPTION = "log-console"; + private const string TEMPDIR_OPTION = "tempdir"; + private const string LOG_RETENTION_OPTION = "log-retention"; + private const string HELP_OPTION = "help"; + #if DEBUG private const bool DEBUG_MODE = true; @@ -56,6 +66,10 @@ namespace Duplicati.Server private const bool DEBUG_MODE = false; #endif + /// + /// Options to be ignored when validating the command line + /// + public static readonly HashSet ValidationIgnoredOptions = new HashSet(StringComparer.OrdinalIgnoreCase); /// /// The log tag for messages from this class /// @@ -65,20 +79,10 @@ namespace Duplicati.Server /// public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR; - /// - /// Name of the database file - /// - public const string SERVER_DATABASE_FILENAME = "Duplicati-server.sqlite"; - /// /// The environment variable prefix /// - private static readonly string ENV_NAME_PREFIX = Duplicati.Library.AutoUpdater.AutoUpdateSettings.AppName.ToUpper(CultureInfo.InvariantCulture); - - /// - /// The name of the environment variable that holds the path to the data folder used by Duplicati - /// - private static readonly string DATAFOLDER_ENV_NAME = ENV_NAME_PREFIX + "_HOME"; + private static readonly string ENV_NAME_PREFIX = AutoUpdateSettings.AppName.ToUpperInvariant(); /// /// Gets the folder where Duplicati data is stored @@ -173,11 +177,6 @@ namespace Duplicati.Server /// public static LogWriteHandler LogHandler { get => FIXMEGlobal.LogHandler; } - /// - /// Used to check the origin of the web server (e.g. Tray icon or a stand alone Server) - /// - public static string Origin { get => FIXMEGlobal.Origin; set => FIXMEGlobal.Origin = value; } - private static System.Threading.Timer PurgeTempFilesTimer = null; public static int ServerPort @@ -194,12 +193,6 @@ namespace Duplicati.Server set { DataConnection.ApplicationSettings.IsFirstRun = value; } } - public static string StartedBy - { - get { return Origin; } - set { Origin = value; } - } - public static bool ServerPortChanged { get { return DataConnection.ApplicationSettings.ServerPortChanged; } @@ -217,7 +210,7 @@ namespace Duplicati.Server [STAThread] public static int Main(string[] _args) { - Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, Library.AutoUpdater.PackageHelper.NamedExecutable.Server, out var preloadDbSettings); + PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.Server, out var preloadDbSettings); //If this executable is invoked directly, write to console, otherwise throw exceptions var writeToConsoleOnException = FIXMEGlobal.Origin == "Server"; @@ -228,26 +221,27 @@ namespace Duplicati.Server //Find commandline options here for handling special startup cases var args = new List(_args); - var optionsWithFilter = Library.Utility.FilterCollector.ExtractOptions(new List(args)); + var optionsWithFilter = FilterCollector.ExtractOptions(new List(args)); var commandlineOptions = optionsWithFilter.Item1; var filter = optionsWithFilter.Item2; - if (_args.Select(s => s.ToLower()).Intersect(AlternativeHelpStrings.Select(x => x.ToLower())).Any()) + if (HelpOptionExtensions.IsArgumentAnyHelpString(args)) { return ShowHelp(writeToConsoleOnException); } if (commandlineOptions.ContainsKey("tempdir") && !string.IsNullOrEmpty(commandlineOptions["tempdir"])) { - Library.Utility.SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"]; + SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"]; } - Library.Utility.SystemContextSettings.StartSession(); + SystemContextSettings.StartSession(); ApplyEnvironmentVariables(commandlineOptions); + ApplySecretProvider(commandlineOptions, CancellationToken.None).Await(); - var parameterFileOption = commandlineOptions.Keys.Select(s => s.ToLower()) - .Intersect(ParameterFileOptionStrings.Select(x => x.ToLower())).FirstOrDefault(); + var parameterFileOption = PARAMETERS_FILE_OPTION_EXTRAS.Prepend(PARAMETERS_FILE_OPTION) + .FirstOrDefault(x => commandlineOptions.ContainsKey(x)); if (parameterFileOption != null && !string.IsNullOrEmpty(commandlineOptions[parameterFileOption])) { @@ -259,6 +253,9 @@ namespace Duplicati.Server ConfigureLogging(commandlineOptions); + // Validate after logging is configured + CommandLineArgumentValidator.ValidateArguments(SupportedCommands, commandlineOptions, KnownDuplicateOptions, ValidationIgnoredOptions); + var crashed = false; var terminated = false; try @@ -277,18 +274,18 @@ namespace Duplicati.Server ApplicationExitEvent = new System.Threading.ManualResetEvent(false); - Library.AutoUpdater.UpdaterManager.OnError += obj => + UpdaterManager.OnError += obj => { DataConnection.LogError(null, "Error in updater", obj); }; - DuplicatiWebserver = StartWebServer(commandlineOptions, DataConnection).ConfigureAwait(false).GetAwaiter().GetResult(); + DuplicatiWebserver = StartWebServer(commandlineOptions, DataConnection).Await(); - UpdatePoller.Init(); + UpdatePoller.Init(Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_UPDATE_CHECK_OPTION)); SetPurgeTempFilesTimer(commandlineOptions); - SetLiveControls(); + LiveControl.StateChanged = LiveControl_StateChanged; SetWorkerThread(); @@ -300,9 +297,10 @@ namespace Duplicati.Server DataConnection.ReWriteAllFieldsIfEncryptionChanged(); DataConnection.SetPreloadSettingsIfChanged(preloadDbSettings); + EmitWarningsForConfigurationIssues(commandlineOptions); - Library.Logging.Log.WriteInformationMessage(LOGTAG, "ServerStarted", Strings.Program.ServerStarted(DuplicatiWebserver.Port)); - logMessageToConsole(Strings.Program.ServerStarted(DuplicatiWebserver.Port)); + Log.WriteInformationMessage(LOGTAG, "ServerStarted", Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); + logMessageToConsole(Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); if (FIXMEGlobal.Origin == "Server" && DataConnection.ApplicationSettings.AutogeneratedPassphrase) { @@ -311,7 +309,7 @@ namespace Duplicati.Server var scheme = DataConnection.ApplicationSettings.UseHTTPS ? "https" : "http"; var url = $"{scheme}://{hostname}:{DuplicatiWebserver.Port}/signin.html?token={signinToken}"; - Library.Logging.Log.WriteWarningMessage(LOGTAG, "ServerStartedSignin", null, Strings.Program.ServerStartedSignin(url)); + Log.WriteWarningMessage(LOGTAG, "ServerStartedSignin", null, Strings.Program.ServerStartedSignin(url)); logMessageToConsole(Strings.Program.ServerStartedSignin(url)); } @@ -319,7 +317,7 @@ namespace Duplicati.Server { if (t.Exception != null) { - Library.Logging.Log.WriteWarningMessage(LOGTAG, "ServerCrashed", t.Exception, Strings.Program.ServerCrashed(t.Exception.Message)); + Log.WriteWarningMessage(LOGTAG, "ServerCrashed", t.Exception, Strings.Program.ServerCrashed(t.Exception.Message)); logMessageToConsole(Strings.Program.ServerStartedSignin(Strings.Program.ServerCrashed(t.Exception.ToString()))); } @@ -327,12 +325,31 @@ namespace Duplicati.Server ApplicationExitEvent.Set(); }); + var stopCounter = 0; + Console.CancelKeyPress += (sender, e) => + { + if (Interlocked.Increment(ref stopCounter) <= 1) + { + Log.WriteInformationMessage(LOGTAG, "CancelKeyPressed", "Cancel key pressed, stopping server"); + Task.Run(() => DuplicatiWebserver?.Stop()); + } + else + { + Log.WriteWarningMessage(LOGTAG, "CancelKeyPressed", null, "Cancel key pressed twice, terminating now"); + if (OperatingSystem.IsWindows()) + Environment.FailFast("Cancel key pressed twice, terminating now"); + else + Environment.Exit(0); + } + }; + ServerStartedEvent.Set(); ApplicationExitEvent.WaitOne(); } catch (SingleInstance.MultipleInstanceException mex) { crashed = true; + Log.WriteErrorMessage(LOGTAG, "MultipleInstanceError", mex, Strings.Program.ServerCrashed(mex.Message)); System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); if (!writeToConsoleOnException) throw; @@ -342,6 +359,7 @@ namespace Duplicati.Server catch (Exception ex) { crashed = true; + Log.WriteErrorMessage(LOGTAG, "ServerCrashed", ex, Strings.Program.ServerCrashed(ex.Message)); System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); if (writeToConsoleOnException) { @@ -353,6 +371,8 @@ namespace Duplicati.Server } finally { + Log.WriteInformationMessage(LOGTAG, "ServerStopping", Strings.Program.ServerStopping); + var steps = new Action[] { () => StatusEventNotifyer.SignalNewEvent(), () => { if (ShutdownModernWebserver != null) ShutdownModernWebserver(); }, @@ -363,7 +383,11 @@ namespace Duplicati.Server () => PurgeTempFilesTimer?.Dispose(), () => Library.UsageReporter.Reporter.ShutDown(), () => PingPongThread?.Interrupt(), - () => LogHandler?.Dispose() + () => + { + Library.Logging.Log.WriteInformationMessage(LOGTAG, "ServerStopped", Strings.Program.ServerStopped); + LogHandler?.Dispose(); + } }; foreach (var teardownStep in steps) @@ -400,15 +424,14 @@ namespace Duplicati.Server parsedOptions.Servername, parsedOptions.AllowedHostnames, parsedOptions.DisableStaticFiles, - parsedOptions.SPAPaths + parsedOptions.SPAPaths, + parsedOptions.CorsOrigins ); - var server = new DuplicatiWebserver(); - - server.InitWebServer(mappedSettings, connection); + var server = DuplicatiWebserver.CreateWebServer(mappedSettings, connection); // Start the server, but catch any configuration issues - var task = server.Start(mappedSettings); + var task = server.Start(); await Task.WhenAny(task, Task.Delay(500)); if (task.IsCompleted) await task; @@ -432,15 +455,15 @@ namespace Duplicati.Server FIXMEGlobal.Scheduler.SubScribeToNewSchedule(() => SignalNewEvent(null, null)); FIXMEGlobal.WorkThread.OnError += (worker, task, exception) => { - Program.DataConnection.LogError(task?.BackupID, "Error in worker", exception); + DataConnection.LogError(task?.BackupID, "Error in worker", exception); }; var lastScheduleId = FIXMEGlobal.NotificationUpdateService.LastDataUpdateId; - Program.StatusEventNotifyer.NewEvent += (sender, e) => + StatusEventNotifyer.NewEvent += (sender, e) => { if (lastScheduleId == FIXMEGlobal.NotificationUpdateService.LastDataUpdateId) return; lastScheduleId = FIXMEGlobal.NotificationUpdateService.LastDataUpdateId; - Program.Scheduler.Reschedule(); + Scheduler.Reschedule(); }; void RegisterTaskResult(long id, Exception ex) @@ -448,17 +471,17 @@ namespace Duplicati.Server lock (MainLock) { // If the new results says it crashed, we store that instead of success - if (Program.TaskResultCache.Count > 0 && Program.TaskResultCache.Last().Key == id) + if (TaskResultCache.Count > 0 && TaskResultCache.Last().Key == id) { - if (ex != null && Program.TaskResultCache.Last().Value == null) - Program.TaskResultCache.RemoveAt(Program.TaskResultCache.Count - 1); + if (ex != null && TaskResultCache.Last().Value == null) + TaskResultCache.RemoveAt(TaskResultCache.Count - 1); else return; } - Program.TaskResultCache.Add(new KeyValuePair(id, ex)); - while (Program.TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE) - Program.TaskResultCache.RemoveAt(0); + TaskResultCache.Add(new KeyValuePair(id, ex)); + while (TaskResultCache.Count > MAX_TASK_RESULT_CACHE_SIZE) + TaskResultCache.RemoveAt(0); } } @@ -466,18 +489,11 @@ namespace Duplicati.Server FIXMEGlobal.WorkThread.OnError += (worker, task, exception) => { RegisterTaskResult(task.TaskID, exception); }; } - private static void SetLiveControls() - { - LiveControl.StateChanged += LiveControl_StateChanged; - LiveControl.ThreadPriorityChanged += LiveControl_ThreadPriorityChanged; - LiveControl.ThrottleSpeedChanged += LiveControl_ThrottleSpeedChanged; - } - private static void SetPurgeTempFilesTimer(Dictionary commandlineOptions) { var lastPurge = new DateTime(0); - System.Threading.TimerCallback purgeTempFilesCallback = (x) => + TimerCallback purgeTempFilesCallback = (x) => { try { @@ -538,6 +554,9 @@ namespace Duplicati.Server DataConnection.ExecuteWithCommand((con) => con.ExecuteNonQuery("DELETE FROM TokenFamily")); } + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN)) + DataConnection.ApplicationSettings.EnableForeverTokens(); + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA)) DataConnection.ApplicationSettings.DisableVisualCaptcha = Library.Utility.Utility.ParseBool(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA], true); @@ -551,6 +570,82 @@ namespace Duplicati.Server DataConnection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES]); else if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT)) DataConnection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE) && !string.IsNullOrEmpty(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE])) + try + { + DataConnection.ApplicationSettings.Timezone = TimeZoneHelper.FindTimeZone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]); + } + catch (Exception ex) + { + throw new UserInformationException(Strings.Program.InvalidTimezone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]), "InvalidTimeZone", ex); + } + + // The database has recorded a new version + if (DataConnection.ApplicationSettings.UpdatedVersion != null) + { + // Check if the running version is newer than the recorded version + if (UpdaterManager.TryParseVersion(DataConnection.ApplicationSettings.UpdatedVersion.Version) <= UpdaterManager.TryParseVersion(UpdaterManager.SelfVersion.Version)) + { + // Clean up lingering update notifications + var updateNotifications = DataConnection.GetNotifications().Where(x => x.Action == "update:new").ToList(); + foreach (var n in updateNotifications) + DataConnection.DismissNotification(n.ID); + + // Clear up the recorded version + DataConnection.ApplicationSettings.UpdatedVersion = null; + } + } + } + + private static void EmitWarningsForConfigurationIssues(Dictionary commandlineOptions) + { + if (DataConnection.ApplicationSettings.LastConfigIssueCheckVersion != UpdaterManager.SelfVersion.Version) + { + var updateNotifications = DataConnection.GetNotifications().Where(x => x.Action.StartsWith("config:issue:")).ToList(); + foreach (var n in updateNotifications) + DataConnection.DismissNotification(n.ID); + + if (!DataConnection.IsEncryptingFields && !Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION)) + { + DataConnection.RegisterNotification( + Serialization.NotificationType.Warning, + "Unencrypted database", + "The database is not encrypted. This is a security risk and should be fixed as soon as possible.", + null, + null, + "config:issue:unencrypted-database", + null, + "UnencryptedDatabase", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == "config:issue:unencrypted-database") ?? self; + } + ); + } + + if (OperatingSystem.IsWindows() && DataFolder.StartsWith(Util.AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase)) + { + DataConnection.RegisterNotification( + Serialization.NotificationType.Warning, + "Incorrect storage folder", + "The server configuraion is stored inside the Windows folder. Please move the configuration to a different location, or it may be deleted on Windows version upgrades.", + null, + null, + "config:issue:windows-folder-used", + null, + "UnencryptedDatabase", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == "config:issue:windows-folder-used") ?? self; + } + ); + } + + DataConnection.ApplicationSettings.LastConfigIssueCheckVersion = UpdaterManager.SelfVersion.Version; + } } private static void CreateApplicationInstance(bool writeToConsoleOnExceptionw) @@ -596,54 +691,89 @@ namespace Duplicati.Server if (!string.IsNullOrWhiteSpace(envval)) commandlineOptions[key] = envval; } + + // Set the encryption key from the environment variable + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)) && string.IsNullOrWhiteSpace(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION))) + commandlineOptions[SETTINGS_ENCRYPTION_KEY_OPTION] = Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME); + } + + private static async Task ApplySecretProvider(Dictionary commandlineOptions, CancellationToken cancellationToken) + => FIXMEGlobal.SecretProvider = await SecretProviderHelper.ApplySecretProviderAsync([], [], commandlineOptions, TempFolder.SystemTempPath, FIXMEGlobal.SecretProvider, cancellationToken).ConfigureAwait(false); + + private class ConsoleLogDestination(LogMessageType level) : ILogDestination + { + public void WriteMessage(LogEntry entry) + { + if (entry.Level >= level) + Console.WriteLine(entry.AsString(true)); + } } private static void ConfigureLogging(Dictionary commandlineOptions) { - //Log various information in the logfile - if (DEBUG_MODE && !commandlineOptions.ContainsKey("log-file")) + if (DEBUG_MODE && !commandlineOptions.ContainsKey(LOG_FILE_OPTION)) { var prefix = System.Reflection.Assembly.GetEntryAssembly().GetName().Name.StartsWith("Duplicati.Server") ? "server" : "trayicon"; - commandlineOptions["log-file"] = System.IO.Path.Combine(StartupPath, $"Duplicati-{prefix}.debug.log"); - commandlineOptions["log-level"] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); - if (System.IO.File.Exists(commandlineOptions["log-file"])) - { - System.IO.File.Delete(commandlineOptions["log-file"]); - } + commandlineOptions[LOG_FILE_OPTION] = System.IO.Path.Combine(StartupPath, $"Duplicati-{prefix}.debug.log"); + commandlineOptions[LOG_LEVEL_OPTION] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); + if (System.IO.File.Exists(commandlineOptions[LOG_FILE_OPTION])) + System.IO.File.Delete(commandlineOptions[LOG_FILE_OPTION]); } - // Setup the log redirect - Library.Logging.Log.StartScope(LogHandler, null); + Log.StartScope(LogHandler, null); - if (commandlineOptions.ContainsKey("log-file")) + if (commandlineOptions.ContainsKey(LOG_FILE_OPTION)) { var loglevel = Library.Logging.LogMessageType.Warning; - if (commandlineOptions.ContainsKey("log-level")) - Enum.TryParse(commandlineOptions["log-level"], true, out loglevel); + if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); - LogHandler.SetServerFile(commandlineOptions["log-file"], loglevel); + LogHandler.SetServerFile(commandlineOptions[LOG_FILE_OPTION], loglevel); + } + + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, LOG_CONSOLE_OPTION)) + { + var loglevel = Library.Logging.LogMessageType.Information; + if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); + + LogHandler.AppendLogDestination(new ConsoleLogDestination(loglevel), loglevel); } if (commandlineOptions.TryGetValue(WINDOWS_EVENTLOG_OPTION, out var source) && !string.IsNullOrEmpty(source)) { if (!OperatingSystem.IsWindows()) { - Library.Logging.Log.WriteWarningMessage(LOGTAG, "WindowsLogNotSupported", null, Strings.Program.WindowsEventLogNotSupported); - } - else if (!WindowsEventLogSource.SourceExists(source)) - { - Library.Logging.Log.WriteWarningMessage(LOGTAG, "WindowsLogMissing", null, Strings.Program.WindowsEventLogSourceNotFound(source)); + Log.WriteWarningMessage(LOGTAG, "WindowsLogNotSupported", null, Strings.Program.WindowsEventLogNotSupported); } else { - var loglevel = Library.Logging.LogMessageType.Information; - if (commandlineOptions.ContainsKey(WINDOWS_EVENTLOG_LEVEL_OPTION)) - Enum.TryParse(commandlineOptions[WINDOWS_EVENTLOG_LEVEL_OPTION], true, out loglevel); + if (!WindowsEventLogSource.SourceExists(source)) + { + Log.WriteInformationMessage(LOGTAG, "WindowsLogMissingCreating", null, Strings.Program.WindowsEventLogSourceNotFound(source)); + try + { + WindowsEventLogSource.CreateEventSource(source); + } + catch (Exception ex) + { + Log.WriteWarningMessage(LOGTAG, "WindowsLogFailedCreate", ex, Strings.Program.WindowsEventLogSourceNotCreated(source)); + } + } - LogHandler.AppendLogDestination(new WindowsEventLogSource(source), loglevel); + if (WindowsEventLogSource.SourceExists(source)) + { + var loglevel = Library.Logging.LogMessageType.Information; + if (commandlineOptions.ContainsKey(WINDOWS_EVENTLOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[WINDOWS_EVENTLOG_LEVEL_OPTION], true, out loglevel); + + LogHandler.AppendLogDestination(new WindowsEventLogSource(source), loglevel); + } } } + + CrashlogHelper.OnUnobservedTaskException += (ex) => LogHandler.WriteMessage(new Library.Logging.LogEntry(ex.Message, null, Library.Logging.LogMessageType.Error, LOGTAG, "UnobservedTaskException", ex)); } private static int ShowHelp(bool writeToConsoleOnExceptionw) @@ -652,7 +782,7 @@ namespace Duplicati.Server { Console.WriteLine(Strings.Program.HelpDisplayDialog); - foreach (Library.Interface.ICommandLineArgument arg in SupportedCommands) + foreach (var arg in SupportedCommands) Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); return 0; @@ -661,43 +791,15 @@ namespace Duplicati.Server throw new Exception("Server invoked with --help"); } - public static string GetDataFolderPath(Dictionary commandlineOptions) + public static Connection GetDatabaseConnection(Dictionary commandlineOptions, bool silentConsole) { - var serverDataFolder = Environment.GetEnvironmentVariable(DATAFOLDER_ENV_NAME); - if (commandlineOptions.ContainsKey("server-datafolder")) - serverDataFolder = commandlineOptions["server-datafolder"]; + DataFolder = DataFolderManager.DATAFOLDER; - if (string.IsNullOrEmpty(serverDataFolder)) - { - bool portableMode = commandlineOptions.ContainsKey("portable-mode") - ? Library.Utility.Utility.ParseBool(commandlineOptions["portable-mode"], true) - : (DEBUG_MODE ? true : false); // Default to portable mode in debug mode + // Emit a warning if the database is stored in the Windows folder + if (Util.IsPathUnderWindowsFolder(DataFolder)) + Log.WriteWarningMessage(LOGTAG, "DatabaseInWindowsFolder", null, "The database is stored in the Windows folder, this is not recommended as it will be deleted on Windows upgrades."); - if (DEBUG_MODE && portableMode) - { - //debug mode uses a lock file located in the app folder - return StartupPath; - } - else if (portableMode) - { - //Portable mode uses a data folder in the application home dir - System.IO.Directory.SetCurrentDirectory(StartupPath); - return System.IO.Path.Combine(StartupPath, "data"); - } - else - { - //Normal release mode uses the systems "(Local) Application Data" folder - // %LOCALAPPDATA% on Windows, ~/.config on Linux, ~/Library/Application\ Support on MacOS - return DatabaseLocator.GetDefaultStorageFolder(SERVER_DATABASE_FILENAME, Library.AutoUpdater.AutoUpdateSettings.AppName); - } - } - else - return Util.AppendDirSeparator(Environment.ExpandEnvironmentVariables(serverDataFolder).Trim('"')); - } - - public static Database.Connection GetDatabaseConnection(Dictionary commandlineOptions, bool silentConsole) - { - DataFolder = GetDataFolderPath(commandlineOptions); + CrashlogHelper.DefaultLogDir = DataFolder; var sqliteVersion = new Version(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteVersion); if (sqliteVersion < new Version(3, 6, 3)) @@ -711,15 +813,15 @@ namespace Duplicati.Server try { - DatabasePath = System.IO.Path.Combine(DataFolder, SERVER_DATABASE_FILENAME); + DatabasePath = System.IO.Path.Combine(DataFolder, DataFolderManager.SERVER_DATABASE_FILENAME); if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(DatabasePath))) System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(DatabasePath)); // Attempt to open the database, removing any encryption present - Duplicati.Library.SQLiteHelper.SQLiteLoader.OpenDatabase(con, DatabasePath, Library.SQLiteHelper.SQLiteRC4Decrypter.GetEncryptionPassword(commandlineOptions)); + Library.SQLiteHelper.SQLiteLoader.OpenDatabase(con, DatabasePath, Library.SQLiteHelper.SQLiteRC4Decrypter.GetEncryptionPassword(commandlineOptions)); - Duplicati.Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Duplicati.Library.RestAPI.Database.DatabaseConnectionSchemaMarker)); + Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, DatabasePath, typeof(Library.RestAPI.Database.DatabaseConnectionSchemaMarker)); } catch (Exception ex) { @@ -732,12 +834,14 @@ namespace Duplicati.Server var disableDbEncryption = Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION); var requireDbEncryptionKey = Library.Utility.Utility.ParseBoolOption(commandlineOptions, REQUIRE_DB_ENCRYPTION_KEY_OPTION); - var hasEncryptionKey = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); - var usingBlacklistedKey = Library.Encryption.EncryptedFieldHelper.IsDefaultKeyBlacklisted; - var hasValidEncryptionKey = Library.Encryption.EncryptedFieldHelper.HasValidDefaultKey; + var encKey = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION)); + var usingBlacklistedKey = encKey?.IsBlacklisted ?? false; + var hasValidEncryptionKey = encKey != null; - if (requireDbEncryptionKey && !(hasEncryptionKey || disableDbEncryption)) - throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey"); + FIXMEGlobal.SettingsEncryptionKeyProvidedExternally = hasValidEncryptionKey; + + if (requireDbEncryptionKey && !(hasValidEncryptionKey || disableDbEncryption)) + throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey"); if (!hasValidEncryptionKey) { @@ -767,7 +871,7 @@ namespace Duplicati.Server if (!hasValidEncryptionKey && !disableDbEncryption) { disableDbEncryption = true; - Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "MissingEncryptionKey", null, Strings.Program.NoEncryptionKeySpecified(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + Log.WriteWarningMessage(LOGTAG, "MissingEncryptionKey", null, Strings.Program.NoEncryptionKeySpecified(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); if (!silentConsole) Console.WriteLine(Strings.Program.NoEncryptionKeySpecified(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); } @@ -775,12 +879,12 @@ namespace Duplicati.Server if (usingBlacklistedKey && !disableDbEncryption) { disableDbEncryption = true; - Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "BlacklistedEncryptionKey", null, Strings.Program.BlacklistedEncryptionKey(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + Log.WriteErrorMessage(LOGTAG, "BlacklistedEncryptionKey", null, Strings.Program.BlacklistedEncryptionKey(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); if (!silentConsole) Console.WriteLine(Strings.Program.BlacklistedEncryptionKey(Library.Encryption.EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); } - return new Database.Connection(con, disableDbEncryption); + return new Connection(con, disableDbEncryption, encKey); } public static void StartOrStopUsageReporter() @@ -790,8 +894,7 @@ namespace Duplicati.Server || string.Equals(DataConnection.ApplicationSettings.UsageReporterLevel, "disabled", StringComparison.OrdinalIgnoreCase); - Library.UsageReporter.ReportType reportLevel; - if (!Enum.TryParse(DataConnection.ApplicationSettings.UsageReporterLevel, true, out reportLevel)) + if (!Enum.TryParse(DataConnection.ApplicationSettings.UsageReporterLevel, true, out var reportLevel)) Library.UsageReporter.Reporter.SetReportLevel(null, disableUsageReporter); else Library.UsageReporter.Reporter.SetReportLevel(reportLevel, disableUsageReporter); @@ -802,54 +905,37 @@ namespace Duplicati.Server StatusEventNotifyer.SignalNewEvent(); } - /// - /// Handles a change in the LiveControl and updates the Runner - /// - /// - /// - private static void LiveControl_ThreadPriorityChanged(object sender, EventArgs e) - { - StatusEventNotifyer.SignalNewEvent(); - } - - /// - /// Handles a change in the LiveControl and updates the Runner - /// - /// - /// - private static void LiveControl_ThrottleSpeedChanged(object sender, EventArgs e) - { - StatusEventNotifyer.SignalNewEvent(); - } - /// /// This event handler updates the trayicon menu with the current state of the runner. /// /// - private static void LiveControl_StateChanged(object sender, EventArgs e) + private static void LiveControl_StateChanged(LiveControls.LiveControlEvent e) { var worker = FIXMEGlobal.WorkThread; - switch (LiveControl.State) + var appSettings = FIXMEGlobal.DataConnection.ApplicationSettings; + switch (e.State) { case LiveControls.LiveControlState.Paused: { worker.Pause(); - var t = worker.CurrentTask; - t?.Pause(); + worker.CurrentTask?.Pause(e.TransfersPaused); + appSettings.PausedUntil = e.WaitTimeExpiration; break; } case LiveControls.LiveControlState.Running: { worker.Resume(); - var t = worker.CurrentTask; - t?.Resume(); + worker.CurrentTask?.Resume(); + appSettings.PausedUntil = null; break; } default: - throw new InvalidOperationException($"State of {nameof(LiveControl)} was not recognized!"); + Log.WriteWarningMessage(LOGTAG, "InvalidPauseResumeState", null, Strings.Program.InvalidPauseResumeState(LiveControl.State)); + break; } StatusEventNotifyer.SignalNewEvent(); + } /// @@ -881,57 +967,78 @@ namespace Duplicati.Server /// private static readonly string DEFAULT_LOG_RETENTION = "30D"; + /// + /// The options related to the secret provider + /// + private static readonly IReadOnlyList SECRET_PROVIDER_OPTIONS = new Options(new Dictionary()).SupportedCommands.Where(x => x.Name.StartsWith("secret-provider")).ToList(); + + /// + /// Gets additional commandline arguments support on Windows + /// + private static readonly ICommandLineArgument[] WindowsOptions = OperatingSystem.IsWindows() + ? [ + new CommandLineArgument(WINDOWS_EVENTLOG_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogwindowseventlogShort, Strings.Program.LogwindowseventlogLong), + new CommandLineArgument(WINDOWS_EVENTLOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LogwindowseventloglevelShort, Strings.Program.LogwindowseventloglevelLong, Library.Logging.LogMessageType.Information.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))) + ] + : []; + /// /// Gets a list of all supported commandline options /// - public static Library.Interface.ICommandLineArgument[] SupportedCommands - => (OperatingSystem.IsWindows() - ? new[] { - new Duplicati.Library.Interface.CommandLineArgument(WINDOWS_EVENTLOG_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogwindowseventlogShort, Strings.Program.LogwindowseventlogLong), - new Duplicati.Library.Interface.CommandLineArgument(WINDOWS_EVENTLOG_LEVEL_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LogwindowseventloglevelShort, Strings.Program.LogwindowseventloglevelLong, Library.Logging.LogMessageType.Information.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))) - } - : [] - ) - .Concat([ - new Duplicati.Library.Interface.CommandLineArgument("tempdir", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.TempdirShort, Strings.Program.TempdirLong, System.IO.Path.GetTempPath()), - new Duplicati.Library.Interface.CommandLineArgument("help", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription), - new Duplicati.Library.Interface.CommandLineArgument("parameters-file", Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong2, "", ParameterFileOptionStrings), - new Duplicati.Library.Interface.CommandLineArgument("portable-mode", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription), - new Duplicati.Library.Interface.CommandLineArgument("log-file", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription), - new Duplicati.Library.Interface.CommandLineArgument("log-level", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, Library.Logging.LogMessageType.Warning.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBROOT, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, WebServerLoader.DEFAULT_OPTION_WEBROOT), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_DISABLEHTTPS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableHTTPSDescription, Strings.Program.WebserverDisableHTTPSDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_REMOVESSLCERTIFICATE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverRemoveCertificateDescription, Strings.Program.WebserverRemoveCertificateDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_SSLCERTIFICATEFILE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_SSLCERTIFICATEFILEPASSWORD, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_INTERFACE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, WebServerLoader.DEFAULT_OPTION_INTERFACE), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PASSWORD, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverAllowedhostnamesDescription, Strings.Program.WebserverAllowedhostnamesDescription, null, [WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverResetJwtConfigDescription, Strings.Program.WebserverResetJwtConfigDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableVisualCaptchaDescription, Strings.Program.WebserverDisableVisualCaptchaDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_API_ONLY, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverApiOnlyDescription, Strings.Program.WebserverApiOnlyDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableSigninTokensDescription, Strings.Program.WebserverDisableSigninTokensDescription), - new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SPAPATHS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverSpaPathsDescription, Strings.Program.WebserverSpaPathsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), - new Duplicati.Library.Interface.CommandLineArgument(PING_PONG_KEEPALIVE_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong), - new Duplicati.Library.Interface.CommandLineArgument("log-retention", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION), - new Duplicati.Library.Interface.CommandLineArgument("server-datafolder", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DATAFOLDER_ENV_NAME), System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName)), - new Duplicati.Library.Interface.CommandLineArgument(DISABLE_DB_ENCRYPTION_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisabledbencryptionShort, Strings.Program.DisabledbencryptionLong), - new Duplicati.Library.Interface.CommandLineArgument(REQUIRE_DB_ENCRYPTION_KEY_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.RequiredbencryptionShort, Strings.Program.RequiredbencryptionLong), - ]) - .ToArray(); + public static ICommandLineArgument[] SupportedCommands => + [ + .. WindowsOptions, + new CommandLineArgument(TEMPDIR_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.TempdirShort, Strings.Program.TempdirLong, System.IO.Path.GetTempPath()), + new CommandLineArgument(HELP_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription), + new CommandLineArgument(PARAMETERS_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong, "", PARAMETERS_FILE_OPTION_EXTRAS), + new CommandLineArgument(DataFolderManager.PORTABLE_MODE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription, DataFolderManager.PORTABLE_MODE.ToString().ToLowerInvariant()), + new CommandLineArgument(LOG_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription), + new CommandLineArgument(LOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, Library.Logging.LogMessageType.Warning.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))), + new CommandLineArgument(LOG_CONSOLE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogConsoleDescription, Strings.Program.LogConsoleDescription, false.ToString()), + new CommandLineArgument(WebServerLoader.OPTION_WEBROOT, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, WebServerLoader.DEFAULT_OPTION_WEBROOT), + new CommandLineArgument(WebServerLoader.OPTION_PORT, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, WebServerLoader.DEFAULT_OPTION_PORT.ToString()), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEHTTPS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableHTTPSDescription, Strings.Program.WebserverDisableHTTPSDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_REMOVESSLCERTIFICATE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverRemoveCertificateDescription, Strings.Program.WebserverRemoveCertificateDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription), + new CommandLineArgument(WebServerLoader.OPTION_INTERFACE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, WebServerLoader.DEFAULT_OPTION_INTERFACE), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverAllowedhostnamesDescription, Strings.Program.WebserverAllowedhostnamesDescription, null, [WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverResetJwtConfigDescription, Strings.Program.WebserverResetJwtConfigDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverEnableForeverTokenDescription, Strings.Program.WebserverEnableForeverTokenDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_VISUAL_CAPTCHA, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableVisualCaptchaDescription, Strings.Program.WebserverDisableVisualCaptchaDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_API_ONLY, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverApiOnlyDescription, Strings.Program.WebserverApiOnlyDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableSigninTokensDescription, Strings.Program.WebserverDisableSigninTokensDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SPAPATHS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverSpaPathsDescription, Strings.Program.WebserverSpaPathsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverTimezoneDescription, Strings.Program.WebserverTimezoneDescription, TimeZoneHelper.GetLocalTimeZone(), null, TimeZoneHelper.GetTimeZones().Select(x => x.Id).ToArray()), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_CORS_ORIGINS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverCorsOriginsDescription, Strings.Program.WebserverCorsOriginsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), + new CommandLineArgument(PING_PONG_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong), + new CommandLineArgument(DISABLE_UPDATE_CHECK_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisableupdatecheckShort, Strings.Program.DisableupdatecheckLong), + new CommandLineArgument(LOG_RETENTION_OPTION, CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION), + new CommandLineArgument(DataFolderManager.SERVER_DATAFOLDER_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DataFolderManager.DATAFOLDER_ENV_NAME), DataFolderManager.DATAFOLDER), + new CommandLineArgument(DISABLE_DB_ENCRYPTION_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisabledbencryptionShort, Strings.Program.DisabledbencryptionLong), + new CommandLineArgument(REQUIRE_DB_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.RequiredbencryptionShort, Strings.Program.RequiredbencryptionLong), + new CommandLineArgument(SETTINGS_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Program.SettingsencryptionkeyShort, Strings.Program.SettingsencryptionkeyLong(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)), + .. SECRET_PROVIDER_OPTIONS + ]; - private static bool ReadOptionsFromFile(string filename, ref Library.Utility.IFilter filter, List cargs, Dictionary options) + /// + /// List of known duplicate option names + /// + public static readonly HashSet KnownDuplicateOptions = new HashSet(StringComparer.OrdinalIgnoreCase); + + private static bool ReadOptionsFromFile(string filename, ref IFilter filter, List cargs, Dictionary options) { try { - List fargs = new List(Library.Utility.Utility.ReadFileWithDefaultEncoding(Environment.ExpandEnvironmentVariables(filename)).Replace("\r\n", "\n").Replace("\r", "\n").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + var fargs = new List(Library.Utility.Utility.ReadFileWithDefaultEncoding(Environment.ExpandEnvironmentVariables(filename)).Replace("\r\n", "\n").Replace("\r", "\n").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); var newsource = new List(); string newtarget = null; string prependfilter = null; string appendfilter = null; string replacefilter = null; - var tmpparsed = Library.Utility.FilterCollector.ExtractOptions(fargs, (key, value) => + var tmpparsed = FilterCollector.ExtractOptions(fargs, (key, value) => { if (key.Equals("source", StringComparison.OrdinalIgnoreCase)) { @@ -968,21 +1075,21 @@ namespace Duplicati.Server // If the user specifies parameters-file, all filters must be in the file. // Allowing to specify some filters on the command line could result in wrong filter ordering if (!filter.Empty && !newfilter.Empty) - throw new Duplicati.Library.Interface.UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2, "FiltersCannotBeUsedOnCommandLineAndInParameterFile"); + throw new UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2, "FiltersCannotBeUsedOnCommandLineAndInParameterFile"); if (!newfilter.Empty) filter = newfilter; if (!string.IsNullOrWhiteSpace(prependfilter)) - filter = Library.Utility.FilterExpression.Combine(Library.Utility.FilterExpression.Deserialize(prependfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)), filter); + filter = FilterExpression.Combine(FilterExpression.Deserialize(prependfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)), filter); if (!string.IsNullOrWhiteSpace(appendfilter)) - filter = Library.Utility.FilterExpression.Combine(filter, Library.Utility.FilterExpression.Deserialize(appendfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries))); + filter = FilterExpression.Combine(filter, FilterExpression.Deserialize(appendfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries))); if (!string.IsNullOrWhiteSpace(replacefilter)) - filter = Library.Utility.FilterExpression.Deserialize(replacefilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); + filter = FilterExpression.Deserialize(replacefilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); - foreach (KeyValuePair keyvalue in opt) + foreach (var keyvalue in opt) options[keyvalue.Key] = keyvalue.Value; if (!string.IsNullOrEmpty(newtarget)) @@ -996,7 +1103,7 @@ namespace Duplicati.Server if (cargs.Count >= 1 && cargs[0].Equals("backup", StringComparison.OrdinalIgnoreCase)) cargs.AddRange(newsource); else if (newsource.Count > 0) - Library.Logging.Log.WriteVerboseMessage(LOGTAG, "NotUsingBackupSources", Strings.Program.SkippingSourceArgumentsOnNonBackupOperation); + Log.WriteVerboseMessage(LOGTAG, "NotUsingBackupSources", Strings.Program.SkippingSourceArgumentsOnNonBackupOperation); return true; } diff --git a/Duplicati/Server/SingleInstance.cs b/Duplicati/Server/SingleInstance.cs index 381f4869a..31ee87d9f 100644 --- a/Duplicati/Server/SingleInstance.cs +++ b/Duplicati/Server/SingleInstance.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Server/WebServerLoader.cs b/Duplicati/Server/WebServerLoader.cs index e7d703d52..3b82cf767 100644 --- a/Duplicati/Server/WebServerLoader.cs +++ b/Duplicati/Server/WebServerLoader.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -28,6 +28,8 @@ using Duplicati.Server.Database; using Microsoft.AspNetCore.Connections; using Duplicati.Library.Utility; +#nullable enable + namespace Duplicati.Server; /// @@ -65,6 +67,11 @@ public static class WebServerLoader /// public const string OPTION_WEBSERVICE_RESET_JWT_CONFIG = "webservice-reset-jwt-config"; + /// + /// Option for enabling the forever token + /// + public const string OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN = "webservice-enable-forever-token"; + /// /// Option for disabling the visual captcha /// @@ -91,6 +98,14 @@ public static class WebServerLoader /// Option for setting the webservice SPA paths /// public const string OPTION_WEBSERVICE_SPAPATHS = "webservice-spa-paths"; + /// + /// The CORS origins to allow + /// + public const string OPTION_WEBSERVICE_CORS_ORIGINS = "webservice-cors-origins"; + /// + /// Option for setting the webservice timezone + /// + public const string OPTION_WEBSERVICE_TIMEZONE = "webservice-timezone"; /// /// The default path to the web root @@ -110,22 +125,22 @@ public static class WebServerLoader /// /// Option for setting if to use HTTPS /// - public const string OPTION_DISABLEHTTPS = "webservice-disable-https"; + public const string OPTION_WEBSERVICE_DISABLEHTTPS = "webservice-disable-https"; /// /// Option for removing the SSL certificate from the datbase /// - public const string OPTION_REMOVESSLCERTIFICATE = "webservice-remove-sslcertificate"; + public const string OPTION_WEBSERVICE_REMOVESSLCERTIFICATE = "webservice-remove-sslcertificate"; /// /// Option for setting the webservice SSL certificate /// - public const string OPTION_SSLCERTIFICATEFILE = "webservice-sslcertificatefile"; + public const string OPTION_WEBSERVICE_SSLCERTIFICATEFILE = "webservice-sslcertificatefile"; /// /// Option for setting the webservice SSL certificate key /// - public const string OPTION_SSLCERTIFICATEFILEPASSWORD = "webservice-sslcertificatepassword"; + public const string OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD = "webservice-sslcertificatepassword"; /// /// The default listening interface @@ -143,15 +158,17 @@ public static class WebServerLoader /// The allowed hostnames /// If static files should be disabled /// The paths to serve as SPAs + /// The origins to allow for CORS public record ParsedWebserverSettings( string WebRoot, int Port, System.Net.IPAddress Interface, - X509Certificate2? Certificate, + X509Certificate2Collection? Certificate, string Servername, IEnumerable AllowedHostnames, bool DisableStaticFiles, - IEnumerable SPAPaths + IEnumerable SPAPaths, + IEnumerable CorsOrigins ); @@ -160,7 +177,7 @@ public static class WebServerLoader /// /// A set of options /// The method to start the server - public static async Task TryRunServer(IReadOnlyDictionary options, Connection connection, Func> createServer) + public static async Task TryRunServer(IReadOnlyDictionary options, Connection connection, Func> createServer) { var ports = Enumerable.Empty(); options.TryGetValue(OPTION_PORT, out var portstring); @@ -187,11 +204,11 @@ public static class WebServerLoader else if (interfacestring != "loopback") listenInterface = System.Net.IPAddress.Parse(interfacestring); - var removeCertificate = Library.Utility.Utility.ParseBoolOption(options, OPTION_REMOVESSLCERTIFICATE); - connection.ApplicationSettings.DisableHTTPS = removeCertificate || Library.Utility.Utility.ParseBoolOption(options, OPTION_DISABLEHTTPS); + var removeCertificate = Library.Utility.Utility.ParseBoolOption(options, OPTION_WEBSERVICE_REMOVESSLCERTIFICATE); + connection.ApplicationSettings.DisableHTTPS = removeCertificate || Library.Utility.Utility.ParseBoolOption(options, OPTION_WEBSERVICE_DISABLEHTTPS); - options.TryGetValue(OPTION_SSLCERTIFICATEFILE, out var certificateFile); - options.TryGetValue(OPTION_SSLCERTIFICATEFILEPASSWORD, out var certificateFilePassword); + options.TryGetValue(OPTION_WEBSERVICE_SSLCERTIFICATEFILE, out var certificateFile); + options.TryGetValue(OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD, out var certificateFilePassword); certificateFilePassword = certificateFilePassword?.Trim(); if (string.IsNullOrEmpty(certificateFile) && !string.IsNullOrEmpty(certificateFilePassword)) @@ -216,7 +233,7 @@ public static class WebServerLoader if (options.ContainsKey(OPTION_WEBROOT)) { - string userroot = options[OPTION_WEBROOT]; + var userroot = options[OPTION_WEBROOT]; #if DEBUG //In debug mode we do not care where the path points #else @@ -237,10 +254,11 @@ public static class WebServerLoader -1, listenInterface, connection.ApplicationSettings.UseHTTPS ? connection.ApplicationSettings.ServerSSLCertificate : null, - string.Format("{0} v{1}", Library.AutoUpdater.AutoUpdateSettings.AppName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Version), + string.Format("{0} v{1}", Library.AutoUpdater.AutoUpdateSettings.AppName, Library.AutoUpdater.UpdaterManager.SelfVersion.Version), (connection.ApplicationSettings.AllowedHostnames ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries), Duplicati.Library.Utility.Utility.ParseBoolOption(options, OPTION_WEBSERVICE_API_ONLY), - spaPathsString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + spaPathsString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries), + options.GetValueOrDefault(OPTION_WEBSERVICE_CORS_ORIGINS)?.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty() ); // Materialize the list of ports, and move the last-used port to the front, so we try the last-known port first diff --git a/Duplicati/Server/WindowsEventLogSource.cs b/Duplicati/Server/WindowsEventLogSource.cs index ce519fb56..be5ee55b6 100644 --- a/Duplicati/Server/WindowsEventLogSource.cs +++ b/Duplicati/Server/WindowsEventLogSource.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,6 +19,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Diagnostics; using System.Runtime.Versioning; @@ -32,6 +34,11 @@ namespace Duplicati.Server [SupportedOSPlatform("windows")] public class WindowsEventLogSource : ILogDestination, IDisposable { + /// + /// The log tag for the windows events + /// + private static readonly string LOGTAG = Log.LogTagFromType(); + /// /// The event log to write to /// @@ -42,8 +49,9 @@ namespace Duplicati.Server /// /// The source of the log messages /// The log to write to - public WindowsEventLogSource(string source, string log = "Application") + public WindowsEventLogSource(string source, string? log = null) { + (source, log) = SplitSource(source, log); m_eventLog = new EventLog { Source = source, @@ -57,15 +65,28 @@ namespace Duplicati.Server /// The source to check /// True if the source exists public static bool SourceExists(string source) - => EventLog.SourceExists(source); + { + try + { + (source, _) = SplitSource(source, null); + return EventLog.SourceExists(source); + } + catch (Exception ex) + { + Log.WriteWarningMessage(LOGTAG, "EventLogLookupFailure", ex, $"Failed to test if event log {source} exists"); + } + + return false; + } /// /// Creates a new event source /// /// The source to create /// The log to write to - public static void CreateEventSource(string source, string log = "Application") + public static void CreateEventSource(string source, string? log = null) { + (source, log) = SplitSource(source, log); if (!SourceExists(source)) EventLog.CreateEventSource(source, log); } @@ -77,6 +98,27 @@ namespace Duplicati.Server public void WriteMessage(LogEntry entry) => m_eventLog.WriteEntry(entry.AsString(true), ToEventLogType(entry.Level)); + /// + /// Parse a log and source name from a string, using a colon as a separator. + /// If no colon is found, the source is assumed to be "Application". + /// + /// The source name to parse + /// + private static (string Log, string Source) SplitSource(string source, string? log = null) + { + if (source.Contains(':') && string.IsNullOrWhiteSpace(log)) + { + var parts = source.Split(':', 2); + if (!string.IsNullOrWhiteSpace(parts[0]) && !string.IsNullOrWhiteSpace(parts[1])) + return (parts[1], parts[0]); + } + + if (string.IsNullOrWhiteSpace(log)) + log = "Duplicati 2"; + + return (source, log); + } + /// /// Converts a log message type to an windows event log type /// diff --git a/Duplicati/Server/webroot/index.html b/Duplicati/Server/webroot/index.html index 7fb82b716..d979ecc54 100644 --- a/Duplicati/Server/webroot/index.html +++ b/Duplicati/Server/webroot/index.html @@ -23,12 +23,12 @@ var d = new Date(); d.setTime(d.getTime() + (90*24*60*60*1000)); - document.cookie = 'default-theme=' + theme + '; expires=' + d.toUTCString() + '; path=/'; + document.cookie = 'default-client=' + theme + '; expires=' + d.toUTCString() + '; path=/'; // Make an absolute path redirect, as relative redirects break if the port is non-standard var path = window.location.pathname; - if (path.lastIndexOf('.html') == path.length - '.html'.length) + if (path.lastIndexOf('.html') !== -1 && path.lastIndexOf('.html') == path.length - '.html'.length) path = path.substr(0, path.lastIndexOf('/')); if (path.lastIndexOf('/') + 1 != path.length) @@ -38,11 +38,9 @@ window.location.replace(path); } - var theme = parseCookies()['default-theme']; + var theme = parseCookies()['default-client']; if ((theme || '').trim().length == 0) - theme = 'ngax'; - if (theme == 'greeno') // Make sure users are redirected automatically - theme = 'ngax'; + theme = 'ngclient'; setCookieAndRun(theme); // Refresh diff --git a/Duplicati/Server/webroot/login.html b/Duplicati/Server/webroot/login.html index d19c6ef11..6ed8dba81 100644 --- a/Duplicati/Server/webroot/login.html +++ b/Duplicati/Server/webroot/login.html @@ -39,6 +39,12 @@

+ Password missing or lost? diff --git a/Duplicati/Server/webroot/login/login.css b/Duplicati/Server/webroot/login/login.css index f01a7829b..edf45e726 100644 --- a/Duplicati/Server/webroot/login/login.css +++ b/Duplicati/Server/webroot/login/login.css @@ -76,3 +76,10 @@ input { #login fieldset input[type="submit"]:hover { background: #65b1dd; } + +#login a.helplink { + display: inline-block; + margin-top: 16px; + margin-left: 16px; + color: #777; +} diff --git a/Duplicati/Server/webroot/ngax/index.html b/Duplicati/Server/webroot/ngax/index.html index 9130ead8b..5f72a20fa 100755 --- a/Duplicati/Server/webroot/ngax/index.html +++ b/Duplicati/Server/webroot/ngax/index.html @@ -28,7 +28,7 @@ - + @@ -79,7 +79,6 @@ - @@ -97,13 +96,13 @@ - + @@ -191,7 +190,7 @@ -
+
@@ -257,7 +256,11 @@ + + @@ -305,6 +308,7 @@ diff --git a/Duplicati/Server/webroot/ngax/less/base.less b/Duplicati/Server/webroot/ngax/less/base.less index 08f988e92..f078ff459 100644 --- a/Duplicati/Server/webroot/ngax/less/base.less +++ b/Duplicati/Server/webroot/ngax/less/base.less @@ -618,6 +618,28 @@ div.captcha { } } +.pauseoptions { + div.sublabel { + clear: both; + padding: 0 31px; + font-style: italic; + } + + .input.checkbox { + margin-top: 10px; + input.checkbox { + width: auto; + height: auto; + } + + label { + line-height: normal; + padding: 0 15px; + width: auto; + } + } +} + .centered-text { text-align: center; } @@ -1207,6 +1229,11 @@ body { } .tasks { + .orderbox + { + float: right; + } + .tasklist { .task { border-top: 1px solid #eee; @@ -2200,6 +2227,29 @@ div.modal-dialog { } } +.confirmdelete { + div.overview { + width: 420px; + } + div.word { + font-weight: bold; + padding: 12px; + border: 1px solid black; + margin-top: 12px; + margin-bottom: 12px; + background-color: #e0e0e0; + width: 420px; + } + + span.backupname { + font-weight: bold; + } + + input.typedword { + float: none; + } +} + /* Progress bar styles from Bootstrap */ .progress-bar-striped { background-image: linear-gradient(45deg, rgb(255 255 255 / 15%) 25%, transparent 25%, transparent 50%, rgb(255 255 255 / 15%) 50%, rgb(255 255 255 / 15%) 75%, transparent 75%, transparent); diff --git a/Duplicati/Server/webroot/ngax/scripts/angular-gettext-cli_compiled_js_output.js b/Duplicati/Server/webroot/ngax/scripts/angular-gettext-cli_compiled_js_output.js index fd40cc9dd..eee0e8be7 100644 --- a/Duplicati/Server/webroot/ngax/scripts/angular-gettext-cli_compiled_js_output.js +++ b/Duplicati/Server/webroot/ngax/scripts/angular-gettext-cli_compiled_js_output.js @@ -1,34 +1,34 @@ angular.module('backupApp').run(['gettextCatalog', function (gettextCatalog) { /* jshint -W100 */ gettextCatalog.setStrings('bn', {"- pick an option -":"-একটি বিকল্প নির্বাচন করুন-","...loading...":"...চালু হচ্ছে...","AWS Access ID":"AWS এর প্রবেশ আইডি","About":"সম্পর্কে","About {{appname}}":"{{appname}} সম্পর্কে","Access denied":"প্রবেশাধিকার বাতিল","Add a new backup":"একটি নতুন ব্যাকআপ যোগ করুন","Add a path directly":"সরাসরি একটি গন্তব্য যোগ করুন","Add advanced option":"উন্নত বিকল্প যোগ করুন","Add backup":"ব্যাকআপ যোগ করুন","Add filter":"ফিল্টার যোগ করুন","Add path":"গন্তব্য যোগ করুন","Advanced Options":"উন্নত বিকল্পগুলি","Advanced options":"উন্নত বিকল্পগুলি","Advanced:":"উন্নত:","Allow remote access (requires restart)":"দূরবর্তী অ্যাক্সেসের অনুমতি দিন (পুনর্সূচনা প্রয়োজন)","Allowed days":"অনুমোদিত দিন","An existing file was found at the new location":"একটি বিদ্যমান ফাইল নতুন স্থানে রয়েছে","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"একটি বিদ্যমান ফাইল নতুন স্থানে আছে\nআপনি কি নিশ্চিত যে আপনি একটি বিদ্যমান ফাইলে ডাটাবেস যুক্ত করতে চান?","Anonymous usage reports":"অজ্ঞাত ব্যবহারের রিপোর্ট","Back":"পিছনে","Backup location":"ব্যাকআপ স্থান","Backup retention":"ব্যাকআপ ধারণসংখ্যা","Backup:":"ব্যাকআপ:","Beta":"বিটা","Browse":"ব্রাউজ করুন","Browser default":"ব্রাউজার ডিফল্ট","Cancel":"বাতিল","Changelog":"পরিবর্তণের তালিকা","Chose a storage type to get started":"শুরু করার জন্য একটি স্টোরেজের ধরন নির্বাচন করুন","Compact now":"এখনি কম্প্যাক্ট করুন"}); - gettextCatalog.setStrings('ca', {"- pick an option -":"- trieu una opció -","...loading...":"S'està carregant...","AWS Access ID":"ID d'accés d'AWS","AWS Access Key":"Clau d'accés d'AWS","AWS IAM Policy":"Política IAM d'AWS","About":"Quant a","About {{appname}}":"Quant al {{appname}}","Access Key":"Clau d'accés","Access denied":"S'ha denegat l'accés","Access to user interface":"Accés a la interfície d'usuari","Account name":"Nom del compte","Add a new backup":"Afegeix una nova còpia de seguretat","Add a path directly":"Afegeix una ruta directament","Add advanced option":"Afegeix una opció avançada","Add backup":"Afegeix una còpia de seguretat","Add filter":"Afegeix un filtre","Add path":"Afegeix una ruta","Added":"Afegits","Adjust bucket name?":"Voleu modificar el nom del contenidor?","Advanced Options":"Opcions avançades","Advanced options":"Opcions avançades","Advanced:":"Avançat:","All Hyper-V Machines":"Totes les màquines de l'Hyper-V","All Microsoft SQL Databases":"Totes les bases de dades SQL de Microsoft","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tots els informes d'ús s'envien anònimament i no contenen cap informació personal. Contenen informació sobre el maquinari i el sistema operatiu, el tipus de capa d'accés de dades, la durada de la còpia de seguretat, la mida general de les dades d'origen i dades similars. No contenen rutes, noms de fitxers, noms d'usuari, contrasenyes o dades sensibles similars.","Allow remote access (requires restart)":"Permet l'accés remot (cal reiniciar el programa)","Allowed days":"Dies permesos","An existing file was found at the new location":"S'ha trobat un fitxer existent a la nova ubicació","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"S'ha trobat un fitxer existent a la nova ubicació.\nSegur que voleu que la base de dades apunti a un fitxer existent?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"S'ha trobat una base de dades local existent per a l'emmagatzematge.\nSi reaprofiteu la base de dades, permetreu que les instàncies de la línia d'ordres i del servidor funcionin amb el mateix emmagatzematge remot.\n\n Voleu fer servir la base de dades existent?","Anonymous usage reports":"Informes d'ús anònims","Applications":"Aplicacions","As Command-line":"Com a línia d'ordres","AuthID":"AuthID","Authentication password":"Contrasenya per a l'autenticació","Authentication username":"Nom d'usuari per a l'autenticació","Autogenerated passphrase":"Contrasenya generada automàticament","B2 Application Key":"Clau d'aplicació de B2","B2 Cloud Storage Account ID":"ID del compte de B2 Cloud Storage","B2 Cloud Storage Application Key":"Clau d'aplicació de B2 Cloud Storage","Back":"Enrere","Backup complete!":"S'ha completat la còpia de seguretat!","Backup destination":"Destinació de la còpia de seguretat","Backup location":"Ubicació de la còpia de seguretat","Backup retention":"Preservació de la còpia de seguretat","Backup:":"Còpia de seguretat:","Beta":"Beta","Broken access":"L'accés està trencat","Browse":"Navega","Browser default":"Valor per defecte del navegador","Bucket create location":"Ubicació de creació del contenidor","Bucket name":"Nom del contenidor","Bucket storage class":"Classe d'emmagatzematge del contenidor","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Si permeteu l'accés remot, el servidor escolta les peticions de qualsevol ordinador de la xarxa. Si activeu aquesta opció, assegureu-vos que sempre feu servir l'ordinador en una xarxa protegida amb un tallafoc.","Cache Files":"Fitxers de memòria cau","Canary":"Canary","Cancel":"Cancel·la","Cannot move to existing file":"No s'ha pogut canviar al fitxer existent","Changelog":"Registre de canvis","Changelog for {{appname}} {{version}}":"Registre de canvis del {{appname}} {{version}}","Check failed:":"Ha fallat la comprovació:","Check for updates now":"Comprova ara si hi ha actualitzacions","Chose a storage type to get started":"Trieu un tipus d'emmagatzematge per començar","Click the AuthID link to create an AuthID":"Feu clic a l'enllaç d'AuthID per crear una AuthID","Click to set throttle options":"Feu clic per definir les opcions de velocitat","Compact Phase":"Fase de compactació","Compact now":"Compacta ara","Computer":"Ordinador","Configuration file:":"Fitxer de configuració:","Configuration:":"Configuració:","Configure a new backup":"Configura una nova còpia de seguretat","Confirm delete":"Confirma l'eliminació","Confirmation required":"Es requereix una confirmació","Connect":"Connecta","Connect now":"Connecta ara","Connection lost":"S'ha perdut la connexió","Connection worked!":"Ha funcionat la connexió!","Container name":"Nom del contenidor","Container region":"Regió del contenidor","Continue":"Continua","Continue without encryption":"Continua sense xifratge","Copied!":"S'ha copiat!","Copy":"Copia","Copy Destination URL to Clipboard":"Copia l'URL de destinació al porta-retalls","Copy failed. Please manually copy the URL":"Ha fallat la còpia. Copieu l'URL manualment","Core options":"Opcions principals","Counting ({{files}} files found, {{size}})":"S'està comptant (s'han trobat {{files}} fitxers, {{size}})","Crashes only":"Només fallades","Create folder?":"Voleu crear una carpeta?","Created new limited user":"S'ha creat un nou usuari limitat","Current action:":"Acció actual:","Current file:":"Fitxer actual:","Current version is {{versionname}} ({{versionnumber}})":"La versió actual és {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Extrem d'S3 personalitzat","Custom authentication url":"URL d'autenticació personalitzat","Custom backup retention":"Preservació de còpies de seguretat personalitzada","Custom location ({{server}})":"Ubicació personalitzada ({{server}})","Custom region for creating buckets":"Regió de creació de contenidors personalitzada","Custom region value ({{region}})":"Valor de regió personalitzat ({{region}})","Custom server url ({{server}})":"URL del servidor personalitzat ({{server}})","Custom storage class ({{class}})":"Classe d'emmagatzematge personalitzada ({{class}})","Days":"Dies","Default":"Per defecte","Default ({{channelname}})":"Per defecte ({{channelname}})","Default excludes":"Exclusions per defecte","Default options":"Opcions per defecte","Delete":"Elimina","Delete Phase (Old Backup Versions)":"Fase d'eliminació (versions antigues de la còpia de seguretat)","Delete backup":"Elimina la còpia de seguretat","Delete backups that are older than":"Elimina les còpies de seguretat anteriors a","Delete local database":"Elimina la base de dades local","Delete remote files":"Elimina els fitxers remots","Delete the local database":"Elimina la base de dades local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Voleu eliminar {{filecount}} fitxers ({{filesize}}) de l'emmagatzematge remot?","Deleted":"Eliminats","Deleted Versions":"Versions eliminades","Deleted files":"Fitxers eliminats","Description (optional)":"Descripció (opcional)","Description:":"Descripció:","Desktop":"Escriptori","Destination":"Destinació","Destination path":"Ruta de destinació","Disabled":"Desactivat","Dismiss":"Ignora","Dismiss all":"Ignora-ho tot","Display and color theme":"Visualització i tema de color","Do you really want to delete the backup: \"{{name}}\" ?":"Segur que voleu eliminar la còpia de seguretat «{{name}}»?","Do you really want to delete the local database for: {{name}}":"Segur que voleu eliminar la base de dades local de «{{name}}»?","Done":"Fet","Download":"Baixa","Downloaded files":"Fitxers baixats","Duplicate option {{opt}}":"Opció duplicada {{opt}}","Duplicati Website":"Lloc web del Duplicati","Duplicati forum":"Fòrum del Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"El Duplicati s'executarà quan arrenqui, però es mantindrà pausat durant el període especificat. El Duplicati ocuparà els recursos del sistema mínims i no s'executaran còpies de seguretat.","Duration":"Durada","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada còpia de seguretat té una base de dades local associada que emmagatzema informació sobre la còpia de seguretat remota a l'ordinador local.\n Quan elimineu una còpia de seguretat, també podeu eliminar la base de dades local sense que això afecti la possibilitat de restaurar els fitxers remots.\n Si feu servir la base de dades local per a còpies de seguretat des de la línia d'ordres, hauríeu de mantenir la base de dades.","Edit as list":"Edita com a llista","Edit as text":"Edita com a text","Encrypt file":"Xifra el fitxer","Encryption":"Xifratge","Encryption changed":"S'ha canviat el xifratge","End":"Final","Enter URL":"Introduïu l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Introduïu un pla de preservació manualment. Les expressions són D/W/Y per a dies/setmanes/anys i U per a il·limitat. La sintaxi és: 7D:1D,4W:1W,36M:1M. Aquest exemple preserva una còpia de seguretat per a cadascun dels pròxims 7 dies, per a cadascuna de les pròximes 4 setmanes, i per a cadascun dels pròxims 36 mesos. Això també es pot escriure així: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduïu la contrasenya de la còpia de seguretat, si en té","Enter configuration details":"Introduïu els detalls de configuració","Enter encryption passphrase":"Introduïu la contrasenya de xifratge","Enter expression here":"Introduïu l'expressió aquí","Enter the destination path":"Introduïu la ruta de destinació","Error":"Error","Error!":"S'ha produït un error!","Errors and crashes":"Errors i fallades","Examined":"Examinats","Exclude":"Exclusions","Exclude directories whose names contain":"Exclou carpetes amb un nom que contingui","Exclude expression":"Exclou una expressió","Exclude file":"Exclou un fitxer","Exclude file extension":"Exclou una extensió de fitxer","Exclude files whose names contain":"Exclou fitxers amb un nom que contingui","Exclude filter group":"Exclou un grup de filtres","Exclude folder":"Exclou una carpeta","Exclude regular expression":"Exclou una expressió regular","Existing file found":"S'ha trobat un fitxer existent","Experimental":"Experimental","Export":"Exporta","Export backup configuration":"Exporta la configuració de la còpia de seguretat","Export configuration":"Exporta la configuració","Export passwords":"Exporta les contrasenyes","External link":"Enllaç extern","FTP (Alternative)":"FTP (alternatiu)","Failed to build temporary database: {{message}}":"No s'ha pogut crear la base de dades temporal: {{message}}","Failed to connect:":"No s'ha pogut connectar:","Failed to connect: {{message}}":"No s'ha pogut connectar: {{message}}","Failed to delete:":"No s'ha pogut eliminar:","Failed to fetch path information: {{message}}":"No s'ha pogut recollir la informació de les rutes: {{message}}","Failed to find backup:":"No s'ha pogut trobar la còpia de seguretat:","Failed to read backup defaults:":"No s'han pogut llegir els valors per defecte de la còpia de seguretat:","Failed to restore files: {{message}}":"No s'han pogut restaurar els fitxers: {{message}}","Failed to save:":"No s'ha pogut desar:","File":"Fitxer","Files larger than:":"Fitxers més grans que:","Filters":"Filtres","Finished!":"S'ha acabat!","First run setup":"Configuració inicial","Folder":"Carpeta","Folder path":"Ruta de la carpeta","Fri":"Divendres","GByte":"GBytes","GByte/s":"GByte/s","GCS Project ID":"ID del projecte de GCS","General":"General","General backup settings":"Paràmetres generals de la còpia de seguretat","General options":"Opcions generals","Generate":"Genera","Generate IAM access policy":"Genera una política d'accés IAM","Group email":"Adreça electrònica del grup","Hidden files":"Fitxers ocults","Hide":"Amaga","Hide hidden folders":"Amaga les carpetes ocultes","Home":"Inici","Hostnames":"Noms","Hours":"Hores","How do you want to handle existing files?":"Què voleu fer amb els fitxers existents?","Hyper-V Machine":"Màquina de l'Hyper-V","Hyper-V Machine:":"Màquina de l'Hyper-V:","Hyper-V Machines":"Màquines de l'Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si s'ha sobrepassat una data, la tasca s'executarà tan aviat com sigui possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si es troba com a mínim una còpia de seguretat més recent, s'eliminaran totes les còpies de seguretat anteriors a aquesta data.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si no introduïu una ruta, s'emmagatzemaran tots els fitxers a la carpeta d'inici de sessió.\nSegur que voleu fer això?","If you do not enter an API Key, the tenant name is required":"Si no introduïu una clau API, heu d'indicar el nom d'inquilí","Import":"Importa","Import Destination URL":"Importa un URL de destinació","Import backup configuration":"Importa una configuració de còpia de seguretat","Import from a file":"Importa des d'un fitxer","Import metadata":"Importa les metadades","Include a file?":"Voleu incloure un fitxer?","Include expression":"Inclou una expressió","Include regular expression":"Inclou una expressió regular","Incorrect answer, try again":"La resposta és incorrecta, torneu-ho a provar","Individual builds for developers only. Not for use with important data.":"Compilacions individuals només per a desenvolupadors. No ho feu servir amb dades importants.","Information":"Informació","Invalid characters in path":"La ruta conté caràcters no vàlids","Invalid retention time":"El període de preservació no és vàlid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"És possible connectar-se a alguns servidors FTP sense contrasenya.\nSegur que el vostre servidor FTP suporta l'accés sense contrasenya?","KByte":"KBytes","KByte/s":"KByte/s","Keep a specific number of backups":"Preserva un nombre específic de còpies de seguretat","Keep all backups":"Preserva totes les còpies de seguretat","Keystone API version":"Versió de l'API de Keystone","Language in user interface":"Idioma de la interfície d'usuari","Last month":"El mes passat","Last successful backup:":"Última còpia de seguretat completada:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauració completada: {{time}} (durada: {{duration || '0 segons'}})","Latest":"Versió més recent","Libraries":"Biblioteques","Live":"En viu","Load a configuration from an exported job or a storage provider":"Importeu una configuració des d'una tasca exportada o des d'un proveïdor d'emmagatzematge","Load destination from an exported job or a storage provider":"Importeu una destinació des d'una tasca exportada o des d'un proveïdor d'emmagatzematge","Load older data":"Carrega dades més antigues","Local Repository":"Dipòsit local","Local database path:":"Ruta de la base de dades local:","Local repository":"Dipòsit local","Local storage":"Emmagatzematge local","Location":"Ubicació","Location where buckets are created":"Ubicació on es creen els contenidors","Log data for {{Backup.Backup.Name}}":"Dades de registre de {{Backup.Backup.Name}}","Log data from the server":"Dades de registre del servidor","Log out":"Surt","MByte":"MBytes","MByte/s":"MByte/s","Maintenance":"Manteniment","Manually type path":"Escriviu la ruta manualment","Max download speed":"Velocitat màxima de baixada","Max upload speed":"Velocitat màxima de càrrega","Menu":"Menú","Microsoft SQL Database:":"Base de dades SQL de Microsoft:","Microsoft SQL Databases":"Bases de dades SQL de Microsoft","Minimum redundancy":"Redundància mínima","Minimum redundancy is 1.0":"La redundància mínima és de 1.0","Minutes":"Minuts","Missing name":"No s'ha definit un nom","Missing passphrase":"No s'ha definit una contrasenya","Missing sources":"No s'ha definit un origen","Modified":"Modificats","Mon":"Dilluns","Months":"Mesos","Move existing database":"Mou una base de dades existent","Move failed:":"No s'ha pogut moure:","My Documents":"Documents","My Music":"Música","My Photos":"Fotografies","My Pictures":"Imatges","Name":"Nom","Never":"Mai","New user name is {{user}}.\nUpdated credentials to use the new limited user":"El nou nom d'usuari és {{user}}.\nS'han actualitzat les credencials per fer servir el nou usuari limitat","Next":"Següent","Next scheduled run:":"Pròxima execució programada:","Next scheduled task:":"Pròxima tasca programada:","Next task:":"Pròxima tasca:","Next time":"La pròxima vegada","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No s'ha especificat cap certificat anteriorment, comproveu amb l'administrador del servidor que la clau és correcta: {{key}} \n\nVoleu aprovar aquesta clau d'amfitrió?","No editor found for the "{{backend}}" storage type":"No s'ha trobat cap editor per a l'emmagatzematge del tipus «{{backend}}»","No encryption":"Sense xifratge","No items selected":"No s'ha seleccionat cap element","No items to restore, please select one or more items":"No hi ha elements per restaurar, seleccioneu-ne un o més","No passphrase entered":"No s'ha introduït cap contrasenya","No scheduled tasks":"No hi ha tasques planificades","Non-matching passphrase":"La contrasenya no coincideix","None / disabled":"Cap / desactivat","Not using encryption":"El xifratge està desactivat","Nothing will be deleted. The backup size will grow with each change.":"No s'eliminarà res. La mida de la còpia de seguretat augmentarà després de cada canvi.","OK":"D'acord","Once there are more backups than the specified number, the oldest backups are deleted.":"Una vegada hi ha més còpies de seguretat que el nombre especificat, s'eliminen les còpies de seguretat més antigues.","OpenStack AuthURI":"AuthURI de l'OpenStack","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Oberts","Operating System":"Sistema operatiu","Operation":"Operació","Operations:":"Operacions:","Optional authentication password":"Contrasenya per a l'autenticació (opcional)","Optional authentication username":"Nom d'usuari per a l'autenticació (opcional)","Options":"Opcions","Original location":"Ubicació original","Others":"Altres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Al llarg del temps, les còpies de seguretat s'eliminaran automàticament. Es conservarà una còpia de seguretat per a cadascun dels darrers 7 dies, les darreres 4 setmanes i els darrers 12 mesos. Sempre hi haurà com a mínim una còpia de seguretat restant.","Overwrite":"Sobreescriu-los","Passphrase":"Contrasenya","Passphrase (if encrypted)":"Contrasenya (si el fitxer està xifrat)","Passphrase changed":"S'ha canviat la contrasenya","Passphrases are not matching":"Les contrasenyes no coincideixen","Passphrases do not match":"Les contrasenyes no coincideixen","Password":"Contrasenya","Path":"Ruta","Path not found":"No s'ha trobat la ruta","Path on server":"Ruta al servidor","Path or subfolder in the bucket":"Ruta o subcarpeta al contenidor","Pause":"Pausa","Pause after startup or hibernation":"Pausa després de l'arrencada o la hibernació","Pause options":"Opcions de pausa","Permissions":"Permisos","Pick location":"Trieu una ubicació","Point to your backup files and restore from there":"Indiqueu on són els vostres fitxers de còpia de seguretat i feu una restauració des d'allà","Port":"Port","Prevent tray icon automatic log-in":"Impedeix l'inici de sessió automàtic de la safata del sistema","Previous":"Enrere","Progress:":"Progrés:","ProjectID is optional if the bucket exist":"La ProjectID és opcional si el contenidor existeix","Proprietary":"De propietat","Purge Phase":"Fase de purga","Purging files complete!":"S'ha completat la purga de fitxers!","Recreate (delete and repair)":"Recrea (elimina i repara)","Recreate Database Phase":"Fase de recreació de la base de dades","Relative paths not allowed":"No es permet l'ús de rutes relatives","Reload":"Actualitza","Remote":"Remot","Remote Path":"Ruta remota","Remote Repository":"Dipòsit remot","Remote path":"Ruta remota","Remote repository":"Dipòsit remot","Remote volume size":"Mida dels volums remots","Remove":"Elimina","Remove option":"Elimina l'opció","Removed files":"Fitxers eliminats","Repair":"Repara","Repair Phase":"Fase de reparació","Repeat Passphrase":"Repetiu la contrasenya","Reporting:":"S'està informant:","Reset":"Reinicialitza","Restore":"Restaura","Restore complete!":"S'ha completat la restauració!","Restore files":"Restaura fitxers","Restore from":"Restaura des de","Restore from backup configuration":"Restaura des d'una configuració de còpia de seguretat","Restore options":"Opcions de restauració","Restore read/write permissions":"Restaura els permisos de lectura/escriptura","Resume":"Reprèn","Rewritten File Lists":"Llistes de fitxers reescrits","Run again every":"Torna a executar cada","Run now":"Executa ara","Running commandline entry":"S'està executant una entrada de la línia d'ordres","Running task:":"Tasca en execució:","S3 Compatible":"Compatible amb S3","Same as the base install version: {{channelname}}":"La mateixa que la versió base d'instal·lació: {{channelname}}","Sat":"Dissabte","Save":"Desa","Save and repair":"Desa i repara","Save different versions with timestamp in file name":"Desa les versions diferents amb una marca horària al nom del fitxer","Save immediately":"Desa immediatament","Schedule":"Planificació","Search":"Cerca","Search for files":"Cerca fitxers","Seconds":"Segons","Select a log level and see messages as they happen:":"Trieu un nivell de registre i vegeu els nous missatges al moment:","Select files":"Seleccioneu els fitxers","Server":"Servidor","Server and port":"Servidor i port","Server hostname or IP":"Nom del servidor o IP","Server is currently paused,":"El servidor està pausat actualment,","Server is currently paused, do you want to resume now?":"El servidor està pausat actualment, voleu reprendre la tasca ara?","Server password":"Contrasenya del servidor","Server paused":"S'ha pausat el servidor","Server state properties":"Propietats de l'estat del servidor","Settings":"Configuració","Show":"Mostra","Show advanced editor":"Mostra l'editor avançat","Show hidden folders":"Mostra les carpetes ocultes","Show log":"Mostra el registre","Show treeview":"Mostra la vista en arbre","Sia server password":"Contrasenya del servidor de Sia","Smart backup retention":"Preservació de còpies de seguretat intel·ligent","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns proveïdors de l'OpenStack permeten fer servir una clau API en comptes d'una contrasenya i un nom d'inquilí","Source Data":"Dades d'origen","Source data":"Dades d'origen","Source folders":"Carpetes d'origen","Source:":"Origen:","Specific builds for developers only. Not for use with important data.":"Compilacions específiques només per a desenvolupadors. No ho feu servir amb dades importants.","Standard protocols":"Protocols estàndard","Start":"Inici","Stop after the current file":"Atura després del fitxer actual","Stop now":"Atura ara","Stop running backup":"Atura la còpia de seguretat en execució","Stop running task":"Atura la tasca en execució","Stopping task:":"S'està aturant la tasca:","Storage Type":"Tipus d'emmagatzematge","Storage class":"Classe d'emmagatzematge","Storage class for creating a bucket":"Classe d'emmagatzematge per crear un contenidor","Stored":"Emmagatzemat","Strong":"Forta","Success":"Èxit","Sun":"Diumenge","Symbolic link":"Enllaç simbòlic","System Files":"Fitxers del sistema","System default ({{levelname}})":"Valor per defecte del sistema ({{levelname}})","System files":"Fitxers del sistema","System info":"Informació del sistema","System properties":"Propietats del sistema","TByte":"TBytes","TByte/s":"TByte/s","Task is running":"La tasca s'està executant","Temporary Files":"Fitxers temporals","Temporary files":"Fitxers temporals","Test Phase":"Fase de comprovació","Test connection":"Comprova la connexió","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"El camp «{{fieldname}}» conté un caràcter no vàlid: {{character}} (valor: {{value}}, índex: {{pos}})","The backup is missing, has it been deleted?":"No s'ha trobat la còpia de seguretat; l'heu eliminat?","The backup was temporary and does not exist anymore, so the log data is lost":"La còpia de seguretat era temporal i ja no existeix, per la qual cosa s'han perdut les dades del registre","The bucket name should be all lower-case, convert automatically?":"El nom del contenidor ha d'estar en minúscules; voleu convertir-lo automàticament?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"És recomanable que deseu la configuració en un lloc segur. Segur que voleu desar un fitxer sense xifrar amb les vostres contrasenyes?","The dark theme (by Michal)":"Tema fosc (per Michal)","The default blue on white theme (by Alex)":"Tema per defecte, blau sobre blanc (per Alex)","The folder {{folder}} does not exist.\nCreate it now?":"La carpeta {{folder}} no existeix.\nVoleu crear-la ara?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clau de l'amfitrió ha canviat, comproveu amb l'administrador del servidor que això és correcte, o podríeu ser víctima d'un atac d'intermediari.\n\nVoleu substituir la clau d'amfitrió actual («{{prev}}») amb la clau d'amfitrió «{{key}}»?","The passwords do not match":"Les contrasenyes no coincideixen","The path does not appear to exist, do you want to add it anyway?":"Sembla que la ruta no existeix, voleu afegir-la igualment?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"La ruta no acaba amb un caràcter «{{dirsep}}», la qual cosa vol dir que heu triat un fitxer, no una carpeta.\n\nVoleu incloure el fitxer especificat?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"La ruta ha de ser absoluta, és a dir, ha de començar amb una barra «/»","The region parameter is only applied when creating a new bucket":"El paràmetre de regió només s'aplica quan es crea un contenidor","The region parameter is only used when creating a bucket":"El paràmetre de regió només es fa servir quan es crea un contenidor","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"No s'ha pogut validar el certificat del servidor.\nVoleu aprovar el certificat SSL amb la suma «{{hash}}»?","The storage class affects the availability and price for a stored file":"La classe d'emmagatzematge afecta la disponibilitat i el preu dels fitxers emmagatzemats","The target folder contains encrypted files, please supply the passphrase":"La carpeta de destinació conté fitxers encriptats; introduïu-ne la contrasenya","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'usuari té massa permisos. Voleu crear un nou usuari limitat, amb permisos només per a la ruta seleccionada?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Aquesta còpia de seguretat s'ha creat en un altre sistema operatiu. Si restaureu fitxers sense especificar una carpeta de destinació, pot ser que es restaurin fitxers en llocs inesperats. Segur que voleu continuar sense seleccionar una carpeta de destinació?","This month":"Aquest mes","This week":"Aquesta setmana","Throttle settings":"Opcions de velocitat","Thu":"Dijous","Time":"Hora","To File":"A un fitxer","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Per confirmar que voleu eliminar tots els fitxers remots de «{{name}}, escriviu la paraula que veieu a continuació","To export without a passphrase, uncheck the \"Encrypt file\" box":"Per fer una exportació sense contrasenya, desactiveu la casella «Xifra el fitxer»","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Per evitar diversos atacs basats en el DNS, el Duplicati limita els noms de servidor permesos als d'aquesta llista. Sempre es permet l'accés directe a localhost o per IP. Podeu indicar diversos noms de servidor separant-los amb un punt i coma. Si cap dels noms d'ordinador permesos és un asterisc (*), es permeten tots els noms d'ordinador i es desactiva aquesta característica. Si el camp és buit, només es permet l'accés a localhost o per adreça IP.","Today":"Avui","Trust host certificate?":"Voleu confiar en el certificat de l'amfitrió?","Trust server certificate?":"Voleu confiar en el certificat del servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Proveu les noves característiques que estem preparant. És la versió més estable disponible actualment. Proveu la funció de restauració abans de fer-ho servir en entorns de producció.","Tue":"Dimarts","Type passphrase here.":"Escriviu la contrasenya aquí.","Type to highlight files":"Escriviu per ressaltar fitxers","Unknown backup size and versions":"No s'han pogut determinar la mida de la còpia de seguretat i les versions","Until resumed":"Fins que es reprengui","Update channel":"Canal d'actualitzacions","Update failed:":"Ha fallat l'actualització:","Updating with existing database":"S'està actualitzant amb una base de dades existent","Uploaded files":"Fitxers carregats","Usage statistics":"Estadístiques d'ús","Usage statistics, warnings, errors, and crashes":"Estadístiques d'ús, avisos, errors i fallades","Use SSL":"Fes servir SSL","Use existing database?":"Voleu fer servir la base de dades existent?","Use weak passphrase":"Fes servir una contrasenya dèbil","Useless":"Inútil","User data":"Dades d'usuari","User domain name":"Nom de domini de l'usuari","User has too many permissions":"L'usuari té massa permisos","User interface settings":"Paràmetres de la interfície d'usuari","Username":"Nom d'usuari","Verifications":"Verificacions","Verify files":"Verifica els fitxers","Verifying answer":"S'està verificant la resposta","Version ID":"ID de la versió","Very strong":"Molt forta","Very weak":"Molt dèbil","Visit us on":"Visiteu-nos a","WARNING: This will prevent you from restoring the data in the future.":"AVÍS: Això impedirà que restaureu les dades més endavant.","Waiting for task to begin":"S'està esperant que la tasca comenci","Warnings, errors and crashes":"Avisos, errors i fallades","We recommend that you encrypt all backups stored outside your system":"És recomanable que xifreu totes les còpies de seguretat emmagatzemades fora del vostre ordinador","Weak":"Dèbil","Weak passphrase":"Contrasenya dèbil","Wed":"Dimecres","Weeks":"Setmanes","Where do you want to restore from?":"Des d'on voleu fer la restauració?","Where do you want to restore the files to?":"On voleu restaurar els fitxers?","Years":"Anys","Yes":"Sí","Yes, I have stored the passphrase safely":"Sí, he desat la contrasenya en un lloc segur","Yes, I understand the risk":"Sí, entenc els riscos","Yes, I'm brave!":"Sí, no tinc por!","Yes, please break my backup!":"Sí, destrossa'm la còpia de seguretat!","Yesterday":"Ahir","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Esteu canviant la ruta d'una base de dades existent.\nSegur que voleu fer això?","You are currently running {{appname}} {{version}}":"Actualment esteu executant el {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Heu canviat el mode de xifratge. Pot ser que això trenqui alguna cosa. És recomanable que creeu una nova còpia de seguretat en comptes de fer això","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Heu canviat la contrasenya, i això no està implementat. És recomanable que creeu una nova còpia de seguretat en comptes de fer això.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Heu decidit no xifrar la còpia de seguretat. És recomanable que xifreu totes les dades emmagatzemades en un servidor remot.","You have chosen to restore to a new location, but not entered one":"Heu decidit fer la restauració en una nova ubicació, però no n'heu indicat cap","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Heu generat una contrasenya forta. Assegureu-vos que heu copiat la contrasenya en un lloc segur, perquè no podreu recuperar les dades si la perdeu.","You must choose at least one source folder":"Heu de triar com a mínim una carpeta d'origen","You must enter a domain name to use v3 API":"Heu d'introduir un nom de domini per fer servir l'API v3","You must enter a name for the backup":"Heu d'introduir un nom per a la còpia de seguretat","You must enter a passphrase or disable encryption":"Heu d'introduir una contrasenya o desactivar el xifratge","You must enter a password to use v3 API":"Heu d'introduir una contrasenya per fer servir l'API v3","You must enter a positive number of backups to keep":"Heu d'introduir un nombre positiu de còpies de seguretat que voleu preservar","You must enter a tenant (aka project) name to use v3 API":"Heu d'introduir un nom d'inquilí (projecte) per fer servir l'API v3","You must enter a valid duration for the time to keep backups":"Heu d'introduir una durada vàlida de preservació de les còpies de seguretat","You must fill in the password":"Heu d'introduir la contrasenya","You must fill in the server name or address":"Heu d'introduir el nom o l'adreça del servidor","You must fill in the username":"Heu d'introduir el nom d'usuari","You must fill in {{field}}":"Heu d'introduir el camp «{{field}}»","You must select or fill in the AuthURI":"Heu de triar o introduir l'AuthURI","You must select or fill in the server":"Heu de triar o introduir el servidor","You must specify a path":"Heu d'especificar una ruta","Your files and folders have been restored successfully.":"S'han restaurat els fitxers i carpetes correctament.","Your passphrase is easy to guess. Consider changing passphrase.":"La contrasenya és fàcil d'endevinar. Penseu a canviar la contrasenya.","bucket/folder/subfolder":"contenidor/carpeta/subcarpeta","byte":"bytes","byte/s":"byte/s","custom":"personalitzat","resume now":"reprèn ara","unless you are explicitly specifying --group-id":"excepte si especifiqueu explícitament el paràmetre --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"El {{appname}} ha estat desenvolupat principalment per {{dev1}} i {{dev2}}. Podeu baixar-vos el {{appname}} des de {{websitename}}. El {{appname}} està publicat sota la {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"Queden {{files}} fitxers ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versió","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versions"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} hores","{{number}} Minutes":"{{number}} minuts","{{time}} (took {{duration}})":"{{time}} (ha tardat {{duration}})"}); - gettextCatalog.setStrings('cs', {"- pick an option -":"- vyberte jednu z možností -","...loading...":"…načítání…","API key":"Klíč k aplikačnímu programovému rozhraní (API)","AWS Access ID":"Přístupový identifikátor ke službě AWS","AWS Access Key":"Přístupový klíč ke službě AWS","AWS IAM Policy":"Zásady IAM služby AWS","About":"O aplikaci","About {{appname}}":"O aplikaci {{appname}}","Access Key":"Přístupový klíč","Access denied":"Přístup odepřen","Access grant":"Udělení přístupu","Access to user interface":"Přístup k uživatelskému rozhraní","Account name":"Název účtu","Add a new backup":"Přidat novou zálohu","Add a path directly":"Přidat popis umístění přímo","Add advanced option":"Přidat pokročilou volbu","Add backup":"Přidat zálohu","Add filter":"Přidat filtr","Add path":"Přidat popis umístění","Added":"Přidáno","Adjust bucket name?":"Přizpůsobit název „nádoby“ (bucket)?","Advanced Options":"Pokročilé volby","Advanced options":"Pokročilé volby","Advanced:":"Pokročilé:","All Hyper-V Machines":"Všechny Hyper-V stroje","All Microsoft SQL Databases":"Všechny Microsoft SQL databáze","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Veškerá hlášení o využívání jsou posílána anonymně a neobsahují žádné osobní údaje. Obsahují informace o hardware a operačním systému, typu podpůrné vrstvy (backend), trvání zálohy, celkové velikosti zdrojových dat a podobně.\nNeobsahují popisy umístění, názvy souborů, uživatelská jména, hesla nebo podobné citlivé údaje.","Allow remote access (requires restart)":"Umožnit přístup na dálku (vyžaduje restart)","Allowed days":"Dny, ve které je přístup umožněn","An existing file was found at the new location":"V novém umístění byl nalezen už existující soubor","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"V novém umístění byl nalezen už existující soubor\nOpravdu chcete nasměrovat databázi do existujícího souboru?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Byla nalezena existující místní databáze pro ukládání.\nOpětovné využití databáze umožní, aby instance pro příkazový řádek a server fungovaly na stejném vzdáleném úložišti.\n\nChcete použít existující databázi?","Anonymous usage reports":"Anonymní hlášení o použití","Applications":"Aplikace","As Command-line":"Jako příkazový řádek","AuthID":"AuthID","Authentication method":"Způsob autentizace","Authentication method ({{auth_method}})":"Způsob autentizace ({{auth_method}})","Authentication password":"Ověřovací heslo","Authentication username":"Ověřovací uživatelské jméno","Autogenerated passphrase":"Automaticky vytvořená heslová fráze","B2 Application ID":"B2 Aplikační ID","B2 Application Key":"Aplikační klíč ke službě B2","B2 Cloud Storage Account ID":"Identifikátor účtu u cloudového úložiště B2","B2 Cloud Storage Application ID":"Aplikační klíč ke cloudovému úložišti B2","B2 Cloud Storage Application Key":"Aplikační klíč ke cloudovému úložišti B2","Back":"Zpět","Backup complete!":"Záloha dokončena!","Backup destination":"Cíl zálohy","Backup location":"Umístění zálohy","Backup retention":"Doba uchovávání záloh","Backup:":"Záloha:","Beta":"Vývojová testovací (beta)","Broken access":"Nefunkční přístup","Browse":"Procházet","Browser default":"Výchozí nastavení webového prohlížeče","Bucket create location":"Umístění ve kterém „nádobu“ (bucket) vytvořit","Bucket name":"Název „nádoby“ (bucket)","Bucket storage class":"Třída úložiště nesoucí „nádobu“ (bucket)","Building list of files to restore …":"Vytváření seznamu souborů k obnovení…","Building partial temporary database …":"Vytváření částečné dočasné databáze…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Umožněním přístupu na dálku, server očekává požadavky z libovolného stroje na síti. Pokud tuto volbu zapnete, počítač používejte pouze na síti, zabezpečené bránou firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Ve výchozím stavu ikona v oznamovací oblasti otevře uživatelské rozhraní s tokenem, který ho odemkne. To zajistí že můžete přistupovat k uživatelskému rozhraní z ikony v oznamovací oblasti, zatímco po ostatních bude vyžadovat zadání hesla. Pokud upřednostňujete zadávání hesla i při přístupu k uživatelskému rozhraní z ikony v oznamovací oblasti, zapněte tuto volbu.","Cache Files":"Soubory mezipaměti","Canary":"Kanárek","Cancel":"Storno","Cannot move to existing file":"Nelze přesunout do existujícího souboru","Changelog":"Seznam změn","Changelog for {{appname}} {{version}}":"Seznam změn v {{appname}} {{version}}","Check failed:":"Zjištění se nezdařilo:","Check for updates now":"Zjistit dostupnost případných aktualizací nyní","Checking for updates …":"Zjišťování dostupnosti případných aktualizací…","Chose a storage type to get started":"Pro začátek vyberte typ úložiště","Click the AuthID link to create an AuthID":"AuthID vytvoříte kliknutím na odkaz AuthID","Click to set throttle options":"Kliknutím nastavte předvolby přiškrcování","Client library to use":"Používaná klientská knihovna","Commandline …":"Příkazový řádek…","Compact Phase":"Fáze zkompaktňování","Compact now":"Zkompaktnit nyní","Compacting remote data …":"Zkompaktňování dat na protějšku…","Complete log":"Úplný záznam událostí","Completing backup …":"Dokončování zálohy…","Completing previous backup …":"Dokončování předchozí zálohy…","Computer":"Počítač","Configuration file:":"Soubor s nastaveními:","Configuration:":"Nastavení:","Configure a new backup":"Nastavit novou zálohu","Confirm delete":"Potvrzení smazání","Confirm encryption passphrase":"Potvrzení zadání šifrovací heslové fráze","Confirm passphrase":"Zopakování zadání heslové fráze","Confirmation required":"Vyžadováno potvrzení","Connect":"Připojit","Connect now":"Připojit nyní","Connecting to server …":"Připojování k serveru…","Connection lost":"Spojení ztraceno","Connection worked!":"Spojení funguje!","Container name":"Název kontejneru","Container region":"Region umístění kontejneru","Continue":"Pokračovat","Continue without encryption":"Pokračovat bez šifrování","Copied!":"Zkopírováno!","Copy":"Kopírovat","Copy Destination URL to Clipboard":"Zkopírovat URL adresu cíle do schránky","Copy failed. Please manually copy the URL":"Kopie se nezdařila. Zkopírujte URL adresu ručně","Core options":"Základní volby","Counting ({{files}} files found, {{size}})":"Počítání ({{files}} souborů nalezeno, {{size}})","Crashes only":"Pouze pády","Create bug report …":"Vytvořit hlášení chyby…","Create folder?":"Vytvořit složku?","Created new limited user":"Vytvořen nový uživatelský účet s omezenými oprávněními","Creating bug report …":"Vytvořit hlášení chyby…","Creating new user with limited access …":"Vytváření nového uživatele s omezeným přístupem…","Creating target folders …":"Vytváření cílových složek…","Creating temporary backup …":"Vytváření dočasné zálohy…","Current action:":"Stávající akce:","Current file:":"Stávající soubor:","Current version is {{versionname}} ({{versionnumber}})":"Stávající verze je {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Vlastní S3 koncový bod","Custom Satellite":"Vlastní satelit","Custom Satellite ({{satellite}})":"Vlastní satelit ({{satellite}})","Custom authentication url":"Vlastní ověřovací URL adresa","Custom backup retention":"Uživatelem určená doba uchovávání záloh","Custom location ({{server}})":"Vlastní umístění ({{server}})","Custom region for creating buckets":"Vlastní region pro vytváření „nádob“ (bucket)","Custom region value ({{region}})":"Hodnota pro vlastní region ({{region}})","Custom server url ({{server}})":"Vlastní URL adresa serveru ({{server}})","Custom storage class ({{class}})":"Vlastní třída úložiště ({{class}})","Database …":"Databáze…","Days":"Dnů","Default":"Výchozí","Default ({{channelname}})":"Výchozí ({{channelname}})","Default excludes":"Ve výchozím stavu vynecháno","Default options":"Výchozí volby","Delete":"Smazat","Delete Phase (Old Backup Versions)":"Fáze mazání (staré verze zálohy)","Delete backup":"Smazat zálohu","Delete backups that are older than":"Smazat zálohy starší než","Delete local database":"Smazat místní databázi","Delete remote files":"Smazat soubory na protějšku","Delete the local database":"Smazat místní databázi","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Smazat {{filecount}} souborů ({{filesize}}) ze vzdáleného úložiště?","Delete …":"Smazat…","Deleted":"Smazáno","Deleted Versions":"Smazané verze","Deleted files":"Smazané soubory","Deleting remote files …":"Mazání souborů na protějšku…","Deleting unwanted files …":"Mazání nepotřebných souborů…","Description (optional)":"Popis (volitelné)","Description:":"Popis:","Desktop":"Osobní počítač","Destination":"Cíl","Destination path":"Cílové umístění","Disabled":"Vypnuto","Dismiss":"Zavřít","Dismiss all":"Zavřít vše","Display and color theme":"Motiv vzhledu zobrazení a barev","Do you really want to delete the backup: \"{{name}}\" ?":"Opravdu chcete smazat zálohu: „{{name}}“?","Do you really want to delete the local database for: {{name}}":"Opravdu chcete smazat místní databázi pro: {{name}}","Done":"Hotovo","Download":"Stáhnout","Downloaded files":"Stažené soubory","Downloading files …":"Stahování souborů…","Downloading update…":"Stahování aktualizace…","Duplicate option {{opt}}":"Volba duplikace {{opt}}","Duplicati Website":"Webové stránky projektu Duplicati","Duplicati forum":"Diskuzní fórum o Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati se zahájí při spuštění, ale po dobu průběhu zůstane v pozastaveném stavu. Bude zabírat co nejméně systémových prostředků a nebudou spouštěny žádné zálohy.","Duration":"Doba trvání","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Ke každé záloze je přiřazena místní databáze, která uchovává informace o vzdálené záloze na místním stroji.\n Při mazání zálohy je také možné smazat lokální databázi aniž by tím byla postižena schopnost obnovovat vzdálené soubory.\n Pokud používáte místní databáze pro zálohy z příkazového řádku, měli byste databázi ponechat.","Edit as list":"Upravit jako seznam","Edit as text":"Upravit jako text","Edit …":"Upravit…","Encrypt file":"Zašifrovat soubor","Encryption":"Šifrování","Encryption changed":"Šifrování změněno","Encryption passphrase":"Šifrovací heslová fráze","End":"Konec","Enter URL":"Zadejte URL adresu","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Zadejte strategii uchovávání záloh ručně. Výplň je D/W/Y pro dny/týdny/roky a U pro neomezené. Forma zápisu je: 7D:1D,4W:1W,36M:1M. V tomto příkladu je ponechána jedna záloha z každého dne po dobu příštích 7 dnů, jedna z každého týdne po dobu příštích 4 týdnů a jedna z každého měsíce po dobu příštích 36 měsíců. Je možné zapsat také jako 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Zadejte záložní heslovou frázi, pokud existuje","Enter configuration details":"Zadejte podrobnosti nastavení","Enter encryption passphrase":"Zadejte šifrovací heslovou frázi","Enter expression here":"Sem zadejte výraz","Enter the destination path":"Zadejte popis cílového umístění ","Error":"Chyba","Error!":"Chyba!","Errors and crashes":"Chyby a pády","Examined":"Prozkoumáno","Exclude":"Vynechat","Exclude directories whose names contain":"Vynechat složky jejichž názvy obsahují","Exclude expression":"Výraz pro vynechané","Exclude file":"Vynechat soubor","Exclude file extension":"Vynechat soubory s příponami","Exclude files whose names contain":"Vynechat soubory jejichž názvy obsahují","Exclude filter group":"Skupina filtru vynechání","Exclude folder":"Vynechat složku","Exclude regular expression":"Regulární výraz pro vynechávané","Existing file found":"Nalezen existující soubor","Experimental":"Experimentální","Export":"Exportovat","Export backup configuration":"Exportovat zálohu nastavení","Export configuration":"Exportovat nastavení","Export passwords":"Exportovat hesla","Export …":"Export…","Exporting …":"Exportování…","External link":"Vnější odkaz","FTP (Alternative)":"FTP (alternativní)","Failed to build temporary database: {{message}}":"Nepodařilo se vytvořit dočasnou databázi: {{message}}","Failed to connect:":"Nepodařilo se připojit:","Failed to connect: {{message}}":"Nepodařilo se připojit: {{message}}","Failed to delete:":"Nepodařilo se smazat:","Failed to fetch path information: {{message}}":"Nepodařilo se stáhnout informaci o popisu umístění: {{message}}","Failed to find backup:":"Zálohu se nepodařilo nalézt:","Failed to read backup defaults:":"Nepodařilo se načíst výchozí parametry zálohy:","Failed to restore files: {{message}}":"Nepodařilo se obnovit soubory: {{message}}","Failed to save:":"Nepodařilo se uložit:","Fetching path information …":"Získávání informací o popisu umístění…","File":"Soubor","Files larger than:":"Soubory větší než:","Filters":"Filtry","Finished!":"Dokončeno!","First run setup":"Úvodní nastavení při prvním spuštění","Folder":"Složka","Folder path":"Popis umístění složky","Fri":"Pá","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS identifikátor projektu","General":"Obecné","General backup settings":"Obecná nastavení zálohy","General options":"Obecné volby","Generate":"Vytvořit","Generate IAM access policy":"Vytvořit IAM zásady přístupu","Getting file versions …":"Získávání verzí souboru…","Group email":"E-mail skupiny","Hidden files":"Skryté soubory","Hide":"Skrýt","Hide hidden folders":"Skrýt skryté složky","Home":"Domovská složka","Hostnames":"Názvy strojů","Hours":"Hodin","How do you want to handle existing files?":"Jak chcete zacházet s existujícími soubory?","Hyper-V Machine":"Hyper-V stroj","Hyper-V Machine:":"Hyper-V stroj:","Hyper-V Machines":"Hyper-V stroje","ID:":"Identifikátor:","If a date was missed, the job will run as soon as possible.":"Pokud chybělo datum, úloha bude spuštěna co možná nejdříve.","If at least one newer backup is found, all backups older than this date are deleted.":"Pokud je nalezena alespoň jedna novější záloha, všechny zálohy starší než tento datum budou smazány.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Pokud nezadáte popis umístění, všechny soubory budou uloženy v přihlašovací složce.\nJe to to, co chcete?","If you do not enter an API Key, the tenant name is required":"Pokud nezadáte klíč k API, je vyžadováno jméno nájemníka (tenant)","Import":"Import","Import Destination URL":"Importovat URL adresu cíle","Import backup configuration":"Importovat nastavení zálohy","Import from a file":"Importovat ze souboru","Import metadata":"Importovat metadata","Importing …":"Importování…","Include a file?":"Zahrnout soubor?","Include expression":"Výraz pro zahrnutí","Include regular expression":"Regulární výraz pro zahrnutí","Incorrect answer, try again":"Nesprávná odpověď, zkuste to znovu","Individual builds for developers only. Not for use with important data.":"Jednotlivá sestavení určená pouze pro vývojáře. Nepoužívejte pro důležitá data.","Information":"Informace","Invalid characters in path":"Neplatné znaky v popisu umístění","Invalid retention time":"Neplatná doba ponechání","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"K některým FTP serverům je možné se připojit i bez hesla.\nOpravdu to tento FTP server umožňuje?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Ponechat konkrétní počet záloh","Keep all backups":"Ponechat všechny zálohy","Keystone API version":"Verze aplikačního program. rozhraní stavebního bloku","Language in user interface":"Jazyk textů v uživatelském rozhraní","Last month":"Minulý měsíc","Last successful backup:":"Minulá úspěšná záloha:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Minulé úspěšné obnovení: {{time}} (trvalo {{duration || '0 sekund'}})","Latest":"Poslední","Libraries":"Knihovny","Listing backup dates …":"Vypisování datumů záloh…","Listing remote files for purge …":"Vypisování souborů na protějšku, které trvale vymazat…","Listing remote files …":"Vypisování souborů na protějšku…","Live":"Aktuální","Load a configuration from an exported job or a storage provider":"Načíst nastavení z exportované úlohy nebo z poskytovatele úložiště","Load destination from an exported job or a storage provider":"Načíst cíl z exportované úlohy nebo poskytovatele úložiště","Load older data":"Načíst starší data","Loading …":"Načítání…","Local Repository":"Místní repozitář","Local database path:":"Popis umístění místní databáze:","Local repository":"Místní repozitář","Local storage":"Místní úložiště","Location":"Umístění","Location where buckets are created":"Umístění, ve kterém jsou „nádoby“ (bucket) vytvářeny","Log data for {{Backup.Backup.Name}}":"Zaznamenávat (log) údaje pro {{Backup.Backup.Name}}","Log data from the server":"Zaznamenávat data ze serveru","Log out":"Odhlásit se","MByte":"MB","MByte/s":"MB/s","Maintenance":"Údržba","Manually type path":"Zadejte popis umístění ručně","Max download speed":"Nejvyšší rychlost stahování","Max upload speed":"Nejvyšší rychlost odesílání","Menu":"Nabídka","Microsoft SQL Database:":"Databáze Microsoft SQL:","Microsoft SQL Databases":"Databáze Microsoft SQL","Minimum redundancy":"Minimální redundance","Minimum redundancy is 1.0":"Minimální redundance je 1.0","Minutes":"Minut","Missing name":"Chybějící název","Missing passphrase":"Chybějící heslová fráze","Missing sources":"Chybějící zdroje","Modified":"Změněno","Mon":"Po","Months":"Měsíců","Move existing database":"Přesunout existující databázi","Move failed:":"Přesun se nezdařil:","My Documents":"Moje dokumenty","My Music":"Hudba","My Photos":"Fotografie","My Pictures":"Obrázky","Name":"Název","Never":"Nikdy","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nové uživatelské jméno je {{user}}.\nNyní budou používány přihlašovací údaje tohoto uživatele","Next":"Další","Next scheduled run:":"Příští naplánované spuštění:","Next scheduled task:":"Příští naplánovaná úloha:","Next task:":"Příští úloha:","Next time":"Příště","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Předtím nebyl určen žádný certifikát, ověřte se správcem serveru že klíč je správný: {{key}}\n\nSchvalujete tento klíč stroje?","No editor found for the "{{backend}}" storage type":"Nebyl nalezen žádný editor pro typ úložiště „{{backend}}“","No encryption":"Nešifrovat","No items selected":"Nejsou vybrané žádné položky","No items to restore, please select one or more items":"Žádné položky pro obnovení – vyberte alespoň jednu","No passphrase entered":"Není zadaná žádná heslová fráze","No scheduled tasks":"Žádné naplánované úlohy","Non-matching passphrase":"Zadání heslové fráze se neshodují","None / disabled":"Žádné / vypnuté","Not using encryption":"Nepoužívá šifrování","Nothing will be deleted. The backup size will grow with each change.":"Nic nebude smazáno. Velikost zálohy naroste při každé změně.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Jakmile je zde více záloh než zadané číslo, nejstarší zálohy budou smazané.","OpenStack AuthURI":"AuthURI pro OpenStack","OpenStack Object Storage / Swift":"Objektové úložiště OpenStack (Swift)","Opened":"Otevřeno","Operating System":"Operační systém","Operation":"Operace","Operations:":"Operace:","Optional authentication password":"Volitelné ověřovací heslo","Optional authentication username":"Volitelné uživatelské jméno pro ověření","Options":"Předvolby","Original location":"Původní umístění","Others":"Ostatní","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Po čase jsou zálohy automaticky odmazávány. Bude udržována jedna záloha z každého dne za minulých 7 dnů, jedna z každého týdne za minulé 4 týdny a jedna z každého měsíce za minulých 12 měsíců. A vždy zde bude přinejmenším jedna ponechaná záloha.","Overwrite":"Přepsat","Passphrase":"Heslová fráze","Passphrase (if encrypted)":"Heslová fráze (v případě, že je použito šifrování)","Passphrase changed":"Heslová fráze změněna","Passphrases are not matching":"Zadání heslové fráze se neshodují","Passphrases do not match":"Zadání heslové fráze se neshodují","Password":"Heslo","Patching files with local blocks …":"Opravování souborů pomocí místních bloků…","Path":"Popis umístění","Path not found":"Umístění nenalezeno","Path on server":"Popis umístění na serveru","Path or subfolder in the bucket":"Umístění nebo podsložka v „nádobě“ (bucket)","Pause":"Pozastavit","Pause after startup or hibernation":"Pozastavit po spuštění nebo hibernaci","Pause options":"Předvolby pozastavení","Permissions":"Přístupová práva","Pick location":"Vyberte umístění","Point to your backup files and restore from there":"Nasměrujte na soubory se zálohou a obnovte odsud","Port":"Port","Prevent tray icon automatic log-in":"Zabránit automatickému přihlašování ikony v oznamovací oblasti","Previous":"Předchozí","Progress:":"Postup:","ProjectID is optional if the bucket exist":"Pokud „nádoba“ (bucket) existuje, je identifikátor projektu (ProjectID) nepovinný","Proprietary":"Proprietární","Purge Phase":"Fáze trvalého mazání","Purging files complete!":"Trvalé smazání souborů dokončeno!","Purging files …":"Trvalé vymazávání souborů…","Rebuilding local database …":"Znovuvytváření místní databáze…","Recreate (delete and repair)":"Vytvořit znovu (smazat a opravit)","Recreate Database Phase":"Fáze znovuvytváření databáze","Recreating database …":"Znovuvytváření databáze…","Registering temporary backup …":"Registrace dočasné zálohy…","Relative paths not allowed":"Vztažené (relativní) popisy umístění není možné použít","Reload":"Načíst znovu","Remote":"Vzdálené","Remote Path":"Vzdálené umístění","Remote Repository":"Vzdálený repozitář","Remote path":"Vzdálené umístění","Remote repository":"Vzdálený repozitář","Remote volume size":"Velikost vzdáleného svazku","Remove":"Odebrat","Remove option":"Odebrat volbu","Removed files":"Odebrané soubory","Repair":"Opravit","Repair Phase":"Fáze oprav","Repairing database …":"Oprava databáze…","Repeat Passphrase":"Zopakování heslové fráze","Reporting:":"Hlášení:","Reset":"Resetovat","Restore":"Obnovit","Restore complete!":"Obnovení dokončeno!","Restore files":"Obnovit soubory","Restore files …":"Obnovit soubory…","Restore from":"Obnovit z","Restore from backup configuration":"Obnovit nastavení ze zálohy","Restore options":"Volby obnovení","Restore read/write permissions":"Obnovit práva pro čtení/zápis","Restored Files":"Obnovené soubory","Restored Folders":"Obnovené složky","Restored Symlinks":"Obnovené symbolické odkazy","Restoring files …":"Obnovování souborů…","Resume":"Pokračovat","Rewritten File Lists":"Seznamy přepsaných souborů","Run again every":"Spustit znovu každou","Run now":"Spustit nyní","Running commandline entry":"Spuštěná položka příkazového řádku","Running task:":"Spuštěná úloha:","Running …":"Spuštěné…","S3 Compatible":"Kompatibilní s S3","Same as the base install version: {{channelname}}":"Stejné jako základní nainstalovaná verze: {{channelname}}","Sat":"So","Satellite":"Satelit","Save":"Uložit","Save and repair":"Uložit a opravit","Save different versions with timestamp in file name":"Uložit různé verze odlišené časovou značkou v názvu souboru","Save immediately":"Okamžitě uložit","Scanning existing files …":"Skenování existujících souborů…","Scanning for local blocks …":"Skenování místních bloků…","Schedule":"Plán","Search":"Hledat","Search for files":"Hledat soubory","Seconds":"Sekund","Select a log level and see messages as they happen:":"Vyberte úroveň podrobnosti zaznamenávaných událostí a sledujte zprávy:","Select files":"Vybrat soubory","Server":"Server","Server and port":"Server a port","Server hostname or IP":"Název nebo IP adresa serveru","Server is currently paused,":"Server je nyní pozastavený,","Server is currently paused, do you want to resume now?":"Server je nyní pozastavený, chcete ho nyní znovu spustit?","Server password":"Heslo serveru","Server paused":"Server pozastaven","Server state properties":"Vlastnosti stavu serveru","Settings":"Nastavení","Show":"Zobrazit","Show advanced editor":"Zobrazit pokročilý editor","Show hidden folders":"Zobrazit skryté složky","Show log":"Zobrazit záznam událostí (log)","Show log …":"Zobrazit záznam událostí (log)…","Show treeview":"Zobrazit stromový pohled","Sia server password":"Heslo Sia serveru","Smart backup retention":"Chytrá doba uchovávání záloh","Some OpenStack providers allow an API key instead of a password and tenant name":"Někteří poskytovatelé OpenStack umožňují použití klíče k API namísto hesla a jména nájemníka (tenant)","Some S3 providers might only be compatible with a certain client library":"Někteří S3 poskytovatelé mohou být kompatibilní pouze s některými klientskými knihovnami","Source Data":"Zdrojová data","Source Files":"Zdrojové soubory","Source data":"Zdrojová data","Source folders":"Zdrojové složky","Source:":"Zdroj:","Specific builds for developers only. Not for use with important data.":"Konkrétní sestavení určená pouze pro vývojáře. Nepoužívejte pro důležitá data.","Standard protocols":"Standardní protokoly","Start":"Začátek","Starting backup …":"Spouštění zálohy…","Starting restore …":"Spouštění obnovení…","Starting the restore process …":"Spouštění procesu obnovení…","Stop after current file":"Zastavit po stávajícím souboru","Stop after the current file":"Zastavit po stávajícím souboru","Stop now":"Zastavit nyní","Stop running backup":"Zastavit probíhající zálohu","Stop running task":"Zastavit probíhající úlohu","Stopping after the current file:":"Zastavování pro stávajícím souboru:","Stopping task:":"Zastavování úlohy:","Storage Type":"Typ úložiště","Storage class":"Třída úložiště","Storage class for creating a bucket":"Třída úložiště pro vytváření „nádoby“ (bucket)","Stored":"Uloženo","Strong":"Silné","Success":"Úspěch","Sun":"Ne","Symbolic link":"Symbolický odkaz","System Files":"Systémové soubory","System default ({{levelname}})":"Systémové výchozí ({{levelname}})","System files":"Systémové soubory","System info":"Informace o systému","System properties":"Vlastnosti systému","TByte":"TB","TByte/s":"TB/s","Task is running":"Úloha je spuštěná","Temporary Files":"Dočasné soubory","Temporary files":"Dočasné soubory","Test Phase":"Fáze zkoušení","Test connection":"Vyzkoušet spojení","Testing permissions …":"Zkoušení přístupových práv…","Testing …":"Testování…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Kolonka „{{fieldname}}“ obsahuje neplatný znak: {{character}} (hodnota: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Záloha chybí, byla smazána?","The backup was temporary and does not exist anymore, so the log data is lost":"Záloha byla dočasná a už neexistuje, takže data záznamu událostí jsou ztracena","The bucket name should be all lower-case, convert automatically?":"Název nádoby by měl být malými písmeny, převést automaticky?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Nastavení by měla být uchovávána bezpečně. Opravdu chcete uložit nešifrovaný soubor obsahující vaše hesla?","The dark theme (by Michal)":"Tmavé téma vzhledu (od Michala)","The default blue on white theme (by Alex)":"Výchozí téma vzhledu modrá na bílé (od Alexe)","The folder {{folder}} does not exist.\nCreate it now?":"Složka {{folder}} neesxistuje.\nVytvořit nyní?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Klíč stroje se změnil, zkontrolujte se správcem serveru zda je správný, protože byste mohli být obětí útoku typu člověk uprostřed (man-in-the-midle).\n\nChcete NAHRADIT STÁVAJÍCÍ klíč stroje \"{{prev}}\" NAHLÁŠENÝM klíčem stroje: {{key}}?","The passwords do not match":"Zadání hesla se neshodují","The path does not appear to exist, do you want to add it anyway?":"Popisované umístění zdá se neexistuje, přejete si ho přidat i tak?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Dané umístění nekončí na znak „{{dirsep}}“, což znamená, že jste zahrnuli soubor, ne složku.\n\nChcete zahrnout daný soubor?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Je třeba, aby se jednalo o úplný popis umístění, tj. aby začínal dopředným lomítkem „/“","The region parameter is only applied when creating a new bucket":"Parametr region je použit pouze při vytváření nové „nádoby“ (bucket)","The region parameter is only used when creating a bucket":"Parametr region je použit pouze při vytváření „nádoby“ (bucket)","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certifikát serveru se nepodařilo ověřit.\nChcete schválit SSL certifikát s otiskem: {{hash}}?","The storage class affects the availability and price for a stored file":"Třída úložiště ovlivňuje dostupnost a cenu za uložení souboru","The target folder contains encrypted files, please supply the passphrase":"Cílová složka obsahuje zašifrované soubory, zadejte heslovou frázi","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Uživatel má příliš vysoká přístupová práva. Chcete vytvořit nového uživatele s právy omezenými pouze na vybraný popis umístění?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Tato záloha byla vytvořena na jiném operačním systému. Obnovení souborů bez zadání cílové složky může způsobit, že soubory budou obnoveny do neočekávaných míst. Opravdu chcete pokračovat bez zvolení cílové složky?","This month":"Tento měsíc","This week":"Tento týden","Throttle settings":"Nastavení přiškrcování","Thu":"Čt","Time":"Čas","To File":"Do souboru","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Svůj úmysl smazat všechny vzdálené soubory pro „{{name}}“ potvrďte opsáním níže uvedeného slova ","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pro exportování bez heslové fráze odškrtněte „Šifrovat soubor“","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Z důvodu prevence různým útokům prostřednictvím DNS, Duplicati omezuje názvy strojů, kterým je umožněn přístup na ty, vypsané zde. Přímý přístup na IP adresu a localhost je umožněn vždy. Je možné zadat vícero názvů strojů, oddělovaných středníkem. Pokud je některý z názvů povolených strojů hvězdička (*), je přístup umožněn ze všech strojů a tato funkce je vypnuta. Pokud kolonka není vyplněna, je umožněn přístup pouze na IP adresu a localhost.","Today":"Út","Trust host certificate?":"Důvěřovat certifikátu stroje?","Trust server certificate?":"Důvěřovat certifikátu serveru?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Vyzkoušejte nové funkce, na kterých pracujeme. V současnosti nejstabilnější dostupná verze. Pořádně si vyzkoušejte obnovu dat, než toto použijete v produkčních prostředích.","Tue":"Út","Type passphrase here.":"Sem zadejte heslovou frázi.","Type to highlight files":"Soubory zvýrazňujte psaním","Unknown backup size and versions":"Neznámá velikost a verze databáze","Until resumed":"Dokud není pokračováno","Update channel":"Aktualizační kanál","Update failed:":"Aktualizace se nezdařila:","Updating with existing database":"Aktualizace se stávající databází","Uploaded files":"Nahrané soubory","Uploading verification file …":"Nahrávání ověřovacího souboru…","Usage statistics":"Statistiky využití","Usage statistics, warnings, errors, and crashes":"Statistiky využití, varování, chyby a pády","Use SSL":"Použít SSL","Use existing database?":"Použít existující databázi?","Use weak passphrase":"Použít slabou heslovou frázi","Useless":"Nepoužitelné","User data":"Uživatelská data","User domain name":"Název domény uživatele","User has too many permissions":"Uživatel má příliš mnoho oprávnění","User interface settings":"Nastavení uživatelského rozhraní","Username":"Uživatelské jméno","Vacuuming database …":"Úklid v databázi…","Validating …":"Ověřování…","Verifications":"Ověřování","Verify files":"Ověřit soubory","Verifying answer":"Ověřování odpovědi","Verifying backend data …":"Ověřování dat podpůrné vrstvy (backend)…","Verifying files …":"Ověřování správnosti souborů…","Verifying remote data …":"Ověřování správnosti dat na protějšku…","Verifying restored files …":"Ověřování obnovených souborů…","Verifying …":"Ověřování…","Version ID":"Identif. verze","Very strong":"Velmi silné","Very weak":"Velmi slabé","Visit us on":"Navštivte nás na","WARNING: This will prevent you from restoring the data in the future.":"VAROVÁNÍ: toto zabrání v budoucnu obnovovat data!","Waiting for task to begin":"Čekání na zahájení úlohy","Waiting for upload to finish …":"Čeká se na dokončení nahrání…","Warnings, errors and crashes":"Varování, chyby a pády","We recommend that you encrypt all backups stored outside your system":"Doporučujeme šifrovat všechny zálohy, které jsou ukládány mimo váš stroj","Weak":"Slabé","Weak passphrase":"Slabá heslová fráze","Wed":"St","Weeks":"Týdny","Where do you want to restore from?":"Odkud chcete obnovit?","Where do you want to restore the files to?":"Kam chcete soubory obnovit?","Years":"Let","Yes":"Ano","Yes, I have stored the passphrase safely":"Ano, heslovou frázi mám bezpečně uloženou","Yes, I understand the risk":"Ano, rozumím riziku","Yes, I'm brave!":"Ano, mám odvahu!","Yes, please break my backup!":"Ano, chci rozbít své zálohy!","Yesterday":"Včera","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Měníte umístění databáze pryč z existující databáze.\nOpravdu je to to, co chcete?","You are currently running {{appname}} {{version}}":"Nyní provozujete {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Zálohu můžete zastavit po dokončení probíhajícího nahrávání souboru.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Úlohu můžete ukončit buď teď hned, nebo procesu umožnit zpracovat stávající soubor a pak zastavit.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Změnili jste režim šifrování. To může něco rozbít. Doporučujeme namísto toho vytvořit novou zálohu","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Změnili jste heslovou frázi, což není podporováno. Doporučujeme namísto toho vytvořit novou zálohu.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Zvolili jste že záloha nebude šifrována. Šifrování je doporučeno pro veškerá data ukládaná na vzdálený server.","You have chosen to restore to a new location, but not entered one":"Zvolili jste obnovu do nového umístění, ale nezadali jste ho","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vytvořili jste odolnou heslovou frázi. Tu si dobře uschovejte, protože v případě její ztráty data nebude možné obnovit.","You must choose at least one source folder":"Je třeba zvolit alespoň jednu zdrojovou složku","You must enter a domain name to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat doménový název","You must enter a name for the backup":"Je třeba zadat název zálohy","You must enter a passphrase or disable encryption":"Buď je třeba zadat heslovou frázi nebo šifrování vypnout","You must enter a password to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat heslo","You must enter a positive number of backups to keep":"Je třeba zadat kladný počet záloh které uchovávat","You must enter a tenant (aka project) name to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat název projektu (tenant)","You must enter a valid duration for the time to keep backups":"Je třeba zadat platnou dobu po kterou ponechávat zálohy","You must enter a valid retention policy string":"Je třeba zadat platný řetězec zásady doby uchovávání záloh","You must fill in the password":"Je třeba vyplnit heslo","You must fill in the server name or address":"Je třeba vyplnit název nebo adresu serveru","You must fill in the username":"Je třeba vyplnit uživatelské jméno","You must fill in {{field}}":"Je třeba vyplnit kolonku {{field}}","You must select or fill in the AuthURI":"Je třeba vybrat nebo vyplnit AuthURI","You must select or fill in the server":"Je třeba vybrat nebo vyplnit server","You must specify a path":"Je třeba zadat popis umístění","Your files and folders have been restored successfully.":"Soubory a složky byly úspěšně obnoveny.","Your passphrase is easy to guess. Consider changing passphrase.":"Vaše heslová fráze je snadno uhodnutelná. Zvažte volbu jiné.","bucket/folder/subfolder":"nadoba/slozka/podslozka","byte":"B","byte/s":"B/s","custom":"vlastní","resume now":"pokračovat nyní","unless you are explicitly specifying --group-id":"pokud výslovně neuvedete --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} bylo vyvinuto hlavně {{dev1}} a {{dev2}}. {{appname}} je možné si stáhnout z {{websitename}}. {{appname}} je šířeno pod licencí {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} souborů ({{size}}) zbývá {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verze","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verze","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzí","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzí"],"{{number}} Hour":"{{number}} hodin","{{number}} Hours":"{{number}} hodin","{{number}} Minutes":"{{number}} minut","{{time}} (took {{duration}})":"{{time}} (trvalo {{duration}})"}); - gettextCatalog.setStrings('da', {"- pick an option -":"- vælg indstilling -","...loading...":"...indlæser...","API key":"API Key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Om","About {{appname}}":"Om {{appname}}","Access Key":"Access Key","Access denied":"Adgang nægtet","Access grant":"Adgang godkendt","Access to user interface":"Adgang til brugerflade","Account name":"Kontonavn","Add a new backup":"Tilføj en ny backup","Add a path directly":"Tilføj en sti","Add advanced option":"Tilføj en avanceret indstilling","Add backup":"Tilføj backup","Add filter":"Tilføj filter","Add path":"Tilføj sti","Added":"Tilføjet","Adjust bucket name?":"Tilpas bucket navnet?","Advanced Options":"Avancerede indstillinger","Advanced options":"Avancerede indstillinger","Advanced:":"Avanceret:","All Hyper-V Machines":"Alle Hyper-V maskiner","All Microsoft SQL Databases":"Alle Microsoft SQL databaser","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle brugsrapporter bliver sendt anonymt og indeholder ikke personlige oplysninger. De indeholder oplysninger om hardware, operativsystem, destinationstype, backup varighed, backup størrelse og lignende information. De indeholder ikke stier, filnavne, brugernavne, adgangskoder eller lignende følsom information.","Allow remote access (requires restart)":"Tillad fjernadgang (kræver genstart)","Allowed days":"Tilladte dage","An existing file was found at the new location":"En eksisterende fil blev fundet på den nye placering","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"En eksisterende fil blev funder på den nye placering.\nEr du sikker på at du vil have databasen til at pege på en eksisterende fil?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"En eksisterende lokal database for destinationen er fundet.\nHvis du genbruger databasen, kan du bruge både kommandolinie og serveren til at arbejde på samme destination.\n\nVil du bruge den eksisterende database?","Anonymous usage reports":"Anonyme brugsstatistiker","Applications":"Applikationer","As Command-line":"Som kommandolinie","AuthID":"AuthID","Authentication method":"Godkendelsesmetode","Authentication method ({{auth_method}})":"Godkendelsesmetode ({{auth_method}})","Authentication password":"Adgangskode til godkendelse","Authentication username":"Brugernavn til godkendelse","Autogenerated passphrase":"Autogenereret adgangssætning","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Tilbage","Backup complete!":"Backup fuldført!","Backup destination":"Backup destination","Backup location":"Backup placering","Backup retention":"Backup fastholdelse","Backup:":"Backup:","Beta":"Beta","Broken access":"Adgang defekt","Browse":"Gennemse","Browser default":"Browser standard","Bucket create location":"Bucket placering ved oprettelse","Bucket name":"Bucket navn","Bucket storage class":"Bucket storage class","Building list of files to restore …":"Opbygger liste af filer for gendannelse ...","Building partial temporary database …":"Bygger en midlertidig database ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ved at tillade fjernadgang vil serveren lytte efter forespørgsler fra en hver maskine på dit netværk. Hvis du slår denne indstilling til, så vær sikker på at computeren er på et sikkert netværk beskyttet af en firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Som standard vil systembakke-ikonet åbne brugerfladen med en token der låser applikationen op. Dette sikrer at du kan tilgå brugerfladen fra systembakke-ikonet, mens andre brugerkonti skal indtaste en adgangskode. Foretrækker du at skulle skrive adgangskoden, selv når du åbner via systembakke-ikonet, så slå denne indstilling til.","Cache Files":"Cache Filer","Canary":"Canary","Cancel":"Annuller","Cannot move to existing file":"Kan ikke flytte til eksisterende fil","Changelog":"Ændringslog","Changelog for {{appname}} {{version}}":"Ændringslog for {{appname}} {{version}}","Check failed:":"Kontrol fejlede:","Check for updates now":"Tjek for opdateringer nu","Checking for updates …":"Leder efter opdateringer ...","Chose a storage type to get started":"Valgte en destinationstype at komme i gang","Click the AuthID link to create an AuthID":"Click på AuthID linket for at oprettet et AuthID","Click to set throttle options":"Klik for at sætte hastigheds begrænsning","Client library to use":"Klient bibliotek som skal bruges","Commandline …":"Kommandolinie ...","Compact Phase":"Komprimeringsfase","Compact now":"Komprimer nu","Compacting remote data …":"Komprimerer data på destinationen ...","Complete log":"Samlet log","Completing backup …":"Fuldfører backup ...","Completing previous backup …":"Fuldfører forrige backup ...","Computer":"Computer","Configuration file:":"Konfigurationsfil:","Configuration:":"Konfiguration:","Configure a new backup":"Indstil en ny backup","Confirm delete":"Bekræft sletning","Confirm encryption passphrase":"Bekræft krypteringskoden","Confirm passphrase":"Bekræft adgangskode","Confirmation required":"Bekræftelse kræves","Connect":"Forbind","Connect now":"Forbind nu","Connecting to server …":"Forbinder til server ...","Connection lost":"Forbindelse mistet","Connection worked!":"Forbindelsen virkede!","Container name":"Container navn","Container region":"Container region","Continue":"Fortsæt","Continue without encryption":"Fortsæt uden kryptering","Copied!":"Kopieret!","Copy":"Kopier","Copy Destination URL to Clipboard":"Kopier URL-destinationsadressen til udklipsholder","Copy failed. Please manually copy the URL":"Kopiering mislykkedes. Kopier venligst URL-adressen manuelt","Core options":"Grund indstillinger","Counting ({{files}} files found, {{size}})":"Tæller ({{files}} filer fundet, {{size}})","Crashes only":"Kun nedbrud","Create bug report …":"Opret fejlrapport ...","Create folder?":"Opret mappe?","Created new limited user":"Opret en ny begrænset bruger","Creating bug report …":"Opretter fejlrapport ...","Creating new user with limited access …":"Opretter en ny bruger med begrænset adgang ...","Creating target folders …":"Opretter destinations mapper ...","Creating temporary backup …":"Opretter en midlertidig backup ...","Current action:":"Nuværende handling:","Current file:":"Nuværende fil:","Current version is {{versionname}} ({{versionnumber}})":"Nuværende version er {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Brugerdefineret S3 endpoint","Custom Satellite":"Brugerdefineret Satellit","Custom Satellite ({{satellite}})":"Brugerdefineret Satellit ({{satellite}})","Custom authentication url":"Brugerdefineret godkendelses url","Custom backup retention":"Brugerdefineret backup fastholdelse","Custom location ({{server}})":"Brugerdefineret placering ({{server}})","Custom region for creating buckets":"Brugerdefineret region for at oprette buckets","Custom region value ({{region}})":"Brugerdefineret regions værdi ({{region}})","Custom server url ({{server}})":"Brugerdefineret server url ({{server}})","Custom storage class ({{class}})":"Brugerdefineret storage class ({{klasse}})","Database …":"Database ...","Days":"Dage","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standard ekskluderinger","Default options":"Standardindstillinger","Delete":"Slet","Delete Phase (Old Backup Versions)":"Slettefase (Gamle backup-versioner)","Delete backup":"Slet backup","Delete backups that are older than":"Slet sikkerhedskopier, der er ældre end","Delete local database":"Slet lokal database","Delete remote files":"Slette filer fra destinationen","Delete the local database":"Slet den lokale database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Slet {{filecount}} filer ({{filesize}}) fra destinationen?","Delete …":"Slet ...","Deleted":"Slettet","Deleted Versions":"Slettede versioner","Deleted files":"Slettede filer","Deleting remote files …":"Sletter filer fra destinationen ...","Deleting unwanted files …":"Sletter uønskede filer ...","Description (optional)":"Beskrivelse (valgfrit)","Description:":"Beskrivelse:","Desktop":"Skrivebord","Destination":"Destination","Destination path":"Destinations sti","Disabled":"Deaktiveret","Dismiss":"Afvis","Dismiss all":"Afvis alle","Display and color theme":"Visning og farvevalg","Do you really want to delete the backup: \"{{name}}\" ?":"Vil du virkelig slette backupen: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Vil du virkelig slette den lokale database for: {{navn}}","Done":"Færdig","Download":"Download","Downloaded files":"Downloadede filer","Downloading files …":"Downloader filer ...","Downloading update…":"Downloader opdatering ...","Duplicate option {{opt}}":"Dublet af indstilling {{opt}}","Duplicati Website":"Duplicati hjemmeside","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati kører når startet, men forbliver i pause-tilstand. Duplicati optager minimale systemressourcer og ingen backups vil køre.","Duration":"Varighed","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Hver backup har en lokal database tilknyttet, som gemmer information om data på fjerndestinationen lokalt på maskinen.\nNår du sletter en backup kan du også slette den lokale database uden at dette påvirker muligheden for at gendanne filer.\nHvis du bruger den lokale database til at køre backup via kommandolinien skal du beholde databasen.","Edit as list":"Rediger som liste","Edit as text":"Rediger som tekst","Edit …":"Rediger ...","Encrypt file":"Krypter fil","Encryption":"Kryptering","Encryption changed":"Kryptering ændret","Encryption passphrase":"Krypteringssætning","End":"Afsluttet","Enter URL":"Indtast URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Indtast manuelt en fastholdelsesstrategi. Variablerne er D/W/Y for henholdsvis dage/uger/år or U for ubegrænset. Syntaksen er: 7D:1D,4W:1W,36M:1M. Dette eksempel fastholder én backup for hver af de næste 7 dage, én for hver af de næste 4 uger og én for hver af de næste 36 måneder. Det samme kan også opnås ved at skrive 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Indtast adgangssætning til backup, hvis defineret","Enter configuration details":"Indtast konfigurationsdetaljer","Enter encryption passphrase":"Indtast adgangssætning til kryptering","Enter expression here":"Indtast udtryk her","Enter the destination path":"Indtast destinations stien","Error":"Fejl","Error!":"Fejl!","Errors and crashes":"Fejl og nedbrud","Examined":"Undersøgt","Exclude":"Eksludér","Exclude directories whose names contain":"Ekskluder mapper hvor navnet indeholder","Exclude expression":"Excluder udtryk","Exclude file":"Excluder fil","Exclude file extension":"Ekskluder filendelse","Exclude files whose names contain":"Ekskluder filer hvor navnet indeholder","Exclude filter group":"Ekskluderings filter gruppe","Exclude folder":"Ekskluder mappe","Exclude regular expression":"Ekskluder regulært udtryk","Existing file found":"Eksisterende fil fundet","Experimental":"Eksperimental","Export":"Eksporter","Export backup configuration":"Eksporter backup konfiguration","Export configuration":"Eksporter konfiguration","Export passwords":"Eksportér adgangskoder","Export …":"Eksport ...","Exporting …":"Eksporterer ...","External link":"Eksternt link","FTP (Alternative)":"FTP (alternativ)","Failed to build temporary database: {{message}}":"Kunne ikke bygge midlertidig database: {{message}}","Failed to connect:":"Kunne ikke forbinde:","Failed to connect: {{message}}":"Kunne ikke forbinde: {{message}}","Failed to delete:":"Kunne ikke slette:","Failed to fetch path information: {{message}}":"Kunne ikke hente sti-information: {{message}}","Failed to find backup:":"Kunne ikke finde backup:","Failed to read backup defaults:":"Kunne ikke læse backup standardværdier:","Failed to restore files: {{message}}":"Kunne ikke gendanne filer: {{message}}","Failed to save:":"Kunne ikke gemme:","Fetching path information …":"Henter information om stier ...","File":"Fil","Files larger than:":"Filer større end:","Filters":"Filtre","Finished!":"Færdig!","First run setup":"Førstegangsopsætning","Folder":"Mappe","Folder path":"Mappe sti","Fri":"Fre","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Projekt ID","General":"Generelt","General backup settings":"Generelle backup indstillinger","General options":"Generelle indstillinger","Generate":"Generér","Generate IAM access policy":"Generér IAM access policy","Getting file versions …":"Henter fil versioner ...","Group email":"Gruppe email","Hidden files":"Skjulte filer","Hide":"Skjul","Hide hidden folders":"Skjul skjulte filer","Home":"Hjem","Hostnames":"Hostnavne","Hours":"Timer","How do you want to handle existing files?":"Hvordan vil du håndtere eksisterende filer?","Hyper-V Machine":"Hyper-V maskine","Hyper-V Machine:":"Hyper-V maskine:","Hyper-V Machines":"Hyper-V maskiner","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Hvis der ikke blev kørt på det angivne tidspunkt, vil jobbet køre så hurtigt som muligt.","If at least one newer backup is found, all backups older than this date are deleted.":"Hvis der findes mindst en nyere sikkerhedskopi, slettes alle backups, der er ældre end denne dato.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Hvis du ikke indtaster en sti, vil alle filer blive gemt i login mappen.\nEr du sikke på at det er det du vil gøre?","If you do not enter an API Key, the tenant name is required":"Hvis du ikke indtaster en API key, skal du angive tenant navnet","Import":"Importér","Import Destination URL":"Importer destinations URL","Import backup configuration":"Importer backup konfiguration","Import from a file":"Importer fra en fil","Import metadata":"Importer metadata","Importing …":"Importerer ...","Include a file?":"Inkluder en fil?","Include expression":"Inkluder udtryk","Include regular expression":"Inkluder regulært udtryk","Incorrect answer, try again":"Forkert svar, prøv igen","Individual builds for developers only. Not for use with important data.":"Individuelle versioner kun for udviklere. Bør ikke bruges med vigtig data.","Information":"Information","Invalid characters in path":"Ugyldige tegn i stien","Invalid retention time":"Ugyldig bevaringstid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Det er muligt at oprette forbindelse til visse FTP servere uden adgangskode.\nEr du sikker på din FTP-server understøtter login uden adgangskode?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Gem et bestemt antal backups","Keep all backups":"Gem alle backups","Keystone API version":"Keystone API version","Language in user interface":"Sprog i brugergrænsefladen","Last month":"Sidste måned","Last successful backup:":"Sidst gennemførte backup:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Sidste gennemførte gendannelse: {{time}} (took {{duration || '0 seconds'}})","Latest":"Nyeste","Libraries":"Biblioteker","Listing backup dates …":"Noterer backup datoer...","Listing remote files for purge …":"Noterer filer fra destinationen til rensning ...","Listing remote files …":"Noterer filer fra destinationen ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Indlæs konfiguration fra en eksporteret fil eller en pladsudbyder","Load destination from an exported job or a storage provider":"Indlæs destination fra en eksporteret fil eller en pladsudbyder","Load older data":"Indlæs ældre data","Loading …":"Indlæser ...","Local Repository":"Lokal fortegnelse","Local database path:":"Lokal database sti:","Local repository":"Lokal fortegnelse","Local storage":"Local opbevaring","Location":"Placering","Location where buckets are created":"Placering hvor buckets bliver oprettet","Log data for {{Backup.Backup.Name}}":"Logdata for {{Backup.Backup.Name}}","Log data from the server":"Logdata fra serveren","Log out":"Log ud","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Vedligehold","Manually type path":"Indtast en sti manuelt","Max download speed":"Max downloadhastighed","Max upload speed":"Maks uploadhastighed","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Databaser","Minimum redundancy":"Mindste tilladte redundans","Minimum redundancy is 1.0":"Mindste redundans er 1.0","Minutes":"Minutter","Missing name":"Navn mangler","Missing passphrase":"Adgangssætning mangler","Missing sources":"Kilder mangler","Modified":"Ændret","Mon":"Man","Months":"Måneder","Move existing database":"Flyt eksisterende database","Move failed:":"Flytning fejlede:","My Documents":"Mine dokumenter","My Music":"Min musik","My Photos":"Mine foto","My Pictures":"Mine billeder","Name":"Navn","Never":"Aldrig","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nyt bruger navn er {{user}}.\nLoginoplysninger er opdateret til den nye begrænsede bruger","Next":"Næste","Next scheduled run:":"Næste planlagte kørsel:","Next scheduled task:":"Næste planlagte opgave:","Next task:":"Næste opgave:","Next time":"Næste tidspunkt","No":"Nej","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Intet certifikat har været anvendt før, kontroller venligst at nøglen er korrekt hos serveradministratoren: {{key}} \n\nVil du godkende den angivne nøgle?","No editor found for the "{{backend}}" storage type":"Ingen editor blev fundet for "{{backend}}" destinationen","No encryption":"Ingen kryptering","No items selected":"Ingen emner valgt","No items to restore, please select one or more items":"Ingen emner er valgt til gendannelse, vælg venligst en eller flere emner","No passphrase entered":"Ingen adgangssætning angivet","No scheduled tasks":"Ingen planlagte opgaver","Non-matching passphrase":"Uoverenstemmelse mellem adgangssætninger","None / disabled":"Ingen / deaktiveret","Not using encryption":"Uden kryptering","Nothing will be deleted. The backup size will grow with each change.":"Intet vil blive slettet. Backup størrelsen vokser med hver ændring.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Når der er flere backups end det angivne antal, slettes de ældste sikkerhedskopier.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Åbnet","Operating System":"Operativ System","Operation":"Operation","Operations:":"Operationer:","Optional authentication password":"Valgfri adgangskode til godkendelse","Optional authentication username":"Valgfrit brugernavn til godkendelse","Options":"Indstillinger","Original location":"Oprindelig placering","Others":"Andre","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Over tid vil backups blive slettet automatisk. Der vil forblive en backup for hver af de sidste 7 dage, hver af de sidste 4 uger, hver af de sidste 12 måneder. Der vil altid være mindst en tilbageværende backup.","Overwrite":"Overskriv","Passphrase":"Adgangssætning","Passphrase (if encrypted)":"Adgangssætning (hvis krypteret)","Passphrase changed":"Adgangssætning ændret","Passphrases are not matching":"Adgangssætninger er ikke ens","Passphrases do not match":"Adgangssætninger er ikke identiske","Password":"Adgangskode","Patching files with local blocks …":"Opdaterer filer med lokale blokke ...","Path":"Sti","Path not found":"Stien blev ikke fundet","Path on server":"Sti på server","Path or subfolder in the bucket":"Sti eller undermappe i bucket","Pause":"Pause","Pause after startup or hibernation":"Pause efter start eller dvale","Pause options":"Pause indstillinger","Permissions":"Tilladelser","Pick location":"Vælg placering","Point to your backup files and restore from there":"Udpeg dine backup-filer og gendan fra dem","Port":"Port","Prevent tray icon automatic log-in":"Forhindre automatisk login-in fra system ikonet","Previous":"Forrige","Progress:":"Fremgang:","ProjectID is optional if the bucket exist":"ProjectID er valgfrit hvis bucket eksisterer","Proprietary":"Proprietære","Purge Phase":"Rensningsfase","Purging files complete!":"Rensning af filer gennemført!","Purging files …":"Fjerner filer ...","Rebuilding local database …":"Genopbygger lokal database ...","Recreate (delete and repair)":"Gendan (slet og reparer)","Recreate Database Phase":"Database gendannelsesfase ...","Recreating database …":"Gendanner database ...","Registering temporary backup …":"Registrerer midlertidig backup ...","Relative paths not allowed":"Relative stier er ikke tilladt","Reload":"Genindlæs","Remote":"Destination","Remote Path":"Destinations sti","Remote Repository":"Ekstern fortegnelse","Remote path":"Destinations sti","Remote repository":"Ekstern fortegnelse","Remote volume size":"Volume størrelse","Remove":"Fjern","Remove option":"Fjern indstilling","Removed files":"Fjernede filer","Repair":"Reparer","Repair Phase":"Reparationsfase","Repairing database …":"Reparere database ...","Repeat Passphrase":"Gentag adgangssætning","Reporting:":"Rapporterer:","Reset":"Nulstil","Restore":"Gendan","Restore complete!":"Gendannelse fuldført!","Restore files":"Gendan filer","Restore files …":"Gendan filer ...","Restore from":"Gendan fra","Restore from backup configuration":"Gendan fra konfiguration i backup","Restore options":"Indstillinger til gendannelse","Restore read/write permissions":"Gendan læse/skrive tilladelser","Restored Files":"Gendannede filer","Restored Folders":"Gendannede mapper","Restored Symlinks":"Gendannede Symlinks","Restoring files …":"Gendanner filer ...","Resume":"Genoptag","Rewritten File Lists":"Genskrevne fil-lister","Run again every":"Kør igen hver","Run now":"Kør nu","Running commandline entry":"Kører kommandolinie opgave","Running task:":"Kørende opgave:","Running …":"Kører ...","S3 Compatible":"S3 kompatibel","Same as the base install version: {{channelname}}":"Samme som grundinstallationsversionen: {{channelname}}","Sat":"Lør","Save":"Gem","Save and repair":"Gem og reparer","Save different versions with timestamp in file name":"Gem forskellige versioner med tidstempel i filnavnet","Save immediately":"Gem med det samme","Scanning existing files …":"Skanner eksisterende filer ...","Scanning for local blocks …":"Scanner for lokale blokke ...","Schedule":"Planlagt","Search":"Søg","Search for files":"Søg efter filer","Seconds":"Sekunder","Select a log level and see messages as they happen:":"Vælg et log niveau og se beskeder som de kommer:","Select files":"Vælg filer","Server":"Server","Server and port":"Server og port","Server hostname or IP":"Server navn eller IP","Server is currently paused,":"Serveren er sat på pause.","Server is currently paused, do you want to resume now?":"Serveren er sat på pause, vil du genoptage med det samme?","Server password":"Server adgangskode","Server paused":"Server på pause","Server state properties":"Egenskaber for serveren","Settings":"Indstillinger","Show":"Vis","Show advanced editor":"Vis avanceret redigering","Show hidden folders":"Vis skjulte mapper","Show log":"Vis log","Show log …":"Vis log ...","Show treeview":"Vis træstruktur","Sia server password":"Sia server adgangskode","Smart backup retention":"Smart backupfastholdelse","Some OpenStack providers allow an API key instead of a password and tenant name":"Visse OpenStack udbydere tillader en API nøgle istedet for en adgangskode og et tenant navn","Source Data":"Kilde data","Source Files":"Kilde filer","Source data":"Kilde data","Source folders":"Kilde mapper","Source:":"Kilde:","Specific builds for developers only. Not for use with important data.":"Specifikke versioner kun til udviklere. Bør ikke bruges med vigtig data.","Standard protocols":"Standard protokoller","Start":"Start","Starting backup …":"Starter backup ...","Starting restore …":"Starter gendannelse ...","Starting the restore process …":"Starter gendannelses processen ...","Stop after current file":"Stop efter den nuværende fil","Stop after the current file":"Stop efter den nuværende fil","Stop now":"Stop nu","Stop running backup":"Stop den kørende backup","Stop running task":"Stop den kørende opgave","Stopping after the current file:":"Stopper efter den nuværende fil:","Stopping task:":"Stopper opgave:","Storage Type":"Opbevaringstype","Storage class":"Opbevaringsklasse","Storage class for creating a bucket":"Opbevaringsklasse når der oprettes en bucket","Stored":"Gemt","Strong":"Stærk","Success":"Succes","Sun":"Søn","Symbolic link":"Symbolsk kæde","System Files":"System Filer","System default ({{levelname}})":"System standard ({{levelname}})","System files":"System filer","System info":"System info","System properties":"System egenskaber","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Opgave kører","Temporary Files":"Midlertidige Filer","Temporary files":"Midlertidige filer","Test Phase":"Testfase","Test connection":"Test forbindelse","Testing permissions …":"Tester tilladelser ...","Testing …":"Tester ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"'{{fieldname}}' feltet indeholder ugyldige karakterer: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Backup mangler, er den blevet slettet?","The backup was temporary and does not exist anymore, so the log data is lost":"Backup var midlertidig og eksisterer ikke længere, log data er dermed tabt","The bucket name should be all lower-case, convert automatically?":"Bucket navnet bør være med små bogstaver, konverter automatisk?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Opsætningen bør holdes hemmelig. Er du sikker på at du vil gemme en ikke-krypteret fil der indeholder dine adgangskoder?","The dark theme (by Michal)":"Mørke farver (af Michal)","The default blue on white theme (by Alex)":"Standard blå på hvid (af Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Mappen {{folder}} eksisterer ikke.\nOpret den nu?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Nøglen fra værten er ændret, kontroller venligst med server administratoren om dette er korrekt, ellers kan du være offer for et MAN-IN-THE-MIDDLE angreb.\n\nVil du ERSTATTE din NUVÆRENDE værtsnøgle \"{{prev}}\" med den RAPPORTEREDE værtsnøgle: {{key}}?","The passwords do not match":"Adgangskoderne er ikke ens","The path does not appear to exist, do you want to add it anyway?":"Stien ser ikke ud til at findes, vil du tilføje den alligevel?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Stien slutter ikke med '{{dirsep}}' tegnet, hvilket betyder at du inkluderer en file og ikke en mappe.\n\nVil du inkludere den valgte fil?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Stien skal være en absolut sti, altså skal den starte med '/'","The region parameter is only applied when creating a new bucket":"Regionsparameteren anvendes kun når der oprettes en ny bucket","The region parameter is only used when creating a bucket":"Regionsparameteren bruges kun når der oprettes en ny bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Server certifikatet kunne ikke valideres.\nVil du godkende SSL certifikatet med dette hash: {{hash}}?","The storage class affects the availability and price for a stored file":"Opbevaringsklasen påvirker tilgængeligheden og prisen for en opbevaret fil","The target folder contains encrypted files, please supply the passphrase":"Destinationsmappen indeholder krypterede filer, angiv venligst adgangssætningen","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Brugeren har for mange tilladelser. Vil du oprette en ny begrænset bruger der kun har adgang til den valgte sti?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Denne backup blev oprettet på et andet operativsystem. Når der gendannes filer uden at angive en destination, kan disse blive oprettet på uventede placeringer. Er du sikker på at du vil fortsætte uden at vælge en destinationsmappe?","This month":"Denne måned","This week":"Denne uge","Throttle settings":"Indstillinger for hastighedsbegrænsning","Thu":"Tor","Time":"Tid","To File":"Til fil","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"For at bekræfte at du vil slette all fjernfiler til \"{{name}}\", indtast venligst det ord ud ser herunder","To export without a passphrase, uncheck the \"Encrypt file\" box":"For at eksportere uden en adgangsætning, fjern mærket ud for \"Krypter filen\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"For at forhindre forskellige DNS baserede angreb svarer Duplicati kun på hostnavne der er angivet her. Direkte adgang over IP eller localhost er altid tilladt. Flere hostnavne kan angives med en semikolonseparator. Hvis nogen af de tilladte hostnavne er en stjerne (*), vil alle hostnavne være tilladt og denne indstilling slået fra. Hvis feltet er tomt vil kun IP addresse og localhost adgangvære tilladt.","Today":"I dag","Trust host certificate?":"Stol på værtscertifikatet?","Trust server certificate?":"Stol på server certifikatet?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Prøv nye funktioner vi arbejder på. Den mest stabile version tilgængelig på nuværende tidspunkt. Test gendannelse af data før du bruger dette i produktions miljøer.","Tue":"Tir","Type passphrase here.":"Indtast adgangssætning her.","Type to highlight files":"Skriv for at markere filer","Unknown backup size and versions":"Ukendt backup størrelse og versionsantal","Until resumed":"Indtil genoptaget","Update channel":"Opdateringskanal","Update failed:":"Opdatering fejlede:","Updating with existing database":"Opdaterer med eksisterende database","Uploaded files":"Uploadede filer","Uploading verification file …":"Uploader verifikationsfil ...","Usage statistics":"Brugsstatistik","Usage statistics, warnings, errors, and crashes":"Brugsstatistik, advarsler, fejl og nedbrud","Use SSL":"Brug SSL","Use existing database?":"Brug eksisterende database?","Use weak passphrase":"Brug svag adgangssætning","Useless":"Ubrugelig","User data":"Brugerdata","User domain name":"Bruger domæne navn","User has too many permissions":"Brugeren har for mange tilladelser","User interface settings":"Indstillinger til brugergrænseflade","Username":"Brugernavn","Vacuuming database …":"Støvsuger databasen ...","Validating …":"Validerer ...","Verifications":"Verificeringer","Verify files":"Verificer filer","Verifying answer":"Verificerer svar","Verifying backend data …":"Verificerer destinations data ...","Verifying files …":"Verificerer filer ...","Version ID":"Versions-id","Very strong":"Meget stærk","Very weak":"Meget svag","Visit us on":"Besøg os på","WARNING: This will prevent you from restoring the data in the future.":"ADVARSEL: Dette vil forhindre dig i at gendanne data i fremtiden.","Waiting for task to begin":"Venter på at opgaven starter","Warnings, errors and crashes":"Advarsler, fejl og nedbrud","We recommend that you encrypt all backups stored outside your system":"Vi anbefaler at du krypterer alle backups der er gemt uden for dit system","Weak":"Svag","Weak passphrase":"Svag adgangssætning","Wed":"Ons","Weeks":"Uger","Where do you want to restore from?":"Hvor vil du gerne gendanne fra?","Where do you want to restore the files to?":"Hvor vil du gendanne filerne til?","Years":"År","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, jeg har opbevaret adgangssætningen sikkert","Yes, I understand the risk":"Ja, jeg forstår risikoen","Yes, I'm brave!":"Ja, jeg er modig!","Yes, please break my backup!":"Ja, ødelæg venligst min backup!","Yesterday":"I går","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Du er ved at ændre database stien væk fra en eksisterende database.\nEr du sikker på at det er det du vil?","You are currently running {{appname}} {{version}}":"Du kører med {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Du har skiftet krypteringsmetode. Dette kan ødelægge ting. Du opfordres til at oprette en ny backup i stedet.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Du har skiftet adgangssætningen, hvilket ikke understøttes. Du opfordres til at oprette en ny backup i stedet.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Du har valgt at undlade at kryptere din backup. Kryptering anbefales for alt data der gemmes på en fjerndestination.","You have chosen to restore to a new location, but not entered one":"Du har valgt at gendanne til en ny placering, men ikke angivet en","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Du har genereret en stærk adgangssætning. Sørg for, at du har en sikker kopi, da data ikke kan gendannes, hvis du mister adgangssætningen.","You must choose at least one source folder":"Du skal vælge mindst en kilde mappe","You must enter a domain name to use v3 API":"Du er nødt til at angive et domæne navn for at bruge v3 API'en","You must enter a name for the backup":"Du skal angive et navn for denne backup","You must enter a passphrase or disable encryption":"Du skal indtaste en adgangssætning eller fravælge kryptering","You must enter a password to use v3 API":"Du skal angive en adgangskode for at bruge v3 API'en","You must enter a positive number of backups to keep":"Du skal indtaste et positivt antal backups der skal bevares","You must enter a tenant (aka project) name to use v3 API":"Du er nødt til at angive et tenant (projekt) navn for at bruge v3 API'en","You must enter a valid duration for the time to keep backups":"Du skal angive en gyldig periode som backups gemmes i","You must fill in the password":"Du skal angive en adgangskode","You must fill in the server name or address":"Du skal angive server navnet eller adressen","You must fill in the username":"Du skal angive et brugernavn","You must fill in {{field}}":"Du skal udfylde {{field}}","You must select or fill in the AuthURI":"Du skal vælge eller udfylde AuthURI","You must select or fill in the server":"Du skal vælge eller indtaste server navnet","You must specify a path":"Du skal angive en sti","Your files and folders have been restored successfully.":"Dine filer og mapper blev gendannet korrekt.","Your passphrase is easy to guess. Consider changing passphrase.":"Din kodesætning er let at gætte. Overvej at skifte den.","bucket/folder/subfolder":"buvket/mappe/undermappe","byte":"byte","byte/s":"byte/s","custom":"tilpasset","resume now":"genoptag nu","unless you are explicitly specifying --group-id":"Medmindre du eksplicit angiver --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} er primært udviklet af {{dev1}} og {{dev2}}. {{appname}} kan downloades fra {{websitename}}. {{appname}} er licenseret med {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} filer ({{size}}) tilbage {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versioner"],"{{number}} Hour":"{{number}} Timer","{{number}} Hours":"{{number}} Timer","{{number}} Minutes":"{{number}} Minutter","{{time}} (took {{duration}})":"{{time}} (varighed: {{duration}})"}); - gettextCatalog.setStrings('de', {"- pick an option -":"- Option auswählen -","...loading...":"...laden...","API key":"API-Key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Über","About {{appname}}":"Über {{appname}}","Access Key":"Zugriffsschlüssel","Access denied":"Zugriff verweigert","Access grant":"Zugriffs-Grant","Access to user interface":"Zugriff auf die Benutzeroberfläche","Account name":"Kontoname","Add a new backup":"Neues Backup hinzufügen","Add a path directly":"Pfad direkt eingeben","Add advanced option":"Option für Profis hinzufügen","Add backup":"Sicherung hinzufügen","Add filter":"Filter hinzufügen","Add path":"Pfad hinzufügen","Added":"Hinzugefügt","Adjust bucket name?":"Bucket-Name anpassen?","Advanced Options":"Optionen für Profis","Advanced options":"Optionen für Profis","Advanced:":"Für Profis:","All Hyper-V Machines":"Alle Hyper-V Maschinen","All Microsoft SQL Databases":"Alle Microsoft SQL-Datenbanken","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle Nutzungsberichte werden anonym verschickt und enthalten keine personenbezogenen oder personenbeziehbare Daten. Sie enthalten Daten über Hardware, Betriebssystem, das verwendete Backend, die Sicherungsdauer, die Gesamtgröße der Sicherungen und ähnliche Daten. Sie enthalten NICHT Pfade, Dateinamen, Benutzernamen, Passwörter oder andere sensible Informationen.","Allow remote access (requires restart)":"Fernzugriff erlauben (Neustart notwendig)","Allowed days":"Erlaubte Tage","An existing file was found at the new location":"An dem angegebenen Ort wurde eine bereits vorhandene Datenbank gefunden.","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Eine vorhandene Datenbank wurde gefunden.\nSoll diese Datenbank von nun an verwendet werden?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Eine lokale Datenbank für den Onlinespeicher wurde gefunden.\nMit dieser Datenbank können GUI und Kommandozeile auf dem gleichen Onlinespeicher arbeiten.\n\nSoll die lokale Datenbank genutzt werden?","Anonymous usage reports":"Anonyme Nutzungsberichte","Applications":"Anwendungen","As Command-line":"als Befehl für Kommandozeile","AuthID":"AuthID","Authentication method":"Authentifizierungs-Methode","Authentication method ({{auth_method}})":"Authentifizierungs-Methode ({{auth_method}})","Authentication password":"Passwort für Anmeldung","Authentication username":"Benutzername für Anmeldung","Autogenerated passphrase":"Automatisch generierte Passphrase","B2 Application ID":"B2-Anwendungs-ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Zurück","Backup complete!":"Sicherung abgeschlossen!","Backup destination":"Sicherungsziel","Backup location":"Sicherungsort","Backup retention":"Sicherungsaufbewahrung","Backup:":"Sicherung:","Beta":"Beta","Broken access":"Defekter Zugriff","Browse":"Durchsuchen","Browser default":"Browserstandard","Bucket create location":"Bucket-Speicherort","Bucket name":"Bucket-Name","Bucket storage class":"Bucket Speicherklasse","Building list of files to restore …":"Erstellen einer Liste von wiederherzustellenden Dateien...","Building partial temporary database …":"Temporäre Datenbank wird erstellt...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Bei erlaubtem Fernzugriff wird der Server auf Anfragen von jedem Computer Ihres Netzwerks antworten. Stellen Sie bei Aktivierung dieser Option bitte sicher, dass Sie den Computer immer in einem sicheren, durch eine Firewall geschützten Netzwerk verwenden.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Standardmäßig öffnet das Taskleistensymbol den Zugriff auf die Benutzeroberfläche. Dies stellt sicher, dass Sie über das Taskleistensymbol auf die Benutzeroberfläche zugreifen können. Wenn Sie es bevorzugen, dass das Passwort auch beim Zugriff auf die Benutzeroberfläche über das Taskleistensymbol eingegeben werden muss, aktivieren Sie diese Option.","Cache Files":"Dateien cachen","Canary":"Canary","Cancel":"Abbrechen","Cannot move to existing file":"Verschieben auf bereits existierende Datei nicht möglich","Changelog":"Änderungsprotokoll","Changelog for {{appname}} {{version}}":"Änderungsprotokoll für {{appname}} {{version}}","Check failed:":"Prüfung fehlgeschlagen:","Check for updates now":"Aktualisierung suchen","Checking for updates …":"Aktualisierungen werden gesucht …","Chose a storage type to get started":"Wähle einen Speichertypen zum Starten","Click the AuthID link to create an AuthID":"Auf AuthID klicken um eine AuthID zu erstellen","Click to set throttle options":"Zum Einstellen der Drosselungsoptionen anklicken","Client library to use":"Zu benutzende Client Bibliothek","Commandline …":"Kommandozeile …","Compact Phase":"Komprimierungsphase","Compact now":"Sicherung komprimieren","Compacting remote data …":"Remotedaten verkleinern...","Complete log":"Vollständiges Protokoll","Completing backup …":"Sicherung wird abgeschlossen …","Completing previous backup …":"Vorherige Sicherung wird abgeschlossen …","Computer":"Computer","Configuration file:":"Konfigurationsdatei:","Configuration:":"Konfiguration:","Configure a new backup":"Neue Sicherung konfigurieren","Confirm delete":"Löschen bestätigen","Confirm encryption passphrase":"Verschlüsselungspassphrase bestätigen","Confirm passphrase":"Passphrase bestätigen","Confirmation required":"Bestätigung erfolderlich","Connect":"Verbinden","Connect now":"Jetzt verbinden","Connecting to server …":"Verbindung zum Server wird hergestellt …","Connection lost":"Verbindung verloren","Connection worked!":"Verbindung erfolgreich!","Container name":"Container-Name","Container region":"Container-Region","Continue":"Fortfahren","Continue without encryption":"Ohne Verschlüsselung fortfahren","Copied!":"Kopiert!","Copy":"Kopie","Copy Destination URL to Clipboard":"Ziel-URL in Zwischenablage kopieren","Copy failed. Please manually copy the URL":"Kopie fehlgeschlagen. Bitte kopiere die URL manuell","Core options":"Allgemeine Optionen","Counting ({{files}} files found, {{size}})":"Dateien ermitteln ({{files}} files found, {{size}})","Crashes only":"Nur Abstürze","Create bug report …":"Fehlerbericht erstellen...","Create folder?":"Ordner erstellen?","Created new limited user":"Nutzer mit eingeschränkten Rechten anlegen","Creating bug report …":"Fehlerbericht wird erstellt... ","Creating new user with limited access …":"Neuer Benutzer mit eingeschränktem Zugriff wird erstellt …","Creating target folders …":"Zielverzeichnisse erstellen... ","Creating temporary backup …":"Temporäre Sicherung wird erstellt …","Current action:":"Aktuelle Aktion:","Current file:":"Aktuelle Datei:","Current version is {{versionname}} ({{versionnumber}})":"Aktuelle Version: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Benutzerdefinierter S3 endpoint","Custom Satellite":"Benutzerdefinierter Satellit","Custom Satellite ({{satellite}})":"Benutzerdefinierter Satellit ({{satellite}})","Custom authentication url":"Benutzerdefinierte URL für Authentifizierung","Custom backup retention":"Benutzerdefinierte Sicherungsaufbewahrung","Custom location ({{server}})":"Benutzerdefinierter Standort ({{server}})","Custom region for creating buckets":"Benutzerdefinierte Region, um Buckets zu erstellen","Custom region value ({{region}})":"Benutzerdefinierter Wert für Region ({{region}})","Custom server url ({{server}})":"Benutzerdefinierte Server-URL ({{server}})","Custom storage class ({{class}})":"Benutzerdefinierte Speicher-Klasse ({{class}})","Database …":"Datenbank …","Days":"Tage","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standardmäßig ausgeschlossen","Default options":"Standard-Optionen","Delete":"Löschen","Delete Phase (Old Backup Versions)":"Phase Löschen (alte Sicherungsversionen)","Delete backup":"Sicherung löschen","Delete backups that are older than":"Sicherungen löschen, die älter sind als","Delete local database":"Lokale Datenbank löschen","Delete remote files":"Remote-Dateien löschen","Delete the local database":"Die lokale Datenbank löschen","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} Dateien ({{filesize}}) vom Remote-Speicher löschen?","Delete …":"Löschen …","Deleted":"Gelöscht","Deleted Versions":"Gelöschte Versionen","Deleted files":"Gelöschte Dateien","Deleting remote files …":"Remote-Dateien löschen... ","Deleting unwanted files …":"Unnötige Daten löschen... ","Description (optional)":"Beschreibung (optional)","Description:":"Beschreibung:","Desktop":"Desktop","Destination":"Ziel","Destination path":"Ziel-Pfad","Disabled":"Deaktiviert","Dismiss":"Verwerfen","Dismiss all":"Alles ausblenden","Display and color theme":"Darstellung und Farbthema","Do you really want to delete the backup: \"{{name}}\" ?":"Möchten Sie die Sicherung wirklich löschen: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Möchten Sie die lokale Datenbank wirklich löschen für: {{name}}","Done":"Fertig","Download":"Herunterladen","Downloaded files":"Heruntergeladene Dateien","Downloading files …":"Dateien werden heruntergeladen …","Downloading update…":"Aktualisierung wird heruntergeladen …","Duplicate option {{opt}}":"doppelte Option {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati Forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati wird beim Start ausgeführt und verbleibt für die angegebene Dauer im pausierten Zustand. Dabei belegt Duplicati minimale Systemressourcen und Backups werden nicht ausgeführt.","Duration":"Dauer","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Jeder Sicherung ist eine lokale Datenbank zugeordnet, die Informationen über die Fernsicherung auf dem lokalen Rechner speichert.\\nWenn Sie eine Sicherung löschen, können Sie auch die lokale Datenbank löschen, ohne die Wiederherstellbarkeit der entfernten Dateien zu beeinträchtigen.\\nWenn Sie die lokale Datenbank für Sicherungen von der Kommandozeile aus verwenden, sollten Sie die Datenbank behalten.","Edit as list":"Als Liste bearbeiten","Edit as text":"Als Text bearbeiten","Edit …":"Bearbeiten …","Encrypt file":"Datei verschlüsseln","Encryption":"Verschlüsselung","Encryption changed":"Verschlüsselung geändert","Encryption passphrase":"Verschlüsselungspassphrase","End":"Ende","Enter URL":"URL eingeben","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Gib manuell die Aufbewahrungregeln an. Platzhalter sind D/W/Y für Tag/Woche/Jahr und U für unbegrenzt. Die Syntax lautet 7D:1D,4W:1W,36M:1M. Dieses Beispiel behält eine Sicherung für jeden der nächsten 7 Tage, jede der nächsten 4 Wochen und jeden der nächsten 36 Monate. Die Schreibweise 1W:1D,1M:1W,3Y:1M ist ebenso gültig.","Enter backup passphrase, if any":"Sicherungspassphrase eingeben, falls vorhanden","Enter configuration details":"Konfigurationsdetails eingeben","Enter encryption passphrase":"Verschlüsselungpassphrase eingeben","Enter expression here":"Ausdruck hier eingeben","Enter the destination path":"Ziel-Pfad angeben","Error":"Fehler","Error!":"Fehler!","Errors and crashes":"Fehler und Abstürze","Examined":"Geprüft","Exclude":"Ausschließen","Exclude directories whose names contain":"Ordner ausschließen dessen Namen beinhaltet","Exclude expression":"Filter (ausschließen)","Exclude file":"Datei ausschließen","Exclude file extension":"Dateiendung ausschließen","Exclude files whose names contain":"Dateien ausschließen dessen Namen beinhaltet","Exclude filter group":"Filtergruppe ausschließen","Exclude folder":"Ordner ausschließen","Exclude regular expression":"Regulären Ausdruck (ausschließen)","Existing file found":"Vorhandene Datenbank gefunden","Experimental":"Experimental","Export":"Exportieren","Export backup configuration":"Sicherungskonfiguration exportieren","Export configuration":"Konfiguration exportieren","Export passwords":"Passwort exportieren","Export …":"Exportieren …","Exporting …":"Am Exportieren …","External link":"Externer Link","FTP (Alternative)":"FTP (Alternativ)","Failed to build temporary database: {{message}}":"Erstellen der temporären Datenbank fehlgeschlagen: {{message}}","Failed to connect:":"Verbindung fehlgeschlagen:","Failed to connect: {{message}}":"Verbindung fehlgeschlagen: {{message}}","Failed to delete:":"Löschen fehlgeschlagen:","Failed to fetch path information: {{message}}":"Konnte Pfadangaben nicht abrufen: {{message}}","Failed to find backup:":"Sicherung konnte nicht gefunden werden:","Failed to read backup defaults:":"Sicherungsstandardeinstellungen konnten nicht gelesen werden:","Failed to restore files: {{message}}":"Wiederherstellung der Dateien fehlgeschlagen: {{message}}","Failed to save:":"Fehler beim Speichern:","Fetching path information …":"Abrufen von Pfadinformationen...","File":"Datei","Files larger than:":"Dateien größer als:","Filters":"Filter","Finished!":"Fertiggestellt!","First run setup":"Zuerst Setup starten","Folder":"Ordner","Folder path":"Ordnerpfad","Fri":"Fr","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"Allgemein","General backup settings":"Allgemeine Sicherungseinstellungen","General options":"Allgemeine Einstellungen","Generate":"Erzeugen","Generate IAM access policy":"IAM-Zugriffsrichtlinie generieren","Getting file versions …":"Dateiversionen werden abgerufen …","Group email":"Gruppen-E-Mail","Hidden files":"Versteckte Dateien","Hide":"Ausblenden","Hide hidden folders":"versteckte Ordner ausblenden","Home":"Home","Hostnames":"Hostnamen","Hours":"Stunden","How do you want to handle existing files?":"Wie sollen bestehende Dateien behandelt werden?","Hyper-V Machine":"Hyper-V-Maschine","Hyper-V Machine:":"Hyper-V-Maschine:","Hyper-V Machines":"Hyper-V-Maschinen","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Wurde ein Zeitpunkt verpasst, startet die Sicherung so bald wie möglich.","If at least one newer backup is found, all backups older than this date are deleted.":"Falls mindestens eine neuere Sicherung gefunden wird, werden alle Sicherungen älter als dieses Datum gelöscht.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ohne Pfad werden alle Dateien im Anmeldeverzeichnis gespeichert.\\nMöchten Sie das?","If you do not enter an API Key, the tenant name is required":"Wenn kein API Schlüssel angegeben wurde, ist der Tenant-Name erforderlich.","Import":"Importieren","Import Destination URL":"Ziel-URL importieren","Import backup configuration":"Sicherungskonfiguration importieren","Import from a file":"Von einer Datei importieren","Import metadata":"Importiere Metadata","Importing …":"Am Importieren …","Include a file?":"Datei einschießen?","Include expression":"Filter (einschließen)","Include regular expression":"Regulären Ausdruck (einschließen)","Incorrect answer, try again":"Fehlerhafte Antwort, versuche es erneut","Individual builds for developers only. Not for use with important data.":"Individuelle Builds nur für Entwickler. Nicht für die Verwendung mit wichtigen Daten.","Information":"Information","Invalid characters in path":"Unzulässige Zeichen im Pfad","Invalid retention time":"Ungültige Aufbewahrungszeit","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Manche FTP-Server erlauben ein Verbinden ohne Passwort.\nSind Sie sicher, dass Ihr FTP-Server dazu gehört?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Eine bestimmte Anzahl von Sicherungen behalten","Keep all backups":"Alle Sicherungen behalten","Keystone API version":"Keystone API Version","Language in user interface":"Sprache der Benutzeroberfläche","Last month":"Letzter Monat","Last successful backup:":"Letzte erfolgreiche Sicherung:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Letzte erfolgreiche Wiederherstellung: {{time}} (dauerte {{duration || '0 Sekunden'}})","Latest":"Neueste","Libraries":"Bibliotheken","Listing backup dates …":"Sicherungsdaten werden aufgelistet …","Listing remote files for purge …":"Auflisten von Remote-Dateien fürs Löschen...","Listing remote files …":"Auflisten von Remote-Dateien...","Live":"Live","Load a configuration from an exported job or a storage provider":"Konfiguration aus einem exportierten Job oder Speicheranbieter laden","Load destination from an exported job or a storage provider":"Ziel aus einem exportierten Job oder Speicheranbieter laden","Load older data":"ältere Einträge laden","Loading …":"Laden...","Local Repository":"Lokales Repository","Local database path:":"Lokale Datenbank:","Local repository":"Lokales Repository","Local storage":"Lokaler Speicher","Location":"Ort","Location where buckets are created":"Speicherort, wo die Buckets erstellt werden","Log data for {{Backup.Backup.Name}}":"Protokolldaten für {{Backup.Backup.Name}}","Log data from the server":"Protokolldaten vom Server","Log out":"Abmelden","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Wartung","Manually type path":"Pfad eingeben","Max download speed":"Max. Downloadgeschwindigkeit","Max upload speed":"Max. Uploadgeschwindigkeit","Menu":"Menü","Microsoft SQL Database:":"Microsoft SQL Datenbank:","Microsoft SQL Databases":"Microsoft SQL Datenbanken","Minimum redundancy":"Minimale Redundanz","Minimum redundancy is 1.0":"Die minimale Redundanz ist 1,0","Minutes":"Minuten","Missing name":"Name fehlt","Missing passphrase":"Passphrase fehlt","Missing sources":"Quelle fehlt","Modified":"Geändert","Mon":"Mo","Months":"Monate","Move existing database":"Datenbank verschieben","Move failed:":"Verschieben fehlgeschlagen:","My Documents":"Dokumente","My Music":"Musik","My Photos":"Meine Fotos","My Pictures":"Bilder","Name":"Name","Never":"Nie","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Neuer Benutzername ist {{user}}.\nZugangsdaten für eingeschränken Benutzer verwendet","Next":"Weiter","Next scheduled run:":"Nächste geplante Ausführung:","Next scheduled task:":"Nächste geplante Aufgabe:","Next task:":"Nächste Aufgabe:","Next time":"Nächstes Mal","No":"Nein","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Es wurde kein Zertifikat angegeben, bitte überprüfen Sie mit dem Serveradministrator, ob der Schlüssel korrekt ist: {{key}}\\n\\nMöchten Sie den angegebenen Host-Schlüssel bestätigen?","No editor found for the "{{backend}}" storage type":"Kein Editor für den "{{backend}}" Speichertyp gefunden","No encryption":"Keine Verschlüsselung","No items selected":"Nichts ausgewählt","No items to restore, please select one or more items":"Es wurden keine Daten für die Wiederherstellung ausgewählt. Wähle eine Datei oder einen Ordner aus.","No passphrase entered":"Keine Passphrase eingegeben","No scheduled tasks":"Keine geplanten Aufgaben","Non-matching passphrase":"Nicht übereinstimmende Passphrase","None / disabled":"Keine / deaktiviert","Not using encryption":"Verschlüsselung nicht verwenden","Nothing will be deleted. The backup size will grow with each change.":"Es wird nichts gelöscht. Die Sicherungsgröße erhöht sich mit jeder Änderung.","OK":"OK","Official releases":"Offizielle Versionen","Once there are more backups than the specified number, the oldest backups are deleted.":"Sobald mehr Sicherungen als angegeben vorhanden sind, werden die ältesten Sicherungen gelöscht.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Geöffnet","Operating System":"Betriebssystem","Operation":"Operation","Operations:":"Operationen:","Optional authentication password":"Passwort für Anmeldung (optional)","Optional authentication username":"Benutzername für Anmeldung (optional)","Options":"Optionen","Original location":"Ursprünglicher Speicherort","Others":"Weitere","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Mit der Zeit werden die Sicherungen automatisch gelöscht. Es bleibt eine Sicherung für jeden der letzten 7 Tage, jede der letzten 4 Wochen und jeden der letzten 12 Monate erhalten. Es bleibt immer mindestens eine Sicherung erhalten.","Overwrite":"Überschreiben","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (falls verschlüsselt)","Passphrase changed":"Passphrase gändert","Passphrases are not matching":"Passphrasen stimmen nicht überein","Passphrases do not match":"Passphrasen stimmen nicht überein","Password":"Passwort","Patching files with local blocks …":"Dateien mit vorhandenen Daten aufbauen...","Path":"Pfad","Path not found":"Pfad nicht gefunden","Path on server":"Pfad auf Server","Path or subfolder in the bucket":"Pfad oder Unterverzeichnis im Bucket","Pause":"Pause","Pause after startup or hibernation":"Pause nach dem Start oder Aufwachen","Pause options":"Anhalten Optionen","Permissions":"Berechtigungen","Pick location":"Speicherort auswählen","Point to your backup files and restore from there":"Sicherungsdateien auswählen und wiederherstellen","Port":"Port","Prevent tray icon automatic log-in":"Verhindert das automatische Anmelden per Taskleistensymbol","Previous":"Zurück","Progress:":"Fortschritt:","ProjectID is optional if the bucket exist":"Die Projekt-ID ist optional, wenn der Bucket existiert","Proprietary":"Proprietär","Purge Phase":"Aufräumphase","Purging files complete!":"Löschen von Dateien abgeschlossen!","Purging files …":"Dateien bereinigen...","Rebuilding local database …":"Lokale Datenbank wird neu aufgebaut …","Recreate (delete and repair)":"Wiederherstellen (löschen und reparieren)","Recreate Database Phase":"Datenbank-Wiederherstellungsphase","Recreating database …":"Datenbank wird neu erstellt …","Registering temporary backup …":"Temporäre Sicherung wird registriert …","Relative paths not allowed":"Relative Pfade sind nicht möglich","Reload":"Neu laden","Remote":"Remote","Remote Path":"Entfernter Pfad","Remote Repository":"Entferntes Repository","Remote path":"Entfernter Pfad","Remote repository":"Entferntes Repository","Remote volume size":"Remote-Volume-Größe","Remove":"Entfernen","Remove option":"Option entfernen","Removed files":"Entfernte Dateien","Repair":"Reparieren","Repair Phase":"Reparatur Phase","Repairing database …":"Datenbank wird repariert …","Repeat Passphrase":"Passphrase wiederholen","Reporting:":"Bericht:","Reset":"Zurücksetzen","Restore":"Wiederherstellen","Restore complete!":"Wiederherstellung komplett!","Restore files":"Dateien wiederherstellen","Restore files …":"Dateien wiederherstellen …","Restore from":"Wiederherstellen von","Restore from backup configuration":"Aus Sicherungskonfiguration wiederherstellen","Restore options":"Wiederherstellungsoptionen","Restore read/write permissions":"Schreib- und Leserechte wiederherstellen","Restored Files":"Dateien wiederhergestellt","Restored Folders":"Ordner wiederhergestellt","Restored Symlinks":"Symbolische Verknüpfungen wiederhergestellt","Restoring files …":"Dateien werden wiederhergestellt …","Resume":"Fortsetzen","Rewritten File Lists":"Neu geschrieben Dateiliste","Run again every":"Wiederholen alle","Run now":"Jetzt sichern","Running commandline entry":"Führe Kommandozeilenbefehl aus","Running task:":"Laufende Aufgabe:","Running …":"Läuft...","S3 Compatible":"S3 Kompatibel","Same as the base install version: {{channelname}}":"Wie die zuerst installierte Version: {{channelname}}","Sat":"Sa","Satellite":"Satellit","Save":"Speichern","Save and repair":"Speichern und reparieren","Save different versions with timestamp in file name":"Mehrere Versionen mit Zeitstempel im Dateinamen speichern","Save immediately":"Sofort speichern","Scanning existing files …":"Vorhandene Dateien werden gescannt …","Scanning for local blocks …":"Scannen nach lokalen Blöcken...","Schedule":"Zeitplan","Search":"Suche","Search for files":"Dateien suchen","Seconds":"Sekunden","Select a log level and see messages as they happen:":"Wähle eine Protokollierungsstufe aus und sehe dir die Meldungen an während sie erstellt werden:","Select files":"Wähle Dateien","Server":"Server","Server and port":"Server und Port","Server hostname or IP":"Server-Hostname oder IP","Server is currently paused,":"Server ist pausiert,","Server is currently paused, do you want to resume now?":"Server ist zurzeit pausiert, Server starten?","Server password":"Server-Passwort","Server paused":"Server pausiert","Server state properties":"Server Zustandseigenschaften","Settings":"Einstellungen","Show":"Anzeigen","Show advanced editor":"Erweiterten Editor anzeigen","Show hidden folders":"Versteckte Ordner anzeigen","Show log":"Protokolldatei anzeigen","Show log …":"Protokoll anzeigen...","Show treeview":"Baumansicht anzeigen","Sia server password":"Sia Server-Passwort","Smart backup retention":"Intelligente Sicherungsaufbewahrung","Some OpenStack providers allow an API key instead of a password and tenant name":"Einige OpenStack Anbieter erlauben einen API Schlüssel anstelle eines Passwortes und Tenant Namen","Some S3 providers might only be compatible with a certain client library":"Manche S3 Anbieter sind nur mit bestimmten Client Bibliotheken kompatibel","Source Data":"Quell-Daten","Source Files":"Quelldateien","Source data":"Quell-Daten","Source folders":"Quell-Verzeichnisse","Source:":"Quelle:","Specific builds for developers only. Not for use with important data.":"Spezifische Builds nur für Entwickler. Nicht für die Verwendung mit wichtigen Daten.","Stable":"Stabil","Standard protocols":"Standardprotokolle","Start":"Beginn","Starting backup …":"Sicherung wird gestartet …","Starting restore …":"Wiederherstellung wird gestartet …","Starting the restore process …":"Starten des Wiederherstellungsprozesses...","Stop after current file":"Stopp nach aktueller Datei","Stop after the current file":"Beende nach aktueller Datei","Stop now":"Beenden","Stop running backup":"Laufende Sicherung anhalten","Stop running task":"Beende laufenden Vorgang","Stopping after the current file:":"Anhalten nach der aktuellen Datei:","Stopping task:":"Beende Vorgang","Storage Type":"Speichertyp","Storage class":"Speicherklasse","Storage class for creating a bucket":"Speicherklasse zum Erstellen eines Bucket","Stored":"Gespeichert","Strong":"Stark","Success":"Erfolgreich","Sun":"So","Symbolic link":"Symbolischer Link","System Files":"Systemdateien","System default ({{levelname}})":"System-Standard ({{levelname}})","System files":"Systemdateien","System info":"System-Informationen","System properties":"System-Eigenschaften","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Aufgabe wird ausgeführt","Temporary Files":"Temporäre Dateien","Temporary files":"Temporäre Dateien","Test Phase":"Test Phase","Test connection":"Verbindung prüfen","Testing permissions …":"Berechtigungen werden überprüft …","Testing …":"Prüfung...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Das Feld '{{fieldname}}' beinhaltet ein ungültiges Zeichen: {{character}} (Wert: {{value}}, Position: {{pos}})","The backup is missing, has it been deleted?":"Die Sicherung fehlt, wurde sie gelöscht?","The backup was temporary and does not exist anymore, so the log data is lost":"Die Sicherung war temporär und existiert nicht mehr, die Protokolldaten sind daher verloren","The bucket name should be all lower-case, convert automatically?":"Der Bucket sollte klein geschrieben sein. Jetzt klein schreiben?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Die Konfiguration sollte sicher aufbewahrt werden. Sicher, dass eine unverschlüsselte Datei mit Ihren Passwörtern gespeichert werden soll?","The dark theme (by Michal)":"Dunkles Thema (von Michal)","The default blue on white theme (by Alex)":"Blau-auf-Weiß Thema (von Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Der Ordner {{folder}} existiert nicht.\nOrdner erstellen?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Der Host-Schlüssel wurde geändert, bitte prüfen Sie mit dem Server-Administrator, ob dieser korrekt ist, sonst könnten Sie das Opfer eines MAN-IN-THE-MIDDLE-Angriffs werden.\\n\\nMöchten Sie Ihren AKTUELLEN Host-Schüssel \"{{prev}}\" durch den GEMELDETEN Host-Schüssel {{key}} ersetzen?","The passwords do not match":"Die Passwörter stimmen nicht überein","The path does not appear to exist, do you want to add it anyway?":"Der Pfad scheint nicht zu existieren. Möchten Sie ihn trotzdem hinzufügen?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Der Pfad endet nicht mit dem Zeichen \"{{dirsep}}\", was bedeutet, dass Sie eine Daten und kein Verzeichnis einschließen.\\n\\nMöchten Sie die angegebene Datei einschließen?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Der Pfad muss ein absoluter Pfad sein. Das heißt, er muss mit '/' beginnen","The region parameter is only applied when creating a new bucket":"Der Bereich Parameter wird nur angewendet, wenn ein neuer Bucket erzeugt wird","The region parameter is only used when creating a bucket":"Der Bereich Parameter wird nur angewendet, wenn ein Bucket erzeugt wird","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Das Server Zertifikat konnte nicht validiert werden.\\nMöchten Sie das SSL-Zertifikat mit dem folgenden Hash bestätigen: {{hash}}?","The storage class affects the availability and price for a stored file":"Die Speicherklasse wirkt sich auf die Verfügbarkeit und den Preis einer gespeicherten Datei aus","The target folder contains encrypted files, please supply the passphrase":"Der Zielordner enthält verschlüsselte Dateien, bitte stelle die Passphrase bereit","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Der Nutzer hat zu viele Berechtigungen. Möchten Sie einen neuen eingeschränkten Nutzer erstellen, welcher nur Zugriffsrechte für den ausgewählten Pfad hat?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Dieses Backup wurde mit einem anderen Betriebssystem erstellt. Die Wiederherstellung von Dateien ohne Angabe eines Zielordners kann dazu führen, dass Dateien an unerwarteten Stellen wiederhergestellt werden. Sind Sie sicher, dass Sie fortfahren möchten, ohne ein Zielverzeichnis zu wählen?","This month":"Dieser Monat","This week":"Diese Woche","Throttle settings":"Drosselungseinstellungen","Thu":"Do","Time":"Zeit","To File":"als Datei","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Zum Bestätigen für das Löschen der Remote-Dateien für \"{{name}}\", bitte das unten angegebene Wort eingeben","To export without a passphrase, uncheck the \"Encrypt file\" box":"Deaktiviere »Datei verschlüsseln«, um ohne eine Passphrase zu exportieren","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Um verschiedene DNS-basierte Angriffe zu verhindern, beschränkt Duplicati die erlaubten Hostnamen auf die hier aufgeführten. Direkter IP-Zugriff und localhost ist immer erlaubt. Mehrere Hostnamen können mit einem Semikolon-Trennzeichen versehen werden. Wenn einer der zulässigen Hostnamen ein Sternchen (*) ist, sind alle Hostnamen zulässig und diese Funktion ist deaktiviert. Is das Feld leer, sind nur IP-Adresse und lokaler Host-Zugriff zulässig.","Today":"Heute","Trust host certificate?":"Host Zertifikat vertrauen?","Trust server certificate?":"Server Zertifikat vertrauen?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probiere neue Funktionen aus, an denen wir gerade arbeiten. Derzeit die stabilste verfügbare Version. Vor der Verwendung im produktiven Umfeld teste bitte die Wiederherstellung der Daten.","Tue":"Di","Type passphrase here.":"Hier Passphrase eingeben.","Type to highlight files":"Tippen, um Dateien zu markieren","Unknown backup size and versions":"Unbekannte Backupgröße und -versionen","Until resumed":"Bis zur Wiederaufnahme","Update channel":"Update-Kanal","Update failed:":"Update fehlgeschlagen:","Updating with existing database":"Datenbank wird aktualisiert","Uploaded files":"Hochgeladene Dateien","Uploading verification file …":"Verifikationsdatei wird hochgeladen …","Usage statistics":"Nutzungsstatistiken","Usage statistics, warnings, errors, and crashes":"Nutzungsberichte, Warnungen, Fehler und Abstürze","Use SSL":"SSL benutzen","Use existing database?":"Bestehende Datenbank nutzen?","Use weak passphrase":"Schwache Passphrase verwenden","Useless":"Nutzlos","User data":"Benutzer Daten","User domain name":"Benutzer Domänenname ","User has too many permissions":"Nutzer hat zu viele Rechte","User interface settings":"Einstellungen der Benutzeroberfläche","Username":"Benutzername","Vacuuming database …":"Datenbank wird bereinigt …","Validating …":"Validieren...","Verifications":"Überprüfungen","Verify files":"Dateien prüfen","Verifying answer":"Antwort verifizieren","Verifying backend data …":"Verifizierung von Backend-Daten...","Verifying files …":"Dateien überprüfen... ","Verifying remote data …":"Remotedaten prüfen ...","Verifying restored files …":"Wiederhergestellte Dateien werden überprüft …","Verifying …":"Am Überprüfen …","Version ID":"Version ID","Very strong":"Sehr stark","Very weak":"Sehr schwach","Visit us on":"Besuche uns auf","WARNING: This will prevent you from restoring the data in the future.":"WARNUNG: Dadurch können Sie die Daten in Zukunft nicht wiederherstellen.","Waiting for task to begin":"Warte darauf, loslegen zu können","Waiting for upload to finish …":"Warte auf Ende des Uploads... ","Warnings, errors and crashes":"Warnungen, Fehler und Abstürze","We recommend that you encrypt all backups stored outside your system":"Wir empfehlen, dass Sie alle Backups verschlüsseln, die außerhalb Ihres Systems gespeichert werden.","Weak":"Schwach","Weak passphrase":"Schwache Passphrase","Wed":"Mi","Weeks":"Wochen","Where do you want to restore from?":"Von wo wollen Sie wiederherstellen?","Where do you want to restore the files to?":"Wohin sollen die Dateien wiederhergestellt werden?","Years":"Jahre","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, ich habe die Passphrase sicher gespeichert","Yes, I understand the risk":"Ja, ich habe die Risiken verstanden","Yes, I'm brave!":"Ja, ich bin mutig!","Yes, please break my backup!":"Ja, bitte zerstöre meine Sicherung!","Yesterday":"Gestern","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Sie ändern gerade den Datenbankpfad einer existierenden lokalen Datenbank.\nSind Sie sicher, dass Sie das wollen?","You are currently running {{appname}} {{version}}":"Aktuell wird {{appname}} {{version}} verwendet","You can stop the backup after any file uploads currently in progress have finished.":"Nachdem alle derzeit laufenden Datei-Uploads abgeschlossen sind, kann das Backup gestoppt werden.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Die Aufgabe kann sofort angehalten werden, oder nachdem der Prozess die aktuelle Datei abgeschlossen hat.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Sie haben die Verschlüsselungsmethode geändert. Dies könnte Daten zerstören. Wir empfehlen Ihnen, stattdessen eine neue Sicherung zu erstellen","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Sie haben die Passphrase geändert, was nicht unterstützt wird. Bitte erstellen Sie stattdessen eine neue Sicherung.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Sie haben ausgewählt, dass die Sicherung nicht verschlüsselt werden soll. Die Verschlüsselung wird für alle auf einem Remote-Server gespeicherten Daten empfohlen.","You have chosen to restore to a new location, but not entered one":"Wiederherstellen an einen neuen Ort wurde gewählt, aber kein Ort angegeben","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Sie haben eine starke Passphrase erstellt. Stellen Sie sicher, dass Sie diese an einem sicheren Ort aufbewahren, da die Daten bei Verlust der Passphrase nicht wiederhergestellt werden können.","You must choose at least one source folder":"Sie müssen mindestens ein Quellverzeichnis wählen.","You must enter a domain name to use v3 API":"Eingabe vom Domänennamens für die Verwendungder v3-API","You must enter a name for the backup":"Sie müssen einen Namen für die Sicherung eingeben.","You must enter a passphrase or disable encryption":"Sie müssen eine Passphrase eingeben oder die Verschlüsselung deaktivieren.","You must enter a password to use v3 API":"Gib ein Passwort für die Verwendungder v3-API an","You must enter a positive number of backups to keep":"Sie müssen eine positive Anzahl der zu behaltenden Sicherungen eingeben.","You must enter a tenant (aka project) name to use v3 API":"Gib einen Kundennamen (bzw. Projektnamen) für die Verwendungder v3-API","You must enter a valid duration for the time to keep backups":"Sie müssen eine gültige Aufbewahrungsdauer für die Sicherungen eingeben.","You must enter a valid retention policy string":"Sie müssen eine gültige Aufbewahrungsregel angeben.","You must fill in the password":"Sie müssen ein Passwort eintragen.","You must fill in the server name or address":"Sie müssen einen Servernamen oder eine Adresse eintragen.","You must fill in the username":"Sie müssen einen Benutzernamen eintragen.","You must fill in {{field}}":"{{field}} muss ausgefüllt sein","You must select or fill in the AuthURI":"Sie müssen die AuthURI auswählen oder eintragen.","You must select or fill in the server":"Sie müssen den Server auswählen oder eintragen.","You must specify a path":"Sie müssen einen Pfad angeben.","Your files and folders have been restored successfully.":"Dateien und Ordner erfolgreich wiederhergestellt.","Your passphrase is easy to guess. Consider changing passphrase.":"Ihre Passphrase ist leicht zu erraten. Erwägen Sie eine Änderung der Passphrase.","bucket/folder/subfolder":"Bucket/Ordner/Unterordner","byte":"Byte","byte/s":"Byte/s","custom":"benutzerdefiniert","resume now":"Jetzt starten","unless you are explicitly specifying --group-id":"es sei denn, Sie geben explizit --group-id an","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} wurde hauptsächlich von {{dev1}} und {{dev2}} entwickelt. {{appname}} kann unter folgender Adresse heruntergeladen werden: {{websitename}}. {{appname}} ist unter {{licensename}} lizenziert.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} Dateien ({{size}}) zu erledigen {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versionen"],"{{number}} Hour":"{{number}} Stunde","{{number}} Hours":"{{number}} Stunden","{{number}} Minutes":"{{number}} Minuten","{{time}} (took {{duration}})":"{{time}} (dauerte {{duration}})"}); - gettextCatalog.setStrings('en_GB', {"- pick an option -":"- pick an option -","...loading...":"...loading...","API key":"API key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"About","About {{appname}}":"About {{appname}}","Access Key":"Access Key","Access denied":"Access denied","Access grant":"Access grant","Access to user interface":"Access to user interface","Account name":"Account name","Add a new backup":"Add a new backup","Add a path directly":"Add a path directly","Add advanced option":"Add advanced option","Add backup":"Add backup","Add filter":"Add filter","Add path":"Add path","Added":"Added","Adjust bucket name?":"Adjust bucket name?","Advanced Options":"Advanced Options","Advanced options":"Advanced options","Advanced:":"Advanced:","All Hyper-V Machines":"All Hyper-V Machines","All Microsoft SQL Databases":"All Microsoft SQL Databases","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.","Allow remote access (requires restart)":"Allow remote access (requires restart)","Allowed days":"Allowed days","An existing file was found at the new location":"An existing file was found at the new location","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"An existing file was found at the new location\nAre you sure you want the database to point to an existing file?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?","Anonymous usage reports":"Anonymous usage reports","Applications":"Applications","As Command-line":"As Command-line","AuthID":"AuthID","Authentication method":"Authentication method","Authentication method ({{auth_method}})":"Authentication method ({{auth_method}})","Authentication password":"Authentication password","Authentication username":"Authentication username","Autogenerated passphrase":"Autogenerated passphrase","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Back","Backup complete!":"Backup complete!","Backup destination":"Backup destination","Backup location":"Backup location","Backup retention":"Backup retention","Backup:":"Backup:","Beta":"Beta","Broken access":"Broken access","Browse":"Browse","Browser default":"Browser default","Bucket create location":"Bucket create location","Bucket name":"Bucket name","Bucket storage class":"Bucket storage class","Building list of files to restore …":"Building list of files to restore …","Building partial temporary database …":"Building partial temporary database …","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.","Cache Files":"Cache Files","Canary":"Canary","Cancel":"Cancel","Cannot move to existing file":"Cannot move to existing file","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog for {{appname}} {{version}}","Check failed:":"Check failed:","Check for updates now":"Check for updates now","Checking for updates …":"Checking for updates …","Chose a storage type to get started":"Chose a storage type to get started","Click the AuthID link to create an AuthID":"Click the AuthID link to create an AuthID","Click to set throttle options":"Click to set throttle options","Client library to use":"Client library to use","Commandline …":"Command Line …","Compact Phase":"Compact Phase","Compact now":"Compact now","Compacting remote data …":"Compacting remote data …","Complete log":"Complete log","Completing backup …":"Completing backup …","Completing previous backup …":"Completing previous backup …","Computer":"Computer","Configuration file:":"Configuration file:","Configuration:":"Configuration:","Configure a new backup":"Configure a new backup","Confirm delete":"Confirm delete","Confirm encryption passphrase":"Confirm encryption passphrase","Confirm passphrase":"Confirm passphrase","Confirmation required":"Confirmation required","Connect":"Connect","Connect now":"Connect now","Connecting to server …":"Connecting to server …","Connection lost":"Connection lost","Connection worked!":"Connection worked!","Container name":"Container name","Container region":"Container region","Continue":"Continue","Continue without encryption":"Continue without encryption","Copied!":"Copied!","Copy":"Copy","Copy Destination URL to Clipboard":"Copy Destination URL to Clipboard","Copy failed. Please manually copy the URL":"Copy failed. Please manually copy the URL","Core options":"Core options","Counting ({{files}} files found, {{size}})":"Counting ({{files}} files found, {{size}})","Crashes only":"Crashes only","Create bug report …":"Create bug report …","Create folder?":"Create folder?","Created new limited user":"Created new limited user","Creating bug report …":"Creating bug report …","Creating new user with limited access …":"Creating new user with limited access …","Creating target folders …":"Creating target folders …","Creating temporary backup …":"Creating temporary backup …","Current action:":"Current action:","Current file:":"Current file:","Current version is {{versionname}} ({{versionnumber}})":"Current version is {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Custom S3 endpoint","Custom Satellite":"Custom Satellite","Custom Satellite ({{satellite}})":"Custom Satellite ({{satellite}})","Custom authentication url":"Custom authentication url","Custom backup retention":"Custom backup retention","Custom location ({{server}})":"Custom location ({{server}})","Custom region for creating buckets":"Custom region for creating buckets","Custom region value ({{region}})":"Custom region value ({{region}})","Custom server url ({{server}})":"Custom server url ({{server}})","Custom storage class ({{class}})":"Custom storage class ({{class}})","Database …":"Database …","Days":"Days","Default":"Default","Default ({{channelname}})":"Default ({{channelname}})","Default excludes":"Default excludes","Default options":"Default options","Delete":"Delete","Delete Phase (Old Backup Versions)":"Delete Phase (Old Backup Versions)","Delete backup":"Delete backup","Delete backups that are older than":"Delete backups that are older than","Delete local database":"Delete local database","Delete remote files":"Delete remote files","Delete the local database":"Delete the local database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Delete {{filecount}} files ({{filesize}}) from the remote storage?","Delete …":"Delete …","Deleted":"Deleted","Deleted Versions":"Deleted Versions","Deleted files":"Deleted files","Deleting remote files …":"Deleting remote files …","Deleting unwanted files …":"Deleting unwanted files …","Description (optional)":"Description (optional)","Description:":"Description:","Desktop":"Desktop","Destination":"Destination","Destination path":"Destination path","Disabled":"Disabled","Dismiss":"Dismiss","Dismiss all":"Dismiss all","Display and color theme":"Display and color theme","Do you really want to delete the backup: \"{{name}}\" ?":"Do you really want to delete the backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Do you really want to delete the local database for: {{name}}","Done":"Done","Download":"Download","Downloaded files":"Downloaded files","Downloading files …":"Downloading files …","Downloading update…":"Downloading update…","Duplicate option {{opt}}":"Duplicate option {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.","Duration":"Duration","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.","Edit as list":"Edit as list","Edit as text":"Edit as text","Edit …":"Edit …","Encrypt file":"Encrypt file","Encryption":"Encryption","Encryption changed":"Encryption changed","Encryption passphrase":"Encryption passphrase","End":"End","Enter URL":"Enter URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Enter backup passphrase, if any","Enter configuration details":"Enter configuration details","Enter encryption passphrase":"Enter encryption passphrase","Enter expression here":"Enter expression here","Enter the destination path":"Enter the destination path","Error":"Error","Error!":"Error!","Errors and crashes":"Errors and crashes","Examined":"Examined","Exclude":"Exclude","Exclude directories whose names contain":"Exclude directories whose names contain","Exclude expression":"Exclude expression","Exclude file":"Exclude file","Exclude file extension":"Exclude file extension","Exclude files whose names contain":"Exclude files whose names contain","Exclude filter group":"Exclude filter group","Exclude folder":"Exclude folder","Exclude regular expression":"Exclude regular expression","Existing file found":"Existing file found","Experimental":"Experimental","Export":"Export","Export backup configuration":"Export backup configuration","Export configuration":"Export configuration","Export passwords":"Export passwords","Export …":"Export …","Exporting …":"Exporting …","External link":"External link","FTP (Alternative)":"FTP (Alternative)","Failed to build temporary database: {{message}}":"Failed to build temporary database: {{message}}","Failed to connect:":"Failed to connect:","Failed to connect: {{message}}":"Failed to connect: {{message}}","Failed to delete:":"Failed to delete:","Failed to fetch path information: {{message}}":"Failed to fetch path information: {{message}}","Failed to find backup:":"Failed to find backup:","Failed to read backup defaults:":"Failed to read backup defaults:","Failed to restore files: {{message}}":"Failed to restore files: {{message}}","Failed to save:":"Failed to save:","Fetching path information …":"Fetching path information …","File":"File","Files larger than:":"Files larger than:","Filters":"Filters","Finished!":"Finished!","First run setup":"First run setup","Folder":"Folder","Folder path":"Folder path","Fri":"Fri","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"General","General backup settings":"General backup settings","General options":"General options","Generate":"Generate","Getting file versions …":"Getting file versions …","Group email":"Group email","Hidden files":"Hidden files","Hide":"Hide","Hide hidden folders":"Hide hidden folders","Home":"Home","Hostnames":"Hostnames","Hours":"Hours","How do you want to handle existing files?":"How do you want to handle existing files?","Hyper-V Machine":"Hyper-V Machine","Hyper-V Machine:":"Hyper-V Machine:","Hyper-V Machines":"Hyper-V Machines","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"If a date was missed, the job will run as soon as possible.","If at least one newer backup is found, all backups older than this date are deleted.":"If at least one newer backup is found, all backups older than this date are deleted.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?","If you do not enter an API Key, the tenant name is required":"If you do not enter an API Key, the tenant name is required","Import":"Import","Import Destination URL":"Import Destination URL","Import backup configuration":"Import backup configuration","Import from a file":"Import from a file","Import metadata":"Import metadata","Importing …":"Importing …","Include a file?":"Include a file?","Include expression":"Include expression","Include regular expression":"Include regular expression","Incorrect answer, try again":"Incorrect answer, try again","Individual builds for developers only. Not for use with important data.":"Individual builds for developers only. Not for use with important data.","Information":"Information","Invalid characters in path":"Invalid characters in path","Invalid retention time":"Invalid retention time","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"It is possible to connect to some FTP servers without a password.\nAre you sure your FTP server supports password-less logins?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Keep a specific number of backups","Keep all backups":"Keep all backups","Keystone API version":"Keystone API version","Language in user interface":"Language in user interface","Last month":"Last month","Last successful backup:":"Last successful backup:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Last successful restore: {{time}} (took {{duration || '0 seconds'}})","Latest":"Latest","Libraries":"Libraries","Listing backup dates …":"Listing backup dates …","Listing remote files for purge …":"Listing remote files for purge …","Listing remote files …":"Listing remote files …","Live":"Live","Load a configuration from an exported job or a storage provider":"Load a configuration from an exported job or a storage provider","Load destination from an exported job or a storage provider":"Load destination from an exported job or a storage provider","Load older data":"Load older data","Loading …":"Loading …","Local Repository":"Local Repository","Local database path:":"Local database path:","Local repository":"Local repository","Local storage":"Local storage","Location":"Location","Location where buckets are created":"Location where buckets are created","Log data for {{Backup.Backup.Name}}":"Log data for {{Backup.Backup.Name}}","Log data from the server":"Log data from the server","Log out":"Log out","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Maintenance","Manually type path":"Manually type path","Max download speed":"Max download speed","Max upload speed":"Max upload speed","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Databases","Minimum redundancy":"Minimum redundancy","Minimum redundancy is 1.0":"Minimum redundancy is 1.0","Minutes":"Minutes","Missing name":"Missing name","Missing passphrase":"Missing passphrase","Missing sources":"Missing sources","Modified":"Modified","Mon":"Mon","Months":"Months","Move existing database":"Move existing database","Move failed:":"Move failed:","My Documents":"My Documents","My Music":"My Music","My Photos":"My Photos","My Pictures":"My Pictures","Name":"Name","Never":"Never","New user name is {{user}}.\nUpdated credentials to use the new limited user":"New user name is {{user}}.\nUpdated credentials to use the new limited user","Next":"Next","Next scheduled run:":"Next scheduled run:","Next scheduled task:":"Next scheduled task:","Next task:":"Next task:","Next time":"Next time","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?","No editor found for the "{{backend}}" storage type":"No editor found for the "{{backend}}" storage type","No encryption":"No encryption","No items selected":"No items selected","No items to restore, please select one or more items":"No items to restore, please select one or more items","No passphrase entered":"No passphrase entered","No scheduled tasks":"No scheduled tasks","Non-matching passphrase":"Non-matching passphrase","None / disabled":"None / disabled","Not using encryption":"Not using encryption","Nothing will be deleted. The backup size will grow with each change.":"Nothing will be deleted. The backup size will grow with each change.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Once there are more backups than the specified number, the oldest backups are deleted.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Opened","Operating System":"Operating System","Operation":"Operation","Operations:":"Operations:","Optional authentication password":"Optional authentication password","Optional authentication username":"Optional authentication username","Options":"Options","Original location":"Original location","Others":"Others","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.","Overwrite":"Overwrite","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (if encrypted)","Passphrase changed":"Passphrase changed","Passphrases are not matching":"Passphrases are not matching","Passphrases do not match":"Passphrases do not match","Password":"Password","Patching files with local blocks …":"Patching files with local blocks …","Path":"Path","Path not found":"Path not found","Path on server":"Path on server","Path or subfolder in the bucket":"Path or subfolder in the bucket","Pause":"Pause","Pause after startup or hibernation":"Pause after startup or hibernation","Pause options":"Pause options","Permissions":"Permissions","Pick location":"Pick location","Point to your backup files and restore from there":"Point to your backup files and restore from there","Port":"Port","Prevent tray icon automatic log-in":"Prevent tray icon automatic log-in","Previous":"Previous","Progress:":"Progress:","ProjectID is optional if the bucket exist":"ProjectID is optional if the bucket exist","Proprietary":"Proprietary","Purge Phase":"Purge Phase","Purging files complete!":"Purging files complete!","Purging files …":"Purging files …","Rebuilding local database …":"Rebuilding local database …","Recreate (delete and repair)":"Recreate (delete and repair)","Recreate Database Phase":"Recreate Database Phase","Recreating database …":"Recreating database …","Registering temporary backup …":"Registering temporary backup …","Relative paths not allowed":"Relative paths not allowed","Reload":"Reload","Remote":"Remote","Remote Path":"Remote Path","Remote Repository":"Remote Repository","Remote path":"Remote path","Remote repository":"Remote repository","Remote volume size":"Remote volume size","Remove":"Remove","Remove option":"Remove option","Removed files":"Removed files","Repair":"Repair","Repair Phase":"Repair Phase","Repairing database …":"Repairing database …","Repeat Passphrase":"Repeat Passphrase","Reporting:":"Reporting:","Reset":"Reset","Restore":"Restore","Restore complete!":"Restore complete!","Restore files":"Restore files","Restore files …":"Restore files …","Restore from":"Restore from","Restore from backup configuration":"Restore from backup configuration","Restore options":"Restore options","Restore read/write permissions":"Restore read/write permissions","Restored Files":"Restored Files","Restored Folders":"Restored Folders","Restored Symlinks":"Restored Symlinks","Restoring files …":"Restoring files …","Resume":"Resume","Rewritten File Lists":"Rewritten File Lists","Run again every":"Run again every","Run now":"Run now","Running commandline entry":"Running command line entry","Running task:":"Running task:","Running …":"Running …","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Same as the base install version: {{channelname}}","Sat":"Sat","Satellite":"Satellite","Save":"Save","Save and repair":"Save and repair","Save different versions with timestamp in file name":"Save different versions with timestamp in file name","Save immediately":"Save immediately","Scanning existing files …":"Scanning existing files …","Scanning for local blocks …":"Scanning for local blocks …","Schedule":"Schedule","Search":"Search","Search for files":"Search for files","Seconds":"Seconds","Select a log level and see messages as they happen:":"Select a log level and see messages as they happen:","Select files":"Select files","Server":"Server","Server and port":"Server and port","Server hostname or IP":"Server hostname or IP","Server is currently paused,":"Server is currently paused,","Server is currently paused, do you want to resume now?":"Server is currently paused, do you want to resume now?","Server password":"Server password","Server paused":"Server paused","Server state properties":"Server state properties","Settings":"Settings","Show":"Show","Show advanced editor":"Show advanced editor","Show hidden folders":"Show hidden folders","Show log":"Show log","Show log …":"Show log …","Show treeview":"Show treeview","Sia server password":"Sia server password","Smart backup retention":"Smart backup retention","Some OpenStack providers allow an API key instead of a password and tenant name":"Some OpenStack providers allow an API key instead of a password and tenant name","Some S3 providers might only be compatible with a certain client library":"Some S3 providers might only be compatible with a certain client library","Source Data":"Source Data","Source Files":"Source Files","Source data":"Source data","Source folders":"Source folders","Source:":"Source:","Specific builds for developers only. Not for use with important data.":"Specific builds for developers only. Not for use with important data.","Standard protocols":"Standard protocols","Start":"Start","Starting backup …":"Starting backup …","Starting restore …":"Starting restore …","Starting the restore process …":"Starting the restore process …","Stop after current file":"Stop after current file","Stop after the current file":"Stop after the current file","Stop now":"Stop now","Stop running backup":"Stop running backup","Stop running task":"Stop running task","Stopping after the current file:":"Stopping after the current file:","Stopping task:":"Stopping task:","Storage Type":"Storage Type","Storage class":"Storage class","Storage class for creating a bucket":"Storage class for creating a bucket","Stored":"Stored","Strong":"Strong","Success":"Success","Sun":"Sun","Symbolic link":"Symbolic link","System Files":"System Files","System default ({{levelname}})":"System default ({{levelname}})","System files":"System files","System info":"System info","System properties":"System properties","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Task is running","Temporary Files":"Temporary Files","Temporary files":"Temporary files","Test Phase":"Test Phase","Test connection":"Test connection","Testing permissions …":"Testing permissions …","Testing …":"Testing …","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"The backup is missing, has it been deleted?","The backup was temporary and does not exist anymore, so the log data is lost":"The backup was temporary and does not exist anymore, so the log data is lost","The bucket name should be all lower-case, convert automatically?":"The bucket name should be all lower-case, convert automatically?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?","The dark theme (by Michal)":"The dark theme (by Michal)","The default blue on white theme (by Alex)":"The default blue on white theme (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"The folder {{folder}} does not exist.\nCreate it now?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?","The passwords do not match":"The passwords do not match","The path does not appear to exist, do you want to add it anyway?":"The path does not appear to exist, do you want to add it anyway?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"The path must be an absolute path, i.e. it must start with a forward slash '/'","The region parameter is only applied when creating a new bucket":"The region parameter is only applied when creating a new bucket","The region parameter is only used when creating a bucket":"The region parameter is only used when creating a bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?","The storage class affects the availability and price for a stored file":"The storage class affects the availability and price for a stored file","The target folder contains encrypted files, please supply the passphrase":"The target folder contains encrypted files, please supply the passphrase","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?","This month":"This month","This week":"This week","Throttle settings":"Throttle settings","Thu":"Thu","Time":"Time","To File":"To File","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below","To export without a passphrase, uncheck the \"Encrypt file\" box":"To export without a passphrase, uncheck the \"Encrypt file\" box","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost/127.0.0.1 are always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.","Today":"Today","Trust host certificate?":"Trust host certificate?","Trust server certificate?":"Trust server certificate?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.","Tue":"Tue","Type passphrase here.":"Type passphrase here.","Type to highlight files":"Type to highlight files","Unknown backup size and versions":"Unknown backup size and versions","Until resumed":"Until resumed","Update channel":"Update channel","Update failed:":"Update failed:","Updating with existing database":"Updating with existing database","Uploaded files":"Uploaded files","Uploading verification file …":"Uploading verification file …","Usage statistics":"Usage statistics","Usage statistics, warnings, errors, and crashes":"Usage statistics, warnings, errors, and crashes","Use SSL":"Use SSL","Use existing database?":"Use existing database?","Use weak passphrase":"Use weak passphrase","Useless":"Useless","User data":"User data","User domain name":"User domain name","User has too many permissions":"User has too many permissions","User interface settings":"User interface settings","Username":"Username","Vacuuming database …":"Vacuuming database …","Validating …":"Validating …","Verifications":"Verifications","Verify files":"Verify files","Verifying answer":"Verifying answer","Verifying backend data …":"Verifying backend data …","Verifying files …":"Verifying files …","Verifying remote data …":"Verifying remote data …","Verifying restored files …":"Verifying restored files …","Verifying …":"Verifying …","Version ID":"Version ID","Very strong":"Very strong","Very weak":"Very weak","Visit us on":"Visit us on","WARNING: This will prevent you from restoring the data in the future.":"WARNING: This will prevent you from restoring the data in the future.","Waiting for task to begin":"Waiting for task to begin","Waiting for upload to finish …":"Waiting for upload to finish …","Warnings, errors and crashes":"Warnings, errors and crashes","We recommend that you encrypt all backups stored outside your system":"We recommend that you encrypt all backups stored outside your system","Weak":"Weak","Weak passphrase":"Weak passphrase","Wed":"Wed","Weeks":"Weeks","Where do you want to restore from?":"Where do you want to restore from?","Where do you want to restore the files to?":"Where do you want to restore the files to?","Years":"Years","Yes":"Yes","Yes, I have stored the passphrase safely":"Yes, I have stored the passphrase safely","Yes, I understand the risk":"Yes, I understand the risk","Yes, I'm brave!":"Yes, I'm brave!","Yes, please break my backup!":"Yes, please break my backup!","Yesterday":"Yesterday","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"You are changing the database path away from an existing database.\nAre you sure this is what you want?","You are currently running {{appname}} {{version}}":"You are currently running {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"You can stop the backup after any file uploads currently in progress have finished.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"You can stop the task immediately, or allow the process to continue its current file and then stop.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.","You have chosen to restore to a new location, but not entered one":"You have chosen to restore to a new location, but not entered one","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.","You must choose at least one source folder":"You must choose at least one source folder","You must enter a domain name to use v3 API":"You must enter a domain name to use v3 API","You must enter a name for the backup":"You must enter a name for the backup","You must enter a passphrase or disable encryption":"You must enter a passphrase or disable encryption","You must enter a password to use v3 API":"You must enter a password to use v3 API","You must enter a positive number of backups to keep":"You must enter a positive number of backups to keep","You must enter a tenant (aka project) name to use v3 API":"You must enter a tenant (aka project) name to use v3 API","You must enter a valid duration for the time to keep backups":"You must enter a valid duration for the time to keep backups","You must enter a valid retention policy string":"You must enter a valid retention policy string","You must fill in the password":"You must fill in the password","You must fill in the server name or address":"You must fill in the server name or address","You must fill in the username":"You must fill in the username","You must fill in {{field}}":"You must fill in {{field}}","You must select or fill in the AuthURI":"You must select or fill in the AuthURI","You must select or fill in the server":"You must select or fill in the server","You must specify a path":"You must specify a path","Your files and folders have been restored successfully.":"Your files and folders have been restored successfully.","Your passphrase is easy to guess. Consider changing passphrase.":"Your passphrase is easy to guess. Consider changing passphrase.","bucket/folder/subfolder":"bucket/folder/subfolder","byte":"byte","byte/s":"byte/s","custom":"custom","resume now":"resume now","unless you are explicitly specifying --group-id":"unless you are explicitly specifying --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} files ({{size}}) to go {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Hour","{{number}} Hours":"{{number}} Hours","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (took {{duration}})"}); - gettextCatalog.setStrings('es', {"- pick an option -":"- escoja una opción -","...loading...":"...cargando...","API key":"Clave API","AWS Access ID":"AWS Acceso ID","AWS Access Key":"AWS Clave de aceso","AWS IAM Policy":"AWS IAM Política","About":"Acerca de","About {{appname}}":"Acerca de {{appname}}","Access Key":"Clave de acceso","Access denied":"Acceso denegado","Access grant":"Acceso concedido","Access to user interface":"Acceso a la interfaz de usuario","Account name":"Nombre de la cuenta","Add a new backup":"Añadir nueva copia de seguridad","Add a path directly":"Agregar la ruta directamente","Add advanced option":"Añadir opción avanzada","Add backup":"Añadir copia de seguridad","Add filter":"Añadir filtro","Add path":"Añadir ruta","Added":"Agregado","Adjust bucket name?":"¿Ajustar el nombre del deposito?","Advanced Options":"Opciones Avanzadas","Advanced options":"Opciones avanzadas","Advanced:":"Avanzado:","All Hyper-V Machines":"Todas las máquinas de Hyper-V","All Microsoft SQL Databases":"Las bases de datos de Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos los informes de uso son enviados anónimamente y no contienen ninguna información personal. Contiene información sobre hardware y sistema operativo, el tipo de respaldo, duración de copia de seguridad, tamaño de fuente de datos y similares. No contiene rutas, nombres de archivos, nombres de usuarios, contraseñas o información sensible similar.","Allow remote access (requires restart)":"Permitir el acceso remoto (requiere reiniciar)","Allowed days":"Días permitidos","An existing file was found at the new location":"Se encontró un archivo existente en la nueva ubicación","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Se encontró un archivo existente en la nueva ubicación\n¿Está seguro que desea que la base de datos apunte a un archivo existente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Se ha encontrado una base de datos local existente para el almacenamiento.\nVolver a utilizar la base de datos permitirá a las instancias de línea de comandos y al servidor trabajar con el mismo almacenamiento remoto.\n\n¿Desea utilizar la base de datos existente?","Anonymous usage reports":"Informes de uso anónimos","Applications":"Aplicaciones","As Command-line":"Como Línea de comandos","AuthID":"AuthID","Authentication method":"Método de autentificación","Authentication method ({{auth_method}})":"Método de autentificación ({{auth_method}})","Authentication password":"Contraseña de autenticación","Authentication username":"Nombre de usuario de autenticación","Autogenerated passphrase":"Autogenerar frase de seguridad","B2 Application ID":"ID de la aplicación B2","B2 Application Key":"B2 clave de aplicación","B2 Cloud Storage Account ID":"B2 Cuenta Cloud Storage ID","B2 Cloud Storage Application ID":"ID de la aplicación de almacenamiento en la nube B2","B2 Cloud Storage Application Key":"B2 Clave de aplicación de Cloud Storage","Back":"Volver","Backup complete!":"Respaldo completo!","Backup destination":"Destino de la copia de seguridad","Backup location":"Ubicación de la copia de seguridad","Backup retention":"Conservación de copia de respaldo","Backup:":"Copia de seguridad:","Beta":"Beta","Broken access":"Acceso roto","Browse":"Navega","Browser default":"Navegador por defecto","Bucket create location":"Crear la ubicación del depósito","Bucket name":"Nombre del depósito","Bucket storage class":"Categoría de almacenamiento del depósito","Building list of files to restore …":"Creando una lista de archivos para restaurar ...","Building partial temporary database …":"Construyendo una base de datos parcial temporal ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Permitiendo el acceso remoto, el servidor atenderá requerimientos desde\ncualquier equipo de su red. Si Ud. habilita esta opción, asegurese siempre de usar\nla computadora dentro de una red protegida por un firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"De forma predeterminada, el icono de la bandeja abrirá la interfaz de usuario con un token que desbloquea la interfaz de usuario. Esto asegura que pueda acceder a la interfaz de usuario desde el icono de la bandeja, mientras que requiere que otros ingresen una contraseña. Si prefiere tener que escribir la contraseña, incluso al acceder a la interfaz de usuario desde el icono de la bandeja, habilite esta opción.","Cache Files":"Archivos caché","Canary":"Experimental e inestable (Canary)","Cancel":"Cancelar","Cannot move to existing file":"No se puede mover al archivo existente","Changelog":"Registro de cambios","Changelog for {{appname}} {{version}}":"Registro de cambios para {{appname}} {{version}}","Check failed:":"Error en chequeo:","Check for updates now":"Comprobar actualizaciones ahora","Checking for updates …":"Buscando actualizaciones ...","Chose a storage type to get started":"Elija un tipo de almacenamiento para empezar","Click the AuthID link to create an AuthID":"Haga clic en el enlace de AuthID para crear una AuthID","Click to set throttle options":"Acceda para opciones de aceleración","Client library to use":"Biblioteca cliente para usar","Commandline …":"Línea de comandos ...","Compact Phase":"Fase de compactación","Compact now":"Compactar ahora","Compacting remote data …":"Compactando datos remotos ...","Complete log":"Registro completo","Completing backup …":"Completando copia de seguridad ...","Completing previous backup …":"Completando copia de seguridad precia ...","Computer":"Ordenador","Configuration file:":"Archivo de configuración:","Configuration:":"Configuración:","Configure a new backup":"Configurar nueva copia de seguridad","Confirm delete":"Confirmar borrado","Confirm encryption passphrase":"Confirmar frase de seguridad cifrada","Confirm passphrase":"Confirme contraseña","Confirmation required":"Confirmación necesaria","Connect":"Conectar","Connect now":"Conectar ahora","Connecting to server …":"Conectando al servidor ...","Connection lost":"Conexión perdida","Connection worked!":"¡La conexión funcionó!","Container name":"Nombre del contenedor","Container region":"Contenedor de región","Continue":"Continuar","Continue without encryption":"Continuar sin cifrado","Copied!":"¡Copiado!","Copy":"Copia","Copy Destination URL to Clipboard":"Copiar la URL de destino al portapapeles","Copy failed. Please manually copy the URL":"Copía fallida. Por favor, copia manualmente la dirección URL","Core options":"Opciones de base","Counting ({{files}} files found, {{size}})":"Contando ({{files}} archivos encontrados, {{size}})","Crashes only":"Sólo bloqueos","Create bug report …":"Crear informe de errores ...","Create folder?":"¿Crear carpeta?","Created new limited user":"Creó un nuevo usuario limitado","Creating bug report …":"Creando informe de errores ...","Creating new user with limited access …":"Creando nuevo usuario con acceso limitado ...","Creating target folders …":"Creando carpetas de destino …","Creating temporary backup …":"Creando copia de seguridad temporal ...","Current action:":"Proceso actual:","Current file:":"Archivo actual:","Current version is {{versionname}} ({{versionnumber}})":"La versión actual es {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Personalizada S3 endpoint","Custom Satellite":"Satélite personalizado","Custom Satellite ({{satellite}})":"Satélite personalizado ({{satellite}})","Custom authentication url":"Url de autenticación personalizada","Custom backup retention":"Conservación de copia de respaldo personalizada","Custom location ({{server}})":"Ubicación personalizada ({{server}})","Custom region for creating buckets":"Región personalizada para la creación de depósitos","Custom region value ({{region}})":"Personalizar el valor de la región ({{region}})","Custom server url ({{server}})":"Url del servidor personalizada ({{server}})","Custom storage class ({{class}})":"Categoría de almacenamiento personalizado ({{class}})","Database …":"Base de datos ...","Days":"Días","Default":"Por defecto","Default ({{channelname}})":"({{channelname}}) por defecto","Default excludes":"Exclusiones por defecto","Default options":"Opciones por defecto","Delete":"Eliminar","Delete Phase (Old Backup Versions)":"Elimine Fase (Versiones Antiguas del Respaldo)","Delete backup":"Eliminar copia de seguridad","Delete backups that are older than":"Eliminar copias de seguridad que tengan mas de","Delete local database":"Eliminar base de datos local","Delete remote files":"Eliminar archivos remotos","Delete the local database":"Eliminar la base de datos local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"¿Eliminar {{filecount}} archivos con ({{filesize}}) del almacenamiento remoto?","Delete …":"Eliminar ...","Deleted":"Eliminado","Deleted Versions":"Versiones eliminadas","Deleted files":"Archivos eliminados","Deleting remote files …":"Eliminando archivos remotos ...","Deleting unwanted files …":"Eliminando archivos no deseados ...","Description (optional)":"Descripción (opcional)","Description:":"Descripción:","Desktop":"Escritorio","Destination":"Destino","Destination path":"Ruta de destino","Disabled":"Desactivar","Dismiss":"Descartar","Dismiss all":"Ignorar todo","Display and color theme":"Apariencia y esquema de colores","Do you really want to delete the backup: \"{{name}}\" ?":"¿Realmente desea eliminar la copia de seguridad: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Realmente desea eliminar la base de datos local: {{name}}","Done":"Hecho","Download":"Descargar","Downloaded files":"Ficheros descargados","Downloading files …":"Descargando archivos ...","Downloading update…":"Descargando actualización ...","Duplicate option {{opt}}":"Opciones de duplicado {{opt}}","Duplicati Website":"Sitio Web Duplicati","Duplicati forum":"Foro de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati se ejecutará cuando inicie, pero permanecerá en stand-by mientras se ejecute.\nDuplicati ocupará minimos recursos del sistema y ningúna tarea de respaldo se ejectutará.","Duration":"Duración","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada copia tiene una base de datos local asociada que almacena información sobre la copia de seguridad remota en la máquina local.\nAl eliminar una copia de seguridad, también puede borrar la base de datos local sin afectar a la habilidad de restaurar los archivos remotos.\nSi está utilizando la base de datos local para copias de seguridad desde la línea de comandos, debe mantener la base de datos.","Edit as list":"Editar lista","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Cifrar archivo","Encryption":"Cifrado","Encryption changed":"Cambios de cifrado","Encryption passphrase":"Contraseña de cifrado","End":"Fin","Enter URL":"Introduzca URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ingrese una estrategia de retención en forma manual. Los campos son D/W/Y para dias/semanas/años y U para \"ilimitado\". La sintaxis es: 7D:1D,4W:1W,36M:1M. Este ejemplo mantiene una copia para cada uno de los 7 dias, una para cada una de las 4 semanas y una por cada uno de los próximos 36 meses. Esto también puede escribirse como 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduzca la frase de seguridad, si la hay","Enter configuration details":"Introduzca los detalles de configuración","Enter encryption passphrase":"Introduzca la frase de seguridad","Enter expression here":"Introduzca aquí la expresión","Enter the destination path":"Introduzca la ruta de destino","Error":"Error","Error!":"¡Error!","Errors and crashes":"Errores y bloqueos","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir directorios cuyos nombres contienen","Exclude expression":"Excluir expresión","Exclude file":"Excluir archivos","Exclude file extension":"Excluir extensión de archivo","Exclude files whose names contain":"Excluir archivos cuyos nombres contengan","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Excluir la carpeta","Exclude regular expression":"Excluir la expresión regular","Existing file found":"Archivo existente encontrado","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuración de copia de seguridad","Export configuration":"Exportar configuración","Export passwords":"Exportar contraseñas","Export …":"Exportar ...","Exporting …":"Exportando ...","External link":"Enlace externo","FTP (Alternative)":"FTP (Alternativa)","Failed to build temporary database: {{message}}":"Error al crear base de datos temporal: {{message}}","Failed to connect:":"Fallo al conectar:","Failed to connect: {{message}}":"No se pudo conectar: {{message}}","Failed to delete:":"Error al eliminar:","Failed to fetch path information: {{message}}":"Error al recuperar información de la ruta: {{message}}","Failed to find backup:":"Error para encontrar respaldo:","Failed to read backup defaults:":"Error al leer los valores predeterminados de copia de seguridad:","Failed to restore files: {{message}}":"Fallo al restaurar archivos: {{message}}","Failed to save:":"Error al guardar:","Fetching path information …":"Obteniendo información de ruta ...","File":"Archivo","Files larger than:":"Archivos que superen:","Filters":"Filtros","Finished!":"¡Terminado!","First run setup":"Configuración de primera ejecución","Folder":"Carpeta","Folder path":"Ruta de la carpeta","Fri":"Vie","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Proyecto ID","General":"General","General backup settings":"Configuración general de la copia de seguridad","General options":"Opciones generales","Generate":"Generar","Generate IAM access policy":"Generar política de acceso IAM","Getting file versions …":"Obteniendo versiones de archivos ...","Group email":"Correo del grupo","Hidden files":"Archivos ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar carpetas ocultas","Home":"Inicio","Hostnames":"Nombres de host","Hours":"Horas","How do you want to handle existing files?":"¿Cómo desea manejar los archivos existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si la fecha se paso, se ejecutará el trabajo tan pronto como sea posible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si al menos una copia mas nueva es encontrada, todas las copias anteriores\na ese día s eliminarán.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si no introduce una ruta, todos los archivos se almacenarán en la carpeta de inicio de sesión.\n¿Está seguro que es lo que quiere?","If you do not enter an API Key, the tenant name is required":"Si no introduce una clave API, requerirá el nombre de cliente","Import":"Importar","Import Destination URL":"Importar Destino URL","Import backup configuration":"Importar configuración de copias de seguridad","Import from a file":"Importar desde un archivo","Import metadata":"Importar metadatos","Importing …":"Importando ...","Include a file?":"¿Incluir un archivo?","Include expression":"Incluir una expresión","Include regular expression":"Incluir una expresión regular","Incorrect answer, try again":"Respuesta incorrecta, intente de nuevo","Individual builds for developers only. Not for use with important data.":"Compilaciones individuales solo para desarrolladores. No usar con datos importantes.","Information":"Información","Invalid characters in path":"Caracteres no válidos en la ruta","Invalid retention time":"Tiempo de retención no válido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Es posible conectar a un FTP sin contraseña.\n¿Está seguro que su servidor FTP admite los inicios de sesión sin contraseña?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Mantener un número específico de copias de seguridad","Keep all backups":"Mantener todas las copias de seguridad","Keystone API version":"Versión de la API de Keystone","Language in user interface":"Idioma de interfaz de usuario","Last month":"Mes pasado","Last successful backup:":"Última copia de seguridad exitosa","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauración exitosa: {{time}} (took {{duration || '0 seconds'}})","Latest":"Más reciente","Libraries":"Librerías","Listing backup dates …":"Listando fechas de las copias de seguridad","Listing remote files for purge …":"Listando archivos remotos para purgar ...","Listing remote files …":"Listando archivos remotos ...","Live":"En vivo","Load a configuration from an exported job or a storage provider":"Cargar una configuración desde un trabajo exportado o un proveedor de almacenamiento","Load destination from an exported job or a storage provider":"Cargar un destino desde un trabajo exportado o un proveedor de almacenamiento","Load older data":"Cargar datos anteriores","Loading …":"Cargando ...","Local Repository":"Repositorio Local","Local database path:":"Ruta de la base de datos local:","Local repository":"Repositorio local","Local storage":"Almacenamiento local","Location":"Localización","Location where buckets are created":"La ubicación donde se crean los depósitos","Log data for {{Backup.Backup.Name}}":"Registrar datos para {{Backup.Backup.Name}}","Log data from the server":"Registrar datos desde el servidor","Log out":"Desconectar","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Mantenimiento","Manually type path":"Escribir manualmente la ruta","Max download speed":"Velocidad máxima de descarga","Max upload speed":"Velocidad máxima de carga","Menu":"Menú","Microsoft SQL Database:":"Base de datos Microsoft SQL:","Microsoft SQL Databases":"Bases de datos Microsoft SQL:","Minimum redundancy":"Redundancia mínima","Minimum redundancy is 1.0":"Redundancia mínima es 1.0","Minutes":"Minutos","Missing name":"Falta el nombre","Missing passphrase":"Falta la frase de seguridad","Missing sources":"Faltan las fuentes","Modified":"Modificado","Mon":"Lun","Months":"Meses","Move existing database":"Mover base de datos existente","Move failed:":"Fallos al mover:","My Documents":"Mis Documentos","My Music":"Mi Música","My Photos":"Mis Fotos","My Pictures":"Mis Imágenes","Name":"Nombre","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"El nuevo nombre de usuario es {{user}}.\nCredenciales actualizadas para el nuevo usuario restringido","Next":"Siguiente","Next scheduled run:":"Siguiente ejecución programada:","Next scheduled task:":"Siguiente tarea programada:","Next task:":"Siguiente tarea:","Next time":"La próxima vez","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No se especificó previamente un certificado, por favor verifica con el administrador del servidor que la llave es correcta: {{key}}\n\n¿Desea aprobar la llave del host reportada?","No editor found for the "{{backend}}" storage type":"Ningún editor para el "{{backend}}" tipo de almacenamiento","No encryption":"Sin cifrado","No items selected":"No hay artículos seleccionados","No items to restore, please select one or more items":"No hay artículos para restaurar, seleccione uno o más elementos","No passphrase entered":"No se introdujo clave de seguridad","No scheduled tasks":"No hay tareas programadas","Non-matching passphrase":"No coincide la frase de seguridad","None / disabled":"Ninguno / desactivado","Not using encryption":"Sin usar cifrado","Nothing will be deleted. The backup size will grow with each change.":"Nada será borrado. El tamaño de la copia de seguridad aumentará con cada cambio.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Una vez que haya más copias de seguridad que el número especificado, se eliminarán las copias de seguridad más antiguas.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Abierto","Operating System":"Sistema operativo","Operation":"Operación","Operations:":"Operaciones:","Optional authentication password":"Contraseña de autentificación opcional","Optional authentication username":"Nombre de usuario para autentificación opcional","Options":"Opciones","Original location":"Localización original","Others":"Otros","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Con el tiempo, las copias de seguridad se eliminarán automáticamente. Seguirá habiendo una copia de seguridad para cada uno de los últimos 7 días, cada una de las últimas 4 semanas, cada uno de los últimos 12 meses. Siempre permanecerá, al menos, una copia de seguridad.","Overwrite":"Sobrescribir","Passphrase":"Frase de seguridad","Passphrase (if encrypted)":"Frase de seguridad (con cifrado)","Passphrase changed":"Frase de seguridad cambiada","Passphrases are not matching":"Las frases de seguridad no coinciden","Passphrases do not match":"Las frases de seguridad no coinciden","Password":"Contraseña","Patching files with local blocks …":"Parchear archivos con bloques locales","Path":"Ruta","Path not found":"Ruta no encontrada","Path on server":"Ruta del servidor","Path or subfolder in the bucket":"Ruta o subcarpeta en el depósito","Pause":"Pausa","Pause after startup or hibernation":"Pausar después del arranque o de hibernación","Pause options":"Opciones de pausa","Permissions":"Permisos","Pick location":"Elegir ubicación","Point to your backup files and restore from there":"Indique sus ficheros de copia de seguridad y restáurelos desde allí","Port":"Puerto","Prevent tray icon automatic log-in":"Impedir el inicio de sesión automático con el icono de la bandeja","Previous":"Anterior","Progress:":"Progreso","ProjectID is optional if the bucket exist":"ProjectID es opcional si el depósito existe","Proprietary":"Propietario","Purge Phase":"Fase de purgado","Purging files complete!":"¡Purgado de ficheros finalizado!","Purging files …":"Purgando archivos ...","Rebuilding local database …":"Reconstruyendo base de datos local ...","Recreate (delete and repair)":"Recrear (borrar y reparar)","Recreate Database Phase":"Fase de recreación de base de datos","Recreating database …":"Recreando base de datos …","Registering temporary backup …":"Registrando copia de seguridad temporal …","Relative paths not allowed":"No se permiten rutas relativas","Reload":"Recargar","Remote":"Remoto","Remote Path":"Ruta Remota","Remote Repository":"Repositorio Remoto","Remote path":"Ruta remota","Remote repository":"Repositorio remoto","Remote volume size":"Tamaño de volumen remoto","Remove":"Quitar","Remove option":"Quitar opción","Removed files":"Ficheros borrados","Repair":"Reparar","Repair Phase":"Fase de reparación","Repairing database …":"Reparando base de datos…","Repeat Passphrase":"Repita la frase de seguridad","Reporting:":"Reportando:","Reset":"Resetear","Restore":"Restaurar","Restore complete!":"¡Restauración finalizada!","Restore files":"Restaurar archivos","Restore files …":"Restaurando archivos ...","Restore from":"Restaurar desde","Restore from backup configuration":"Restaurar desde una configuración de copia de seguridad","Restore options":"Opciones de restauración","Restore read/write permissions":"Restaurar permisos de lectura/escritura","Restored Files":"Archivos Restaurados","Restored Folders":"Carpetas Restauradas","Restored Symlinks":"Symlinks restaurados","Restoring files …":"Restaurando archivos ....","Resume":"Resumir","Rewritten File Lists":"Listas de ficheros reescritos","Run again every":"Volver a ejecutar cada","Run now":"Ejecutar ahora","Running commandline entry":"Ejecutando entrada de linea de comandos","Running task:":"Ejecutando tarea:","Running …":"Ejecutando ...","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Igual que la versión base instalada: {{channelname}}","Sat":"Sab","Satellite":"Satélite","Save":"Guardar","Save and repair":"Guardar y reparar","Save different versions with timestamp in file name":"Guardar diferentes versiones con fecha y hora en el nombre de archivo","Save immediately":"Guardar inmediatamente","Scanning existing files …":"Escaneando archivos existentes ...","Scanning for local blocks …":"Buscando bloques locales…","Schedule":"Horario","Search":"Buscar","Search for files":"Buscar archivos","Seconds":"Segundos","Select a log level and see messages as they happen:":"Seleccione un nivel de registro y vea los mensajes a medida que ocurren:","Select files":"Seleccionar ficheros","Server":"Servidor","Server and port":"Servidor y puerto","Server hostname or IP":"Nombre del servidor o IP","Server is currently paused,":"El servidor se encuentra en pausa,","Server is currently paused, do you want to resume now?":"El servidor se encuentra en pausa, ¿quiere reanudar ahora?","Server password":"Contraseña del servidor","Server paused":"Servidor pausado","Server state properties":"Propiedades del estado del servidor","Settings":"Configuraciones","Show":"Mostrar","Show advanced editor":"Mostrar el editor avanzado","Show hidden folders":"Mostrar carpetas ocultas","Show log":"Mostrar registro","Show log …":"Mostrar registro …","Show treeview":"Mostrar vista de árbol","Sia server password":"Contraseña del servidor Sia","Smart backup retention":"Retención de copias inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Algunos proveedores de OpenStack permiten una clave API en lugar de un nombre del cliente y contraseña","Some S3 providers might only be compatible with a certain client library":"Es posible que algunos proveedores de S3 solo sean compatibles con una biblioteca de cliente determinada","Source Data":"Datos de Origen","Source Files":"Archivos de origen","Source data":"Datos de origen","Source folders":"Carpetas de origen","Source:":"Origen:","Specific builds for developers only. Not for use with important data.":"Compilaciones específicas solo para desarrolladores. No usar con datos importantes.","Standard protocols":"Protocolos estándar","Start":"Comenzar","Starting backup …":"Comenzando copia de seguridad","Starting restore …":"Comenzando restauración ...","Starting the restore process …":"Comenzando el proceso de restauración ...","Stop after current file":"Parar después del archivo actual","Stop after the current file":"Detener después del archivo actual","Stop now":"Detener ahora","Stop running backup":"Detener respaldo en curso","Stop running task":"Detener tarea en ejecución","Stopping after the current file:":"Parando después del archivo actual:","Stopping task:":"Deteniendo tarea:","Storage Type":"Tipo de Almacenamiento","Storage class":"Categoría de almacenamiento","Storage class for creating a bucket":"Categoría de almacenamiento para la creación de un depósito","Stored":"Almacenados","Strong":"Fuerte","Success":"Éxito","Sun":"Dom","Symbolic link":"Enlace simbólico","System Files":"Archivos del sistema","System default ({{levelname}})":"Sistema por defecto ({{levelname}})","System files":"Archivos de sistema","System info":"Información del sistema","System properties":"Propiedades del sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"La tarea está ejecutandose","Temporary Files":"Archivos temporales","Temporary files":"Archivos temporales","Test Phase":"Fase de pruebas","Test connection":"Conexión de prueba","Testing permissions …":"Probando permisos…","Testing …":"Probando ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"El campo '{{fieldname}}' contiene un carácter no válido: {{carácter}} (valor: {{valor}}, índice: {{pos}})","The backup is missing, has it been deleted?":"Falta la copia de seguridad, ¿se ha eliminado?","The backup was temporary and does not exist anymore, so the log data is lost":"La copia de seguridad era temporal y ya no existe, por lo que los datos de registro se han perdido.","The bucket name should be all lower-case, convert automatically?":"El nombre del depósito debe ser todo en minúsculas, ¿convertir automáticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuración debe mantenerse segura. ¿Está seguro de que desea guardar un archivo sin cifrar que contenga sus contraseñas?","The dark theme (by Michal)":"Tema oscuro (por Michal)","The default blue on white theme (by Alex)":"Tema por defecto azul sobre blanco (por Alex)","The folder {{folder}} does not exist.\nCreate it now?":"La carpete {{carpeta}} no existe.\n¿La creo ahora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clave de host fue cambiada, compruebe con el administrador del servidor si esto es correcto, de lo contrario usted podría ser víctima de un ataque MAN-IN-THE-MIDDLE.\n\n¿Desea REMPALAZAR su ACTUAL clave de host \"{{prev}}\" con la clave del host REGISTRADA: {{key}}?","The passwords do not match":"Las contraseñas no coinciden","The path does not appear to exist, do you want to add it anyway?":"La ruta parece que no existe, ¿desea agregar de todos modos?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"La ruta no termina con un carácter '{{dirsep}}', que significa que incluye un archivo, no una carpeta.\n\n¿Desea incluir el archivo especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"La ruta debe ser una ruta absoluta, es decir, debe comenzar con una barra '/'","The region parameter is only applied when creating a new bucket":"El parámetro de la región sólo se aplica al crear un nuevo depósito","The region parameter is only used when creating a bucket":"El parámetro de la región sólo se utiliza al crear un depósito","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"El certificado del servidor no puede ser validado.\n¿Quieres aprobar el certificado SSL con el hash: {{hash}}?","The storage class affects the availability and price for a stored file":"La categoría de almacenamiento afecta la disponibilidad y precio de un archivo almacenado","The target folder contains encrypted files, please supply the passphrase":"La carpeta de destino contiene archivos encriptados, por favor suministra la frase de seguridad","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"El usuario tiene demasiados permisos. ¿Quieres crear un usuario nuevo, con sólo permisos para la ruta seleccionada?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Esta copia de seguridad fue creada en otro sistema operativo. Restaurar estos ficheros sin indicar una carpeta de destino puede provocar que sean restaurados en ubicaciones imprevistas ¿Está seguro de que quiere continuar sin elegir una carpeta de destino?","This month":"Este mes","This week":"Esta semana","Throttle settings":"Ajustes de aceleración.","Thu":"Jue","Time":"Hora","To File":"A archivo","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que desea eliminar todos los archivos remotos \"{{name}}\", por favor ingrese la palabra que ves abajo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sin una frase de seguridad, desactive la casilla \"Cifrar el archivo\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar varios ataques basados en DNS, Duplicati limita los nombres de anfitriones permitidos a los que se enumeran aquí. El acceso directo a IP y al anfitrión local siempre está permitido. Se pueden proporcionar varios nombres de anfitrión con un separador de punto y coma. Si alguno de los nombres de anfitrión permitidos es un asterisco (*), todos los nombres de anfitrión están permitidos y esta función está desactivada. Si el campo está vacío, solo se permite el acceso a la dirección IP y al anfitrión local.","Today":"Hoy","Trust host certificate?":"¿Confiar en el certificado del host?","Trust server certificate?":"¿Confiar en el certificado del servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probar las nuevas funciones en las que estamos trabajando. Actualmente, la versión más estable disponible. Pruebe a Restaurar los datos antes de usarlo en entornos de producción","Tue":"Mar","Type passphrase here.":"Escriba la frase de seguridad aquí.","Type to highlight files":"Tipo para seleccionar archivos","Unknown backup size and versions":"Tamaño y versiones de la copia de seguridad desconocidas","Until resumed":"Hasta reanudar","Update channel":"Canal de actualización","Update failed:":"Error de actualización:","Updating with existing database":"Actualizando la base de datos existente","Uploaded files":"Archivos subidos","Uploading verification file …":"Subiendo archivo de verificación…","Usage statistics":"Estadísticas de uso","Usage statistics, warnings, errors, and crashes":"Estadísticas de uso, advertencias, errores y bloqueos","Use SSL":"Usar SSL","Use existing database?":"¿Usar base de datos existente?","Use weak passphrase":"Uso de frase de seguridad débil","Useless":"Inútil","User data":"Datos de usuario","User domain name":"Nombre de dominio de usuario","User has too many permissions":"El usuario tiene demasiados permisos","User interface settings":"Preferencias de la interfaz de usuario","Username":"Nombre de usuario","Vacuuming database …":"Limpiando la base de datos ...","Validating …":"Validando ...","Verifications":"Verificaciones","Verify files":"Verificar archivos","Verifying answer":"Verificando respuesta","Verifying backend data …":"Verificando datos del servidor ...","Verifying files …":"Verificando archivos ...","Verifying remote data …":"Verificando datos remotos ...","Verifying restored files …":"Verificando archivos restaurados ...","Verifying …":"Verificando ...","Version ID":"ID de versión","Very strong":"Muy fuerte","Very weak":"Muy débil","Visit us on":"Visítenos en","WARNING: This will prevent you from restoring the data in the future.":"ADVERTENCIA: Esto le impedirá restaurar los datos en el futuro.","Waiting for task to begin":"Esperando que se inicie la tarea","Waiting for upload to finish …":"Esperando a que finalice la carga …","Warnings, errors and crashes":"Advertencias, errores y bloqueos","We recommend that you encrypt all backups stored outside your system":"Recomendamos cifrar todas las copias de seguridad almacenadas fuera de su sistema","Weak":"Débil","Weak passphrase":"Frase de seguridad débil","Wed":"Mié","Weeks":"Semanas","Where do you want to restore from?":"¿Desde dónde quiere restaurar?","Where do you want to restore the files to?":"¿Dónde desea restaurar los archivos?","Years":"Años","Yes":"Sí","Yes, I have stored the passphrase safely":"Sí, he guardado la frase de seguridad de forma segura","Yes, I understand the risk":"Sí, entiendo el riesgo","Yes, I'm brave!":"Sí, ¡soy valiente!","Yes, please break my backup!":"Sí, por favor, ¡rompe mi copia de seguridad!","Yesterday":"Ayer","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Está cambiando la ruta de la base de datos de una base de datos existente.\n¿Realmente es lo que quieres?","You are currently running {{appname}} {{version}}":"Actualmente está ejecutando {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Puede detener la copia de seguridad después de que finalice cualquier carga de archivo en curso.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Puede detener la tarea inmediatamente o permitir que el proceso continúe con su archivo actual y luego se detenga.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Ha cambiado el modo de encriptación. Esto puede quebrar cosas. Le animamos a crear una nueva copia de seguridad en su lugar","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Ha cambiado la frase de seguridad, la cual no es compatible. Le animamos a crear una nueva copia de seguridad en su lugar.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Ha optado por no cifrar la copia de seguridad. El cifrado se recomienda para todos los datos almacenados en un servidor remoto.","You have chosen to restore to a new location, but not entered one":"Ha elegido restaurar a una nueva ubicación, pero no la ha indicado","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Ha generado una frase de contraseña segura. Asegúrese de haber hecho una copia segura de la frase de contraseña, ya que los datos no se pueden recuperar si la pierde.","You must choose at least one source folder":"Debe seleccionar al menos una carpeta de origen","You must enter a domain name to use v3 API":"Debe ingresar un nombre de dominio para usar la API v3","You must enter a name for the backup":"Debe introducir un nombre para la copia de seguridad","You must enter a passphrase or disable encryption":"Debe ingresar una frase de seguridad o deshabilitar el cifrado","You must enter a password to use v3 API":"Debe ingresar una contraseña para usar la API v3","You must enter a positive number of backups to keep":"Debe especificar un número positivo de copias de seguridad a guardar","You must enter a tenant (aka project) name to use v3 API":"Debe ingresar un nombre de cliente (también conocido como proyecto) para usar la API v3","You must enter a valid duration for the time to keep backups":"Debe introducir una duración válida para el tiempo de retención de las copias de seguridad","You must enter a valid retention policy string":"Debes ingresar una cadena de política de retención válida","You must fill in the password":"Debe rellenar la contraseña","You must fill in the server name or address":"Debe introducir el nombre del servidor o la dirección","You must fill in the username":"Debe rellenar el nombre de usuario","You must fill in {{field}}":"Debe rellenar el {{field}}","You must select or fill in the AuthURI":"Debe seleccionar o rellenar la AuthURI","You must select or fill in the server":"Debe seleccionar o rellenar en el servidor","You must specify a path":"Debe especificar una ruta de acceso","Your files and folders have been restored successfully.":"Los archivos y carpetas han sido restaurados con éxito.","Your passphrase is easy to guess. Consider changing passphrase.":"Tu frase de seguridad es fácil de adivinar. Considere cambiarla.","bucket/folder/subfolder":"depósito/carpeta/subcarpeta","byte":"byte","byte/s":"byte/s","custom":"Personalizar","resume now":"reanudar ahora","unless you are explicitly specifying --group-id":"a menos que usted haya especificando explícitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} fue desarrollado principalmente por {{dev1}} y {{dev2}}. Puede descargarse {{appname}} desde {{websitename}}. {{appname}} está licenciado bajo {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} ficheros ({{size}}) para finalizar {{speed_txt}} ","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versión","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versiones","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versiones"],"{{number}} Hour":"{{number}} Hora","{{number}} Hours":"{{número}} Horas","{{number}} Minutes":"{{number}} Minutos","{{time}} (took {{duration}})":"{{time}} (llevó {{duration}})"}); - gettextCatalog.setStrings('fi', {"- pick an option -":"- Valitse jokin vaihtoehto -","...loading...":"...ladataan...","API key":"API-avain","AWS Access ID":"Tunniste \"Access Key ID\" palveluun AWS","AWS Access Key":"Tunniste \"Access Key ID\" palveluun AWS","AWS IAM Policy":"Palvelun AWS IAM-asetukset","About":"Tietoja","About {{appname}}":"Tietoja sovelluksesta {{appname}}","Access Key":"Pääsyavain","Access denied":"Pääsy evätty","Access to user interface":"Käyttöoikeus käyttöliittymään","Account name":"Käyttäjätunnus","Add a new backup":"Lisää uusi varmuuskopio","Add a path directly":"Lisää suora polku","Add advanced option":"Anna harvoin tarvittava valitsin","Add backup":"Lisää varmuuskopio","Add filter":"Lisää suodatin","Add path":"Lisää polku","Added":"Lisätty","Adjust bucket name?":"Muuta ämpärin nimeä?","Advanced Options":"Harvoin tarvittavat valitsimet","Advanced options":"Harvoin tarvittavat valitsimet","Advanced:":"Harvoin tarvittavat asetukset","All Hyper-V Machines":"Kaikki Hyper-V-virtuaalikoneet","All Microsoft SQL Databases":"Kaikki Microsoft SQL -tietokannat","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Kaikki käyttöraportit lähetetään anonyymisti. Ne eivät sisällä mitään henkilökohtaisia tietoja. Raportit sisältävät tietoja laitteistosta ja käyttöjärjestelmästä, käytetystä etäpalvelusta, varmuuskopion kestosta, varmuuskopioitavan datan määrästä yms.Raportit eivät sisällä polkuja, tiedostonimiä, käyttäjätunnuksia, salasanoja tai vastaavia tietoja.","Allow remote access (requires restart)":"Salli etäyhteydet (Vaatii Duplicatin uudeleenkäynnistämisen)","Allowed days":"Sallitut päivät","An existing file was found at the new location":"Olemassaoleva tiedosto löydettiin uudesta paikasta","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Annettu tietokanta on jo olemassa.\nOletko varma, että haluat käyttää olemassaolevaa tietokantaa?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Löydettiin olemassaoleva paikallinen tietokanta tälle varmuuskopiolle.\nSaman tietokannan käyttäminen mahdollistaa kometorivi-ohjelman ja palvelimen käyttämisen saman varmuuskopion kanssa.\n\nHaluatko käyttää samaa tietokantaa?","Anonymous usage reports":"Anonyymit käyttöraportit","Applications":"Sovellukset","As Command-line":"Komentona","AuthID":"AuthID","Authentication method":"Tunnistautumistapa","Authentication password":"Kirjautumissalasana","Authentication username":"Käyttäjätunnus","Autogenerated passphrase":"Automaattisesti luoto salauslause","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"Tunnus B2 Cloud Storage Account ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Palaa","Backup complete!":"Varmuuskopiointi valmis!","Backup destination":"Sijainti, johon varmuuskopio tehdään","Backup location":"Varmuuskopion sijainti","Backup:":"Varmuuskopio:","Beta":"Beta","Broken access":"Pääsy epäonnistui","Browse":"Selaa","Browser default":"Selaimen oletusasetus","Bucket create location":"Luo ämpäri sijaintiin","Bucket name":"Ämpärin nimi","Bucket storage class":"Ämpärin tallennusluokka","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Sallimalla etäyhteyden ohjelmisto kuuntelee pyyntöjä miltä tahansa laitteelta verkossa. Jos sallit tämän, varmista että tietokoneesi on aina palomuurilla suojatussa verkossa.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Oletuksena huomautusalueen kuvake avaa käyttöliittymän ja poistaa käyttöliitymän lukituksen erillisellä valtuutuksella. Tämä mahdollistaa käyttöliittymän käytön huomautusalueen kuvakkeesta ilman salasanaa, vaikka muille käyttöliittymä on salasanasuojattu. Jos haluat käyttää salasanaa myös huomatusalueen kuvakkeen kanssa, valitse tämä valinta.","Cache Files":"Välimuistitiedostot","Canary":"Canary","Cancel":"Peruuta","Cannot move to existing file":"Ei voida korvata olemassaolevaa tiedostoa","Changelog":"Muutokset","Changelog for {{appname}} {{version}}":"Muutokset versiossa {{appname}} {{version}}","Check failed:":"Päivitysten haku epäonnistui:","Check for updates now":"Tarkista päivitykset","Checking for updates …":"Tarkistetaan päivityksiä ...","Chose a storage type to get started":"Valitseensin tallennustyyppi","Click the AuthID link to create an AuthID":"Klikkaa AuthID-linkkiä luodaksesi AuthID-tunnisteen","Client library to use":"Käytettävä kirjasto","Commandline …":"Komentorivi ...","Compact Phase":"Tiivistys-vaihe","Compact now":"Tiivistä nyt","Compacting remote data …":"Tiiistetään kohteen tiedostoja ...","Complete log":"Koko loki","Completing backup …":"Viimeistellään varmuuskopiota ...","Computer":"Tietokone","Configuration file:":"Asetustiedosto","Configuration:":"Asetukset:","Configure a new backup":"Määrittele uusi varmuuskopio","Confirm delete":"Vahvista poistaminen","Confirm encryption passphrase":"Vahvista salauslause","Confirm passphrase":"Vahvista salasana","Confirmation required":"Tarvitsen vahvistuksen","Connect":"Yhdistä","Connect now":"Yhdistä nyt","Connecting to server …":"Yhdistetään palvelimeen ...","Connection lost":"Yhteys katkesi","Connection worked!":"Yhteys toimi!","Container name":"Kontin nimi","Container region":"Kontin alue","Continue":"Jatka","Continue without encryption":"Jatka salaamatta","Copied!":"Kopioitu!","Copy":"Kopioi","Copy Destination URL to Clipboard":"Kopio etäpalvelimen osoite leikepöydälle","Copy failed. Please manually copy the URL":"Kopionti epäonnistui. Kopio osoite käsin","Core options":"Ydinasetukset","Counting ({{files}} files found, {{size}})":"Lasketaan tiedostoja. (Löydetty {{files}} tiedostoa, {{size}})","Crashes only":"Vain kaatumiset","Create bug report …":"Luo virheraportti ...","Create folder?":"Luo kansio?","Created new limited user":"Luotiin uusi rajoitettu käyttäjä","Creating bug report …":"Luodaan virheraporttia ...","Creating new user with limited access …":"Luodaan uusi rajoitettu käyttäjä","Creating target folders …":"Luodaan kohdekansiot ...","Creating temporary backup …":"Luodaan tilapäinen varmuuskopio ...","Current file:":"Nykyinen tiedosto:","Current version is {{versionname}} ({{versionnumber}})":"Nykyinen versio on {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Vaihtoehtoinen S3 päätepiste","Custom authentication url":"Vaihtoehtoinen autentikointiosoite","Custom location ({{server}})":"Vaihtoehtoinen sijainti ({{server}})","Custom region for creating buckets":"Vaihtoehtoinen alue ämpärin luomista varten","Custom region value ({{region}})":"Vaihtoehtoinen alue ({{region}})","Custom server url ({{server}})":"Vaihtoehtoisen palvelimen osoite ({{server}})","Custom storage class ({{class}})":"Vaihtoehtoinen tallennusluokka ({{class}})","Database …":"Tietokanta ...","Days":"Päivää","Default":"Oletus","Default ({{channelname}})":"Oletus ({{channelname}})","Default options":"Oletusasetukset","Delete":"Poista","Delete backup":"Poista varmuuskopio","Delete backups that are older than":"Poista varmuuskopiot, jotka ovat vanhempia kuin","Delete local database":"Poista paikallinen tietokanta","Delete remote files":"Poista tiedostot etäpalvelimelta","Delete the local database":"Poista paikallinen tietokanta","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Poistetaanko {{filecount}} tiedostoa ({{filesize}}) etäpalvelimelta","Delete …":"Poista ...","Deleted":"Poistettu","Deleted Versions":"Poistetut versiot","Deleted files":"Poistetut tiedostot","Deleting remote files …":"Poistetaan kohteen tiedostoja ...","Deleting unwanted files …":"Poistetaan turhia tiedostoja ...","Description (optional)":"Kuvaus (valinnainen)","Description:":"Kuvaus:","Desktop":"Työpöytä","Destination":"Kohde","Destination path":"Kohdepolku","Disabled":"Poistettu käytöstä","Dismiss":"Ohita","Dismiss all":"Hylkää kaikki","Display and color theme":"Näyttö ja väriteema","Do you really want to delete the backup: \"{{name}}\" ?":"Haluatko varmasti poistaa varmuuskopion \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Haluatko varmasti poistaa varmuuskopion {{name}} paikallisen tietokannan?","Done":"Valmis","Download":"Lataa","Downloaded files":"Ladatut tiedostot","Downloading files …":"Ladataan tiedostoja ...","Downloading update…":"Ladataan päivitystä ...","Duplicate option {{opt}}":"Sama valitsin {{opt}} annettiin kahdesti","Duplicati Website":"Duplicatin verkkosivu","Duplicati forum":"Duplicatin keskustelualue","Duration":"Kesto","Edit as list":"Muokkaa listana","Edit as text":"Muokkaa tekstinä","Edit …":"Muokkaa ...","Encrypt file":"Salaa tiedosto","Encryption":"Salaus","Encryption changed":"Salausasetukset ovat muuttuneet","Encryption passphrase":"Salausavain","End":"Loppu","Enter URL":"Anna URL","Enter backup passphrase, if any":"Anna varmuuskopion salauslause, jos käytät salausta.","Enter encryption passphrase":"Anna salauslause","Enter expression here":"Anna ilmaisu","Enter the destination path":"Anna kohdekansion polku","Error":"Virhe","Error!":"Virhe!","Errors and crashes":"Virheet ja kaatumiset","Exclude":"Ohita","Exclude directories whose names contain":"Ohita kansiot, joiden nimessä on","Exclude expression":"Ohita ilmaisu","Exclude file":"Ohita tiedosto","Exclude file extension":"Ohita tämän tyyppiset tiedostot","Exclude files whose names contain":"Ohita tiedostot, joiden nimessä on","Exclude folder":"Ohita kansio","Exclude regular expression":"Ohita säännöllistä ilmaisua vastaavat kohteet","Existing file found":"Löydettiin olemassaoleva tiedosto","Experimental":"Experimental","Export":"Vie","Export backup configuration":"Vie varmuuskopion asetukset","Export configuration":"Vie asetukset","External link":"Ulkoinen linkki","FTP (Alternative)":"FTP (vaihtoehtoinen)","Failed to build temporary database: {{message}}":"Tilapäisen tietokannan luominen epäonnistui. Virhe: {{message}}","Failed to connect:":"Yhteyden muodostaminen epäonnistui:","Failed to connect: {{message}}":"Yhteyden muodostaminen epäonnistui: {{message}}","Failed to delete:":"Poistaminen epäonnistui:","Failed to fetch path information: {{message}}":"Polkutietojen noutaminen epäonnistui: {{message}}","Failed to find backup:":"Varmuuskopiota ei löydetty:","Failed to read backup defaults:":"Varmuuskopion oletusasetusten lukeminen epäonnistui:","Failed to restore files: {{message}}":"Tiedostojen palauttaminen epäonnistui: {{message}}","Failed to save:":"Tallennus epäonnistui:","File":"Tiedosto","Files larger than:":"Tiedostot, joiden koko on suurempi kuin:","Filters":"Suodattimet","Finished!":"Valmis!","Folder":"Kansio","Folder path":"Kansion polku","Fri":"Pe","GByte":"GT","GByte/s":"GT/s","GCS Project ID":"GCS Projektin ID","General":"Yleinen","General backup settings":"Yleiset varmuuskopioasetukset","General options":"Yleiset asetukset","Generate":"Luo","Generate IAM access policy":"Luo Amazon IAM access policy","Getting file versions …":"Haetaan tiedostojen versioita ...","Group email":"Ryhmäsähköpostiosoite","Hidden files":"Piilotetut tiedostot","Hide":"Piilota","Hide hidden folders":"Älä näytä piilotettuja kansioita","Home":"Etusivu","Hostnames":"Isäntänimet","Hours":"tuntia","How do you want to handle existing files?":"Mitä tehdään olemassa oleville tiedostoille?","Hyper-V Machine":"Hyper-V-virtuaalikone","Hyper-V Machine:":"Hyper-V-virtuaalikone:","Hyper-V Machines":"Hyper-V-virtuaalikoneet","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jos ajastettu varmuuskopio jää tekemättä, se tehdään niin pian kuin mahdollista.","If at least one newer backup is found, all backups older than this date are deleted.":"Kaikki tätä päivämäärää vanhemmat varmuuskopiot poistetaan, mikäli vähintään yksi uudempi varmuuskopio löytyy.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jos et anna polkua, kaikki tiedostot tallennetaan kirjautumiskansioon.\nOletko varma, että haluat tätä?","If you do not enter an API Key, the tenant name is required":"Jos et anna tunnistetta API key, on tunniste \"tenant name\" pakollinen","Import":"Tuo","Import Destination URL":"Tuo etäpalvelimen osoite","Import backup configuration":"Tuo varmuuskopion asetukset","Import from a file":"Tuo tiedostosta","Import metadata":"Tuo metatieto","Importing …":"Tuodaan ...","Include a file?":"Sisällytä tiedosto?","Include expression":"Sisällytä ilmaisua vastaavat kohteet","Include regular expression":"Sisällytä säännöllistä ilmaisua vastaavat kohteet","Incorrect answer, try again":"Virheellinen vastaus. Yritä uudelleen.","Individual builds for developers only. Not for use with important data.":"Yksittäiset versiot, vain ohjelman kehittäjille. Älä käytä tärkeiden tietojen kanssa.","Information":"Informaatio","Invalid characters in path":"Virheellisiä merkkejä polussa","Invalid retention time":"Epäkelpo säilytysaika","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"JOtkut FTP-palvelimet sallivat yhteyden muodostamisen ilman salasanaa.\nOleko varma, että käyttämäsi FTP-palvelin sallii anonyymit kirjautumiset?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Säilytä määritelty määrä varmuuskopioita","Keep all backups":"Säilytä kaikki varmuuskopiot","Language in user interface":"Käytettävä kieli","Last month":"Viime kuussa","Last successful backup:":"Viimeisin onnistunut varmuuskopio:","Latest":"Viimesin","Libraries":"Kirjastot","Listing backup dates …":"Listataan varmuuskopioiden päivämääriä ...","Listing remote files …":"Listataan kohteen tiedostoja ...","Live":"Live","Load older data":"Lataa vanhoja tietoja","Loading …":"Ladataan ...","Local database path:":"Paikallisen tietokannan sijainti:","Local storage":"Paikallinen tilankäyttö","Location":"Sijainti","Location where buckets are created":"Alue, jolle ämpärit luodaan","Log data for {{Backup.Backup.Name}}":"Varmuuskopion {{Backup.Backup.Name}} lokitiedot","Log data from the server":"Palvelimen lokitiedot","Log out":"Kirjaudu ulos","MByte":"MB","MByte/s":"MB/s","Maintenance":"Ylläpito","Manually type path":"Anna polku","Max download speed":"Suurin latausnopeus","Max upload speed":"Suurin lähetysnopeus","Menu":"Valikko","Microsoft SQL Database:":"Microsoft SQL-tietokanta:","Microsoft SQL Databases":"Microsoft SQL -tietokannat","Minutes":"Minuuttia","Missing name":"Et antanut nimeä","Missing passphrase":"Salasana puuttuuEt antanut salasanaa","Missing sources":"Et valinnut varmuuskopioitavia tietostoja","Mon":"ma","Months":"Kuukautta","Move existing database":"Siirrä olemassa oleva tietokanta","Move failed:":"Siirto epäonnistui:","My Documents":"Tiedostot","My Music":"Musiikki","My Photos":"Kuvat","My Pictures":"Kuvat","Name":"Nimi","Never":"Ei koskaan","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Uusi käyttäjätunnus on {{user}}.\nPäivitä tunnukset käyttääksesi uutta rajoitettua käyttäjää.","Next":"Seuraava","Next scheduled run:":"Seuraava varmuuskopio tehdään:","Next scheduled task:":"Seuraava ajoitettu tehtävä:","Next task:":"Seuraava tehtävä:","Next time":"Seuraavalla kerralla","No":"Ei","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Sertifikaattia ei ole määritelty aikaisemmin. Varmista palvelimen ylläpitäjältä, että avain onn oikea: {{key}}\n\nHaluatko hyväksyä tämän avaimen?","No editor found for the "{{backend}}" storage type":"Etäpalvelimelle "{{backend}}" ei löytynyt editoria.","No encryption":"Ei salausta","No items selected":"Et valinnut yhtään kohdetta","No items to restore, please select one or more items":"Et valinnut yhtään tiedostoa palautettavaksi. Valitse yksi tai useampi tiedosto.","No passphrase entered":"Et antanut salasanaa","No scheduled tasks":"Ei ajastettuja tehtäviä","Non-matching passphrase":"Salasanat eivät ole samat","None / disabled":"Ei mitään/poistettu käytöstä","Not using encryption":"Salaus ei ole käytössä","Nothing will be deleted. The backup size will grow with each change.":"Mitään ei poisteta. Varmuuskopion koko kasvaa jokaisella muutoksella.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Vanhimmat varmuuskopiot poistetaan, kun varmuuskopioita on enemmän kuin määritelty määrä.","OpenStack AuthURI":"Openstack autentikointiosoite","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Avattu","Operating System":"Käyttöjärjestelmä","Operations:":"Toimenpiteet:","Optional authentication password":"Salasana (ei välttämätön)","Optional authentication username":"Käyttäjätunnus (ei välttämätön)","Options":"Valitsimet","Original location":"Alkuperäinen sijainti","Others":"Muut","Overwrite":"Korvaa","Passphrase":"Salauslause","Passphrase (if encrypted)":"Salauslause (jos varmuuskopio on salattu)","Passphrase changed":"Salauslause vaihdettiin","Passphrases are not matching":"Salauslauseet eivät täsmää","Passphrases do not match":"Salausavaimet eivät täsmää","Password":"Salasana","Path":"Polku","Path not found":"Polkua ei löydy","Path on server":"Polku etäpalvelimella","Path or subfolder in the bucket":"Ämpärin polku tai alikansio","Pause":"Tauko","Pause after startup or hibernation":"Tauko käynnistyksen tai lepotilasta heräämisen jälkeen","Permissions":"Oikeudet","Pick location":"Valitse sijainti","Port":"Portti","Previous":"Edellinen","Progress:":"Edistyminen: ","ProjectID is optional if the bucket exist":"Tunniste ProjectID on valinnainen, jos ämpäri on jo olemassa","Proprietary":"Suljettu","Rebuilding local database …":"Rakennetaan paikallinen tietokanta uudelleen ...","Recreate (delete and repair)":"Luo uudelleen (poista ja korjaa)","Recreating database …":"Luodaan tietokanta uudelleen ...","Registering temporary backup …":"Rekisteröidään tilapäinen varmuuskopio ...","Relative paths not allowed":"Suhteelliset polut eivät ole sallittuja","Reload":"Lataa uudelleen","Remote":"Etäpalvelimella","Remote Path":"Kohteen polku","Remote path":"Kohteen polku","Remove":"Poista","Remove option":"Poisto-asetukset","Repair":"Korjaa","Repair Phase":"Korjausvaihe","Repairing database …":"Korjataan tietokantaa ...","Repeat Passphrase":"Toista salauslause","Reporting:":"Raportoin:","Reset":"Palauta edelliset asetukset","Restore":"Palauta","Restore complete!":"Palautus valmis!","Restore files":"Palauta tiedostoja","Restore files …":"Palauta tiedostoja ...","Restore from":"Palauta etäpalvelimelta","Restore options":"Palautusasetukset","Restore read/write permissions":"Palauta luku- ja kirjoitusoikeudet","Restored Files":"Palautetut tiedostot","Restored Folders":"Palautetut kansiot","Restoring files …":"Palautetaan tiedostoja ...","Resume":"Jatka","Run again every":"Suorita uudelleen joka","Run now":"Suorita nyt","Running commandline entry":"Ajetaan komentorivin komentoa","Running task:":"Suoritettava tehtävä:","Running …":"Käynnissä ...","S3 Compatible":"S3-yhteensopiva","Same as the base install version: {{channelname}}":"Sama kuin asennettu versio: {{channelname}}","Sat":"La","Save":"Tallenna","Save and repair":"Tallenna ja korjaa","Save different versions with timestamp in file name":"Tallenna eri versiot aikaleima tiedoston nimessä","Save immediately":"Tallenna heti","Schedule":"Aikataulu","Search":"Etsi","Search for files":"Etsi tiedostoja","Seconds":"Sekuntia","Select a log level and see messages as they happen:":"Valitse lokitiedot ja näe ne heti, kun ne ilmoitetaan lokiin:","Select files":"Valitse tiedostot","Server":"Palvelin","Server and port":"Palvelin ja portti:","Server hostname or IP":"Palvelimen nimi ja IP-osoite","Server is currently paused,":"Palvelin on pysäytetty,","Server is currently paused, do you want to resume now?":"Palvelin on pysäytetty, haluatko aktivoida sen nyt?","Server password":"Palvelimen salasana","Server paused":"Palvelin on pysäytetty","Server state properties":"Palvelimen tila","Settings":"Asetukset","Show":"Näytä","Show advanced editor":"Näytä asetusten muokkain","Show hidden folders":"Näytä piilotetut tiedostot","Show log":"Näytä loki","Show treeview":"Näytä puunäkymä","Some OpenStack providers allow an API key instead of a password and tenant name":"Jotkin OpenStack-palveluntarjoajat sallivat API-avaimen käytön salasanan ja käyttäjätunnuksen sijaan","Source Data":"Lähdetiedostot","Source data":"Lähdetiedostot","Source folders":"Lähekansiot","Source:":"Varmuuskopioitavat tiedostot:","Standard protocols":"Standardinmukaiset protokollat","Stop after the current file":"Keskeytä nykyisen tiedoston jälkeen","Stop now":"Keskeytä nyt","Stop running backup":"Keskeytä käynnissä oleva varmuuskopiointi","Storage Type":"Tallennustyyppi","Storage class":"Tallennusluokka","Storage class for creating a bucket":"Tallennusluokka ämpärin luomista varten","Stored":"Tallennettu","Strong":"Vahva","Success":"Onnistui","Sun":"Su","Symbolic link":"Symbolinen linkki","System Files":"Järjestelmätiedostot","System default ({{levelname}})":"Järjestelmän oletus ({{levelname}})","System files":"Järjestelmätiedostot","System info":"Järjestelmän tiedot","System properties":"Järjestelmän ominaisuudet","TByte":"TB","TByte/s":"TB/s","Task is running":"Tehtävää suoritetaan","Temporary Files":"Väliaikaiset tiedostot","Temporary files":"Tilapäistiedostot","Test connection":"Kokeile yhteysasetuksia","The bucket name should be all lower-case, convert automatically?":"Bucketin nimen pitää olla kirjoitettu pienillä kirjaimilla. Muuta automaattisesti?","The dark theme (by Michal)":"Tumma teema (by Michal)","The default blue on white theme (by Alex)":"Oletusteema, sinistä valkoisella (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Kansiota {{folder}} ei ole olemassa. Luodaanko se nyt?","The passwords do not match":"Salasanat eivät täsmää","The path does not appear to exist, do you want to add it anyway?":"Polku ei vaikuta olevan olemassa, haluatko lisätä sen silti?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Polku ei pääty '{{dirsep}}' -merkkiin, eli olet lisäämässä tiedoston etkä kansiota. Haluatko lisätä määritellyn tiedoston?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Polun pitää olla absoluuttinen, eli sen tulee alkaa vinoviivalla \"/\"","The region parameter is only applied when creating a new bucket":"Alue -parametria käytetään vain bucketia luodessa.","The region parameter is only used when creating a bucket":"Alue -parametria käytetään vain bucketia luodessa.","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Palvelimen varmennetta ei pystytty todentamaan. Haluatko hyväksyä SSL-varmenteen, jonka tiiviste on {{hash}}?","The storage class affects the availability and price for a stored file":"Tietovaraston tyyppi vaikuttaa talennetun tiedoston saatavuuteen ja hintaan.","The target folder contains encrypted files, please supply the passphrase":"Kohdekansio sisältää salattuja tiedostoja. Anna salasana","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Käyttäjällä on liikaa oikeuksia. Haluatko luoda uuden rajoitetun käyttäjän, jolla on käyttöoikeus vain valittuun polkuun?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Tämä varmuuskopio on luotu toisessa käyttöjärjestelmässä. Tiedostojen palauttaminen ilman kohdekansion määrittelyä voi johtaa tiedostojen palauttamiseen odottamattomiin paikkoihin. Haluatko varmasti jatkaa määrittelemättä kohdekansiota?","This month":"Tässä kuussa","This week":"Tällä viikolla","Thu":"To","Time":"Aika","To File":"Tiedostoon","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Vahvistaaksesi että haluat poistaa kaikki etäkohteen tiedostot työltä \"{{name}}\", kirjoita alla näkyvä sana","To export without a passphrase, uncheck the \"Encrypt file\" box":"Viedäksesi ilmaan salasanaa poista rasti \"Salaa tiedosto\" -valinnasta","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Erilaisten DNS-hyökkäysten estämiseksi Duplicati rajaa sallitut isäntänimet tässä listattuihin. Suora yhteys IP-osoitteella ja localhost ovat aina sallittuja. Useita isäntänimia voidaan listata erottamalla ne puolipisteellä. Jos yksikin listattu isäntänimi on asteriski (*), sallitaan kaikki isäntänimet, ja tämä toiminto on pois käytöstä. Mikäli kenttä on tyhjä, ainoastaan IP-osoite- ja localhost-yhteys on sallittu.","Today":"Tänään","Trust host certificate?":"Luota palvelimen varmenteeseen?","Trust server certificate?":"Luota palvelimen varmenteeseen?","Tue":"ti","Type passphrase here.":"Kirjoita salausavain tähän.","Type to highlight files":"Kirjoita korostaaksesi tiedostoja","Until resumed":"Toistaiseksi","Update channel":"Päivityskanava","Update failed:":"Päivitys epäonnistui:","Uploading verification file …":"Lähetetään varmennustiedosto ...","Usage statistics":"Käyttötilastot","Usage statistics, warnings, errors, and crashes":"Käyttötilastot, varoitukset, virheet ja kaatumiset","Use SSL":"Käytä SSL:ää","Use existing database?":"Käytä olemassaolevaa tietokantaa?","Use weak passphrase":"Käytä heikkoa salasanaa","Useless":"Hyödytön","User data":"Käyttäjätiedot","User has too many permissions":"Käyttäjällä on liikaa oikeuksia","User interface settings":"Käyttöliittymän asetukset","Username":"Käyttäjätunnus","Vacuuming database …":"Puhdistetaan tietokanta ...","Verify files":"Tarkista tiedostot","Verifying answer":"Tarkistetaan vastausta","Very strong":"Hyvin vahva","Very weak":"Hyvin heikko","Visit us on":"Tutustu meihin","WARNING: This will prevent you from restoring the data in the future.":"VAROITUS: Tämä estää tietojen palauttamisen tulevaisuudessa","Waiting for task to begin":"Odotetaan tehtävän alkamista","Warnings, errors and crashes":"Varoitukset, virheet ja kaatumiset","We recommend that you encrypt all backups stored outside your system":"Suosittelemme salausta varmuuskopioihin, jotka säilötään oman tietokoneesi ulkopuolelle.","Weak":"Heikko","Weak passphrase":"Heikko salasana","Wed":"ke","Weeks":"Viikkoa","Where do you want to restore from?":"Mistä haluat palauttaa?","Where do you want to restore the files to?":"Mihin tiedostot palautetaan?","Years":"Vuotta","Yes":"Kyllä","Yes, I have stored the passphrase safely":"Kyllä, olen tallentanut salasanan turvallisesti","Yes, I understand the risk":"Kyllä, ymmärrän riskin","Yes, I'm brave!":"Kyllä, olen rohkea!","Yes, please break my backup!":"Kyllä, riko varmuuskopioni!","Yesterday":"Eilen","You are currently running {{appname}} {{version}}":"Käytössä oleva versio: {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vaihdoit salausmenetelmää, ja se saattaa rikkoa asioita. Harkitse kokonaan uuden varmuuskopion luomista sen sijaan.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vaihdoit salasanaa, mutta tätä toiminnallisuutta ei tueta. Luo sen sijaan kokonaan uusi varmuuskopio.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Valitsit salaamattoman varmuuskopioinnin. Salaaminen on suositeltua kaikella datalle, joka säilötään etäpalvelimelle.","You have chosen to restore to a new location, but not entered one":"Valitsit palautuksen uuteen sijaintiin, mutta et antanut sijaintia.","You must choose at least one source folder":"Vähintään yksi lähdekansio pitää valita","You must enter a name for the backup":"Varmuuskopiolle pitää antaa nimi","You must enter a passphrase or disable encryption":"Anna salasana tai poista salaus käytöstä","You must enter a positive number of backups to keep":"Syötä säilytettävien varmuuskopioiden määrä (positiivinen kokonaisluku)","You must enter a valid duration for the time to keep backups":"Syötä sallittu varmuuskopioiden säilytysaika","You must fill in the password":"Täytä salasana","You must fill in the server name or address":"Täytä palvelimen nimi tai osoite","You must fill in the username":"Täytä käyttäjätunnus","You must fill in {{field}}":"Täytä kenttä {{field}}","You must select or fill in the AuthURI":"Valitse tai syötä AuthURI","You must select or fill in the server":"Valitse tai syötä palvelin","You must specify a path":"Määritä polku","Your files and folders have been restored successfully.":"Tiedostot ja kansiot palautettiin onnistuneesti.","Your passphrase is easy to guess. Consider changing passphrase.":"Salasanasi on helppo arvata. Harkitse salasanan vaihtamista.","bucket/folder/subfolder":"bucket/kansio/alikansio","byte":"tavu","byte/s":"tavua/s","custom":"mukautettu","resume now":"jatka nyt","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}}n on pääasiallisesti kehittänyt {{dev1}} and {{dev2}}. {{appname}}n voi ladata osoitteesta {{websitename}}. {{appname}} on lisensoitu {{licensename}} -lisenssillä.","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versio","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versiota"],"{{number}} Hour":"{{number}} tuntia","{{number}} Hours":"{{number}} Tuntia","{{number}} Minutes":"{{number}} minuuttia","{{time}} (took {{duration}})":"{{time}} (kesto: {{duration}})"}); - gettextCatalog.setStrings('fr_CA', {"- pick an option -":"- choisissez une option -","...loading...":"... chargement...","AWS Access ID":"Clé d'accès AWS","AWS Access Key":"Clé d'accès secrète AWS","AWS IAM Policy":"AWS IAM Stratégies","About":"À propos","About {{appname}}":"À propos de {{appname}}","Access Key":"Clé d'accès","Access denied":"Accès refusé","Access to user interface":"Accès à l'interface utilisateur","Account name":"Nom du compte","Add a new backup":"Ajouter une nouvelle sauvegarde","Add a path directly":"Ajouter un répertoire directement","Add advanced option":"Ajouter une option avancée","Add backup":"Ajouter une sauvegarde","Add filter":"Ajouter un filtre","Add path":"Ajouter un chemin","Added":"Ajouté","Adjust bucket name?":"Modifier le nom du bucket","Advanced Options":"Options avancées","Advanced options":"options avancées","Advanced:":"Avancé :","All Hyper-V Machines":"Toutes les machines Hyper-V","All Microsoft SQL Databases":"Toutes les bases de données Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tous les rapports d'utilisation sont envoyés de manière anonyme et ne contiennent aucune information personnelle. Ils contiennent des informations sur le matériel et le système d'exploitation, sur le type d'infrastructure, la durée de la sauvegarde, la taille générale des fichiers sources et d'autres données similaires. Ils ne contiennent pas de chemin d'accès, noms de fichiers, noms d'utilisateurs, mots de passe ou des informations sensibles de ce type.","Allow remote access (requires restart)":"Autoriser l'accès à distance (nécessite un redémarrage)","Allowed days":"Jours autorisés","An existing file was found at the new location":"Un fichier existant a été trouvé au nouvel endroit","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fichier existant a été trouvé au nouvel endroit.\nÊtes-vous sûr de vouloir pointer la base de données vers un fichier existant ?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Une base de données locale pour le stockage a été trouvée.\nRéutiliser la base de données va permettre la ligne de commande et les instances serveurs de travailler sur le même stockage à distance.\n\nVoulez-vous utiliser la base de donnée existante ?","Anonymous usage reports":"Rapports d'utilisation anonyme","Applications":"Applications","As Command-line":"Comme ligne de commande","AuthID":"AuthID","Authentication password":"Mot de passe d'identification","Authentication username":"Nom d'utilisateur d'identification","Autogenerated passphrase":"Phrase secrète auto-générée","B2 Application Key":"Clé application B2","B2 Cloud Storage Account ID":"Identifiant du compte B2 Cloud Storage","B2 Cloud Storage Application Key":"Clé d'application B2 Cloud Storage","Back":"Retour","Backup complete!":"Sauvegarde Complète","Backup destination":"Destination de la sauvegarde","Backup location":"Emplacement de la sauvegarde","Backup retention":"Rétention de la sauvegarde","Backup:":"Sauvegarde :","Beta":"Béta","Broken access":"Accès rompu","Browse":"Parcourir","Browser default":"Paramètre par défaut du navigateur","Bucket create location":"Emplacement de la création du bucket","Bucket name":"Nom du bucket","Bucket storage class":"Classe de stockage du bucket","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"En autorisant l'accès à distance, le serveur écoute les requêtes de n'importe quel ordinateur de votre réseau. Si vous activez cette option, assurez-vous de toujours utiliser l'ordinateur sur un réseau protégé par un pare-feu.","Cache Files":"Fichiers de cache","Canary":"Canary","Cancel":"Annuler","Cannot move to existing file":"Impossible de déplacer vers un fichier existant","Changelog":"Journal des modifications","Changelog for {{appname}} {{version}}":"Journal des modifications pour {{appname}} {{version}}","Check failed:":"Vérification échouée :","Check for updates now":"Vérifier les mise à jour maintenant","Chose a storage type to get started":"Sélectionnez un type de stockage pour commencer","Click the AuthID link to create an AuthID":"Cliquez sur le lien AuthID pour créer un AuthID","Click to set throttle options":"Cliquez pour définir les options d'accélération","Compact Phase":"Étape de compactage","Compact now":"Compacter maintenant","Computer":"Ordinateur","Configuration file:":"Fichier de configuration :","Configuration:":"Configuration :","Configure a new backup":"Configurer une nouvelle sauvegarde","Confirm delete":"Confirmer suppression","Confirm encryption passphrase":"Confirmez la phrase secrète de chiffrement","Confirmation required":"Confirmation nécessaire","Connect":"Connecter","Connect now":"Connecter maintenant","Connection lost":"Connexion perdue","Connection worked!":"Connection fonctionnelle !","Container name":"Nom du conteneur","Container region":"Région du conteneur","Continue":"Continuer","Continue without encryption":"Continuer sans chiffrement","Copied!":"Copié!","Copy":"Copie","Copy Destination URL to Clipboard":"Copier l'URL de destination dans le presse-papier","Copy failed. Please manually copy the URL":"Copie échouée. Veuillez copier manuellement l'URL","Core options":"Options du noyau","Counting ({{files}} files found, {{size}})":"Comptage ({{files}} fichiers trouvés, {{size}})","Crashes only":"Uniquement les plantages","Create folder?":"Créer un dossier?","Created new limited user":"Nouvel utilisateur limité créé","Current action:":"Action en cours :","Current file:":"Fichier actuel :","Current version is {{versionname}} ({{versionnumber}})":"La version actuelle est {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"S3 endpoint personnalisé","Custom authentication url":"URL d'authentification personnalisée","Custom backup retention":"Rétention de sauvegarde personnalisée","Custom location ({{server}})":"Emplacement personnalisé ({{server)}}","Custom region for creating buckets":"Région personnalisée pour la créations de buckets","Custom region value ({{region}})":"Valeur personnalisée de région ({{region}})","Custom server url ({{server}})":"URL serveur personnalisée ({{server}})","Custom storage class ({{class}})":"Classe de stockage personnalisée ({{class}})","Days":"Jours","Default":"Défaut","Default ({{channelname}})":"({{channelname}}) par défaut","Default excludes":"Les exclusions par défaut","Default options":"Options par défaut","Delete":"Supprimer","Delete Phase (Old Backup Versions)":"Étape de suppression (ancienne version de sauvegarde)","Delete backup":"Supprimer la sauvegarde","Delete backups that are older than":"Supprimer les sauvegardes plus anciennes que","Delete local database":"Supprimer base de données locale","Delete remote files":"Supprimer les fichiers distants","Delete the local database":"Supprimer la base de données locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Supprimer {{filecount}} fichiers ({{filesize}}) du stockage distant ?","Deleted":"Supprimer","Deleted Versions":"Versions supprimés","Deleted files":"Fichiers supprimés","Description (optional)":"Description (facultatif)","Description:":"Description","Desktop":"Bureau","Destination":"Destination","Destination path":"Chemin de destination","Disabled":"Désactivé","Dismiss":"Rejeter","Dismiss all":"Rejeter la totalité","Display and color theme":"Affichage et couleur","Do you really want to delete the backup: \"{{name}}\" ?":"Voulez-vous vraiment supprimer la sauvegarde : \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Voulez-vous vraiment supprimer la base de données locale pour : {{name}} ?","Done":"Terminé","Download":"Téléchargement","Downloaded files":"Fichiers téléchargés","Duplicate option {{opt}}":"Option de duplication {{opt}}","Duplicati Website":"Site internet de Duplicati","Duplicati forum":"Forum de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati s'exécutera une fois démarré, mais restera en état de pause pendant la durée. Duplicati occupera un minimum de ressources système et aucune sauvegarde ne sera exécutée.","Duration":"Durée","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Chaque sauvegarde a une base de données locale associée à elle, elle stocke des informations localement à propos de la sauvegarde distante.\nQuand vous supprimez une sauvegarde, vous pouvez aussi supprimer la base de données locale sans affecter votre capacité à restaurer vos fichiers distants.\nSi vous utilisez la base de données locale pour vos sauvegardes à partir de la ligne de commande, vous devez conserver la base de données.","Edit as list":"Éditer en tant que liste","Edit as text":"Éditer en tant que texte","Encrypt file":"Chiffrement du fichier","Encryption":"Chiffrement","Encryption changed":"Chiffrement changé","End":"Terminé","Enter URL":"Entrer l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Entrez une stratégie de rétention manuellement. Les espaces réservés sont D / W / Y pour les jours / semaines / années et U pour illimité. La syntaxe est : 7D:1D, 4W:1W, 36M:1M. Cet exemple conserve une sauvegarde pour chacun des 7 prochains jours, une pour chacune des 4 prochaines semaines et une pour chacun des 36 prochains mois. Cela peut également être écrit comme 1W:1D, 1M:1W, 3Y:1M.","Enter backup passphrase, if any":"Entrez la phrase secrète de sauvegarde, si présente","Enter configuration details":"Entrer les détails de configuration","Enter encryption passphrase":"Entrez la phrase secrète de chiffrement","Enter expression here":"Entrez l'expression ici","Enter the destination path":"Entrez le chemin de destination","Error":"Erreur","Error!":"Erreur!","Errors and crashes":"Erreurs et plantages","Examined":"Examiné","Exclude":"Exclure","Exclude directories whose names contain":"Exclure répertoires dont le nom contient","Exclude expression":"Exclure expression","Exclude file":"Exclure fichier","Exclude file extension":"Exclure extension de fichier","Exclude files whose names contain":"Exclure fichiers dont le nom contient","Exclude filter group":"Exclure le groupe de filtres","Exclude folder":"Exclure dossier","Exclude regular expression":"Exclure expression régulière","Existing file found":"Fichier existant trouvé","Experimental":"Expérimental","Export":"Exporter","Export backup configuration":"Exporter la configuration de sauvegarde","Export configuration":"Exporter la configuration","Export passwords":"Exporter les mots de passe","External link":"Lien externe","FTP (Alternative)":"FTP (Alternatif)","Failed to build temporary database: {{message}}":"Échec de la construction de la base de données temporaire : {{message}}","Failed to connect:":"Échec de la connexion :","Failed to connect: {{message}}":"Échec de la connexion : {{message}}","Failed to delete:":"Échec de la suppression :","Failed to fetch path information: {{message}}":"Échec de la récupération des information du chemin : {{message}}","Failed to find backup:":"Impossible de trouver la sauvegarde","Failed to read backup defaults:":"Échec de la lecture des paramètres par défaut de la sauvegarde :","Failed to restore files: {{message}}":"Échec de la restauration des fichiers : {{message}}","Failed to save:":"Échec d'enregistrement :","File":"Fichier","Files larger than:":"Fichiers plus gros que :","Filters":"Filtres","Finished!":"Terminé!","First run setup":"Première mise en route","Folder":"Dossier","Folder path":"Chemin du dossier","Fri":"Ven.","GByte":"Goctet","GByte/s":"GOtects/s","GCS Project ID":"ID du projet GCS","General":"Général","General backup settings":"Paramètres généraux de sauvegarde","General options":"Options générales","Generate":"Générer","Generate IAM access policy":"Générer la statégie d'accès IAM","Group email":"Courriel de groupe","Hidden files":"Fichiers cachés","Hide":"Cacher","Hide hidden folders":"Masquer les dossiers cachés","Home":"Poste de travail","Hostnames":"Les noms d'hôtes","Hours":"Heures","How do you want to handle existing files?":"Comment voulez-vous traiter les fichiers existants?","Hyper-V Machine":"Machine Hyper-V","Hyper-V Machine:":"Machine Hyper-V :","Hyper-V Machines":"Machines Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si une date a été manquée, le travail démarrera dès que possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si au moins une sauvegarde plus récente est trouvée, toutes les sauvegardes antérieures à cette date sont supprimées.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si vous n'entrez pas de chemin, tous les fichiers seront stockés dans le dossier de connexion.\nÊtes-vous sûr que c'est ce que vous voulez ?","If you do not enter an API Key, the tenant name is required":"Si vous n'entrez pas de clé API, le nom de l'entité est requis","Import":"Importer","Import Destination URL":"Importer l'URL de destination","Import backup configuration":"Importer la configuration de sauvegarde","Import from a file":"Importer depuis un fichier","Import metadata":"Importer des métadonnées","Include a file?":"Inclure un fichier ?","Include expression":"Inclure expression","Include regular expression":"Inclure expression régulière","Incorrect answer, try again":"Réponse incorrecte, essayez encore","Individual builds for developers only. Not for use with important data.":"Builds individuelles pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Information":"Information","Invalid characters in path":"Caractères invalides dans le chemin","Invalid retention time":"Temps de rétention invalide","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Il est possible de se connecter à certains FTP sans mot de passe.\nÊtes-vous sûr que votre serveur FTP prend en charge l'identification sans mot de passe ?","KByte":"KOctet","KByte/s":"KOctet/s","Keep a specific number of backups":"Conserver un nombre spécifique de sauvegardes","Keep all backups":"Conserver toutes les sauvegardes","Keystone API version":"Version de l'API Keystone","Language in user interface":"Langue dans l'interface utilisateur","Last month":"Mois dernier","Last successful backup:":"Dernière sauvegarde réussie :","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Dernière restauration réussie : {{time}} (a pris {{duration || '0 secondes'}})","Latest":"Dernière","Libraries":"Librairies","Live":"Direct","Load a configuration from an exported job or a storage provider":"Charger une configuration depuis un export ou un opérateur de stockage","Load destination from an exported job or a storage provider":"Charger la destination depuis un export ou un opérateur de stockage","Load older data":"Charger des données plus anciennes","Local Repository":"Stockage local","Local database path:":"Chemin de la base de données locale :","Local repository":"Stockage local","Local storage":"Stockage local","Location":"Emplacement","Location where buckets are created":"Emplacement ou les buckets sont créés","Log data for {{Backup.Backup.Name}}":"Historique pour {{Backup.Backup.Name}}","Log data from the server":"Données d'historique du serveur","Log out":"Déconnexion","MByte":"MOctet","MByte/s":"MOctet/s","Maintenance":"Maintenance","Manually type path":"Entrée manuelle du chemin","Max download speed":"Vitesse maximum de téléchargement","Max upload speed":"Vitesse maximum de téléversement","Menu":"Menu","Microsoft SQL Database:":"Base de données Microsoft SQL :","Microsoft SQL Databases":"Bases de données Microsoft SQL","Minimum redundancy":"Redondance minimale","Minimum redundancy is 1.0":"La redondance minimale est de 1,0","Minutes":"Minutes","Missing name":"Nom manquant","Missing passphrase":"Phrase secrète manquante","Missing sources":"Sources manquantes","Modified":"Modifié","Mon":"Lun.","Months":"Mois","Move existing database":"Déplacer la base de données existante","Move failed:":"Échec de déplacement :","My Documents":"Mes documents","My Music":"Ma musique","My Photos":"Mes photos","My Pictures":"Mes photos","Name":"Nom","Never":"Jamais","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Le nouveau nom d'utilisateur est {{user}}.\nMise à jour des accès pour le nouvel utilisateur limité","Next":"Suivant","Next scheduled run:":"Prochaine exécution programmée :","Next scheduled task:":"Prochaine tâche planifiée :","Next task:":"Prochaine tâche :","Next time":"Prochaine fois","No":"Non","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Aucun certificat n'a été spécifié auparavant, veuillez vérifier que la clé est correcte auprès de votre administrateur système : {{key}}\n\nVoulez-vous approuver la clé de l'hôte mentionné ?","No editor found for the "{{backend}}" storage type":"Aucun éditeur trouvé pour le "{{backend}}" type de stockage","No encryption":"Pas de chiffrement","No items selected":"Aucun élément sélectionné","No items to restore, please select one or more items":"Aucun élément à restaurer, merci de sélectionner un ou plusieurs éléments","No passphrase entered":"Aucune phrase secrète entrée","No scheduled tasks":"Pas de tâche planifié","Non-matching passphrase":"La phrase secrète ne correspond pas","None / disabled":"Aucun / Désactivé","Not using encryption":"N'utilise pas le chiffrement","Nothing will be deleted. The backup size will grow with each change.":"Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque modification.","OK":"Ok","Once there are more backups than the specified number, the oldest backups are deleted.":"Une fois qu'il y a plus de sauvegardes que le nombre spécifié, les sauvegardes les plus anciennes sont supprimées.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Ouvert","Operating System":"Système d'exploitation","Operation":"Opération","Operations:":"Opérations :","Optional authentication password":"Mot de passe d'identification optionel","Optional authentication username":"Nom d'utilisateur d'identification optionel","Options":"Options","Original location":"Emplacement d'origine","Others":"Autres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Au fil du temps, les sauvegardes seront automatiquement supprimées. Il restera une sauvegarde pour chacun des 7 derniers jours, chacune des 4 dernières semaines, chacun des 12 derniers mois. Il y aura toujours au moins une sauvegarde restante.","Overwrite":"Écraser","Passphrase":"Phrase secrète","Passphrase (if encrypted)":"Phrase secrète (si chiffré)","Passphrase changed":"Phrase secrète changée","Passphrases are not matching":"Les phrases secrètes ne correspondent pas","Passphrases do not match":"Le mot de passe ne correspond pas","Password":"Mot de passe","Path":"Chemin","Path not found":"Chemin non trouvé","Path on server":"Chemin sur le serveur","Path or subfolder in the bucket":"Chemin ou sous-dossier dans le bucket","Pause":"Pause","Pause after startup or hibernation":"Pause après le démarrage ou l'hibernation","Pause options":"Options de pause","Permissions":"Permissions","Pick location":"Choisir l'emplacement","Point to your backup files and restore from there":"Donner votre fichier de sauvegarde et restaurer depuis celui-ci ","Port":"Port","Prevent tray icon automatic log-in":"Empêcher la connexion automatique de l'icône de la barre de tâches","Previous":"Précédent","Progress:":"Statut:","ProjectID is optional if the bucket exist":"Le ProjectID est optionel si le bucket existe","Proprietary":"Propriétaire","Purge Phase":"Étape de purge","Purging files complete!":"Purge des fichiers complétée!","Recreate (delete and repair)":"Récrée (suppression et réparation)","Recreate Database Phase":"Étape de recréation de la base de données","Relative paths not allowed":"Les chemins relatifs ne sont pas autorisés","Reload":"Recharger","Remote":"Distant","Remote Path":"Chemin d'accès distant","Remote Repository":"Stockage distant","Remote path":"Chemin d'accès distant","Remote repository":"Stockage distant","Remote volume size":"Taille du volume distant","Remove":"Retirer","Remove option":"Option de retrait","Removed files":"Fichiers supprimés","Repair":"Réparer","Repair Phase":"Étape de réparation","Repeat Passphrase":"Répeter la phrase secrète","Reporting:":"Communication de données :","Reset":"Réinitialiser","Restore":"Restaurer","Restore complete!":"La restauration a été complétée!","Restore files":"Restaurer les fichiers","Restore from":"Restaurer depuis","Restore from backup configuration":"Restaurer depuis une sauvegarde de configuration","Restore options":"Options de restauration","Restore read/write permissions":"Autorisations de lecture/écriture de restauration","Resume":"Reprendre","Rewritten File Lists":"Réécriture des listes de fichiers","Run again every":"Relancer tous les","Run now":"Démarrer maintenant","Running commandline entry":"Execution d'une ligne de commnde","Running task:":"Tâche en cours :","S3 Compatible":"Compatible S3","Same as the base install version: {{channelname}}":"Identique à la version de base installée : {{channelname}}","Sat":"Sam.","Save":"Enregistrer","Save and repair":"Enregistrer et réparer","Save different versions with timestamp in file name":"Enregistrer des versions différentes avec l'horodatage dans le nom du fichier","Save immediately":"Sauver immédiatement ","Schedule":"Planifier","Search":"Recherche","Search for files":"Recherche de fichiers","Seconds":"Secondes","Select a log level and see messages as they happen:":"Sélectionner un niveau d'historique et voyez les messages quand ils apparaissent :","Select files":"Sélectionner les fichiers","Server":"Serveur","Server and port":"Serveur et port","Server hostname or IP":"Nom d'hôte du serveur ou IP","Server is currently paused,":"Le serveur est actuellement en pause,","Server is currently paused, do you want to resume now?":"Le serveur est actuellement en pause, voulez-vous reprendre maintenant ?","Server password":"Mot de passe du serveur","Server paused":"Serveur en pause","Server state properties":"Propriétés du statut serveur","Settings":"Paramètres","Show":"Montrer","Show advanced editor":"Montrer l'éditeur avancé","Show hidden folders":"Montrer les dossiers cachés","Show log":"Montrer l'historique","Show treeview":"Afficher l'arborescence","Sia server password":"Mot de passe du serveur Sia","Smart backup retention":"Rétention de sauvegarde intelligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Certains fournisseurs OpenStack autorisent une clé API à la place d'un mot de passe et d'un nom d'entité","Source Data":"Données source","Source data":"Données source","Source folders":"Dossiers source","Source:":"Source :","Specific builds for developers only. Not for use with important data.":"Builds spécifiques pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Standard protocols":"Protocoles standards","Start":"Démarrer","Stop after the current file":"Arrêter après le fichier en cour","Stop now":"Arrêter maintenant","Stop running backup":"Arrêter la sauvegarde en cour","Stop running task":"Stopper la tâche en cour","Stopping task:":"Arrêt de la tâche","Storage Type":"Type de stockage","Storage class":"Classe de stockage","Storage class for creating a bucket":"Classe de stockage pour la création d'un bucket","Stored":"Stocké","Strong":"Fort","Success":"Succès","Sun":"Dim.","Symbolic link":"Lien symbolique","System Files":"Fichiers système","System default ({{levelname}})":"Paramètre par défaut du système ({{levelname}})","System files":"Fichiers système","System info":"Info système","System properties":"Propriétés système","TByte":"TOctet","TByte/s":"TOctet/s","Task is running":"La tâche est en cours","Temporary Files":"Fichiers temporaires","Temporary files":"Fichiers temporaires","Test Phase":"Étape de test","Test connection":"Tester la connexion","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Le champ '{{fieldname}}' contient un caractère non valide : {{character}} (valeur : {{value}}, index : {{pos}})","The backup is missing, has it been deleted?":"La sauvegarde est n'existe pas, a-t-elle été supprimée?","The backup was temporary and does not exist anymore, so the log data is lost":"La sauvegarde était temporaire et n'existe plus, les données du journal sont perdues.","The bucket name should be all lower-case, convert automatically?":"Le nom du bucket devrait être entièrement en minuscule, convertir automatiquement ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuration doit être gardée en sécurité. Êtes-vous sûr de vouloir enregistrer un fichier non crypté contenant vos mots de passe?","The dark theme (by Michal)":"Le thème sombre (de Michal)","The default blue on white theme (by Alex)":"Thème par défaut bleu sur fond blanc (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Le dossier {{dossier}} n'existe pas.\nCréez-le maintenant ?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clé de l'hôte a changé, veuillez vérifier avec l'administrateur du serveur si cela est correcte, car il pourrait s'agir d'une attaque de type \"intermédiaire\".\n\nVoulez-vous REMPLACER votre clé d'hôte COURANTE \"{{prev}}\" par la clé MENTIONNÉE : {{key}} ?","The passwords do not match":"Le mot de passe ne correspond pas","The path does not appear to exist, do you want to add it anyway?":"Le chemin ne semble pas exister, voulez-vous l'ajouter quand même ?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Le répertoire ne se termine pas par un caractère '{{dirsep}}', ce qui signifie que vous sélectionnez un fichier et non un dossier.\n\nVoulez-vous inclure le fichier spécifié ?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Le chemin doit être un chemin absolu, c.-à-d. Il doit commencer par un slash avant '/'","The region parameter is only applied when creating a new bucket":"Le paramètre régional n'est appliqué qu'à la création d'un nouveau bucket","The region parameter is only used when creating a bucket":"Le paramètre régional n'est utilisé qu'à la création d'un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Le certificat du serveur n'a pas pu être validé.\nVoulez-vous approuver le certificat SSL avec la somme de contrôle : {{hash}} ?","The storage class affects the availability and price for a stored file":"La classe de stockage affecte la disponibilité et le prix d'un fichier stocké","The target folder contains encrypted files, please supply the passphrase":"Le fichier cible contient des fichiers chiffrés, merci de fournir la phrase secrète","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utilisateur à trop d'autorisations. Voulez-vous créer un nouvel utilisateur limité avec uniquement les autorisations pour les chemins sélectionnés ?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Cette sauvegarde a été créée sur un autre système d’exploitation. Restaurer les fichiers sans préciser un dossier de destination peut créer des fichiers à des endroits inattendus. Êtes-vous surs de vouloir poursuivre sans choisir de dossier de destination ?","This month":"Ce mois","This week":"Cette semaine","Throttle settings":"Options d'accélération","Thu":"Jeu.","Time":"temps","To File":"Vers fichier","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pour confirmer que vous souhaitez supprimer tous les fichiers distants pour \"{{name}}\", veuillez entrer le mot situé ci-dessous","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Pour éviter diverses attaques basées sur le DNS, Duplicati limite les noms d'hôtes autorisés à ceux répertoriés ici. L'accès IP direct et localhost est toujours autorisé. Plusieurs noms d'hôte peuvent être fournis avec un séparateur de points-virgules. Si l'un des noms d'hôtes autorisés est un astérisque (*), tous les noms d'hôte sont autorisés et cette fonctionnalité est désactivée. Si le champ est vide, seule l'adresse IP et l'accès localhost sont autorisés.","Today":"Aujourd'hui","Trust host certificate?":"Faire confiance au certificat de l'hôte ?","Trust server certificate?":"Faire confiance au certificat du serveur ?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Essayez les nouvelles fonctionnalités sur lesquelles nous travaillons. Actuellement la version la plus stable disponible. Testez la restauration des données avant de l'utiliser dans des environnements de production.","Tue":"Mar.","Type passphrase here.":"Tapez mot de passe ici.","Type to highlight files":"Tapez pour mettre en surbrillance les fichiers","Unknown backup size and versions":"Taille et version de sauvegarde inconnue","Until resumed":"Jusqu'à la reprise","Update channel":"Canal de mise à jour","Update failed:":"Échec de mise à jour","Updating with existing database":"Mettre à jour avec une base de données existante","Uploaded files":"Fichiers téléchargés","Usage statistics":"Statistiques d'utilisation","Usage statistics, warnings, errors, and crashes":"Statistiques d'utilisation, avertissements, erreurs et accidents","Use SSL":"Utiliser SSL","Use existing database?":"Utiliser une base de données existante ?","Use weak passphrase":"Utiliser une phrase secrète faible","Useless":"Inutile","User data":"Données utilisateur","User domain name":"Nom de domaine de l'utilisateur","User has too many permissions":"L'utilisateur à trop d'autorisations","User interface settings":"Réglages interface utilisateur","Username":"Nom d'utilisateur","Verifications":"Vérifications","Verify files":"Vérifier les fichiers","Verifying answer":"Vérification de la réponse","Version ID":"ID de version","Very strong":"Très fort","Very weak":"Très faible","Visit us on":"Rendez nous visite sur","WARNING: This will prevent you from restoring the data in the future.":"ATTENTION : Cela va vous empêcher de restaurer vos données dans le futur.","Waiting for task to begin":"En attente du début de la tâche","Warnings, errors and crashes":"Avertissements, erreurs et accidents","We recommend that you encrypt all backups stored outside your system":"Nous vous recommandons de chiffrer toutes les sauvegardes stockées en dehors de votre système","Weak":"Faible","Weak passphrase":"Phrase secrète faible","Wed":"Mer.","Weeks":"Semaines","Where do you want to restore from?":"Ou voulez-vous restaurer vos fichiers ?","Where do you want to restore the files to?":"Ou voulez-vous restaurer vos fichiers ?","Years":"Années","Yes":"Oui","Yes, I have stored the passphrase safely":"Oui, j'ai conservé ma phrase secrète en sécurité","Yes, I understand the risk":"Oui, je comprends le risque","Yes, I'm brave!":"Oui, je suis courageux !","Yes, please break my backup!":"Oui, s'il vous plait cassez ma sauvegarde","Yesterday":"Hier","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Vous êtes en train de changer le chemin de la base de données depuis une base de donnée existante.\nÊtes-vous sûr que c'est ce que vous voulez ?","You are currently running {{appname}} {{version}}":"Vous êtes actuellement en train d'utiliser {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vous avez changé la méthode de chiffrement. Ceci peut endommager certaines choses. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vous avez changé la phrase secrète, ce qui n'est pas pris en charge. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Vous avez choisi de ne pas chiffrer votre sauvegarde. Le chiffrement est recommandé pour toutes les données stockées sur un serveur distant.","You have chosen to restore to a new location, but not entered one":"Vous avez demandé à restaurer vers un nouveau dossier, mais sans indiquer son chemin","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vous avez généré une mot de passe fort. Assurez-vous que vous avez effectué une copie sécurisée de ce mot de passe, car les données ne pourront pas être récupérées si vous le perdez.","You must choose at least one source folder":"Vous devez choisir au moins un dossier source","You must enter a domain name to use v3 API":"Vous devez entrer un nom de domaine pour utiliser l'API v3","You must enter a name for the backup":"Vous devez entrer un nom pour votre sauvegarde","You must enter a passphrase or disable encryption":"Vous devez entrer une phrase secrète ou désactiver le chiffrement","You must enter a password to use v3 API":"Vous devez entrer un mot de passe pour utiliser l'API v3","You must enter a positive number of backups to keep":"Vous devez entrer un nombre positif de sauvegarde à conserver","You must enter a tenant (aka project) name to use v3 API":"Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3","You must enter a valid duration for the time to keep backups":"Vous devez entrer une valeur correcte pour la durée de conservation de vos sauvegardes","You must fill in the password":"Vous devez renseigner le mot de passe","You must fill in the server name or address":"Vous devez renseigner le nom du serveur ou l'adresse","You must fill in the username":"Vous devez renseigner le nom d'utilisateur","You must fill in {{field}}":"Vous devez renseigner le champ : {{field}}","You must select or fill in the AuthURI":"Vous devez sélectionner ou renseigner l'AuthURI","You must select or fill in the server":"Vous devez sélectionner ou renseigner le serveur","You must specify a path":"Vous devez spécifier un chemin.","Your files and folders have been restored successfully.":"Vos fichiers et dossiers ont été restaurés avec succès.","Your passphrase is easy to guess. Consider changing passphrase.":"Votre phrase secrète est facile à deviner. Songez à la changer.","bucket/folder/subfolder":"bucket/dossier/sous-dossier","byte":"octet","byte/s":"octet/s","custom":"personnalisé ","resume now":"reprendre maintenant","unless you are explicitly specifying --group-id":"sauf si vous spécifiez explicitement --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a été principalement développée par {{dev1}} et {{dev2}}. {{appname}} peut être téléchargée depuis {{websitename}}. {{appname}} est sous licence {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fichiers {{size}}) à transferer {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Heure","{{number}} Hours":"{{number}} Heures","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (durée {{duration}})"}); - gettextCatalog.setStrings('fr', {"- pick an option -":"- choisir une option -","...loading...":"...chargement...","API key":"Clé API","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"À propos","About {{appname}}":"À propos de {{appname}}","Access Key":"Clé d'accès","Access denied":"Accès refusé","Access grant":"Octroi d'accès","Access to user interface":"Accès à l'interface utilisateur","Account name":"Nom du compte","Add a new backup":"Ajouter une nouvelle sauvegarde","Add a path directly":"Ajouter un répertoire directement","Add advanced option":"Ajouter une option avancée","Add backup":"Ajouter une sauvegarde","Add filter":"Ajouter un filtre","Add path":"Ajouter un chemin","Added":"Ajouté","Adjust bucket name?":"Modifier le nom du bucket ?","Advanced Options":"Options avancées","Advanced options":"Options avancées","Advanced:":"Avancé :","All Hyper-V Machines":"Toutes les machines Hyper-V","All Microsoft SQL Databases":"Toutes les bases de données Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tous les rapports d'utilisation sont envoyés de manière anonyme et ne contiennent aucune information personnelle. Ils contiennent des informations sur le matériel et le système d'exploitation, le type d'infrastructure, la durée de la sauvegarde, la taille générale des fichiers sources et d'autres données similaires. Ils ne contiennent pas les chemin d'accès, noms de fichiers, noms d'utilisateurs, mots de passe ou informations sensibles de ce type.","Allow remote access (requires restart)":"Autoriser l'accès à distance (nécessite un redémarrage)","Allowed days":"Jours autorisés","An existing file was found at the new location":"Un fichier existant a été trouvé au nouvel emplacement","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fichier existant a été trouvé au nouvel emplacement.\nÊtes-vous sûr de vouloir faire pointer la base de données vers un fichier existant ?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Une base de données locale pour le stockage a été trouvée.\nRéutiliser la base de données va permettre la ligne de commande et les instances serveur de travailler sur le même stockage à distance.\n\nVoulez-vous utiliser la base de donnée existante ?","Anonymous usage reports":"Rapports d'utilisation anonyme","Applications":"Applications","As Command-line":"Comme ligne de commande","AuthID":"AuthID","Authentication method":"Méthode d'authentification","Authentication method ({{auth_method}})":"Méthode d'authentification ({{auth_method}})","Authentication password":"Mot de passe d'identification","Authentication username":"Nom d'utilisateur d'identification","Autogenerated passphrase":"Phrase secrète auto-générée","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Précédent","Backup complete!":"Sauvegarde terminée !","Backup destination":"Destination de sauvegarde","Backup location":"Emplacement de la sauvegarde","Backup retention":"Rétention de la sauvegarde","Backup:":"Sauvegarde :","Beta":"Bêta","Broken access":"Accès rompu","Browse":"Parcourir","Browser default":"Paramètre par défaut du navigateur","Bucket create location":"Emplacement de la création du bucket","Bucket name":"Nom du bucket","Bucket storage class":"Classe de stockage du bucket","Building list of files to restore …":"Création d'une liste de fichiers à restaurer...","Building partial temporary database …":"Création d'une base de données temporaire partielle...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"En autorisant l'accès à distance, le serveur écoute les requêtes de n'importe quel ordinateur de votre réseau. Si vous activez cette option, assurez-vous de toujours utiliser l'ordinateur sur un réseau protégé par un pare-feu paramétré de manière ad-hoc.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Par défaut, l'icône de la barre d'état système ouvre l'interface utilisateur avec un jeton de sécurité. Ceci vous permet d'accéder à l'interface utilisateur à partir de l'icône de la barre d'état système, tout en demandant aux autres utilisateurs d'entrer un mot de passe. Si vous préférez saisir le mot de passe même lorsque vous accédez à l'interface utilisateur à partir de l'icône de la barre des tâches, activez cette option.","Cache Files":"Mettre les fichiers en cache","Canary":"Canary","Cancel":"Annuler","Cannot move to existing file":"Impossible de déplacer vers un fichier existant","Changelog":"Journal des modifications","Changelog for {{appname}} {{version}}":"Journal des modifications pour {{appname}} {{version}}","Check failed:":"Échec de la vérification :","Check for updates now":"Vérifier les mise à jour maintenant","Checking for updates …":"Recherche de mises à jour...","Chose a storage type to get started":"Sélectionner un type de stockage pour commencer","Click the AuthID link to create an AuthID":"Cliquer sur le lien pour créer un AuthID","Click to set throttle options":"Cliquez pour définir les options d'accélération","Client library to use":"Bibliothèque cliente à utiliser","Commandline …":"Ligne de commande...","Compact Phase":"Étape de compression","Compact now":"Compacter maintenant","Compacting remote data …":"Compression des données distantes...","Complete log":"Journal complet","Completing backup …":"Achèvement de la sauvegarde...","Completing previous backup …":"Achèvement de la sauvegarde précédente...","Computer":"Ordinateur","Configuration file:":"Fichier de configuration :","Configuration:":"Configuration :","Configure a new backup":"Configurer une nouvelle sauvegarde","Confirm delete":"Confirmer suppression","Confirm encryption passphrase":"Confirmez la phrase secrète de chiffrement","Confirm passphrase":"Confirmer la phrase secrète","Confirmation required":"Confirmation nécessaire","Connect":"Connecter","Connect now":"Connecter maintenant","Connecting to server …":"Connexion au serveur...","Connection lost":"Connexion perdue","Connection worked!":"Connection fonctionnelle !","Container name":"Nom du conteneur","Container region":"Région du conteneur","Continue":"Continuer","Continue without encryption":"Continuer sans chiffrement","Copied!":"Copié !","Copy":"Copie","Copy Destination URL to Clipboard":"Copier l'URL de destination dans le presse-papier","Copy failed. Please manually copy the URL":"Échec de la copie. Copier l'URL manuellement","Core options":"Options du noyau","Counting ({{files}} files found, {{size}})":"Énumération ({{files}} fichiers trouvés, {{size}})","Crashes only":"Plantages uniquement","Create bug report …":"Créer un rapport d'erreur...","Create folder?":"Créer un dossier ?","Created new limited user":"Nouvel utilisateur limité créé","Creating bug report …":"Création du rapport d'erreur...","Creating new user with limited access …":"Création d'un nouvel utilisateur avec un accès limité...","Creating target folders …":"Création des dossiers de destination...","Creating temporary backup …":"Création d'une sauvegarde temporaire...","Current action:":"Action en cours :","Current file:":"Fichier actuel :","Current version is {{versionname}} ({{versionnumber}})":"Version actuelle : {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"S3 endpoint personnalisé","Custom Satellite":"Satellite personnalisé","Custom Satellite ({{satellite}})":"Satellite personnalisé ({{satellite}})","Custom authentication url":"URL d'authentification personnalisée","Custom backup retention":"Rétention de sauvegarde personnalisée","Custom location ({{server}})":"Emplacement personnalisé ({{server)}}","Custom region for creating buckets":"Région personnalisée pour la créations de buckets","Custom region value ({{region}})":"Valeur personnalisée de région ({{region}})","Custom server url ({{server}})":"URL serveur personnalisée ({{server}})","Custom storage class ({{class}})":"Classe de stockage personnalisée ({{class}})","Database …":"Base de données...","Days":"Jours","Default":"Défaut","Default ({{channelname}})":"({{channelname}}) par défaut","Default excludes":"Exclusions par défaut","Default options":"Options par défaut","Delete":"Supprimer","Delete Phase (Old Backup Versions)":"Étape de suppression (anciennes versions de sauvegarde)","Delete backup":"Supprimer la sauvegarde","Delete backups that are older than":"Supprimer les sauvegardes plus anciennes que","Delete local database":"Supprimer la base de données locale","Delete remote files":"Supprimer les fichiers distants","Delete the local database":"Supprimer la base de données locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Supprimer {{filecount}} fichiers ({{filesize}}) du stockage distant ?","Delete …":"Supprimer...","Deleted":"Supprimé","Deleted Versions":"Versions supprimées","Deleted files":"Fichiers supprimés","Deleting remote files …":"Suppression des fichiers distants...","Deleting unwanted files …":"Suppression des fichiers non désirés...","Description (optional)":"Description (facultative)","Description:":"Description : ","Desktop":"Bureau","Destination":"Destination","Destination path":"Chemin de destination","Disabled":"Désactivé","Dismiss":"Rejeter","Dismiss all":"Rejeter la totalité","Display and color theme":"Thème d'affichage et de couleur","Do you really want to delete the backup: \"{{name}}\" ?":"Voulez-vous vraiment supprimer la sauvegarde : \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Voulez-vous vraiment supprimer la base de données locale pour : {{name}} ?","Done":"Fait","Download":"Téléchargement","Downloaded files":"Fichiers téléchargés","Downloading files …":"Téléchargement des fichiers...","Downloading update…":"Téléchargement de la mise à jour...","Duplicate option {{opt}}":"Option de duplication {{opt}}","Duplicati Website":"Site internet de Duplicati","Duplicati forum":"Forum de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati s'exécutera une fois démarré, mais restera en pause pendant toute la durée. Duplicati occupera un minimum de ressources système et aucune sauvegarde ne sera exécutée.","Duration":"Durée","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Chaque sauvegarde a une base de données locale associée. Elle stocke des informations à propos de la sauvegarde distante.\nQuand vous supprimez une sauvegarde, vous pouvez aussi supprimer la base de données locale sans affecter votre capacité à restaurer vos fichiers distants.\nSi vous utilisez la base de données locale pour vos sauvegardes à partir de la ligne de commande, vous devez conserver la base de données.","Edit as list":"Éditer en tant que liste","Edit as text":"Éditer en tant que texte","Edit …":"Édition...","Encrypt file":"Chiffrement de fichier","Encryption":"Chiffrement","Encryption changed":"Chiffrement modifié","Encryption passphrase":"Phrase de chiffrement","End":"Fin","Enter URL":"Saisir l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Saisir une stratégie de rétention manuellement. Les espaces réservés sont D / W / Y pour les jours / semaines / années et U pour illimité. La syntaxe est : 7D:1D, 4W:1W, 36M:1M. Cet exemple conserve une sauvegarde pour chacun des sept prochains jours, une pour chacune des quatre prochaines semaines et une pour chacun des 36 prochains mois. Cela peut également être écrit comme 1W:1D, 1M:1W, 3Y:1M.","Enter backup passphrase, if any":"Saisir la phrase secrète de sauvegarde, si existante","Enter configuration details":"Saisir les détails de configuration","Enter encryption passphrase":"Saisir la phrase secrète de chiffrement","Enter expression here":"Saisir l'expression ici","Enter the destination path":"Saisir le chemin de destination","Error":"Erreur","Error!":"Erreur !","Errors and crashes":"Erreurs et plantages","Examined":"Examiné","Exclude":"Exclure","Exclude directories whose names contain":"Exclure les répertoires dont le nom contient","Exclude expression":"Exclure l'expression","Exclude file":"Exclure le fichier","Exclude file extension":"Exclure l'extension de fichier","Exclude files whose names contain":"Exclure les fichiers dont les noms contiennent","Exclude filter group":"Exclure le groupe de filtres","Exclude folder":"Exclure le dossier","Exclude regular expression":"Exclure l'expression régulière","Existing file found":"Fichier existant trouvé","Experimental":"Expérimental","Export":"Exporter","Export backup configuration":"Exporter la configuration de sauvegarde","Export configuration":"Exporter la configuration","Export passwords":"Exporter les mots de passe","Export …":"Exporter...","Exporting …":"Export...","External link":"Lien externe","FTP (Alternative)":"FTP (Alternatif)","Failed to build temporary database: {{message}}":"Échec de la construction de la base de données temporaire : {{message}}","Failed to connect:":"Échec de la connexion :","Failed to connect: {{message}}":"Échec de la connexion : {{message}}","Failed to delete:":"Échec de la suppression :","Failed to fetch path information: {{message}}":"Échec de la récupération des information du chemin : {{message}}","Failed to find backup:":"Impossible de trouver la sauvegarde : ","Failed to read backup defaults:":"Échec de la lecture des paramètres par défaut de la sauvegarde :","Failed to restore files: {{message}}":"Échec de la restauration des fichiers : {{message}}","Failed to save:":"Échec d'enregistrement :","Fetching path information …":"Récupération d'informations sur le chemin...","File":"Fichier","Files larger than:":"Fichiers plus gros que :","Filters":"Filtres","Finished!":"Terminé !","First run setup":"Première mise en route","Folder":"Dossier","Folder path":"Chemin du dossier","Fri":"Ven.","GByte":"Go","GByte/s":"Go/s","GCS Project ID":"GCS Project ID","General":"Général","General backup settings":"Paramètres généraux de sauvegarde","General options":"Options générales","Generate":"Générer","Generate IAM access policy":"Générer une politique d'accès IAM","Getting file versions …":"Récupération des versions de fichier...","Group email":"Courriel de groupe","Hidden files":"Fichiers cachés","Hide":"Masquer","Hide hidden folders":"Masquer les dossiers cachés","Home":"Poste de travail","Hostnames":"Noms d'hôtes","Hours":"Heures","How do you want to handle existing files?":"Comment voulez-vous traiter les fichiers existants ?","Hyper-V Machine":"Machine Hyper-V","Hyper-V Machine:":"Machine Hyper-V :","Hyper-V Machines":"Machines Hyper-V","ID:":"ID :","If a date was missed, the job will run as soon as possible.":"Si une date a été manquée, la tâche démarrera dès que possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si au moins une sauvegarde plus récente est trouvée, toutes les sauvegardes antérieures à cette date sont supprimées.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si vous n'entrez pas de chemin, tous les fichiers seront stockés dans le dossier de connexion.\nÊtes-vous sûr que c'est ce que vous voulez ?","If you do not enter an API Key, the tenant name is required":"Si vous n'entrez pas de clé API, le nom de l'entité est requis","Import":"Importer","Import Destination URL":"Importer l'URL de destination","Import backup configuration":"Importer la configuration de sauvegarde","Import from a file":"Importer depuis un fichier","Import metadata":"Importer des métadonnées","Importing …":"Importation...","Include a file?":"Inclure un fichier ?","Include expression":"Inclure expression","Include regular expression":"Inclure expression régulière","Incorrect answer, try again":"Réponse incorrecte, essayez encore","Individual builds for developers only. Not for use with important data.":"Versions individuelles pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Information":"Information","Invalid characters in path":"Caractères invalides dans le chemin","Invalid retention time":"Temps de rétention invalide","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Il est possible de se connecter à certains FTP sans mot de passe.\nÊtes-vous sûr que votre serveur FTP prend en charge l'identification sans mot de passe ?","KByte":"Ko","KByte/s":"Ko/s","Keep a specific number of backups":"Conserver un nombre spécifique de sauvegardes","Keep all backups":"Conserver toutes les sauvegardes","Keystone API version":"Version de l'API Keystone","Language in user interface":"Langue de l'interface utilisateur","Last month":"Mois dernier","Last successful backup:":"Dernière sauvegarde réussie :","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Dernière restauration réussie : {{time}} (a pris {{duration || '0 secondes'}})","Latest":"Dernière","Libraries":"Bibliothèques","Listing backup dates …":"Énumération des dates de sauvegarde...","Listing remote files for purge …":"Énumération des fichiers distants à purger...","Listing remote files …":"Énumération des fichiers distants...","Live":"Direct","Load a configuration from an exported job or a storage provider":"Charger une configuration depuis un export ou un opérateur de stockage","Load destination from an exported job or a storage provider":"Charger la destination depuis un export ou un opérateur de stockage","Load older data":"Charger des données plus anciennes","Loading …":"Chargement...","Local Repository":"Stockage local","Local database path:":"Chemin de la base de données locale :","Local repository":"Stockage local","Local storage":"Stockage local","Location":"Emplacement","Location where buckets are created":"Emplacement ou les buckets sont créés","Log data for {{Backup.Backup.Name}}":"Historique pour {{Backup.Backup.Name}}","Log data from the server":"Données d'historique du serveur","Log out":"Déconnexion","MByte":"Mo","MByte/s":"Mo/s","Maintenance":"Maintenance","Manually type path":"Entrée manuelle du chemin","Max download speed":"Vitesse maximum de téléchargement","Max upload speed":"Vitesse maximum de téléversement","Menu":"Menu","Microsoft SQL Database:":"Base de données Microsoft SQL :","Microsoft SQL Databases":"Bases de données Microsoft SQL","Minimum redundancy":"Redondance minimale","Minimum redundancy is 1.0":"La redondance minimale est de 1,0","Minutes":"Minutes","Missing name":"Nom manquant","Missing passphrase":"Phrase secrète manquante","Missing sources":"Sources manquantes","Modified":"Modifié","Mon":"Lun.","Months":"Mois","Move existing database":"Déplacer la base de données existante","Move failed:":"Échec de déplacement :","My Documents":"Mes documents","My Music":"Ma musique","My Photos":"Mes photos","My Pictures":"Mes photos","Name":"Nom","Never":"Jamais","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Le nouveau nom d'utilisateur est {{user}}.\nMise à jour des accès pour le nouvel utilisateur limité","Next":"Suivant","Next scheduled run:":"Prochaine exécution programmée :","Next scheduled task:":"Prochaine tâche planifiée :","Next task:":"Prochaine tâche :","Next time":"Prochaine fois","No":"Non","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Aucun certificat n'a été spécifié auparavant, veuillez vérifier que la clé est correcte auprès de votre administrateur système : {{key}}\n\nVoulez-vous approuver la clé de l'hôte mentionné ?","No editor found for the "{{backend}}" storage type":"Aucun éditeur trouvé pour le "{{backend}}" type de stockage","No encryption":"Pas de chiffrement","No items selected":"Aucun élément sélectionné","No items to restore, please select one or more items":"Aucun élément à restaurer, merci de sélectionner un ou plusieurs éléments","No passphrase entered":"Aucune phrase secrète entrée","No scheduled tasks":"Aucune tâche planifiée","Non-matching passphrase":"La phrase secrète ne correspond pas","None / disabled":"Aucun / Désactivé","Not using encryption":"Ne pas utiliser le chiffrement","Nothing will be deleted. The backup size will grow with each change.":"Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque modification.","OK":"Ok","Once there are more backups than the specified number, the oldest backups are deleted.":"Une fois qu'il y a plus de sauvegardes que le nombre spécifié, les sauvegardes les plus anciennes sont supprimées.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Ouvert","Operating System":"Système d'exploitation","Operation":"Opération","Operations:":"Opérations :","Optional authentication password":"Mot de passe d'identification optionel","Optional authentication username":"Nom d'utilisateur d'identification optionel","Options":"Options","Original location":"Emplacement d'origine","Others":"Autres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Les sauvegardes seront automatiquement supprimées. Il restera une sauvegarde pour chacun des sept derniers jours, chacune des quatre dernières semaines et chacun des douze derniers mois. Il y aura toujours au moins une sauvegarde.","Overwrite":"Écraser","Passphrase":"Phrase secrète","Passphrase (if encrypted)":"Phrase secrète (si chiffré)","Passphrase changed":"Phrase secrète changée","Passphrases are not matching":"Les phrases secrètes ne correspondent pas","Passphrases do not match":"La phrase secrète ne correspond pas","Password":"Mot de passe","Patching files with local blocks …":"Correction des fichiers avec les blocs locaux...","Path":"Chemin","Path not found":"Chemin non trouvé","Path on server":"Chemin sur le serveur","Path or subfolder in the bucket":"Chemin ou sous-dossier dans le bucket","Pause":"Pause","Pause after startup or hibernation":"Pause après le démarrage ou l'hibernation","Pause options":"Options de pause","Permissions":"Permissions","Pick location":"Choisir emplacement","Point to your backup files and restore from there":"Indiquer l'emplacement des fichiers de sauvegarde ","Port":"Port","Prevent tray icon automatic log-in":"Empêcher la connexion automatique de l'icône de la barre de tâches","Previous":"Précédent","Progress:":"Statut :","ProjectID is optional if the bucket exist":"Le ProjectID est optionel si le bucket existe","Proprietary":"Propriétaire","Purge Phase":"Étape de purge","Purging files complete!":"Nettoyage des fichiers terminé !","Purging files …":"Nettoyage des fichiers…","Rebuilding local database …":"Reconstruction de la base de données locale...","Recreate (delete and repair)":"Régénération (supprimer et réparer)","Recreate Database Phase":"Etape de la régénération de la bases de données","Recreating database …":"Régénération de la base de données...","Registering temporary backup …":"Enregistrement d'une sauvegarde temporaire...","Relative paths not allowed":"Les chemins relatifs ne sont pas autorisés","Reload":"Recharger","Remote":"Distant","Remote Path":"Chemin d'accès distant","Remote Repository":"Stockage distant","Remote path":"Chemin d'accès distant","Remote repository":"Stockage distant","Remote volume size":"Taille du volume distant","Remove":"Supprimer","Remove option":"Option de suppression","Removed files":"Fichiers supprimés","Repair":"Réparer","Repair Phase":"Étape de réparation","Repairing database …":"Réparation de la base de données...","Repeat Passphrase":"Répéter la phrase secrète","Reporting:":"Communication de données :","Reset":"Réinitialiser","Restore":"Restaurer","Restore complete!":"Restauration terminée !","Restore files":"Restaurer les fichiers","Restore files …":"Restaurer les fichiers...","Restore from":"Restaurer depuis","Restore from backup configuration":"Restaurer depuis la sauvegarde de la configuration","Restore options":"Options de restauration","Restore read/write permissions":"Restauration des droits de lecture/écriture","Restored Files":"Fichiers restaurés","Restored Folders":"Dossiers restaurés","Restored Symlinks":"Liens symboliques restaurés","Restoring files …":"Restauration des fichiers...","Resume":"Reprendre","Rewritten File Lists":"Listes de fichiers réécrits","Run again every":"Relancer tous les","Run now":"Démarrer maintenant","Running commandline entry":"Exécution d'une ligne de commande","Running task:":"Tâche en cours :","Running …":"En cours...","S3 Compatible":"Compatible S3","Same as the base install version: {{channelname}}":"Identique à la version de base installée : {{channelname}}","Sat":"Sam.","Satellite":"Satellite","Save":"Enregistrer","Save and repair":"Enregistrer et réparer","Save different versions with timestamp in file name":"Enregistrer des versions différentes avec l'horodatage dans le nom du fichier","Save immediately":"Enregistrer immédiatement ","Scanning existing files …":"Analyse des fichiers existants...","Scanning for local blocks …":"Analyse des blocs locaux...","Schedule":"Planifier","Search":"Rechercher","Search for files":"Rechercher les fichiers","Seconds":"Secondes","Select a log level and see messages as they happen:":"Sélectionner un niveau d'historique et voyez les messages quand ils apparaissent :","Select files":"Sélectionner les fichiers","Server":"Serveur","Server and port":"Serveur et port","Server hostname or IP":"Nom d'hôte du serveur ou IP","Server is currently paused,":"Le serveur est actuellement en pause,","Server is currently paused, do you want to resume now?":"Le serveur est actuellement en pause, voulez-vous reprendre maintenant ?","Server password":"Mot de passe du serveur","Server paused":"Serveur en pause","Server state properties":"Propriétés du statut serveur","Settings":"Paramètres","Show":"Afficher","Show advanced editor":"Afficher l'éditeur avancé","Show hidden folders":"Afficher les dossiers cachés","Show log":"Afficher l'historique","Show log …":"Afficher le journal...","Show treeview":"Afficher l'arborescence","Sia server password":"Mot de passe du serveur Sia","Smart backup retention":"Rétention de sauvegarde intelligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Certains fournisseurs OpenStack autorisent une clé API à la place d'un mot de passe et d'un nom d'entité","Some S3 providers might only be compatible with a certain client library":"Certains fournisseurs S3 pourraient n'être compatibles qu'avec une bibliothèque cliente particulière.","Source Data":"Données source","Source Files":"Fichiers sources","Source data":"Données source","Source folders":"Dossiers source","Source:":"Source :","Specific builds for developers only. Not for use with important data.":"Versions spécifiques pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Standard protocols":"Protocoles standards","Start":"Démarrer","Starting backup …":"Démarrage de la sauvegarde...","Starting restore …":"Démarrage de la restauration...","Starting the restore process …":"Démarrage du processus de restauration...","Stop after current file":"Arrêter après le fichier en cours","Stop after the current file":"Arrêter après le fichier en cours","Stop now":"Arrêter maintenant","Stop running backup":"Arrêter la sauvegarde en cours","Stop running task":"Arrêter la tâche en cours","Stopping after the current file:":"Arrêt après le fichier en cours:","Stopping task:":"Arrêt de la tâche:","Storage Type":"Type de stockage","Storage class":"Classe de stockage","Storage class for creating a bucket":"Classe de stockage pour la création d'un bucket","Stored":"Stocké","Strong":"Fort","Success":"Succès","Sun":"Dim.","Symbolic link":"Lien symbolique","System Files":"Fichiers système","System default ({{levelname}})":"Paramètre par défaut du système ({{levelname}})","System files":"Fichiers système","System info":"Info système","System properties":"Propriétés système","TByte":"TByte","TByte/s":"TByte/s","Task is running":"La tâche est en cours","Temporary Files":"Fichiers temporaires","Temporary files":"Fichiers temporaires","Test Phase":"Étape de test","Test connection":"Tester la connexion","Testing permissions …":"Test des permissions...","Testing …":"Test...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Le champ '{{fieldname}}' contient un caractère non valide : {{character}} (valeur : {{value}}, index : {{pos}})","The backup is missing, has it been deleted?":"La sauvegarde est introuvable, a-t-elle été supprimée?","The backup was temporary and does not exist anymore, so the log data is lost":"La sauvegarde était temporaire et n'existe plus, alors les données du journal sont perdues.","The bucket name should be all lower-case, convert automatically?":"Le nom du bucket devrait être entièrement en minuscule, convertir automatiquement ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuration doit être conservée en sécurité. Êtes-vous sûr de vouloir enregistrer un fichier non chiffré contenant vos mots de passe ?","The dark theme (by Michal)":"Le thème sombre (de Michal)","The default blue on white theme (by Alex)":"Thème par défaut bleu sur fond blanc (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Le dossier {{dossier}} n'existe pas.\nVoulez-vous le créer maintenant ?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clé de l'hôte a changé. Veuillez vérifier avec l'administrateur du serveur si cela est correcte, car il pourrait s'agir d'une attaque de type \"intermédiaire\".\n\nVoulez-vous remplacer votre clé d'hôte actuelle \"{{prev}}\" par la clé indiquée : {{key}} ?","The passwords do not match":"Les mots de passe ne correspondent pas","The path does not appear to exist, do you want to add it anyway?":"Le chemin ne semble pas exister, voulez-vous l'ajouter quand même ?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Le chemin ne se termine pas par un caractère '{{dirsep}}', ce qui signifie que vous sélectionnez un fichier et non un dossier.\n\nVoulez-vous inclure le fichier spécifié ?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Le chemin doit être absolu, c.-à-d. qu'il doit commencer par une barre oblique '/'","The region parameter is only applied when creating a new bucket":"Le paramètre régional n'est appliqué qu'à la création d'un nouveau bucket","The region parameter is only used when creating a bucket":"Le paramètre régional n'est utilisé qu'à la création d'un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Le certificat du serveur n'a pas pu être validé.\nVoulez-vous approuver le certificat SSL avec la somme de contrôle : {{hash}} ?","The storage class affects the availability and price for a stored file":"La classe de stockage affecte la disponibilité et le prix d'un fichier stocké","The target folder contains encrypted files, please supply the passphrase":"Le fichier cible contient des fichiers chiffrés. Indiquer la phrase secrète","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utilisateur a des droits d'accès trop élevés. Voulez-vous créer un nouvel utilisateur limité avec des droits d'accès uniquement pour les chemins sélectionnés ?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Cette sauvegarde a été créée sur un autre système d’exploitation. Restaurer les fichiers sans préciser un dossier de destination peut créer des fichiers à des endroits inattendus. Êtes-vous surs de vouloir poursuivre sans choisir un dossier de destination ?","This month":"Ce mois","This week":"Cette semaine","Throttle settings":"Options de contrôle du débit","Thu":"Jeu.","Time":"Heure","To File":"Vers un fichier","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pour confirmer la suppression de tous les fichiers distants pour \"{{name}}\", entrer le mot affiché ci-dessous","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Pour éviter diverses attaques basées sur le DNS, Duplicati limite les noms d'hôtes autorisés à ceux répertoriés ici. L'accès IP direct et localhost est toujours autorisé. Plusieurs noms d'hôte peuvent être fournis séparés par un points-virgule. Si l'un des noms d'hôte autorisés est un astérisque (*), tous les noms d'hôte sont autorisés et cette fonctionnalité est désactivée. Si le champ est vide, seule l'adresse IP et l'accès localhost sont autorisés.","Today":"Aujourd'hui","Trust host certificate?":"Faire confiance au certificat de l'hôte ?","Trust server certificate?":"Faire confiance au certificat du serveur ?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Essayer les nouvelles fonctionnalités en développement. Actuellement la version la plus stable disponible. Tester la restauration des données avant de l'utiliser dans des environnements de production.","Tue":"Mar.","Type passphrase here.":"Tapez la phrase secrète ici.","Type to highlight files":"Tapez pour mettre en surbrillance les fichiers","Unknown backup size and versions":"Taille et versions des sauvegardes inconnues","Until resumed":"Jusqu'à la reprise","Update channel":"Canal de mise à jour","Update failed:":"Échec de mise à jour","Updating with existing database":"Mettre à jour avec une base de données existante","Uploaded files":"Fichiers téléversés","Uploading verification file …":"Envoi du fichier de vérification...","Usage statistics":"Statistiques d'utilisation","Usage statistics, warnings, errors, and crashes":"Statistiques d'utilisation, avertissements, erreurs et accidents","Use SSL":"Utiliser SSL","Use existing database?":"Utiliser une base de données existante ?","Use weak passphrase":"Utiliser une phrase secrète faible","Useless":"Inutile","User data":"Données utilisateur","User domain name":"Nom de domaine de l'utilisateur","User has too many permissions":"L'utilisateur à trop d'autorisations","User interface settings":"Réglages interface utilisateur","Username":"Nom d'utilisateur","Vacuuming database …":"Nettoyage de la base de données...","Validating …":"Validation...","Verifications":"Vérifications","Verify files":"Vérifier fichier","Verifying answer":"Vérification de la réponse","Verifying backend data …":"Vérification des données du backend...","Verifying files …":"Vérification des fichiers...","Verifying remote data …":"Vérification des données distantes...","Verifying restored files …":"Vérification des fichiers restaurés...","Verifying …":"Vérification...","Version ID":"ID de version","Very strong":"Très fort","Very weak":"Très faible","Visit us on":"Rendez nous visite sur","WARNING: This will prevent you from restoring the data in the future.":"ATTENTION : Cela va vous empêcher de restaurer vos données dans le futur.","Waiting for task to begin":"En attente du début de la tâche","Waiting for upload to finish …":"Attente de la fin du téléversement...","Warnings, errors and crashes":"Avertissements, erreurs et accidents","We recommend that you encrypt all backups stored outside your system":"Nous vous recommandons de chiffrer toutes les sauvegardes stockées en dehors de votre système","Weak":"Faible","Weak passphrase":"Phrase secrète faible","Wed":"Mer.","Weeks":"Semaines","Where do you want to restore from?":"Ou voulez-vous restaurer vos fichiers ?","Where do you want to restore the files to?":"Ou voulez-vous restaurer vos fichiers ?","Years":"Années","Yes":"Oui","Yes, I have stored the passphrase safely":"Oui, j'ai conservé ma phrase secrète en sécurité","Yes, I understand the risk":"Oui, je comprends le risque","Yes, I'm brave!":"Oui, je suis courageux !","Yes, please break my backup!":"Oui, s'il vous plait cassez ma sauvegarde","Yesterday":"Hier","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Vous êtes en train de changer le chemin de la base de données depuis une base de donnée existante.\nÊtes-vous sûr que c'est ce que vous voulez ?","You are currently running {{appname}} {{version}}":"Version installée : {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Vous pouvez arrêter la sauvegarde une fois que l'envoi de fichiers en cours est terminé.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Vous pouvez arrêter la tâche immédiatement, ou permettre au processus de terminer le fichier en cours, puis l'arrêter.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vous avez changé la méthode de chiffrement. Ceci peut endommager certaines choses. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vous avez changé la phrase secrète, ce qui n'est pas pris en charge. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Vous avez choisi de ne pas chiffrer votre sauvegarde. Le chiffrement est recommandé pour toutes les données stockées sur un serveur distant.","You have chosen to restore to a new location, but not entered one":"Vous avez demandé à restaurer vers un nouveau dossier, mais sans indiquer son chemin","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vous avez généré une phrase secrète forte. Assurez-vous que vous avez effectué une copie sécurisée de cette phrase secrète, car les données ne pourront pas être récupérées si vous la perdez.","You must choose at least one source folder":"Vous devez choisir au moins un dossier source","You must enter a domain name to use v3 API":"Vous devez entrer un nom de domaine pour utiliser l'API v3","You must enter a name for the backup":"Vous devez entrer un nom pour votre sauvegarde","You must enter a passphrase or disable encryption":"Vous devez saisir une phrase secrète ou désactiver le chiffrement","You must enter a password to use v3 API":"Vous devez saisir un mot de passe pour utiliser l'API v3","You must enter a positive number of backups to keep":"Vous devez entrer un nombre positif de sauvegarde à conserver","You must enter a tenant (aka project) name to use v3 API":"Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3","You must enter a valid duration for the time to keep backups":"Vous devez entrer une valeur correcte pour la durée de conservation de vos sauvegardes","You must enter a valid retention policy string":"Vous devez saisir une chaîne de politique de conservation valide","You must fill in the password":"Vous devez renseigner le mot de passe","You must fill in the server name or address":"Vous devez renseigner le nom du serveur ou l'adresse","You must fill in the username":"Vous devez renseigner le nom d'utilisateur","You must fill in {{field}}":"Vous devez renseigner le champ : {{field}}","You must select or fill in the AuthURI":"Vous devez sélectionner ou renseigner l'AuthURI","You must select or fill in the server":"Vous devez sélectionner ou renseigner le serveur","You must specify a path":"Vous devez spécifier un chemin.","Your files and folders have been restored successfully.":"Vos fichiers et dossiers ont été restaurés avec succès.","Your passphrase is easy to guess. Consider changing passphrase.":"Votre phrase secrète est facile à deviner. Songez à la changer.","bucket/folder/subfolder":"bucket/dossier/sous-dossier","byte":"byte","byte/s":"byte/s","custom":"personnalisé ","resume now":"reprendre maintenant","unless you are explicitly specifying --group-id":"sauf si vous spécifiez explicitement --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a été principalement développé par {{dev1}} et {{dev2}}. {{appname}} peut être téléchargé depuis {{websitename}}. {{appname}} est sous licence {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fichiers {{size}}) à transférer {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Heure","{{number}} Hours":"{{number}} Heures","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (durée {{duration}})"}); - gettextCatalog.setStrings('hu', {"- pick an option -":"- válasszon -","...loading...":"...töltés...","API key":"API kulcs","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Névjegy","About {{appname}}":"{{appname}} néjegye","Access Key":"Hozzáférési kulcs","Access denied":"Hozzáférés megtagadva","Access to user interface":"Hozzáférés a felhasználói felülethez","Account name":"Fiók név","Add a new backup":"Új mentés hozzáadás","Add a path directly":"Útvonal hozzáadás közvetlenül","Add advanced option":"Haladó beállítás hozzáadása","Add backup":"Mentés hozzáadás","Add filter":"Szűrő hozzáadás","Add path":"Útvonal hozzáadás","Added":"Hozzáadva","Advanced Options":"Haladó beállítások","Advanced options":"Haladó beállítások","Advanced:":"Haladó:","All Hyper-V Machines":"Minden Hyper-V gép","All Microsoft SQL Databases":"Minde Microsoft SQL adatbázik","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Az összes felhasználási jelentést névtelenül küldjük el, és nem tartalmaznak személyes információt. Információkat tartalmaz a hardverről és az operációs rendszerről, a háttér típusáról, a biztonsági mentés időtartamáról, a forrásadatok teljes méretéről és hasonló adatokról. Nem tartalmaz útvonalakat, fájlneveket, felhasználóneveket, jelszavakat vagy hasonló érzékeny információkat.","Allow remote access (requires restart)":"Távoli hozzáférés engedélyezése (újraindítást igényel)","Allowed days":"Engedélyezett napok","An existing file was found at the new location":"Egy létező fájt találtam az új helyen","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Egy létező fájt találtam az új helyen\nBiztos vagy benne hogy az adatbázis a létező fájlra mutasson?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"A tároláshoz létező helyi adatbázis található. Az adatbázis újbóli használata lehetővé teszi, hogy a parancssori és a kiszolgálópéldányok ugyanabban a távoli tárolóban működjenek. \n\nSzeretné használni a meglévő adatbázist?","Anonymous usage reports":"Névtelen használati jelentések","Applications":"Alkalmazások","As Command-line":"Parancssorként","Authentication password":"Hitelesítési jelszó","Authentication username":"Hitelesítési felhasználónév","Autogenerated passphrase":"Automatikusan generált jelszó","Back":"Vissza","Backup complete!":"Mentés kész!","Backup destination":"Mentés cél","Backup location":"Mentés helye","Backup retention":"Mentés késleltetés","Backup:":"Mentés:","Beta":"Béta","Broken access":"Törött hozzáférés","Browse":"Tallóz","Browser default":"Böngésző alapértelmezett","Bucket create location":"Bucket létrehozásának helye","Bucket name":"Bucket neve","Building list of files to restore …":"Fájl lista összeállítás a visszaállításhoz...","Building partial temporary database …":"Részleges ideiglenes adatbázist készítése","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"A távoli elérés engedélyezésével a szerver minden kérésre hallgat a hálózaton. Csak akkor engedélyezd ezt az opciót, ha biztos vagy benne, hogy biztonságos, tűzfallal védett hálózaton van a számítógép.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Alapértelmezés szerint a tálca ikon megnyitja a felhasználói felületet egy tokennel, amely feloldja a felhasználói felületet. Ez biztosítja, hogy a tálcán található ikonnal hozzáférjen a felhasználói felülethez, miközben másoknak is meg kell adniuk a jelszót. Ha inkább be kell írnia a jelszót, akkor is engedélyezze ezt a beállítást, ha a felhasználói felületre a tálcaikonból fér hozzá.","Cache Files":"Gyorsítótás Fájlok","Cancel":"Mégsem","Cannot move to existing file":"Nem lehet létező fájlra átnevezni","Changelog":"Váztozások","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} változásnapló","Check failed:":"Ellenőrzés sikertelen:","Check for updates now":"Frissítés ellenőrzése most","Checking for updates …":"Frissítések ellenőrzése ...","Chose a storage type to get started":"A kezdéshez válassz tárhely típust","Click to set throttle options":"Kattints a sebességkorlátozás beállításához","Commandline …":"Parancssor...","Compact Phase":"Tömörített állapot","Compact now":"Tömörítés most","Compacting remote data …":"Távoli adatok tömörítése...","Complete log":"Teljes napló","Completing backup …":"Mentés befejezése...","Completing previous backup …":"Előző mentés befejezése...","Computer":"Számítógép","Configuration file:":"Konfigurációs fájl:","Configuration:":"Konfiguráció:","Configure a new backup":"Új mentés beállítás","Confirm delete":"Törlés megerősítése","Confirm encryption passphrase":"Titkosítási jelszó megerősítése","Confirm passphrase":"Jelmondat megerősítés","Confirmation required":"Megerősítés szükséges","Connect":"Csatlakozás","Connect now":"Csatlakozás most","Connecting to server …":"Csatlakozás a kiszolgálóhoz...","Connection lost":"Csatlakozás megszakadt","Connection worked!":"Csatlakozás működik!","Container name":"Tároló neve","Container region":"Tároló régió","Continue":"Folytatás","Continue without encryption":"Folytatás titkosítás nélkül","Copied!":"Másolva!","Copy":"Másolás","Copy Destination URL to Clipboard":"Cél URL másolása a Vágólapra","Copy failed. Please manually copy the URL":"Másolás sikertelen. Próbáld meg kézzel másolni az URL-t","Core options":"Mag beállítások","Counting ({{files}} files found, {{size}})":"Számolás ({{files}} megtalált fájl, {{size}})","Crashes only":"Csak összeomlások","Create bug report …":"Hibajelentés készítés...","Create folder?":"Mappa készítés?","Created new limited user":"Új korlátozott felhasználó létrehozva","Creating bug report …":"Hibajelentés készítés...","Creating new user with limited access …":"Új felhasználó létrehozása korlátozott hozzáféréssel...","Creating target folders …":"Cél mappák létrehozása...","Creating temporary backup …":"Ideiglenes mentés létrehozása...","Current action:":"Aktuális művelet:","Current file:":"Aktuális fájl:","Current version is {{versionname}} ({{versionnumber}})":"Aktuális verzió: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Egyéni S3 végpont","Custom authentication url":"Egyéni hitelesítési URL","Custom backup retention":"Egyéni mentés késleltetés","Custom location ({{server}})":"Egyéni hely ({{server}})","Custom region value ({{region}})":"Egyéni régió érték ({{region}})","Custom server url ({{server}})":"Egyéni kiszolgáló URL ({{server}})","Custom storage class ({{class}})":"Egyéni tároló osztály ({{class}})","Database …":"Adatbázis...","Days":"Nap","Default":"Alapértelmezett","Default ({{channelname}})":"Alapértelmezett ({{channelname}})","Default excludes":"Alapértelmezett kihagyások","Default options":"Alapértelmezett beállítások","Delete":"Törlés","Delete Phase (Old Backup Versions)":"Törlési fázis (régi mentés verziók)","Delete backup":"Mentés törlése","Delete backups that are older than":"Ennél régebbi mentések törlése","Delete local database":"Helyi adatbázis törlése","Delete remote files":"Távoli fájlok törlése","Delete the local database":"A helyi adatbázis törlése","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} fájl ({{filesize}}) törlése a távoli tárhelyről?","Delete …":"Törlés...","Deleted":"Törölve","Deleted Versions":"Törölt verziók","Deleted files":"Törölt fájlok","Deleting remote files …":"Távoli fájlok törlése","Deleting unwanted files …":"Felesleges fájlok törlése...","Description (optional)":"Leírás (nem kötelező)","Description:":"Leírás:","Desktop":"Asztal","Destination":"Cél","Destination path":"Cél útvonal","Disabled":"Letiltva","Dismiss":"Elvet","Dismiss all":"Elvet mindent","Display and color theme":"Megjelenés és szín téma","Do you really want to delete the backup: \"{{name}}\" ?":"Biztos, hogy törölni akarod ezt a mentést: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Biztos, hogy törölni akarod ezt a helyi adatbázist: {{name}}","Done":"Kész","Download":"Letöltés","Downloaded files":"Letöltött fájlok","Downloading files …":"Fájlok letöltése...","Downloading update…":"Frissítés letöltése...","Duplicate option {{opt}}":"Dupla beállítás: {{opt}}","Duplicati Website":"Duplicati webodal","Duplicati forum":"Duplicati fórum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"A másolat elindul, amikor elindul, de szüneteltetett állapotban marad mindaddig. A Duplicatiák minimális rendszer erőforrásokat foglalnak el, és biztonsági másolatot nem indítanak.","Duration":"Időtartam","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Minden biztonsági mentéshez egy helyi adatbázis tartozik, amely a távoli biztonsági mentésről információkat tárol a helyi számítógépen. Biztonsági másolat törlésekor törölheti a helyi adatbázist anélkül, hogy befolyásolná a távoli fájlok visszaállításának képességét. Ha a helyi adatbázist a parancssorból készített biztonsági másolatokra használja, meg kell őriznie az adatbázist.","Edit as list":"Szerkesztés listaként","Edit as text":"Szerkesztés szövegként","Edit …":"Szerkesztés...","Encrypt file":"Fájl titkosítás","Encryption":"Titkosítás","Encryption changed":"Titkosítás megváltozott","End":"Vége","Enter URL":"URL megadás","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Adjon meg egy megtartási stratégiát kézzel. A helyőrzők napok / hetek / évek feletti órás / év / év, korlátlan U A szintaxis: 7D: 1D, 4W: 1W, 36M: 1M. Ez a példa egy biztonsági másolatot készít a következő 7 nap mindegyikére, egyet a következő 4 hétre és egy a következő 36 hónapra. Ez is 1W: 1D, 1M: 1W, 3Y: 1M formátumban írható.","Enter backup passphrase, if any":"Mentés jelszó megadása, ha van","Enter configuration details":"Beállítások részletes megadása","Enter encryption passphrase":"Titkosítási jelszó megadása","Enter expression here":"Kifejezés megadása itt","Enter the destination path":"Cél útvonal megadása","Error":"Hiba","Error!":"Hiba!","Errors and crashes":"Hibák és összeomlások","Examined":"Vizsgálva","Exclude":"Kizár","Exclude directories whose names contain":"Könyvtárak kizárása, amelyek neve tartalmazza","Exclude expression":"Kifejezés kizárása","Exclude file":"A fájl kizárása","Exclude file extension":"Fájlkiterjesztés kizárása","Exclude files whose names contain":"Fájlok kizárása, amelyek nevei tartalmazzák","Exclude filter group":"Szűrőcsoport kizárása","Exclude folder":"Mappa kizárása","Exclude regular expression":"Reguláris kifejezés kizárása","Existing file found":"Meglévő fájl található","Experimental":"Kísérleti","Export":"Export","Export backup configuration":"Biztonsági mentés konfiguráció exportálása","Export configuration":"Konfiguráció exportálása","Export passwords":"Jelszó exportálása","Export …":"Exportálás…","Exporting …":"Exportálás ...","External link":"Külső hivatkozás","FTP (Alternative)":"FTP (alternatív)","Failed to build temporary database: {{message}}":"Nem sikerült létrehozni az ideiglenes adatbázist: {{message}}","Failed to connect:":"Nem sikerült csatlakozni:","Failed to connect: {{message}}":"Nem sikerült csatlakozni: {{message}}","Failed to delete:":"A törlés nem sikerült:","Failed to fetch path information: {{message}}":"Nem sikerült letölteni az elérési út adatait: {{message}}","Failed to find backup:":"Nem sikerült megtalálni a biztonsági másolatot:","Failed to read backup defaults:":"A biztonsági másolat alapértelmezett értékeinek olvasása nem sikerült:","Failed to restore files: {{message}}":"A fájlok helyreállítása nem sikerült: {{message}}","Failed to save:":"Nem sikerült elmenteni:","Fetching path information …":"Útvonal-információ lekérése ...","File":"Fájl","Files larger than:":"Fájlok nagyobb mint:","Filters":"Szürők","Finished!":"Kész!","First run setup":"Első futtatáskori beállítás","Folder":"Mappa","Folder path":"Mappa útvonal","Fri":"Pén","GByte":"GByte","GByte/s":"GByte/s","General":"Általános","General backup settings":"Általános mentési beállítások","General options":"Általános beállítások","Generate":"Generál","Getting file versions …":"Fájl verziók lekérdezése...","Group email":"Csoport e-mail","Hidden files":"Rejtett fájlok","Hide":"Elrejt","Hide hidden folders":"Rejtett mappák elrejtése","Home":"Kezdőlap","Hostnames":"Gazdagép nevek","Hours":"Óra","How do you want to handle existing files?":"Hogyan szeretnéd kezelni a létező fájlokat?","Hyper-V Machine":"Hyper-V gép","Hyper-V Machine:":"Hyper-V gép:","Hyper-V Machines":"Hyper-V gépek","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ha egy dátum kimaradt, a lehető leghamarabb elindul.","If at least one newer backup is found, all backups older than this date are deleted.":"Ha legalább egy újabb biztonsági másolatot talál, az összes ezen időpontnál régebbi biztonsági másolatot törli.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ha nem ad meg útvonalat, az összes fájlt a bejelentkezési mappában tárolja. Biztos benne, hogy ezt akarod?","If you do not enter an API Key, the tenant name is required":"Ha nem ad meg API-kulcsot, akkor kötelező a bérlő neve","Import":"Import","Import from a file":"Importálás egy fájlból","Import metadata":"Metaadatok importálása","Importing …":"Importálás...","Incorrect answer, try again":"Érvénytelen válasz, próbáld újra","Information":"Információ","Invalid characters in path":"Érvénytelen karakterek az útvonalban","Invalid retention time":"Érvénytelen késleltetési idő","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Meghatározott számú mentés megtartása","Keep all backups":"Minden mentés megtartása","Language in user interface":"Felhasználói felület nyelve","Last month":"Előző hónap","Last successful backup:":"Utolsó sikeres mentés:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Utolsó sikeres visszaállítás: {{time}} (took {{duration || '0 seconds'}})","Latest":"Legújabb","Libraries":"Könyvtárak","Listing backup dates …":"Mentési dátumok felsorolása…","Listing remote files for purge …":"Távoli fájlok felsorolása a tisztításhoz…","Listing remote files …":"Távoli fájlok felsorolása...","Live":"Élő","Load older data":"Régebbi adatok betöltése","Loading …":"Betöltés...","Local Repository":"Helyi tároló","Local database path:":"Helyi adatbázis útvonal:","Local repository":"Helyi tároló","Local storage":"Helyi tárhely","Location":"Hely","Log out":"Kijelentkezés","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Karbantartás","Manually type path":"Útvonal kézi megadása","Max download speed":"Maximális letöltési sebesség","Max upload speed":"Maximális feltöltési sebesség","Menu":"Menü","Microsoft SQL Database:":"Microsoft SQL adatbázis:","Microsoft SQL Databases":"Microsoft SQL adatbázisok","Minimum redundancy":"Minimális redundancia","Minimum redundancy is 1.0":"A minimális redundancia 1.0","Minutes":"Perc","Missing name":"Hiányzó név","Missing passphrase":"Hiányzó jelszó","Missing sources":"Hiányzó források","Modified":"Módosított","Mon":"Hé","Months":"Hónap","Move existing database":"Létező adatbázis áthelyezése","Move failed:":"Áthelyezés sikertelen:","My Documents":"Dokumentumok","My Music":"Zenék","My Photos":"Fényképek","My Pictures":"Képek","Name":"Név","Never":"Soha","Next":"Következő","Next scheduled run:":"Következő időzített futtatás:","Next scheduled task:":"Következő időzített feladat:","Next task:":"Következő feladat:","Next time":"Következő dátum","No":"Nem","No encryption":"Nincs titkosítás","No items selected":"Nincsenek kijelölt elemek","No passphrase entered":"Nincs megadva jelszó","No scheduled tasks":"Nincs ütemezett feladat","Non-matching passphrase":"Nem egyező jelszavak","None / disabled":"Semmi / letiltva","Not using encryption":"Nem használ titkosítást","Nothing will be deleted. The backup size will grow with each change.":"Semmi sem lesz törölve. A mentés minden változáskor növekedni fog.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"A mentések megadott számának elérését követően, a régebbi mentések törlésre kerülnek.","Opened":"Megnyitva","Operating System":"Operációs rendszer","Operation":"Művelet","Operations:":"Tevékenységek:","Optional authentication password":"Opcionális hitelesítési jelszó","Options":"Beállítások","Original location":"Eredeti hely","Others":"Egyebek","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"A biztonsági másolatok idővel automatikusan törlődnek. Egy biztonsági másolat megmarad az elmúlt 7 napból, az utolsó 4 hétből és az utolsó 12 hónapból. Legalább egy biztonsági másolat mindig marad.","Overwrite":"Felülírás","Passphrase":"Jelmondat","Passphrase (if encrypted)":"Jelszó (ha titkosított)","Passphrase changed":"A jelmondat megváltozott","Passphrases are not matching":"A jelszavak nem egyeznek meg","Passphrases do not match":"A jelszavak nem egyeznek","Password":"Jelszó","Path":"Útvonal","Path not found":"Az útvonal nem található","Path on server":"Útvonal a kiszolgálón","Pause":"Szünet","Pause after startup or hibernation":"Szünet indítás vagy hibernálás után","Pause options":"Szünet beállítások","Permissions":"Engedélyek","Pick location":"Hely választása","Port":"Port","Prevent tray icon automatic log-in":"Tálca ikon automatikus bejelentkezés megakadályozása","Previous":"Előző","Progress:":"Folyamat:","Proprietary":"Tulajdonosi","Purge Phase":"Tisztítási fázis","Purging files complete!":"Fájlok tisztítása befejezve!","Purging files …":"Fájlok tisztítása...","Rebuilding local database …":"Helyi adatbázis újraépítése...","Recreate (delete and repair)":"Újraépítés (törlés és javítás)","Recreate Database Phase":"Adatbázis újraépítési fázis","Recreating database …":"Adatbázis újraépítése...","Registering temporary backup …":"Ideiglenes mentés regisztrálása...","Relative paths not allowed":"Relatív útvonalak nem engedélyezettek","Reload":"Újratöltés","Remote":"Távoli","Remote Path":"Távoli útvonal","Remote Repository":"Távoli tároló","Remote path":"Távoli útvonal","Remote repository":"Távoli tároló","Remote volume size":"Távoli kötet méret","Remove":"Eltávolít","Remove option":"Opció eltávolítás","Removed files":"Eltávolított fájlok","Repair":"Javítás","Repair Phase":"Javítási fázis","Repairing database …":"Adatbázis javítás...","Repeat Passphrase":"Jelmondat ismét","Reporting:":"Jelentés:","Reset":"Visszaállítás","Restore":"Visszaállítás","Restore complete!":"Visszaállítás sikeres!","Restore files":"Fájlok visszaállítása","Restore files …":"Fájlok visszaállítása...","Restore from":"Visszaállítás innen","Restore from backup configuration":"Visszaállítás mentési konfigurációból","Restore options":"Visszaállítási beállítások","Restore read/write permissions":"Irási/olvasási engedélyek visszaállítása","Restored Files":"Visszaállított fájlok","Restored Folders":"Visszaállított mappák","Restored Symlinks":"Visszaállított szimbolikus linkek","Restoring files …":"Fájlok visszaállítása...","Resume":"Folytatás","Rewritten File Lists":"Újraírt fájl listák","Run again every":"Futtassa újra minden","Run now":"Futtatás most","Running commandline entry":"Parancssori bejegyzés futtatása","Running task:":"Futó feladat:","Running …":"Fut...","S3 Compatible":"S3 kompatibilis","Same as the base install version: {{channelname}}":"Ugyanaz, mint az alap telepítési verzió: {{channelname}}","Sat":"Szo","Save":"Mentés","Save and repair":"Mentés és javítás","Save different versions with timestamp in file name":"Eltérő verziók mentése időbélyeggel a fájlnévben","Save immediately":"Mentés azonnal","Scanning existing files …":"Létező fájlok szkennelése...","Scanning for local blocks …":"Helyi blokkok szkennelése...","Schedule":"Időzítés","Search":"Keresés","Search for files":"Fájlok keresése","Seconds":"Másodperc","Select files":"Fájlok kiválasztása","Server":"Kiszolgáló","Server and port":"Kiszolgáló és port","Server hostname or IP":"Kiszolgáló gazdanév vagy IP","Server is currently paused,":"A kiszolgáló jelenleg szünetel.","Server is currently paused, do you want to resume now?":"A kiszolgáló jelenleg szünetel, szeretnéd folytatni?","Server password":"Szerver jelszó","Server paused":"Kiszolgáló szünetel","Server state properties":"Kiszolgáló állapot tulajdonságok","Settings":"Beállítások","Show":"Mutat","Show advanced editor":"Speciális szerkesztő megjelenítése","Show hidden folders":"Rejtett mappák megjelenítése","Show log":"Mutasd a naplót","Show log …":"Mutasd a naplót ...","Show treeview":"Fa nézet megjelenítése","Sia server password":"Sia szerver jelszó","Smart backup retention":"Intelligens mentés késleltetés","Source Data":"Forrás adat","Source Files":"Forrás fájlok","Source data":"Forrás adat","Source folders":"Forrás mappák","Source:":"Forrás:","Specific builds for developers only. Not for use with important data.":"Fejlesztőknek szánt kiadások. Fontos mentésére nem használható.","Standard protocols":"Szabványos protokollok","Start":"Start","Starting backup …":"Mentés indítása...","Starting restore …":"Visszaállítás indítása...","Starting the restore process …":"Visszaállítási folyamat indítása...","Stop after current file":"Leállítás az aktuális fájl után","Stop after the current file":"Leállítás az aktuális fájl után","Stop now":"Leállítás most","Stop running backup":"Mentés futtatásának leállítása","Stop running task":"Feladat futtatásának leállítása","Stopping after the current file:":"Leállítás az aktuális fájl után:","Stopping task:":"Feladat leállítása:","Storage Type":"Tárhely típus","Storage class":"Tároló osztály","Stored":"Tárolva","Strong":"Erős","Success":"Siker","Sun":"V","Symbolic link":"Szimbolikus link","System Files":"Rendszer fájlok","System default ({{levelname}})":"Rendszer alapértelmezés ({{levelname}})","System files":"Rendszer fájlok","System info":"Rendszer információ","System properties":"Rendszer tulajdonságok","TByte":"TByte","TByte/s":"TByete/s","Task is running":"A feladat fut","Temporary Files":"Ideiglenes fájlok","Temporary files":"Ideiglenes fájlok","Test Phase":"Teszt fázis","Test connection":"Kapcsolat tesztelése","Testing permissions …":"Engedélyek tesztelése...","Testing …":"Tesztelés...","The dark theme (by Michal)":"Sötét téma (by Michal)","The default blue on white theme (by Alex)":"Alapértelmezett kék-fehér téma (Alextől)","The folder {{folder}} does not exist.\nCreate it now?":"A mappa nem létezik: {{folder}} .\nLétrehozzam?","The passwords do not match":"A jelszavak nem egyeznek meg","The path does not appear to exist, do you want to add it anyway?":"Úgy tűnik, hogy a megadott útvonal nem létezik, mégis hozzá akarod adni?","This month":"Ez a hónap","This week":"Ez a hét","Throttle settings":"Sebességkorlátozás beállítások","Thu":"Cs","Time":"Idő","To File":"Fájlba","Today":"Ma","Trust host certificate?":"Megbízható a gazdagép tanúsítványa?","Trust server certificate?":"Megbízható kiszolgáló tanúsítványa?","Tue":"K","Type passphrase here.":"Írd ide a jelmondatot","Type to highlight files":"A fájlok kiemeléséhez gépeljen","Unknown backup size and versions":"Ismeretlen biztonsági mentés méret és verziók","Until resumed":"Folytatásig","Update channel":"Frissítési csatorna","Update failed:":"Frissítés sikertelen:","Updating with existing database":"Frissítés létező adatbázissal","Uploaded files":"Fájlok feltöltése","Uploading verification file …":"Ellenőrző fájl feltöltése...","Usage statistics":"Használati statisztikák","Usage statistics, warnings, errors, and crashes":"Használati statisztikák, figyelmeztetések, hibák és összeomlások","Use SSL":"SSL használata","Use existing database?":"Létező adatbázis használata?","Use weak passphrase":"Használja a gyenge jelmondatot","Useless":"Hasztalan","User data":"Felhasználói adat","User domain name":"Felhasználói domain név","User has too many permissions":"A felhasználónak túl sok engedélye van","User interface settings":"Felhasználói felület beállítások","Username":"Felhasználónév","Validating …":"Érvényesítés...","Verifications":"Ellenőrzések","Verify files":"Fájlok ellenőrzése","Verifying answer":"Válasz ellenőrzése","Verifying backend data …":"Háttér adat ellenőrzése...","Verifying files …":"Fájlok ellenőrzése...","Verifying remote data …":"Távoli adatok ellenőrzése...","Verifying restored files …":"Visszaállított fájlok ellenőrzése...","Verifying …":"Ellenőrzés...","Version ID":"Verzió ID","Very strong":"Nagyon erős","Very weak":"Nagyon gyenge","Visit us on":"Látogass meg minket itt","WARNING: This will prevent you from restoring the data in the future.":"FIGYELEM: Ez megakadályozza, hogy a jövőben helyreállítsd az adatokat.","Waiting for task to begin":"Várakozás a feladat elkezdésére","Waiting for upload to finish …":"Várakozás a feltöltés befejezésére...","Warnings, errors and crashes":"Figyelmeztetések, hibák és összeomlások","We recommend that you encrypt all backups stored outside your system":"Javasoljuk, hogy titkosítson minden, a rendszeren kívül tárolt biztonsági másolatot","Weak":"Hét","Weak passphrase":"Gyenge jelmondat","Wed":"Sze","Weeks":"Hét","Where do you want to restore from?":"Honnan szeretnél visszaállítani?","Where do you want to restore the files to?":"Hova szeretnéd visszaállítani a fájlokat?","Years":"Év","Yes":"Igen","Yes, I have stored the passphrase safely":"Igen, biztonságosan tárolom a jelmondatot","Yes, I understand the risk":"Igen, megértettem a kockázatot","Yes, I'm brave!":"Igen, bátor vagyok","Yes, please break my backup!":"Igen, kérlek tedd tönkre a mentésemet!","Yesterday":"Tegnap","You must fill in the password":"Ki kell töltened a jelszót","You must fill in the server name or address":"Ki kell töltened a szerver nevét vagy a címét","You must fill in the username":"Ki kell töltened a felhasználónevet","You must fill in {{field}}":"Ez ki kell töltened: {{field}}","You must specify a path":"Meg kell adnod egy útvonalat","Your files and folders have been restored successfully.":"A fájljaid és mappáid sikeresen vissza lettek állítva.","Your passphrase is easy to guess. Consider changing passphrase.":"A jelszavadat könnyű kitalálni. Érdemes lenne megváltoztatni.","byte":"byte","byte/s":"byte/s","custom":"egyéni","resume now":"folytatás most","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fájl ({{size}}) van még hátra {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzió","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzió"],"{{number}} Hour":"{{number}} óra","{{number}} Hours":"{{number}} óra","{{number}} Minutes":"{{number}} perc"}); - gettextCatalog.setStrings('it', {"(interrupted)":"(interrupted)","- pick an option -":"- seleziona un'opzione -","...loading...":"... caricamento in corso ...","API key":"Chiave API","AWS Access ID":"ID di accesso AWS","AWS Access Key":"Chiave di accesso AWS","AWS IAM Policy":"Norme AWS IAM","About":"Informazioni","About {{appname}}":"Informazioni {{appname}}","Access Key":"Chiave di accesso","Access Key Secret":"Chiave di accesso segreta","Access denied":"Accesso negato","Access grant":"Concessione accesso","Access to user interface":"Accesso all'interfaccia utente","Account name":"Nome account","Add a new backup":"Aggiungi un nuovo backup","Add a path directly":"Aggiungi direttamente un percorso","Add advanced option":"Aggiungi opzione","Add backup":"Aggiungi backup","Add filter":"Aggiungi filtro","Add path":"Aggiungi percorso","Added":"Aggiunto","Adjust bucket name?":"Sistemare il nome bucket?","Advanced Options":"Opzioni Avanzate","Advanced options":"Opzioni avanzate","Advanced:":"Avanzate:","Aliyun OSS Endpoint":"Endpoint Aliyun OSS","All Hyper-V Machines":"Tutte le Macchine Hyper-V","All Microsoft SQL Databases":"Tutti i database Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tutti i rapporti sono inviati in modo anonimo e non contengono informazioni personali. Contengono informazioni sull'hardware, sul sistema operativo, il tipo di backend, la durata del backup, la dimensione complessiva dei dati sorgente ed dati simili. Non contengono i percorsi, nomi dei file, nomi utente, password o altre informazioni sensibili.","Allow remote access (requires restart)":"Consenti accesso remoto (richiede il riavvio)","Allowed days":"Giorni consentiti","An existing file was found at the new location":"Un file esistente è stato trovato nella nuova posizione","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un file esistente è stato trovato nella nuova posizione.\nSei sicuro di volere che il database punti ad un file esistente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Un database locale esistente per l'archiviazione è stato trovato.\nIl riutilizzo del database consentirà alle istanze da riga di comando e dal server di lavorare sullo stesso archivio remoto.\n\nVuoi usare il database esistente?","Anonymous usage reports":"Rapporti d'uso anonimi","Applications":"Applicazioni","As Command-line":"Come riga di comando","AuthID":"AuthID","Authentication method":"Metodo di autenticazione","Authentication method ({{auth_method}})":"Metodo di autenticazione ({{auth_method}})","Authentication password":"Password di autenticazione","Authentication username":"Nome utente di autenticazione","Autogenerated passphrase":"Genera automaticamente passphrase","B2 Application ID":"ID applicazione B2","B2 Application Key":"Chiave Applicazione B2","B2 Cloud Storage Account ID":"ID Account Cloud B2 Storage","B2 Cloud Storage Application ID":"ID applicazione di archiviazione cloud B2","B2 Cloud Storage Application Key":"Chiave applicazione Archiviazione Cloud B2","Back":"Indietro","Backup complete!":"Backup completo!","Backup destination":"Destinazione backup","Backup location":"Posizione Backup","Backup retention":"Conservazione backup","Backup:":"Dimensione backup:","Beta":"Beta","Broken access":"Accesso non riuscito","Browse":"Browse","Browser default":"Browser predefinito","Bucket create location":"Crea posizione bucket","Bucket name":"Nome bucket","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Il nome del bucket può avere una lunghezza compresa tra 3 e 63 caratteri e contenere solo caratteri minuscoli, numeri, punti e trattini","Bucket region":"Regione bucket","Bucket region ap-guangzhou":"Regione bucket ap-guangzhou","Bucket storage class":"Classe bucket","Bucket, format: BucketName-APPID":"Bucket, formato: BucketName-APPID","Building list of files to restore …":"Creazione di un elenco di file da ripristinare ...","Building partial temporary database …":"Creazione di un database temporaneo parziale ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Consentendo l'accesso remoto, il server ascolta le richieste da qualsiasi computer sulla rete. Se abiliti questa opzione, assicurati di utilizzare sempre il computer su una rete sicura protetta da un firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Per impostazione predefinita, l'icona nella barra delle applicazioni aprirà l'interfaccia utente con un token che sblocca l'interfaccia utente. Ciò garantisce che sia possibile accedere all'interfaccia utente dall'icona nella barra delle applicazioni, mentre si richiede agli altri di inserire una password. Se si preferisce digitare la password, anche quando si accede all'interfaccia utente dall'icona nella barra delle applicazioni, abilitare questa opzione.","COS Path or subfolder in the bucket":"Percorso COS o sottocartella nel bucket","COS Secret Key":"Chiave segreta COS","Cache Files":"File Cache","Canary":"Canary","Cancel":"Annulla","Cannot include \"{{text}}\"":"Non può includere \"{{text}}\"","Cannot move to existing file":"Non puoi spostare in un file esistente","Cannot specify filter include or excludes in extra options":"Non è possibile specificare i filtri include o esclude nelle opzioni extra","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog di {{appname}} {{version}}","Check failed:":"Controllo fallito:","Check for updates now":"Controlla aggiornamenti ora","Checking for updates …":"Verifica aggiornamenti …","Chose a storage type to get started":"Scegliere un tipo di archiviazione per iniziare","Click the AuthID link to create an AuthID":"Clicca sul link AuthID per creare un nuovo AuthID","Click to set throttle options":"Clicca per impostare le opzioni di limitazione","Client library to use":"Libreria client da utilizzare","Cloud API Secret Key":"Chiave segreta API Cloud","Commandline …":"Riga di comando …","Compact Phase":"Fase Compattazione","Compact now":"Comprimi","Compacting remote data …":"Compattazione dei dati remoti ...","Complete log":"Registro completo","Completing backup …":"Completamento del backup ...","Completing previous backup …":"Completamento del backup precedente ...","Computer":"Computer","Configuration file:":"File di configurazione:","Configuration:":"Configurazione: ","Configure a new backup":"Configura un nuovo backup","Confirm delete":"Conferma cancellazione","Confirm encryption passphrase":"Conferma passphrase crittografia","Confirm passphrase":"Conferma passphrase","Confirmation required":"Conferma richiesta","Connect":"Connetti","Connect now":"Connetti ora","Connecting to server …":"Connessione al server …","Connection lost":"Connessione persa","Connection worked!":"Connessione funzionante!","Container name":"Nome contenitore","Container region":"Area contenitore","Continue":"Continua","Continue without encryption":"Continua senza crittografia","Copied!":"Copiato!","Copy":"Copia","Copy Destination URL to Clipboard":"Copia URL Destinazione negli Appunti","Copy failed. Please manually copy the URL":"Copia non riuscita. Per favore copia manualmente l'URL","Copy log":"Copia registro","Core options":"Opzioni base","Counting ({{files}} files found, {{size}})":"Conteggio ({{files}} file trovati, {{size}})","Crashes only":"Solo arresti anomali","Create bug report …":"Crea segnalazione bug ...","Create folder?":"Creare cartella?","Created new limited user":"Creato nuovo utente limitato","Creating bug report …":"Creazione segnalazione bug ...","Creating new user with limited access …":"Creazione di un nuovo utente con accesso limitato ...","Creating target folders …":"Creazione di cartelle di destinazione ...","Creating temporary backup …":"Creazione backup temporaneo ...","Current action:":"Azione corrente:","Current file:":"File corrente:","Current version is {{versionname}} ({{versionnumber}})":"La versione attuale è {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Endpoint S3 personalizzato","Custom Satellite":"Satellite personalizzato","Custom Satellite ({{satellite}})":"Satellite personalizzato ({{satellite}})","Custom authentication url":"URL di autenticazione personalizzato","Custom backup retention":"Conservazione backup personalizzato","Custom bucket storage class":"Classe archiviazione bucket personalizzata","Custom location ({{server}})":"Posizione personalizzata ({{server}})","Custom region for creating buckets":"Area personalizzata per la creazione bucket","Custom region value ({{region}})":"Valore area personalizzata ({{region}})","Custom server url ({{server}})":"URL del server personalizzato ({{server}})","Custom storage class ({{class}})":"Classe di archiviazione personalizzata ({{class}})","Database …":"Banca dati …","Days":"Giorni","Default":"Predefinito","Default ({{channelname}})":"Predefinito ({{channelname}})","Default excludes":"Esclusioni predefinite","Default options":"Opzioni predefinite","Delete":"Cancella","Delete Phase (Old Backup Versions)":"Fase Cancellazione (Vecchie versioni di backup)","Delete backup":"Cancella backup","Delete backups that are older than":"Elimina i backup più vecchi di","Delete local database":"Cancella database locale","Delete remote files":"Cancella file remoti","Delete the local database":"Cancella il database locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Cancella {{filecount}} file ({{filesize}}) dall'archivio remoto?","Delete …":"Elimina …","Deleted":"Cancellato","Deleted Versions":"Versioni Cancellate","Deleted files":"File cancellati","Deleting remote files …":"Eliminazione di file remoti ...","Deleting unwanted files …":"Eliminazione di file indesiderati ...","Description (optional)":"Descrizione (facoltativa)","Description:":"Descrizione:","Desktop":"Desktop","Destination":"Destinazione","Destination path":"Percorso destinazione","Disabled":"Disattivato","Dismiss":"Annulla","Dismiss all":"Ignora tutto","Display and color theme":"Tema interfaccia","Do you really want to delete the backup: \"{{name}}\" ?":"Vuoi veramente cancellare il backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Vuoi veramente cancellare il database locale per: {{name}} ?","Done":"Fatto","Download":"Scarica","Downloaded files":"File scaricati","Downloading files …":"Download di file...","Downloading update…":"Download dell'aggiornamento...","Duplicate option {{opt}}":"Opzione duplicata {{opt}}","Duplicati Website":"Sito web di Duplicati","Duplicati forum":"Forum Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati sarà eseguito all'avvio, ma rimarrà in pausa per la durata. Duplicati occuperà risorse di sistema minime e non saranno eseguiti backup.","Duration":"Durata","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Ogni backup dispone di un database locale associato, che archivia le informazioni del backup remoto sul computer locale.\nQuando si cancella un backup, è anche possibile cancellare il database locale senza influire sulla possibilità di ripristinare i file remoti.\nSe si utilizza il database locale per i backup dalla riga di comando, è necessario mantenere il database.","Edit as list":"Modifica come elenco","Edit as text":"Modifica come testo","Edit …":"Modifica …","Encrypt file":"Cripta file","Encryption":"Crittografia","Encryption changed":"Crittografia cambiata","Encryption passphrase":"Passphrase di crittografia","End":"Fine","Enter URL":"Inserisci URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Inserisci una strategia di conservazione manualmente. I segnaposto sono D/W/Y per giorni/settimane/anni e U per illimitato. La sintassi è: 7D:1D,4W:1W,36M:1M. Questo esempio mantiene un backup per ciascuno dei prossimi 7 giorni, uno per ciascuna delle prossime 4 settimane e uno per ciascuno dei 36 mesi successivi. Questo può anche essere scritto come 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Inserisci la passphrase del backup, se presente","Enter configuration details":"Inserisci dettagli configurazione","Enter encryption passphrase":"Inserisci passphrase crittografia","Enter expression here":"Inserisci qui espressione","Enter one argument per line without quotes, e.g. *.txt":"Inserisci un argomento per riga senza virgolette, ad es. *.TXT","Enter the destination path":"Inserisci percorso destinazione","Error":"Errore","Error!":"Errore!","Errors and crashes":"Errori e arresti anomali","Examined":"Esaminato","Exclude":"Escludi","Exclude directories whose names contain":"Escludi cartelle il cui nome contiene","Exclude expression":"Escludi espressione","Exclude file":"Escludi file","Exclude file extension":"Escludi estensione del file","Exclude files whose names contain":"Escludi file il cui nome contiene","Exclude filter group":"Escludi gruppo filtri","Exclude folder":"Escludi cartella","Exclude regular expression":"Escludi espressione regolare","Existing file found":"Trovato file esistente","Experimental":"Sperimentale","Export":"Esporta","Export backup configuration":"Esporta configurazione backup","Export configuration":"Esporta configurazione","Export passwords":"Esporta le password","Export …":"Esporta …","Exporting …":"Esportazione in corso ...","External link":"Link esterno","FTP (Alternative)":"FTP (Alternativo)","Failed to build temporary database: {{message}}":"Fallita creazione del database temporaneo: {{message}}","Failed to connect:":"Connessione fallita:","Failed to connect: {{message}}":"Connessione fallita: {{message}}","Failed to delete:":"Cancellazione fallita: ","Failed to fetch path information: {{message}}":"Recupero informazioni sul percorso fallito: {{message}}","Failed to find backup:":"Impossibile trovare il backup:","Failed to read backup defaults:":"Lettura impostazioni predefinite backup fallita:","Failed to restore files: {{message}}":"Ripristino dei file fallito: {{message}}","Failed to save:":"Salvataggio fallito:","Fatal error, no statistics collected":"Errore fatale, nessuna statistica raccolta","Fetching path information …":"Recupero delle informazioni sul percorso ...","File":"File","Files larger than:":"File più grandi di:","Filters":"Filtri","Finished!":"Finito!","First run setup":"Impostazione prima esecuzione","Folder":"Cartella","Folder path":"Percorso cartella","Fri":"Ven","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID Progetto GCS","General":"Generale","General backup settings":"Impostazioni generali backup","General options":"Opzioni generali","Generate":"Genera","Generate IAM access policy":"Genera criteri di accesso IAM","Getting file versions …":"Ottenere versioni di file ...","Group email":"Email gruppo","Hidden files":"File nascosti","Hide":"Nascondi","Hide hidden folders":"Nascondi cartelle nascoste","Home":"Home","Hostnames":"Nomi host","Hours":"Ore","How do you want to handle existing files?":"Come vuoi gestire i file esistenti?","Hyper-V Machine":"Sitema Hyper-V","Hyper-V Machine:":"Sistema Hyper-V:","Hyper-V Machines":"Sistemi Hyper-V","ID:":"ID:","IDrive Sync directory path":"Percorso cartella di sincronizzazione di IDrive","If a date was missed, the job will run as soon as possible.":"Se una pianificazione non è eseguita, il backup sarà effettuato il prima possibile.","If at least one newer backup is found, all backups older than this date are deleted.":"Se si trova almeno un backup più recente, tutti i backup precedenti a questa data sono eliminati.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se non inserisci un percorso, tutti i file saranno salvati nella cartella di accesso.\nSei sicuro che questo è quello che vuoi?","If you do not enter an API Key, the tenant name is required":"Se non inserisci una Chiave API, è richiesto il nome del detentore","Import":"Importa","Import Destination URL":"Importa URL Destinazione","Import backup configuration":"Importa configurazione backup","Import from a file":"Importa da un file","Import metadata":"Importa metadati","Importing …":"Importazione ...","Include a file?":"Includi un file?","Include expression":"Includi espressione","Include regular expression":"Includi espressione regolare","Incorrect answer, try again":"Risposta errata, riprova","Individual builds for developers only. Not for use with important data.":"Build individuali per soli sviluppatori. Non utilizzare con dati importanti.","Information":"Informazioni","Interrupted, no statistics collected":"Interrotto, nessuna statistica raccolta","Invalid characters in path":"Caratteri non validi nel percorso","Invalid retention time":"Tempo ritenzione non valido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"È possibile connettersi ad alcuni FTP senza una password.\nSei sicuro che il tuo server FTP supporta gli accessi senza password?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Mantieni un numero specifico di backup","Keep all backups":"Mantieni tutti i backup","Keystone API version":"Versione API Keystone","Language in user interface":"Lingua interfaccia utente","Last month":"Lo scorso mese","Last successful backup:":"Ultimo backup riuscito:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Ultimo ripristino riuscito: {{time}} (took {{duration || '0 seconds'}})","Latest":"Più recente","Libraries":"Librerie","Listing backup dates …":"Elenco date di backup ...","Listing remote files for purge …":"Elenco dei file remoti per l'eliminazione ...","Listing remote files …":"Elenco dei file remoti ...","Live":"In tempo reale","Load a configuration from an exported job or a storage provider":"Carica una configurazione da un lavoro esportato o da un provider di archiviazione","Load destination from an exported job or a storage provider":"Carica una destinazione da un lavoro esportato o da un provider di archiviazione","Load older data":"Carica dati precedenti","Loading …":"Caricamento in corso …","Local Repository":"Repository locale","Local database path:":"Percorso database locale:","Local repository":"Repository locale","Local storage":"Archivio locale","Location":"Posizione","Location where buckets are created":"Posizione in cui sono creati i bucket","Log data for {{Backup.Backup.Name}}":"Dati di log per {{Backup.Backup.Name}}","Log data from the server":"Dati di log dal server","Log out":"Log out","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Manutenzione","Manual":"Manuale","Manual update found:":"Aggiornamento manuale trovato:","Manually type path":"Digita manualmente il percorso","Max download speed":"Velocità massima per scaricare","Max upload speed":"Velocità massima per caricare","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Database","Minimum redundancy":"Ridondanza minima","Minimum redundancy is 1.0":"Ridondanza minima è 1.0","Minutes":"Minuti","Missing name":"Nome mancante","Missing passphrase":"Passphrase mancante","Missing sources":"Sorgente mancante","Modified":"Modificato","Mon":"Lun","Months":"Mesi","Move existing database":"Sposta database esistente","Move failed:":"Spostamento fallito:","My Documents":"Documenti","My Music":"Musica","My Photos":"Foto","My Pictures":"Immagini","Name":"Nome","Never":"Mai","New update found: {{message}}":"Nuovo aggiornamento trovato: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Il nuovo nome utente è {{user}}.\nCredenziali aggiornate per utilizzare il nuovo utente limitato","Next":"Avanti","Next scheduled run:":"Prossima esecuzione: ","Next scheduled task:":"Prossima attività pianificata:","Next task:":"Prossima attività:","Next time":"Prossima volta","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nessun certificato è stato specificato in precedenza, per favore verifica con l'amministratore del server che la chiave è corretta: {{key}}\n\nVuoi approvare la chiave host riportata?","No editor found for the "{{backend}}" storage type":"Nessun editor trovato per il "{{backend}}" tipo archivio","No encryption":"Nessuna crittografia","No items selected":"Nessun elemento selezionato","No items to restore, please select one or more items":"Nessun elemento da ripristinare, seleziona uno o più elementi","No passphrase entered":"Nessuna passphrase inserita","No scheduled tasks":"Nessuna attività pianificata","Non-matching passphrase":"Passphrase non corrispondente","None / disabled":"Nessuno / disattivato","Not using encryption":"Non usare la crittografia","Nothing will be deleted. The backup size will grow with each change.":"Niente sarà eliminato. La dimensione del backup crescerà con ogni cambiamento.","OK":"OK","OSS Access Key Secret":"Chiave di accesso segreta OSS","OSS Bucket Region":"Regione del bucket OSS","OSS Endpoint":"Endpoint OSS","OSS Path or subfolder in the bucket":"Percorso OSS o sottocartella nel bucket","OSS Region":"Regione dell'OSS","Once there are more backups than the specified number, the oldest backups are deleted.":"Una volta che ci sono più backup del numero specificato, i backup più vecchi sono cancellati.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aperto","Operating System":"Sistema Operativo","Operation":"Operazione","Operations:":"Operazioni:","Optional authentication password":"Password opzionale per l'autenticazione","Optional authentication username":"Nome utente opzionale per l'autenticazione","Optional region":"Regione opzionale","Optional tenant name":"Nome detentore facoltativo","Options":"Opzioni","Original location":"Percorso originale","Others":"Altri","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Nel corso del tempo i backup saranno eliminati automaticamente. Rimarrà un backup per ciascuno degli ultimi 7 giorni, ognuna delle ultime 4 settimane, ciascuno degli ultimi 12 mesi. Ci sarà sempre almeno un backup rimanente.","Overwrite":"Sovrascrivi","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (se criptato)","Passphrase changed":"Passphrase modificata","Passphrases are not matching":"Passphrase non corrispondenti","Passphrases do not match":"Le passphrase non corrispondono","Password":"Password","Patching files with local blocks …":"Patch di file con blocchi locali ...","Path":"Percorso","Path not found":"Percorso non trovato","Path on server":"Percorso sul server","Path or subfolder in the bucket":"Percorso o sottocartella bucket","Pause":"Pausa","Pause after startup or hibernation":"Pausa dopo avvio o ibernazione","Pause options":"Opzioni pausa","Permissions":"Autorizzazioni","Pick location":"Scegli posizione","Point to your backup files and restore from there":"Puntare ai file di backup e ripristinare da lì","Port":"Porta","Prevent tray icon automatic log-in":"Previeni il log-in automatico dell'icona nella barra delle applicazioni","Previous":"Precedente","Progress:":"Avanzamento:","ProjectID is optional if the bucket exist":"ID Progetto è opzionale se esiste un bucket","Proprietary":"Proprietario","Purge Phase":"Fase eliminazione","Purging files complete!":"Eliminazione dei file completata!","Purging files …":"Eliminazione dei file ...","Rebuilding local database …":"Ricostruzione del database locale ...","Recreate (delete and repair)":"Ricrea (cancella e ripara)","Recreate Database Phase":"Fase ricreazione database","Recreating database …":"Ricreazione del database ...","Region":"Regione","Registering temporary backup …":"Registrazione backup temporaneo ...","Relative paths not allowed":"Percorsi relativi non consentiti","Reload":"Ricarica","Remote":"Remoto","Remote Path":"Percorso remoto","Remote Repository":"Repository remoto","Remote path":"Percorso remoto","Remote repository":"Repository remoto","Remote volume size":"Dimensione volume remoto","Remove":"Rimuovi","Remove option":"Rimuovi opzione","Removed files":"File rimossi","Repair":"Ripara","Repair Phase":"Fase riparazione","Repairing database …":"Ripristino del database ...","Repeat Passphrase":"Ripeti Passphrase","Reporting:":"Segnalazione:","Reset":"Reset","Restore":"Ripristina","Restore complete!":"Ripristino completato!","Restore files":"Ripristina file","Restore files from:":"Ripristina file da:","Restore files …":"Ripristina file ...","Restore from":"Ripristina da","Restore from backup configuration":"Ripristino dalla configurazione backup","Restore options":"Opzioni ripristino","Restore read/write permissions":"Ripristina autorizzazioni lettura/scrittura","Restored Files":"File ripristinati","Restored Folders":"Cartelle ripristinate","Restored Symlinks":"Symlink ripristinati","Restoring files …":"Ripristino di file ...","Resume":"Riprendi","Rewritten File Lists":"Elenchi file riscritti","Run again every":"Esegui ogni","Run now":"Esegui ora","Running commandline entry":"Riga di comando in esecuzione","Running task:":"Attività in esecuzione:","Running …":"In esecuzione …","S3 Compatible":"Compatibile S3","Same as the base install version: {{channelname}}":"Come la versione di base installata: {{channelname}}","Sat":"Sab","Satellite":"Satellitare","Save":"Salva","Save and repair":"Salva e ripara","Save different versions with timestamp in file name":"Salva versioni diverse con timestamp nel nome del file","Save immediately":"Salva immediatamente","Scanning existing files …":"Scansione di file esistenti ...","Scanning for local blocks …":"Scansione per blocchi locali ...","Schedule":"Pianificazione","Search":"Cerca","Search for files":"Cerca per file","Seconds":"Secondi","Select a log level and see messages as they happen:":"Selezionare un livello di log e visiona i messaggi che avvengono:","Select files":"Seleziona file","Server":"Server","Server and port":"Server e porta","Server hostname or IP":"Nome host o IP del server","Server is currently paused,":"Server è attualmente in pausa,","Server is currently paused, do you want to resume now?":"Server attualmente in pausa, vuoi riprendere ora?","Server password":"Password del server","Server paused":"Server in pausa","Server state properties":"Proprietà stato del server","Settings":"Impostazioni","Show":"Mostra","Show advanced editor":"Mostra editor avanzato","Show hidden folders":"Mostra cartelle nascoste","Show log":"Mostra log","Show log …":"Mostra registro …","Show treeview":"Visualizza ad albero","Sia server password":"Password del server Sia","Smart backup retention":"Conservazione intelligente backup","Some OpenStack providers allow an API key instead of a password and tenant name":"Alcuni provider OpenStack consentono una chiave API anziché una password e un nome detentore","Some S3 providers might only be compatible with a certain client library":"Alcuni provider S3 potrebbero essere compatibili solo con una determinata libreria client","Source Data":"Dati Sorgente","Source Files":"Sorgente File","Source data":"Dati sorgente","Source folders":"Cartella sorgente","Source:":"Dimensione sorgente:","Specific builds for developers only. Not for use with important data.":"Build specifiche per soli sviluppatori. Non utilizzare con dati importanti.","Standard protocols":"Protocolli standard","Start":"Avvio","Starting backup …":"Avvio backup ...","Starting restore …":"Avvio ripristino ...","Starting the restore process …":"Avvio del processo di ripristino ...","Stop after current file":"Stop dopo il file corrente","Stop after the current file":"Ferma dopo il file corrente","Stop now":"Ferma adesso","Stop running backup":"Ferma esecuzione backup","Stop running task":"Ferma esecuzione attività","Stopping after the current file:":"Arresto dopo il file corrente:","Stopping task:":"Ferma attività:","Storage Type":"Tipo archivio","Storage class":"Classe archivio","Storage class for creating a bucket":"Classe di archiviazione per la creazione di un bucket","Stored":"Archiviati","Strong":"Forte","Success":"Successo","Sun":"Dom","Symbolic link":"Link simbolico","System Files":"File di Sistema","System default ({{levelname}})":"Sistema predefinito ({{levelname}})","System files":"File di sistema","System info":"Informazioni di sistema","System properties":"Proprietà di sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Attività in esecuzione","Temporary Files":"File Temporanei","Temporary files":"File temporanei","Test Phase":"Fase test","Test connection":"Prova connessione","Testing permissions …":"Test delle autorizzazioni ...","Testing …":"Test in corso...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Il campo '{{fieldname}}' contiene un carattere non valido: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Il backup è mancante, è stato cancellato?","The backup was temporary and does not exist anymore, so the log data is lost":"Il backup era temporaneo e non esiste più, quindi i dati del registro sono persi","The bucket name should be all lower-case, convert automatically?":"Il nome del bucket dovrebbe essere tutto minuscolo, convertirlo automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configurazione dovrebbe essere mantenuta al sicuro. Sei sicuro di voler salvare un file non criptato contenente le tue password?","The dark theme (by Michal)":"Tema scuro (da Michal)","The default blue on white theme (by Alex)":"Predefinito - Tema blu su bianco (da Alex)","The encryption passphrases do not match":"Le passphrase di crittografia non corrispondono","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"La dimensione del file è {{size}}, superiore alla dimensione massima specificata. Se la dimensione del file diminuisce, sarà inclusa nei backup futuri.","The folder {{folder}} does not exist.\nCreate it now?":"La cartella {{folder}} non esiste. \nCreala adesso?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La chiave host è cambiata, per favore consulta l'amministratore del server se questa è corretta, altrimenti potresti essere la vittima di un attacco UOMO-NEL-MEZZO.\n\nVuoi SOSTITUIRE la chiave host CORRENTE \"{{prev}}\" con la chiave host SEGNALATA: {{key}}?","The passwords do not match":"Le password non corrispondono","The path does not appear to exist, do you want to add it anyway?":"Il percorso sembra non esistere, vuoi aggiungerlo comunque?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Il percorso non termina con un carattere '{{dirsep}}', il che significa che si include un file, non una cartella.\n\nVuoi includere il file specificato?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Il percorso deve essere un percorso assoluto, cioè deve iniziare con una barra '/'","The region parameter is only applied when creating a new bucket":"Il parametro area è applicato solo quando si crea un nuovo bucket","The region parameter is only used when creating a bucket":"Il parametro area è utilizzato solo quando si crea un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Il certificato del server non può essere convalidato.\n\nVuoi approvare il certificato SSL con l'hash: {{hash}}?","The storage class affects the availability and price for a stored file":"La classe di archiviazione influisce sulla disponibilità e sul prezzo per un file archiviato","The target folder contains encrypted files, please supply the passphrase":"La cartella di destinazione contiene file criptati, per favore fornisci la passphrase","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utente dispone di troppe autorizzazioni. Vuoi creare un nuovo utente limitato, con solo autorizzazioni per il percorso selezionato?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Questo backup è stato creato su un altro sistema operativo. Il ripristino dei file senza specificare una cartella di destinazione può causare il ripristino di file in luoghi imprevisti. Sei sicuro di voler continuare senza scegliere una cartella di destinazione?","This month":"Questo mese","This week":"Questa settimana","Throttle settings":"Impostazioni limitazione","Thu":"Mar","Time":"Tempo","To File":"Al File","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Per confermare che vuoi cancellare tutti i file remoti che contengono \"{{name}}\", digita la parla che vedi di seguito","To export without a passphrase, uncheck the \"Encrypt file\" box":"Per esportare senza una passphrase, deselezionare la casella \"Cripta file\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Per prevenire vari attacchi basati su DNS, Duplicati limita gli hostname consentiti a quelli qui elencati. L'accesso IP e localhost diretti sono sempre consentiti. Più nomi host possono essere forniti con un separatore di punto e virgola. Se uno qualsiasi dei nomi host consentiti è un asterisco (*), tutti i nomi host sono consentiti e questa funzione è disabilitata. Se il campo è vuoto, sono consentiti solo gli accessi dall'indirizzo IP e localhost.","Today":"Oggi","Trust host certificate?":"Certificato host affidabile?","Trust server certificate?":"Certificato server affidabile?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Prova le nuove funzionalità su cui stiamo lavorando. Attualmente la versione più stabile disponibile. Prova il Ripristino dati prima di utilizzarla negli ambienti di produzione.","Tue":"Gio","Type passphrase here.":"Scrivi la passphrase qui.","Type to highlight files":"Digitare per evidenziare i file","Unknown backup size and versions":"Dimensione e versione backup sconosciute","Until resumed":"Finché non riprende","Update channel":"Canale di aggiornamento","Update failed:":"Aggiornamento fallito:","Updating with existing database":"Aggiornamento con database esistente","Uploaded files":"File caricati","Uploading verification file …":"Caricamento file di verifica ...","Usage statistics":"Statistiche di utilizzo","Usage statistics, warnings, errors, and crashes":"Statistiche di utilizzo, avvisi, errori e arresti anomali","Use SSL":"Usa SSL","Use existing database?":"Usare database esistente?","Use weak passphrase":"Usa passphrase debole","Useless":"Inutile","User data":"Dati utente","User domain name":"Nome dominio utente","User has too many permissions":"L'utente ha troppe autorizzazioni","User interface settings":"Impostazioni interfaccia utente","Username":"Nome utente","Vacuuming database …":"Prelevamento database ...","Validating …":"Convalida in corso ...","Verifications":"Verifiche","Verify encryption passphrase":"Verifica la passphrase di crittografia","Verify files":"Verifica file","Verifying answer":"Verifica risposta","Verifying backend data …":"Verifica dei dati di backend ...","Verifying files …":"Verifica dei file ...","Verifying remote data …":"Verifica dei dati remoti ...","Verifying restored files …":"Verifica dei file ripristinati ...","Verifying …":"Verifica in corso ...","Version ID":"Versione ID","Very strong":"Molto forte","Very weak":"Molto debole","Visit us on":"Seguici su","WARNING: This will prevent you from restoring the data in the future.":"ATTENZIONE: Questo ti impedirà di ripristinare i dati in futuro.","Waiting for task to begin":"In attesa dell'attività per iniziare","Waiting for upload to finish …":"In attesa del completamento del caricamento ...","Warnings, errors and crashes":"Avvisi, errori e arresti anomali","We recommend that you encrypt all backups stored outside your system":"Ti consigliamo di criptare tutti i backup archiviati al di fuori del tuo sistema","Weak":"Debole","Weak passphrase":"Passphrase debole","Wed":"Mer","Weeks":"Settimane","Where do you want to restore from?":"Da dove vuoi ripristinare?","Where do you want to restore the files to?":"Dove vuoi ripristinare i files?","Years":"Anni","Yes":"Si","Yes, I have stored the passphrase safely":"Si, ho archiviato la passphrase in modo sicuro","Yes, I understand the risk":"Sì, capisco il rischio","Yes, I'm brave!":"Sì, sono coraggioso!","Yes, please break my backup!":"Sì, per favore rompi il mio backup!","Yesterday":"Ieri","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Stai cambiando il percorso di un database esistente.\nSei sicuro che questo è ciò che vuoi?","You are currently running {{appname}} {{version}}":"Attualmente stai eseguendo {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"È possibile interrompere il backup al termine di eventuali caricamenti di file attualmente in corso.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"È possibile interrompere l'operazione immediatamente, o consentire il processo di continuare il suo file corrente e poi fermarsi.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Hai modificato l'algoritmo di crittografia. Questa azione potrebbe corrompere i dati. Ti consigliamo di creare un nuovo backup.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Hai modificato la passphrase ma questo non è supportato. Ti consigliamo di creare un nuovo backup.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Hai scelto di non criptare il backup. È consigliabile criptare tutti i dati custoditi su server remoti.","You have chosen to restore to a new location, but not entered one":"Si è scelto di ripristinare in una nuova posizione, ma non ne è stata inserita una","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Hai generato una passphrase forte. Assicurati di aver fatto una copia sicura della passphrase, poiché i dati non possono essere recuperati se perdi la passphrase.","You must choose at least one source folder":"Devi scegliere almeno una cartella sorgente","You must enter a domain name to use v3 API":"Devi inserire un nome di dominio per utilizzare l'API v3","You must enter a name for the backup":"Devi inserire un nome per il backup","You must enter a passphrase or disable encryption":"Devi inserire una passphrase o disattivare la crittografia","You must enter a password to use v3 API":"Devi inserire una password per utilizzare l'API v3","You must enter a positive number of backups to keep":"Devi inserire un numero positivo di backup da mantenere","You must enter a tenant (aka project) name to use v3 API":"Devi inserire un detentore (aka progetto) per utilizzare l'API v3","You must enter a valid duration for the time to keep backups":"Devi inserire un periodo di tempo valido in cui mantenere i backup","You must enter a valid retention policy string":"Devi inserire una stringa di criteri di conservazione valida","You must fill in the password":"Devi compilare in password","You must fill in the server name or address":"Devi compilare in nome del server o indirizzo","You must fill in the username":"Devi compilare in nome utente","You must fill in {{field}}":"Devi compilare in {{field}}","You must select or fill in the AuthURI":"Devi selezionare o compilare in AuthURI","You must select or fill in the server":"Devi selezionare o compilare in server","You must specify a path":"Devi specificare un percorso","Your files and folders have been restored successfully.":"I tuoi file e cartelle sono stati ripristinati correttamente.","Your passphrase is easy to guess. Consider changing passphrase.":"La tua passphrase è facile da indovinare. Considera l'idea di cambiarla.","bucket/folder/subfolder":"bucket/cartella/sottocartella","byte":"byte","byte/s":"byte/s","custom":"Personalizzato","failed":"fallito","resume now":"riprendi ora","unless you are explicitly specifying --group-id":"a meno che tu non stia specificando esplicitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} è stato sviluppato principalmente da {{dev1}} e {{dev2}}. {{appname}} può essere scaricato da {{websitename}}. {{appname}} è sotto la licenza {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"Caricamento di {{files}} file ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versione","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versioni","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versioni"],"{{number}} Hour":"{{number}} Ore","{{number}} Hours":"{{number}} Ore","{{number}} Minutes":"{{number}} Minuti","{{time}} (took {{duration}})":"{{time}} (durata {{duration}})"}); - gettextCatalog.setStrings('ja_JP', {"(1 error{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":"({{$count}}件のエラー{{item.Result.Interrupted? ('、中断されました'|translate) : ''}})","(1 warning{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":"({{$count}}件の警告{{item.Result.Interrupted? ('、中断されました'|translate) : ''}})","(interrupted)":"(中断されました)","- pick an option -":"- オプションを選択してください -","...loading...":"…読み込んでいます…","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"注意:ホストに接続している間、Siaは後から冗長性を増加させます。"," Edit as text":" テキストで編集"," Edit as text":" テキストで編集","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

不正認証のためサーバーへの接続は拒否されました。

\n

再度ログインするか、トレイのアイコンからページを再度開いてください(該当する場合)。

","API key":"APIキー","AWS Access ID":"AWSのアクセスID","AWS Access Key":"AWSのアクセスキー","AWS IAM Policy":"AWSのIAMポリシー","About":"概要","About {{appname}}":"{{appname}}について","Access Key":"アクセスキー","Access Key ID":"アクセスキーのID","Access Key Secret":"アクセスキーのシークレット","Access denied":"アクセスが拒否されました","Access grant":"アクセス権","Access key":"アクセスキー","Access to user interface":"ユーザーインターフェースへのアクセス","Account name":"アカウント名","Add a new backup":"新しいバックアップを作成","Add a path directly":"パスディレクトリを追加","Add advanced option":"高度な設定を追加","Add backup":"バックアップを追加","Add filter":"フィルターを追加","Add path":"パスを追加","Added":"追加済","Adjust bucket name?":"バケットの名称を変更しますか?","Advanced Options":"高度な設定","Advanced options":"高度な設定","Advanced:":"高度:","Aliyun OSS Endpoint":"Aliyun OSSのエンドポイント","Aliyun OSS documents and resources":"Aliyun OSSのドキュメントと参考資料","All Hyper-V Machines":"全てのHyper-Vマシン","All Microsoft SQL Databases":"全てのMicrosoft SQLデータベース","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"使用状況に関する報告は全て匿名で送信され、個人情報を含みません。報告には、ハードウェア、OS、バックエンドの種類、バックアップの保持期間、バックアップ元のデータなどの全体のサイズに関するデータが含まれます。パス、ファイル名、ユーザー名、パスワードなどの機密情報は含まれません。","Allow remote access (requires restart)":"リモートアクセスを許可(要再起動)","Allowed days":"実行を許可する日","An existing file was found at the new location":"既存のファイルが新しい場所で見つかりました","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"既存のファイルが新しい場所で見つかりました。\nデータベースを既存のファイルに指定してよろしいですか?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"保存領域のデータベースがローカルに存在しています。データベースを再利用すると、コマンドラインと、サーバーのインスタンスが、リモートの同じ保存領域で作業できるようになります。\n\nローカルに存在するデータベースを使用しますか?","Anonymous usage reports":"使用状況に関する匿名の報告","Applications":"アプリケーション","As Command-line":"コマンドライン","AuthID":"認証ID","Authentication method":"認証方法","Authentication method ({{auth_method}})":"認証方法({{auth_method}})","Authentication password":"認証パスワード","Authentication username":"認証ユーザー名","Autogenerated passphrase":"自動生成したパスフレーズ","Automatically run backups":"バックアップを自動的に実行","B2 Application ID":"B2 アプリケーションのID","B2 Application Key":"B2 アプリケーションのキー","B2 Cloud Storage Account ID":"B2 クラウドストレージのアカウントのID","B2 Cloud Storage Application ID":"B2 クラウドストレージのアプリケーションのID","B2 Cloud Storage Application Key":"B2 クラウドストレージのアプリケーションのキー","Back":"戻る","Backend modules:

{{item.Key}}

":"バックエンドモジュール:

{{item.Key}}

","Backup complete!":"バックアップが完了しました!","Backup destination":"バックアップ先","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"バックアップは暗号化されていますが、パスフレーズが指定されていません。ファイルを復元するには、以下にパスフレーズを入力するか、GPGによる暗号化を行っている場合は、以下を空欄のままにして、gpgでシステムのキーチェーンからパスフレーズを取得してください。","Backup location":"バックアップの場所","Backup retention":"バックアップの保持期間","Backup:":"バックアップ:","Beta":"ベータ版","Broken access":"アクセスが壊れています","Browse":"参照","Browser default":"ブラウザ設定","Bucket create location":"バケットを作成する場所","Bucket name":"バケット名","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"バケット名は3文字から63文字までの間で指定してください。バケット名には、アルファベットの小文字、数字、点、ダッシュのみを含めることができます。","Bucket region":"バケットのリージョン","Bucket region ap-guangzhou":"バケットのリージョン ap-guangzhou","Bucket storage class":"バケットのストレージクラス","Bucket, format: BucketName-APPID":"バケット名。形式:BucketName-APPID","Building list of files to restore …":"復元するファイルの一覧を作成しています…","Building partial temporary database …":"一時的なデータベースを構築しています…","Busy …":"取り込み中…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"遠隔アクセスを許可すると、サーバーはあなたのネットワークの任意のコンピューターからのリクエストを受け付けます。このオプションを有効にする場合は、ファイヤーウォールで安全に保護されているネットワークのコンピューターを使用してください。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"トレイアイコンは既定で、トークンでロックを解除してユーザーインターフェースを開きます。この場合、他のユーザーはパスワードを入力する必要がありますが、ユーザーはトレイアイコンからユーザーインターフェースにアクセスすることができます。トレイアイコンからアクセスする場合にパスワードを入力するよう設定したい場合は、このオプションを有効にしてください。","COS App ID":"COS AppのID","COS Path or subfolder in the bucket":"COSのパスあるいはバケットのサブフォルダー","COS Secret ID":"COSのシークレットのID","COS Secret Key":"COSの秘密鍵","Cache Files":"キャッシュファイル","Canary":"実験的(カナリア)","Cancel":"キャンセル","Cannot include \"{{text}}\"":"「{{text}}」を含めることはできません","Cannot move to existing file":"既にファイルがあるため移動できません","Cannot specify filter include or excludes in extra options":"追加のオプションに、含めたり除外したりするフィルターを指定することはできません","Change server passphrase":"サーバーのパスフレーズを変更","Changelog":"更新履歴","Changelog for {{appname}} {{version}}":"更新履歴 {{appname}} {{version}}","Check failed:":"確認できませんでした:","Check for updates now":"アップデートを確認","Checking for updates …":"アップデートを確認しています…","Checking …":"確認しています…","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"高速なバックアップには1.0、安定性を求める場合は1.5、速度に代えて安全性を求める場合は2.0を指定してください。","Chose a storage type to get started":"初めにストレージの種類を選択してください","Click the AuthID link to create an AuthID":"認証IDのリンクをクリックして作成してください","Click to set throttle options":"クリックで速度制限のオプションを設定","Client library to use":"使用するクライアントライブラリー","Cloud API Secret ID":"Cloud APIのシークレットID","Cloud API Secret Key":"Cloud APIの秘密鍵","Command":"コマンド","Commandline arguments":"コマンドラインの引数","Commandline …":"コマンドライン…","Compact Phase":"圧縮化の段階","Compact now":"圧縮","Compacting remote data …":"リモートデータを圧縮しています…","Complete log":"完全なログ","Completing backup …":"バックアップを完了しています…","Completing previous backup …":"以前のバックアップを完了しています…","Compression modules:

{{item.Key}}

":"圧縮モジュール:

{{item.Key}}

","Computer":"コンピューター","Configuration file:":"設定ファイル:","Configuration:":"設定:","Configure a new backup":"新しいバックアップを設定","Confirm delete":"削除を確認","Confirm encryption passphrase":"暗号化用パスフレーズを確認","Confirm new password":"新しいパスワードを再度入力してください","Confirm passphrase":"パスフレーズを確認","Confirmation required":"確認が必要です","Connect":"接続","Connect now":"今すぐ接続","Connecting to server …":"サーバーに接続しています…","Connecting to task …":"タスクに接続しています…","Connecting …":"接続しています…","Connection lost":"切断しました","Connection worked!":"接続できました!","Container name":"コンテナ名","Container region":"コンテナのリージョン","Continue":"続行","Continue without encryption":"暗号化なしで続行","Copied!":"コピーしました!","Copy":"コピー","Copy Destination URL to Clipboard":"バックアップ先のURLをクリップボードにコピー","Copy URL":"URLをコピー","Copy failed. Please manually copy the URL":"コピーできませんでした。URLを手動でコピーしてください","Copy log":"ログをコピー","Core options":"中心のオプション","Counting ({{files}} files found, {{size}})":"計測中({{files}}個のファイルが見つかりました。サイズは{{size}})","Crashes only":"クラッシュのみ","Create bug report …":"バグレポートを作成…","Create folder?":"フォルダーを作成しますか?","Created new limited user":"新規の制限ユーザーを作成しました","Creating bug report …":"バグレポートを作成しています…","Creating new user with limited access …":"アクセスが制限されている新規ユーザーを作成しています…","Creating target folders …":"バックアップ先のフォルダーを作成しています…","Creating temporary backup …":"一時的なバックアップを作成しています…","Creating user …":"ユーザーを作成しています…","Current action:":"現在のアクション:","Current file:":"現在のファイル:","Current version is {{versionname}} ({{versionnumber}})":"現在のバージョンは {{versionname}}({{versionnumber}})","Custom S3 endpoint":"ユーザー定義のS3エンドポイント","Custom Satellite":"ユーザー定義のサテライト","Custom Satellite ({{satellite}})":"ユーザー定義のサテライト({{satellite}})","Custom authentication url":"ユーザー定義の認証用URL","Custom backup retention":"ユーザー定義のバックアップの保持期間","Custom bucket storage class":"ユーザー定義のバケットストレージのクラス","Custom location ({{server}})":"ユーザー定義の場所({{server}})","Custom region for creating buckets":"バケットを作成するユーザー定義のリージョン","Custom region value ({{region}})":"ユーザー定義のリージョンの値({{region}})","Custom server url ({{server}})":"ユーザー定義のサーバーURL ({{server}})","Custom storage class ({{class}})":"ユーザー定義の保存領域のクラス({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"非推奨:{{getDeprecationMessage(item)}}","Database …":"データベース…","Days":"日","Default":"初期設定","Default ({{channelname}})":"既定({{channelname}})","Default excludes":"既定で除外するアイテム","Default options":"既定のオプション","Default value: \"{{getDefaultValue(item)}}\"":"既定値:「{{getDefaultValue(item)}}」","Delete":"削除","Delete Phase (Old Backup Versions)":"削除の段階","Delete backup":"バックアップを削除","Delete backups that are older than":"古いバックアップから削除","Delete local database":"ローカルデータベースを削除","Delete remote files":"リモートファイルを削除","Delete the local database":"ローカルデータベースを削除","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}}個のファイル({{filesize}})をリモートの保存領域から削除しますか?","Delete …":"削除...","Deleted":"削除済","Deleted Versions":"削除されたバージョン","Deleted files":"削除されたファイル","Deleting remote files …":"リモートファイルを削除しています…","Deleting unwanted files …":"不要なファイルを削除しています…","Description (optional)":"概要(任意)","Description:":"概要:","Desktop":"デスクトップ","Destination":"バックアップ先","Destination path":"バックアップ先のパス","Direct restore from backup files …":"バックアップファイルから直接復元…","Directory path":"ディレクトリーのパス","Disabled":"無効","Dismiss":"表示しない","Dismiss all":"すべて表示しない","Display and color theme":"テーマカラー","Do you really want to delete the backup: \"{{name}}\" ?":"バックアップ \"{{name}}\" を削除してよろしいですか?","Do you really want to delete the local database for: {{name}}":"{{name}} のデータベースを削除してよろしいですか?","Domain name":"ドメイン名","Done":"完了","Download":"ダウンロード","Downloaded files":"ダウンロードされたファイル","Downloading files …":"ファイルをダウンロードしています…","Downloading update…":"アップデートをダウンロードしています…","Duplicate option {{opt}}":"複製に関するオプション {{opt}}","Duplicati Website":"Duplicatiのウェブサイト","Duplicati forum":"Duplicatiのフォーラム","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicatiはパスフレーズで保護する必要があります。ランダムなパスフレーズを作成しました。\nDuplicatiをトレイアイコンから開く場合はパスフレーズは必要ありませんが、別の場所から開くにはパスフレーズを入力する必要があります。\nパスフレーズを設定しますか?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicatiは起動と同時に実行しますが、ここで指定した時間が経過するまで一時停止の状態を維持します。一時停止の間、Duplicatiは最低限のシステムの処理能力しか使用せず、その間バックアップは実行されません。","Duration":"経過","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"それぞれのバックアップには、ローカルのコンピューターに保存されるデータベースがあります。\nバックアップを削除する際、リモートファイルの復元に影響を与えずにローカルのデータベースを削除することもできます。\nコマンドラインからバックアップ用のローカルのデータベースを使用している場合は、データベースを削除しないでください。","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"それぞれのバックアップには、ローカルのコンピューターに保存されるデータベースがあります。このデータベースには、リモートバックアップに関する情報が保存されており、操作の速度を改善したり、その都度の操作でダウンロードするデータ量を減らしたりする効果があります。","Edit as list":"一覧で編集","Edit as text":"テキストで編集","Edit …":"編集...","Email address of the Office 365 group":"Office 365グループのメールアドレス","Encrypt file":"ファイルを暗号化","Encryption":"暗号化の方式","Encryption changed":"暗号化の方式が変更されました","Encryption modules:

{{item.Key}}

":"暗号化モジュール:

{{item.Key}}

","Encryption passphrase":"暗号化用のパスフレーズ","Encryption passphrase (for verification)":"暗号化用のパスフレーズ(確認用)","End":"終了","Enter URL":"URLを入力してください","Enter a backup destination URL:":"バックアップ先のURLを入力してください。","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"バックアップの保持期間の方針を手動で設定できます。使用できる文字にはD、W、Y、Uがあり、それぞれ日、週、年、無制限(Unlimited)を指します。構文の形式は「7D:1D,4W:1W,36M:1M」となります。この例では、今後7日間にわたり毎日1個ずつ、今後4週間にわたり毎週1個ずつ、今後36か月にわたり毎月1個ずつバックアップが作成、保存されます。これはまた「1W:1D,1M:1W,3Y:1M」と表記することもできます。","Enter a url, or click the "Target URL >" link":"URLを入力するか、「バックアップ用のURL >」のリンクをクリック","Enter backup passphrase, if any":"バックアップのパスフレーズがある場合は入力してください","Enter configuration details":"設定の詳細を入力","Enter encryption passphrase":"暗号化用のパスフレーズを入力してください","Enter expression here":"式をここに入力してください","Enter one argument per line without quotes, e.g. *.txt":"各行に1個の引数を、引用符を付けずに入力してください(例:*.txt)。","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"コマンドラインの形式で1行に1つのオプションを入力してください。例:--dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"コマンドラインの形式で1行に1つのオプションを入力してください。例:{0}","Enter the destination path":"バックアップ先のパスを入力してください","Error":"エラー","Error!":"エラー!","Errors and crashes":"エラーとクラッシュ","Examined":"検査済","Exclude":"除外","Exclude directories whose names contain":"次の文字を含むディレクトリを除外","Exclude expression":"次の文字を含むファイル・ディレクトリを除外","Exclude file":"除外するファイル名","Exclude file extension":"除外する拡張子","Exclude files whose names contain":"次の文字を含むファイルを除外","Exclude filter group":"グループで除外","Exclude folder":"除外するディレクトリ名","Exclude regular expression":"正規表現で除外","Existing file found":"既存のファイルが見つかりました","Experimental":"実験的","Export":"エクスポート","Export backup configuration":"バックアップの設定をエクスポート","Export configuration":"設定をエクスポート","Export passwords":"パスワードをエクスポート","Export …":"エクスポート…","Exporting …":"エクスポートしています…","External link":"外部リンク","FTP (Alternative)":"FTP(代替)","Failed to build temporary database: {{message}}":"一時的なデータベースを構築できませんでした:{{message}}","Failed to connect:":"接続できませんでした:","Failed to connect: {{message}}":"接続できませんでした。{{message}}","Failed to delete:":"削除できませんでした:","Failed to fetch path information: {{message}}":"パスの情報を取得できませんでした:{{message}}","Failed to find backup:":"バックアップが見つかりませんでした:","Failed to get bug report URL: {{message}}":"バグレポートのURLを取得できませんでした:{{message}}","Failed to import: {{message}}":"インポートできませんでした:{{message}}","Failed to read backup defaults:":"バックアップの既定の設定を読み込めませんでした:","Failed to read file: {{message}}":"ファイルを読み込めませんでした:{{message}}","Failed to restore files: {{message}}":"ファイルを復元できませんでした:{{message}}","Failed to save:":"保存できませんでした:","Fatal error, no statistics collected":"深刻なエラーが発生しました。統計は収集されていません","Fetching path information …":"パスの情報を取得しています…","File":"ファイル","Files larger than:":"閾値より大きなファイル:","Filters":"フィルター","Finished!":"完了しました!","First run setup":"初回実行セットアップ","Folder":"フォルダー","Folder in the bucket":"バケット内のフォルダー","Folder path":"フォルダーのパス","Folder path name":"フォルダーのパスの名称","Fri":"金曜日","Full destination path, including the server name, but without https":"サーバーの名称を含む、バックアップ先の完全なパス(httpsは除く)","GByte":"ギガバイト","GByte/s":"ギガバイト秒","GCS Project ID":"GCS プロジェクトID","General":"全般","General backup settings":"バックアップの設定","General options":"設定","Generate":"生成","Generate IAM access policy":"IAMアクセスポリシーを生成","Getting file versions …":"ファイルのバージョンを取得しています…","Group email":"グループの電子メール","Hidden files":"隠しファイル","Hide":"隠す","Hide hidden folders":"隠しフォルダーを表示しない","Home":"ホーム","Hostnames":"ホスト名","Hours":"時間","How do you want to handle existing files?":"既存のファイルはどのように扱いますか?","Hyper-V Machine":"Hyper-V マシン","Hyper-V Machine:":"Hyper-V マシン:","Hyper-V Machines":"Hyper-V マシン","ID:":"ID:","IDrive Sync directory path":"IDrive Syncのディレクトリーのパス","IDrive e2 Access Key ID":"IDrive e2のアクセスキーのID","IDrive e2 Access Key Secret":"IDrive e2のアクセスキーのシークレット","If a date was missed, the job will run as soon as possible.":"予定の日時を逃してしまった場合、ジョブは即座に実行します。","If at least one newer backup is found, all backups older than this date are deleted.":"最低1つ以上のより新しいバックアップが存在する場合、この日付よりも古い全てのバックアップを削除します。","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"バックアップとリモートの保存領域が同期していない場合、データベースを修復して同期させる必要があります。修復が上手く行かない場合は、ローカルのデータベースを削除して、改めてこれを作成してください。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"バックアップのファイルが自動的にダウンロードされなかった場合は、 "名前を付けて保存"を右クリックして選択してください。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"バックアップのファイルが自動的にダウンロードされなかった場合は、 "名前を付けて保存"を右クリックして選択してください。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"パスを入力しない場合、全てのファイルはログインフォルダーに保存されます。\n続行してよろしいですか?","If you do not enter an API Key, the tenant name is required":"APIを入力しない場合、テナント名が必要です","If you want to use the backup later, you can export the configuration before deleting it.":"後にバックアップを使用したい場合は、削除する前に設定をエクスポートできます。","Import":"インポート","Import Destination URL":"バックアップ先のURLをインポート","Import URL":"URLをインポート","Import backup configuration":"バックアップの設定をインポート","Import from a file":"ファイルからインポート","Import metadata":"メタデータをインポート","Importing …":"インポートしています…","Include a file?":"ファイルを含めますか?","Include expression":"次の文字列を含む","Include regular expression":"次の正規表現を含む","Incorrect answer, try again":"答えが正しくありません。もう一度試してください","Individual builds for developers only. Not for use with important data.":"開発者用の個別のビルドです。重要なデータのバックアップには使用しないでください。","Information":"情報","Interrupted, no statistics collected":"中断されました。統計は収集されていません","Invalid characters in path":"無効な文字がパスに含まれています","Invalid retention time":"無効な保持期間が設定されています","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"FTPサーバーの中にはパスワードを入力せずに接続できるものがあります。\nこのFTPサーバーは、パスワード無しのログインをサポートしていますか?","KByte":"キロバイト","KByte/s":"キロバイト秒","Keep a specific number of backups":"指定した数のバックアップを保存","Keep all backups":"全てのバックアップを保存","Keystone API version":"Keystone APIのバージョン","Language in user interface":"言語設定","Last month":"先月","Last successful backup:":"最後に成功したバックアップ:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"最後に成功した復元:{{time}}(完了までの時間 {{duration || '0秒'}})","Latest":"最新","Libraries":"ライブラリー","Listing backup dates …":"バックアップの日付を一覧表示しています…","Listing remote files for purge …":"削除するリモートファイルの一覧を作成しています…","Listing remote files …":"リモートファイルの一覧を作成しています…","Live":"ライブ","Load a configuration from an exported job or a storage provider":"エクスポートしたジョブまたはストレージ提供者から、設定を読み込む","Load destination from an exported job or a storage provider":"エクスポートしたジョブまたはストレージ提供者から、バックアップ先を読み込む","Load older data":"さらに古いデータを読み込む","Loading remote storage usage …":"リモートストレージの使用量を読み込んでいます…","Loading …":"読み込んでいます…","Local Repository":"ローカルのリポジトリー","Local database for {{Backup.Backup.Name}}…loading…":"{{Backup.Backup.Name}}…読み込んでいます…のローカルのデータベース","Local database path:":"ローカルのデータベースのパス:","Local repository":"ローカルのリポジトリー","Local storage":"ローカルストレージ","Location":"場所","Location where buckets are created":"バケットを作成する場所","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}}のログデータ","Log data from the server":"サーバー上のログデータ","Log in":"ログイン","Log out":"ログアウト","MByte":"メガバイト","MByte/s":"メガバイト秒","Maintenance":"メンテナンス","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"Rcloneの実行ファイルをパスで指定するか、実行ファイルの場所を「高度な設定」で指定してください。","Manual":"マニュアル","Manual update found:":"手動アップデートが見つかりました:","Manually type path":"手動でパスを入力","Max download speed":"最大ダウンロード速度","Max upload speed":"最大アップロード速度","Menu":"メニュー","Microsoft SQL Database:":"Microsoft SQLデータベース:","Microsoft SQL Databases":"Microsoft SQLデータベース","Minimum redundancy":"最小の冗長性","Minimum redundancy is 1.0":"最小の冗長性は1.0です","Minutes":"分","Missing name":"名前がありません","Missing passphrase":"パスフレーズがありません","Missing sources":"バックアップ元のファイルがありません","Modified":"変更済","Mon":"月曜日","Months":"月","Move existing database":"既存のデータベースを移動","Move failed:":"移動できませんでした:","My Documents":"マイドキュメント","My Music":"マイミュージック","My Photos":"マイフォト","My Pictures":"マイピクチャ","Name":"名前","Never":"未実行","New Password":"新しいパスワードを入力してください","New update found: {{message}}":"新しいアップデートが見つかりました:{{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新しいユーザー名は{{user}}です。\n新規の制限ユーザーを使用するためのログイン情報を更新しました","Next":"次へ","Next scheduled run:":"次の実行予定日時:","Next scheduled task:":"次に予定されているタスク:","Next task:":"次のタスク:","Next time":"次回","No":"いいえ","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"以前に指定された証明書はありません。鍵が正しいかどうか、サーバーの管理者に確認してください:{{key}} \n\n報告されたホストの鍵を承認してよろしいですか?","No editor found for the "{{backend}}" storage type":""{{backend}}" の保存領域の種類に関するエディターが見つかりませんでした","No encryption":"暗号化なし","No items selected":"アイテムが選択されていません","No items to restore, please select one or more items":"復元するアイテムがありません。1つ以上のアイテムを選択してください","No passphrase entered":"パスフレーズが入力されていません","No scheduled tasks":"予定されているタスクはありません","Non-matching passphrase":"パスフレーズが一致しません","None / disabled":"なし / 無効","Not using encryption":"暗号化を行っていません","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"ここで入力する速度はバイト表記ですが、回線速度は通常、ビットで報告されます。ビットからバイトへと数値を換算するには、これを8で割ってください。8メガビット秒の回線は1メガバイト秒に相当します。","Nothing will be deleted. The backup size will grow with each change.":"バックアップは削除されません。バックアップのサイズはその都度の変更に従って大きくなります。","OK":"OK","OSS Access Key ID":"OSSのアクセスキーのID","OSS Access Key Secret":"OSSのアクセスキーのシークレット","OSS Bucket Region":"OSSのバケットのリージョン","OSS Bucket name":"OSSのバケット名","OSS Endpoint":"OSSのエンドポイント","OSS Path or subfolder in the bucket":"OSSのパスあるいはバケットのサブフォルダー","OSS Region":"OSSのリージョン","Official releases":"公式リリース版","Once there are more backups than the specified number, the oldest backups are deleted.":"指定した数以上のバックアップが作成された場合、古いバックアップから削除されます。","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack オブジェクトストレージ / Swift","Opened":"展開済","Openstack API key are not supported in v3 keystone API":"OpenstackのAPIキーは、バージョン3のkeystone APIではサポートされていません。","Operating System":"オペレーティングシステム","Operation":"操作","Operations:":"操作:","Optional API key":"APIのキー(オプション)","Optional authentication password":"認証に必要なパスワード(オプション)","Optional authentication username":"認証に必要なユーザー名(オプション)","Optional region":"リージョン(オプション)","Optional tenant name":"テナント名(オプション)","Options":"オプション","Options added here are applied to all backups, but can be overridden in each individual backup.":"ここで追加したオプションは全てのバックアップに適用されますが、それぞれのバックアップの設定で上書きすることができます。","Original location":"元の場所","Others":"その他","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"バックアップは時間の経過につれて自動的に削除されます。7日ごと、4週ごと、12ヶ月ごとのバックアップはそれぞれ保持されます。最低でも1つはバックアップが残ります。","Overwrite":"上書き","Passphrase":"パスフレーズ","Passphrase (if encrypted)":"パスフレーズ(暗号化されている場合)","Passphrase changed":"パスフレーズを変更しました","Passphrases are not matching":"パスフレーズが一致しません","Passphrases do not match":"パスフレーズが一致しません","Password":"パスワード","Patching files with local blocks …":"ファイルをローカルのブロックで修復しています…","Path":"パス","Path not found":"パスが見つかりません","Path on server":"サーバー上のパス","Path or subfolder in the bucket":"パスまたはバケットのサブフォルダー","Pause":"一時停止","Pause after startup or hibernation":"起動時またはハイバネート時に一時停止","Pause options":"一時停止の設定","Permissions":"権限","Pick location":"場所を入力","Please select a file to import":"インポートするファイルを選択してください","Point to your backup files and restore from there":"バックアップファイルを指定し、そこから復元","Port":"ポート","Prevent tray icon automatic log-in":"トレイアイコンの自動ログインを行わない","Previous":"前へ","Progress:":"進行度:","ProjectID is optional if the bucket exist":"バケットが存在する場合、ProjectIDはオプションです","Proprietary":"独自プロトコル","Purge Phase":"削除の段階","Purging files complete!":"ファイルを削除しました!","Purging files …":"ファイルを削除しています…","Rebuilding local database …":"ローカルデータベースを再構築しています…","Recreate (delete and repair)":"改めて作成(削除して修復)","Recreate Database Phase":"データベースの再構築の段階","Recreating database …":"データベースを改めて作成しています…","Region":"リージョン","Registering temporary backup …":"一時的なバックアップを登録しています…","Relative paths not allowed":"相対パスは許可されていません","Reload":"更新","Remote":"リモート","Remote Path":"リモートのパス","Remote Repository":"リモートのリポジトリー","Remote path":"リモートのパス","Remote repository":"リモートのリポジトリー","Remote volume size":"リモートのボリュームのサイズ","Remove":"削除","Remove option":"設定を削除","Removed files":"削除したファイル","Repair":"修復","Repair Phase":"修復の段階","Repairing database …":"データベースを修復しています…","Repeat Passphrase":"パスフレーズ(再度)","Reporting:":"報告:","Reset":"リセット","Restore":"復元","Restore complete!":"復元しました!","Restore files":"ファイルの復元","Restore files from:":"ファイルの復元:","Restore files …":"ファイルを復元…","Restore from":"データを復元するバックアップ","Restore from backup configuration":"バックアップの設定から復元","Restore from configuration …":"設定から復元…","Restore options":"復元オプション","Restore read/write permissions":"読み込み/書き込み権限を復元","Restored Files":"復元されたファイル","Restored Folders":"復元されたフォルダー","Restored Symlinks":"復元されたシンボリックリンク","Restoring files …":"ファイルを復元しています…","Resume":"再開","Rewritten File Lists":"ファイルの一覧を書き換えました","Run again every":"実行タイミング","Run now":"すぐに実行","Running commandline entry":"コマンドラインのエントリーを実行しています","Running task:":"タスクを実行しています:","Running …":"実行しています…","Running … stop now":"実行しています … 停止","S3 Compatible":"S3互換","Same as the base install version: {{channelname}}":"基本インストールのバージョンと同じです:{{channelname}}","Sat":"土曜日","Satellite":"サテライト","Save":"保存","Save and repair":"保存して修復","Save different versions with timestamp in file name":"ファイル名にタイムスタンプを入れて、異なるバージョンとして保存","Save immediately":"即座に保存","Scanning existing files …":"ファイルをスキャンしています…","Scanning for local blocks …":"ローカルのブロックをスキャンしています…","Schedule":"スケジュール","Search":"検索","Search for files":"ファイルの検索","Seconds":"秒","Select a log level and see messages as they happen:":"ログの水準を選択すると、メッセージを出力順に表示します。","Select files":"ファイルの選択","Server":"サーバー","Server and port":"サーバーとポート","Server hostname or IP":"サーバーのホスト名またはIPアドレス","Server is currently paused,":"サーバーは現在停止中です。","Server is currently paused, resume now":"サーバーは現在停止中です。再開","Server is currently paused, do you want to resume now?":"サーバーは現在停止中です。再開しますか?","Server password":"サーバーのパスワード","Server paused":"サーバーを一時停止しました","Server state properties":"サーバーの状態に関するプロパティー","Settings":"設定","Show":"表示","Show advanced editor":"拡張エディターを表示","Show hidden folders":"隠しフォルダーを表示","Show log":"ログを表示","Show log …":"ログを表示...","Show treeview":"フォルダーツリーを表示","Sia server password":"Siaサーバーのパスワード","Smart backup retention":"スマートなバックアップ保持期間","Some OpenStack providers allow an API key instead of a password and tenant name":"OpenStackのサービス提供者の中には、パスワードとテナント名の代わりにAPIキーを許可するものもあります","Some S3 providers might only be compatible with a certain client library":"いくつかのS3プロバイダーは特定のクライアントライブラリーにしか対応していないおそれがあります","Source Data":"バックアップ元","Source Files":"バックアップ元のファイル","Source data":"バックアップ元","Source folders":"バックアップ元のフォルダー","Source:":"バックアップ元:","Specific builds for developers only. Not for use with important data.":"開発者用の特定のビルドです。重要なデータのパックアップには使用しないでください。","Stable":"安定版","Standard protocols":"標準プロトコル","Start":"開始","Starting backup …":"バックアップを開始しています…","Starting restore …":"復元を開始しています…","Starting the restore process …":"復元プロセスを開始しています…","Stop after current file":"現在のファイルの後で停止","Stop after the current file":"現在のファイルの後で停止","Stop now":"すぐに停止","Stop running backup":"実行中のバックアップを停止","Stop running task":"実行中のタスクを停止","Stopping after the current file:":"現在のファイルの後で停止:","Stopping task:":"タスクを停止しています:","Storage Type":"ストレージの種類","Storage class":"ストレージのクラス","Storage class for creating a bucket":"バケットを作成する際のストレージのクラス","Stored":"保存済","Strong":"強","Success":"成功","Sun":"日曜日","Symbolic link":"シンボリックリンク","System Files":"システムファイル","System default ({{levelname}})":"システムの既定値({{levelname}})","System files":"システムファイル","System info":"システムの情報","System properties":"システムのプロパティー","TByte":"テラバイト","TByte/s":"テラバイト秒","Target URL >":"バックアップ用のURL >","Target path. Example: /backup":"バックアップ先のパス。例:/backup","Task is running":"タスクは実行中です","Temporary Files":"一時ファイル","Temporary files":"一時ファイル","Tenant name":"テナント名","Tencent Cloud Account APPID":"Tencent CloudアカウントのAPPID","Tencent Cloud COS documents and resources":"Tencent Cloud COSのドキュメントと参考資料","Test Phase":"テストの段階","Test connection":"接続をテスト","Testing connection …":"接続をテストしています…","Testing permissions …":"権限をテストしています…","Testing …":"テストしています…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"「{{fieldname}}」のフィールドには不正な文字「{{character}}」が含まれています(値:{{value}}、インデックス:{{pos}})","The backup is missing, has it been deleted?":"バックアップがありません。削除された模様です","The backup was temporary and does not exist anymore, so the log data is lost":"バックアップは一時的で既に存在しないため、ログデータは削除されています","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"バックアップは「ボリューム」と呼ばれる複数のファイルに分割されます。ここで、各ボリュームの最大のサイズを設定できます。詳細についてはこのページを確認してください。","The bucket name should be all lower-case, convert automatically?":"バケット名には小文字のみが使用できます。自動的に変換しますか?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"設定ファイルは安全に保存すべきです。ファイルにはパスワードが含まれていますが、暗号化せずに保存してよろしいですか?","The connection to the server is lost, attempting again in {{time}} …":"サーバーとの接続が失われました。{{time}}後に再試行します…","The dark theme (by Michal)":"ダークテーマ(by Michal)","The default blue on white theme (by Alex)":"既定の白地に青テーマ(by Alex)","The encryption passphrases do not match":"暗号化用のパスフレーズが一致しません","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"ファイルのサイズが{{size}}であり、指定されている最大のサイズを超えています。サイズが指定されている最大のサイズよりも小さくなると、このファイルは以後のバックアップに含まれます。","The folder {{folder}} does not exist.\nCreate it now?":"フォルダー「{{folder}}」は存在しません。\n作成しますか?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"ホストの鍵が変更されました。変更が正しいかどうか、サーバーの管理者に問い合わせてください。変更が正しくない場合、中間車攻撃を受けているおそれがあります。\n\n現在のホストの鍵「{{prev}}」を、報告されたホストの鍵「{{key}}」で置き換えますか?","The passwords do not match":"パスワードが一致しません","The path does not appear to exist, do you want to add it anyway?":"パスは存在しないようですが、追加してよろしいですか?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"パスは「{{dirsep}}」で終わっていません。フォルダーではなく、ファイルが含まれています。\n\n指定したファイルを含めますか?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"パスにはスラッシュから始まる絶対パスを指定してください","The region parameter is only applied when creating a new bucket":"リージョンパラメーターは、バケットを新たに作成する際にのみ適用されます","The region parameter is only used when creating a bucket":"リージョンパラメーターは、バケットを作成する際にのみ使用されます","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"サーバーの証明書を検証できませんでした。\n次のハッシュ値をもつSSLの証明書を承認してよろしいですか:{{hash}}","The storage class affects the availability and price for a stored file":"保存領域のクラスは、保存されているファイルの利用可能性と価格に影響します","The target folder contains encrypted files, please supply the passphrase":"バックアップ先のフォルダーには暗号化されているファイルがあります。パスフレーズを指定してください。","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"ユーザーに付与されている権限が多すぎます。選択したパスに関する権限のみを有する制限ユーザーを新たに作成しますか?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"このバックアップは別のオペレーティングシステムで作成されました。バックアップの復元先となるフォルダーを指定せずにファイルを復元すると、予期しない場所にファイルが復元される可能性があります。復元先のフォルダーを選択せず続行してよろしいですか?","This month":"当月","This week":"この週","Throttle settings":"速度制限の設定","Thu":"木曜日","Time":"時間","To File":"ファイルへ","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"「{{name}}」の全てのリモートファイルを本当に削除したい場合は、表示されている語を以下に入力してください","To export without a passphrase, uncheck the \"Encrypt file\" box":"パスフレーズなしでエクスポートするには、「ファイルを暗号化」のチェックを外してください","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"バケット名の競合を防ぐため、バケット名の先頭にはアカウントIDを付けることが推奨されます。アカウントIDを自動的に付けますか?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"DNSに基づく攻撃を防ぐため、Duplicatiは、ここに入力されたホスト名しか許可しません。IPアドレスまたはlocalhostによるアクセスは常に許可されます。複数のホスト名を指定する場合は、セミコロンで区切ってください。ただし、アスタリスク(*)がホスト名として入力されている場合は、どのホスト名も許可され、この機能は無効となります。また、ホスト名が入力されていない場合は、IPアドレスまたはlocalhostによるアクセスのみが許可されます。","Today":"今日","Trust host certificate?":"ホストの証明書を信用しますか?","Trust server certificate?":"サーバーの証明書を信用しますか?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"開発中の新機能を試してみてください。現在、最も安定したバージョンが利用できます。本番環境で使用する前に、データの復元のテストを行ってください。","Tue":"火曜日","Type passphrase here.":"ここにパスフレーズを入力してください。","Type to highlight files":"見つけたいファイル名を入力してください","Unknown backup size and versions":"バックアップのサイズとバージョンが不明です","Until resumed":"再開するまで","Update {{state.updatedVersion}} is available. Download now":"アップデート {{state.updatedVersion}} が利用できます。ダウンロード","Update channel":"アップデートチャンネル","Update failed:":"アップデートできませんでした:","Updating with existing database":"既存のデータベースでアップデートしています","Uploaded files":"アップロードされたファイル","Uploading verification file …":"検証用ファイルをアップロードしています…","Usage statistics":"使用状況に関する統計","Usage statistics, warnings, errors, and crashes":"使用状況に関する統計、警告、エラー、クラッシュ","Use SSL":"SSLを使用","Use existing database?":"既存のデータベースを使用しますか?","Use weak passphrase":"弱いパスフレーズを使用","Useless":"弱すぎます","User data":"ユーザーデータ","User domain name":"ユーザーのドメイン名","User has too many permissions":"ユーザーに付与されている権限が多すぎます","User interface settings":"インターフェースの設定","Username":"ユーザー名","Vacuuming database …":"データベースのバキュームを行っています…","Validating …":"検証しています…","Verifications":"検証","Verify encryption passphrase":"暗号化用のパスフレーズを再入力","Verify files":"ファイルを検証","Verifying answer":"回答を検証しています","Verifying backend data …":"バックエンドのデータを検証しています…","Verifying files …":"ファイルを検証しています…","Verifying remote data …":"リモートデータを検証しています…","Verifying restored files …":"復元したファイルを検証しています…","Verifying …":"検証しています…","Version ID":"バージョンID","Very strong":"最強","Very weak":"最弱","Visit us on":"関連リンク","WARNING: The remote database is found to be in use by the commandline library.":"警告:リモートのデータベースはコマンドラインのライブラリーによって使用されています。","WARNING: This will prevent you from restoring the data in the future.":"警告:これを行うと将来データを復元できなくなります。","Waiting for task to begin":"タスクが開始するのを待機しています","Waiting for task to start …":"タスクの開始を待機しています…","Waiting for upload to finish …":"アップロードの完了を待機しています…","Warnings, errors and crashes":"警告、エラー、クラッシュ","We recommend that you encrypt all backups stored outside your system":"システム外に保存する全てのバックアップに関しては、暗号化を行うことを推奨します","Weak":"弱","Weak passphrase":"弱いパスフレーズ","Wed":"水曜日","Weeks":"週","Where do you want to restore from?":"どこから復元しますか?","Where do you want to restore the files to?":"復元したファイルはどこに保存しますか?","Years":"年","Yes":"はい","Yes, I have stored the passphrase safely":"はい、パスフレーズを安全な場所に保存しました","Yes, I understand the risk":"はい、リスクを理解しました","Yes, I'm brave!":"はい、問題ありません!","Yes, please break my backup!":"バックアップが壊れることを了承して続行","Yesterday":"昨日","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"既存のデータベースからデータベースのパスを変更しようとしています。\n続行してよろしいですか?","You are currently running {{appname}} {{version}}":"あなたは現在 {{appname}} {{version}}を使用しています。","You can stop the backup after any file uploads currently in progress have finished.":"現在実行中のファイルのアップロードが終了してからバックアップを停止することができます。","You can stop the task immediately, or allow the process to continue its current file and then stop.":"タスクを即座に停止するか、あるいは、現在のファイルの処理を続行してから停止することができます。","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"暗号化モードが変更されています。データが壊れる可能性があるため、新しいバックアップを代わりに作成することを推奨します","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"パスフレーズが変更されましたが、これはサポートされていません。新しいバックアップを代わりに作成することを推奨します。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"バックアップを暗号化しない設定となっていますが、リモートサーバーに保存する全てのデータに関して、暗号化を行うことを推奨します。","You have chosen to restore to a new location, but not entered one":"新しい場所に復元するよう選択しましたが、場所が入力されていません","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"強力なパスフレーズを生成しました。パスフレーズの紛失時にもデータを復元できるよう、パスフレーズを安全な場所にコピーして保存してください。","You must choose at least one source folder":"最低1つのバックアップ元のフォルダーを選択してください","You must enter a domain name to use v3 API":"バージョン3のAPIを使用するにはドメイン名を入力してください","You must enter a name for the backup":"バックアップの名称を入力してください","You must enter a passphrase or disable encryption":"パスフレーズを入力するか、暗号化を無効にしてください","You must enter a password to use v3 API":"バージョン3のAPIを使用するにはパスワードを入力してください","You must enter a positive number of backups to keep":"保存するバックアップの数を入力してください","You must enter a tenant (aka project) name to use v3 API":"バージョン3のAPIを使用するにはテナント(プロジェクト)名を入力してください","You must enter a tenant name if you do not provide an API key":"APIキーを指定しない場合はテナント名の入力が必要です","You must enter a valid duration for the time to keep backups":"バックアップを保持する期間を正しく指定してください","You must enter a valid retention policy string":"保持期間のポリシーを正しく入力してください","You must enter either a password or an API key":"パスワードかAPIキーを入力してください","You must enter either a password or an API key, not both":"パスワードまたはAPIキーのどちらかを入力してください","You must fill in the password":"パスワードを入力してください","You must fill in the server name or address":"サーバー名またはアドレスを入力してください","You must fill in the username":"ユーザー名を入力してください","You must fill in {{field}}":"{{field}}を入力してください","You must select or fill in the AuthURI":"AuthURIを選択または入力してください","You must select or fill in the server":"サーバーを選択または入力してください","You must specify a path":"パスを指定してください","You should fill in {{field}} {{reason}}":"{{reason}}{{field}}を入力してください。","Your files and folders have been restored successfully.":"ファイルとフォルダーを復元しました。","Your passphrase is easy to guess. Consider changing passphrase.":"設定したパスフレーズは容易に推測できます。パスフレーズの変更を考慮してください。","bucket/folder/subfolder":"バケット/フォルダー/サブフォルダー","byte":"バイト","byte/s":"バイト秒","cos_app_id":"COS AppのID","cos_bucket":"バケット名","cos_region":"リージョン","cos_secret_id":"COSのシークレットのID","cos_secret_key":"COSの秘密鍵","custom":"ユーザー定義","failed":"失敗しました","local repository, e.g. local":"ローカルのリポジトリー名(例:local)","oss_access_key_id":"OSSのアクセスキーのID","oss_access_key_secret":"OSSのアクセスキーのシークレット","oss_bucket_name":"OSSのバケット名","oss_endpoint":"OSSのエンドポイント","oss_region":"OSSのリージョン","remote path, e.g. backup":"リモートのパス(例:backup)","remote repository, e.g. remote":"リモートのリポジトリー名(例:remote)","resume now":"再開","storj_shared_access":"アクセス権","unless you are explicitly specifying --group-id":"--group-idを明示的に指定しているのでない限り、","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}}は最初に{{dev1}}と{{dev2}}によって開発されました。{{appname}}は{{websitename}}からダウンロードできます。{{appname}}は{{licensename}}によってライセンスされています。","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}}は以下のサードパーティー製のライブラリーを使用しています。","{{files}} files ({{size}}) to go {{speed_txt}}":"残り{{files}}個のファイル ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}}個のバージョン","{{number}} Hour":"{{number}}時間","{{number}} Hours":"{{number}}時間","{{number}} Minutes":"{{number}}分","{{time}} (took {{duration}})":"{{time}}(完了までの時間 {{duration}})"}); - gettextCatalog.setStrings('ko', {"- pick an option -":"- 옵션을 선택하십시오 -","...loading...":"...로딩...","About":"정보","About {{appname}}":"{{appname}} 정보","Access Key":"접근 키","Access denied":"접근 불가","Access to user interface":"액세스 설정","Account name":"계정 이름","Add a new backup":"새 백업 추가","Add a path directly":"경로 직접 추가","Add advanced option":"고급 옵션 추가","Add backup":"백업 추가","Add filter":"필터 추가","Add path":"경로 추가","Added":"추가됨","Adjust bucket name?":"버켓 이름을 적용 하시겠습니까?","Advanced Options":"고급 옵션","Advanced options":"고급 옵션","Advanced:":"고급:","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"모든 사용 보고서는 익명으로 전송되며 개인 정보를 포함하지 않습니다. 여기에는 하드웨어 및 운영 체제, 백엔드 유형, 백업 기간, 원본 데이터의 전체 크기 및 이와 유사한 데이터에 대한 정보가 포함되어 있습니다. 경로, 파일 이름, 사용자 이름, 암호 또는 이와 유사한 중요한 정보는 포함되어 있지 않습니다.","Allow remote access (requires restart)":"원격 액세스 허용 (다시 시작 필요)","Allowed days":"허용된 요일","Anonymous usage reports":"익명 사용 보고서","AuthID":"AuthID","Back":"이전","Backup destination":"백업 대상","Backup location":"백업 위치","Backup retention":"백업 보존","Backup:":"백업:","Beta":"Beta","Browse":"찾아보기","Bucket name":"Bucket 이름","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"원격 액세스를 허용하면 서버는 네트워크의 모든 컴퓨터에서 접속할 수 있습니다. 이 옵션을 사용하도록 설정하려면 방화벽으로 보호된 네트워크에서 컴퓨터를 사용하고 있는지 확인하십시오.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"기본적으로 트레이 아이콘은 토큰으로 잠금을 해제합니다. 이렇게 하면 다른 사용자가 암호를 입력하도록 요구하면서 트레이 아이콘에서는 사용자 인터페이스에 액세스할 수 있습니다. 트레이 아이콘에서 사용자 인터페이스에 액세스하는 때도 암호를 입력해야 하는 경우 이 옵션을 사용하도록 설정하십시오.","Canary":"Canary","Cancel":"취소","Cannot move to existing file":"기존 파일로 이동할 수 없습니다","Changelog":"변경로그","Changelog for {{appname}} {{version}}":"{{appname}} {{version}}에 대한 변경로그","Check failed:":"확인 실패:","Check for updates now":"업데이트 확인","Checking for updates …":"업데이트 확인 중 …","Chose a storage type to get started":"시작할 저장소 유형을 선택하세요","Click to set throttle options":"속도 제한 옵션을 설정하려면 클릭","Commandline …":"명령줄 …","Compact now":"최적화 실행","Computer":"내 PC","Configuration file:":"구성 파일:","Configuration:":"구성:","Configure a new backup":"새 백업 구성","Confirm encryption passphrase":"암호화 암호 확인","Connect":"연결","Connect now":"지금 연결하기","Connecting to server …":"서버에 연결하는 중 …","Connection lost":"연결이 끊어짐","Connection worked!":"연결되었습니다!","Continue":"계속","Copied!":"복사됨!","Copy":"복사","Copy Destination URL to Clipboard":"대상 URL을 클립보드에 복사","Core options":"핵심 옵션","Crashes only":"충돌만","Create bug report …":"버그 리포트 생성 …","Create folder?":"폴더를 생성하시겠습니까?","Creating bug report …":"버그 리포트 생성 중 …","Current action:":"현재 작업:","Current file:":"현재 파일:","Custom backup retention":"사용자 지정 백업 보존","Database …":"데이터베이스 …","Days":"일","Default":"기본값","Default ({{channelname}})":"기본값 ({{channelname}})","Default options":"기본 옵션","Delete":"삭제","Delete backup":"백업 삭제","Delete backups that are older than":"이전 백업 삭제","Delete local database":"로컬 데이터베이스 삭제","Delete remote files":"원격 파일 삭제","Delete the local database":"로컬 데이터베이스 삭제","Delete …":"삭제 …","Deleted":"삭제됨","Deleted Versions":"삭제된 버전들","Deleted files":"삭제된 파일들","Deleting unwanted files …":"원치 않는 파일 삭제 중 …","Description (optional)":"설명 (선택 사항)","Desktop":"바탕 화면","Destination":"대상","Destination path":"대상 경로","Disabled":"비활성화","Dismiss":"닫기","Dismiss all":"모두 닫기","Display and color theme":"인터페이스 테마","Done":"완료","Download":"다운로드","Downloading files …":"파일 다운로드 중 …","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati 포럼","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati는 시작할 때 실행되지만 지정된 시간 동안 일시 중지된 상태로 유지됩니다. Duplicati는 최소한의 시스템 리소스를 차지하며 백업이 실행되지 않습니다.","Edit as list":"목록으로 편집","Edit as text":"텍스트로 편집","Edit …":"편집 …","Encrypt file":"파일 암호화","Encryption":"암호화","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"보존 전략을 직접 입력합니다. 자리 표시자는 일/주/년이 각각 D/W/Y이고 U는 무제한입니다. 예) 7D:1D,4W:1W,36M:1M. 이 예제는 다음 7일 각각에 대해 하나의 백업을 유지하며, 다음 4주마다 하나씩, 다음 36개월마다 하나씩 백업합니다. 이것은 또한 1W:1D, 1M:1W,3Y:1M으로 표현할 수 있습니다.","Enter backup passphrase, if any":"백업 암호가 있는 경우 입력합니다.","Enter configuration details":"구성 세부 정보 입력","Enter the destination path":"대상 경로 입력","Error":"오류","Error!":"오류!","Errors and crashes":"오류 및 충돌","Exclude":"제외","Experimental":"Experimental","Export":"내보내기","Export backup configuration":"백업 구성 내보내기","Export configuration":"구성 내보내기","Export passwords":"암호 내보내기","Export …":"내보내기 …","Exporting …":"내보내는 중 …","Fetching path information …":"경로 정보를 가져오는 중 …","Files larger than:":"큰 파일","Filters":"필터","Folder path":"폴더 경로","Fri":"금요일","GByte":"GByte","GByte/s":"GByte/s","General":"일반","General backup settings":"일반 백업 설정","General options":"일반 옵션","Generate":"생성","Getting file versions …":"파일 버전을 구하는 중 ...","Hidden files":"숨김 파일","Hide":"숨기기","Hide hidden folders":"숨김 폴더 숨기기","Home":"홈","Hours":"시","How do you want to handle existing files?":"기존 파일을 어떻게 처리하시겠습니까?","If a date was missed, the job will run as soon as possible.":"날짜를 놓친 경우 작업이 가능한 한 빨리 실행됩니다.","If at least one newer backup is found, all backups older than this date are deleted.":"새 백업이 발견되면 이 날짜보다 오래된 모든 백업이 삭제됩니다.","Import Destination URL":"대상 URL 가져오기","Import backup configuration":"백업 구성 가져오기","Import from a file":"파일에서 가져오기","Import metadata":"메타데이터 가져오기","Individual builds for developers only. Not for use with important data.":"개발자 전용 개별 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오.","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"특정 수의 백업 유지","Keep all backups":"모든 백업 유지","Language in user interface":"인터페이스 언어","Last month":"지난 달","Last successful backup:":"마지막으로 성공한 백업:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"마지막으로 성공한 복원: {{time}} ({{duration || '0초'}} 소요)","Latest":"최근","Libraries":"라이브러리","Load a configuration from an exported job or a storage provider":"내보낸 작업 또는 저장소 공급자에서 구성 로드","Load destination from an exported job or a storage provider":"내보낸 작업 또는 저장소 공급자에서 대상 로드","Load older data":"이전 데이터 로드","Loading …":"로딩 …","Local database path:":"로컬 데이터베이스 경로:","Local repository":"로컬 리포지토리","Local storage":"로컬 저장소","Location":"위치","Log data from the server":"서버에서 가져온 로그 데이터","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"유지 관리","Manually type path":"수동 경로 입력","Max download speed":"최대 다운로드 속도","Max upload speed":"최대 업로드 속도","Microsoft SQL Database:":"Microsoft SQL Database:","Minutes":"분","Mon":"월요일","Months":"분","Move existing database":"기존 데이터베이스 이동","My Documents":"문서","My Music":"음악","My Pictures":"사진","Name":"이름","Never":"없음","Next":"다음","Next scheduled run:":"다음 백업 일정:","Next scheduled task:":"다음 예약 작업:","Next time":"시작","No":"아니오","No encryption":"암호화 없음","No items selected":"선택된 항목 없음","No items to restore, please select one or more items":"복원할 항목이 없습니다. 하나 이상의 항목을 선택하십시오.","No scheduled tasks":"스케줄링된 작업 없음","None / disabled":"비활성화","Nothing will be deleted. The backup size will grow with each change.":"아무 것도 삭제되지 않습니다. 백업 크기는 변경될 때마다 커집니다.","OK":"확인","Once there are more backups than the specified number, the oldest backups are deleted.":"지정된 수보다 많은 백업이 있으면 가장 오래된 백업이 삭제됩니다.","Operations:":"작업:","Options":"옵션","Original location":"원래 위치","Others":"기타","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"시간이 지남에 따라 백업이 자동으로 삭제됩니다. 지난 7일, 지난 4주, 지난 12개월 각각에 대해 하나의 백업이 유지됩니다. 항상 하나 이상의 남은 백업이 있습니다.","Overwrite":"덮어쓰기","Passphrase":"암호","Passphrase (if encrypted)":"암호 (암호화된 경우)","Password":"암호","Path on server":"서버의 경로","Pause":"일시 중지","Pause after startup or hibernation":"부팅 또는 최대 절전 모드 후 일시 중지","Pause options":"일시 중지 옵션","Permissions":"권한","Pick location":"위치 선택","Point to your backup files and restore from there":"백업 파일을 선택하고 복원","Prevent tray icon automatic log-in":"트레이 아이콘 자동 로그인 방지","Previous":"이전","Progress:":"진행률:","Proprietary":"독점","Recreate (delete and repair)":"재생성 (삭제 및 수리)","Recreating database …":"데이터베이스를 다시 만드는 중 …","Remote":"원격","Remote path":"원격 경로","Remote repository":"원격 저장소","Remote volume size":"원격 볼륨 크기","Remove":"제거","Remove option":"설정 제거","Removed files":"파일들 제거","Repair":"수리","Repeat Passphrase":"암호 재입력","Reporting:":"리포트:","Reset":"초기화","Restore":"복원","Restore complete!":"저장이 완료되었습니다!","Restore files":"파일 복원","Restore files …":"파일 복원 …","Restore from":"버전 선택","Restore from backup configuration":"백업 구성에서 복원","Restore options":"복원 옵션","Restore read/write permissions":"읽기/쓰기 권한 복원","Restoring files …":"파일 복원 중 …","Run again every":"실행 주기","Run now":"백업 실행","Same as the base install version: {{channelname}}":"기본 설치 버전과 동일: {{channelname}}","Sat":"토요일","Save":"저장","Save and repair":"저장 및 수리","Save different versions with timestamp in file name":"파일명에 타임스탬프 추가","Save immediately":"즉시 저장","Schedule":"일정","Search":"검색","Search for files":"파일 검색","Seconds":"초","Select a log level and see messages as they happen:":"로그 레벨을 선택하고 발생하는 메시지를 확인하십시오:","Select files":"파일 선택","Server state properties":"서버 상태 속성","Settings":"설정","Show":"표시","Show advanced editor":"고급 편집기 표시","Show hidden folders":"숨김 폴더 표시","Show log":"로그 표시","Show log …":"로그 표시 …","Smart backup retention":"스마트 백업 보존","Source Data":"원본 데이터","Source data":"원본 데이터","Source folders":"원본 폴더","Source:":"대상:","Specific builds for developers only. Not for use with important data.":"개발자 전용 특정 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오.","Standard protocols":"표준 프로토콜","Starting backup …":"백업 시작 중 …","Stop after current file":"현재 파일까지 진행 후 중지","Stop after the current file":"현재 파일까지 진행 후 중지","Stop now":"지금 중지","Stop running backup":"백업 실행 중지","Stopping after the current file:":"현재 파일까지 진행 후 중지 중:","Storage Type":"저장소 유형","Strong":"강한","Success":"성공","Sun":"일요일","System files":"시스템 파일","System info":"시스템 정보","System properties":"시스템 속성","TByte":"TByte","TByte/s":"TByte/s","Temporary Files":"임시 파일","Temporary files":"임시 파일","Test connection":"연결 테스트","The dark theme (by Michal)":"어두운 테마 (by Michal)","The default blue on white theme (by Alex)":"파란색의 밝은 테마 (by Alex)","The passwords do not match":"암호가 일치하지 않음","This month":"이번 달","This week":"이번 주","Throttle settings":"속도 제한 설정","Thu":"목요일","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"현재 작업 중인 새로운 기능을 사용해 보십시오. 현재 가장 안정적인 버전을 사용할 수 있습니다. 프로덕션 환경에서 이 데이터를 사용하기 전에 데이터를 복원합니다.","Tue":"화요일","Type to highlight files":"파일을 강조 표시하려면 입력","Until resumed":"다시 시작할 때까지","Update channel":"업데이트 채널","Usage statistics":"사용 통계","Usage statistics, warnings, errors, and crashes":"사용 통계, 경고, 오류 및 충돌","Useless":"쓸모없는","User data":"사용자 데이터","User interface settings":"인터페이스 설정","Username":"사용자 이름","Verify files":"무결성 확인","Verifying backend data …":"백엔드 데이터 확인 중 …","Verifying files …":"파일 검증 중 …","Verifying remote data …":"원격 데이터 확인 중 …","Very strong":"매우 강한","Very weak":"매우 약한","Visit us on":"Visit us on","Waiting for upload to finish …":"업로드가 완료되기를 기다리는 중 …","Warnings, errors and crashes":"경고, 오류 및 충돌","Weak":"약한","Wed":"수요일","Weeks":"주","Where do you want to restore from?":"어디에서 복원하시겠습니까?","Where do you want to restore the files to?":"파일을 어디에 복원하시겠습니까?","Years":"년","Yes":"예","Yes, I have stored the passphrase safely":"예, 암호를 안전하게 저장했습니다","Yes, I understand the risk":"네, 위험을 이해했습니다.","Yes, I'm brave!":"네,저는 용감합니다!","Yesterday":"어제","You are currently running {{appname}} {{version}}":"현재 사용 중: {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"현재 진행 중인 파일 업로드가 완료된 후 백업을 중지할 수 있습니다.","You must enter a name for the backup":"백업 이름을 입력해야 합니다","You must fill in the password":"암호를 입력해야 합니다","You must fill in the server name or address":"서버 이름 또는 주소를 채워야합니다.","You must fill in the username":"사용자 이름을 채워야합니다.","You must specify a path":"경로를 지정해야 합니다.","Your files and folders have been restored successfully.":"파일 및 폴더가 성공적으로 복원되었습니다.","byte":"byte","byte/s":"byte/s","custom":"사용자 지정","resume now":"지금 다시 시작","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} 파일 ({{size}}), 속도: {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 버전","{{number}} Hour":"{{number}}시간 동안","{{number}} Hours":"{{number}}시간 동안","{{number}} Minutes":"{{number}}분 동안","{{time}} (took {{duration}})":"{{time}} ({{duration}} 소요)"}); - gettextCatalog.setStrings('lt', {"- pick an option -":"- pasirinkite parametrą -","...loading...":"...įkeliama...","API key":"API raktas","AWS Access ID":"AWS prieigos ID","AWS Access Key":"AWS prieigos raktas","AWS IAM Policy":"AWS IAM politika","About":"Apie","About {{appname}}":"Apie {{appname}}","Access Key":"Prieigos raktas","Access denied":"Prieiga uždrausta","Access grant":"Prieiga leista","Access to user interface":"Pasiekti vartotojo sąsają","Account name":"Paskyros vardas","Add a new backup":"Pridėti naują kopiją","Add a path directly":"Pridėti kelią tiesiiogiai","Add advanced option":"Pridėti papildomą parametrą","Add backup":"Pridėti kopiją","Add filter":"Pridėti filtrą","Add path":"Pridėti kelią","Added":"Pridėta","Adjust bucket name?":"Keisti saugyklos pavadinimą?","Advanced Options":"Išplėstiniai parametrai","Advanced options":"Išplėstiniai parametrai","Advanced:":"Papildomai:","All Hyper-V Machines":"Visos Hyper-V mašinos","All Microsoft SQL Databases":"Visos Microsoft SQL duombazės","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Visos naudojimo ataskaitos siunčiamos anonimiškai ir jose nėra jokios asmeninės informacijos. Juose pateikiama informacija apie techninę įrangą ir operacinę sistemą, saugyklos tipą, kopijos kūrimo laiką, visų kopijuojamų failų dydį ir pan. Juose nėra kelių, failų pavadinimų, naudotojų, slaptažodžių ir panašios privačios informacijos.","Allow remote access (requires restart)":"Leisti nuotolinę prieigą (reikia paleisti iš naujo)","Allowed days":"Leidžiamos dienos","An existing file was found at the new location":"Naujoje vietoje rasti jau esantys failai","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Naujoje vietoje rasti jau esantys failai.\nAr tikrai norite duomenų bazę rašyti vietoj esamų failų?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Buvo rasta esama vietinė duomenų saugykla.\nNaudojant tą pačią duombazę, komandinės eilutės ir serverio procesai galės veikti toje pačioje nuotolinėje saugykloje.\n\n Ar norite naudoti esamą duomenų bazę?","Anonymous usage reports":"Anoniminės naudojimo ataskaitos","Applications":"Programos","As Command-line":"Kaip komandinę eilutę","AuthID":"AuthID","Authentication method":"Autorizacijos metodas","Authentication method ({{auth_method}})":"Autorizacijos metodas ({{auth_method}})","Authentication password":"Autorizacijos slaptažodis","Authentication username":"Autorizacijos naudotojas","Autogenerated passphrase":"Automatiškai sugeneruota slapta frazė","B2 Application ID":"B2 programos ID","B2 Application Key":"B2 programos raktas","B2 Cloud Storage Account ID":"B2 debesų saugyklos paskyros ID","B2 Cloud Storage Application ID":"B2 debesų saugyklos programos ID","B2 Cloud Storage Application Key":"B2 debesų saugyklos programos raktas","Back":"Atgal","Backup complete!":"Kopija padaryta!","Backup destination":"Kopijų saugojimo vieta","Backup location":"Kopijų saugojimo vieta","Backup retention":"Atsarginės kopijos saugojimo laikas","Backup:":"Kopija:","Beta":"Beta","Broken access":"Sugadinta prieiga","Browse":"Naršyti","Browser default":"Naršyklės numatyta reišmė","Bucket create location":"Sukurti saugyklos vietą","Bucket name":"Saugyklos pavadinimas","Bucket storage class":"Saugyklos klasė","Building list of files to restore …":"Kuriamas atkūriamų failų sąrašas...","Building partial temporary database …":"Kuriama dalinė laikina duomenų bazė...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Leidus nuotolinę prieigą, serveris atsakys į visas užklausas tinke. Jei įjungsite - įsitikinkite, kad kompiuteris yra už geros ugniasienės.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Pradžioje dėklo piktograma naudojama vartotojo aplinkos atidarymui. Tai užtikrina, kad aplinka būtu pasiekiama, kai tuo tarpu kiti turi įvesti slaptažodį. Jei norite, kad būtu reikalaujama slaptažodžio visais atvejais - įjunkite šį nustatymą.","Cache Files":"Talpyklos failai","Canary":"Canary","Cancel":"Atšaukti","Cannot move to existing file":"Negalima perkelti į esamo failo vietą","Changelog":"Pakeitimų žurnalas","Changelog for {{appname}} {{version}}":"Programos {{appname}} {{version}} pakeitimų žurnalas","Check failed:":"Patikrinimas nepavyko:","Check for updates now":"Ieškoti atnaujinimų dabar","Chose a storage type to get started":"Norėdami pradėti pasirinkite saugyklos tipą","Click the AuthID link to create an AuthID":"Norėdami sukurti AuthID paspauskite AuthID nuorodą","Click to set throttle options":"Spustelėkite, kad nustatyti akceleratoriaus parametrus","Compact now":"Suspausti dabar","Computer":"Kompiteris","Configuration file:":"Konfigūracijos failas:","Configuration:":"Konfigūracija:","Configure a new backup":"Derinti naują kopiją","Confirm delete":"Patvirtinkite tryminą","Confirmation required":"Reikalingas patvirtinimas","Connect":"Prisijungti","Connect now":"Prisijungti dabar","Connection lost":"Prisijungimas nutrūko","Connection worked!":"Prisijungti pavyko!","Container name":"Konteinerio pavadinimas","Container region":"Konteinerio regionas","Continue":"Tęsti","Continue without encryption":"Tęsti be šifravimo","Copied!":"Nukopijuota!","Copy":"Kopija","Copy Destination URL to Clipboard":"Kopijuoti paskirties URL į iškarpinę","Copy failed. Please manually copy the URL":"Kopijavimas nepavyko. Nukopijuokite URL rankiniu būdu","Core options":"Pagrindiniai parametrai","Counting ({{files}} files found, {{size}})":"Skaičiuojama, rasta failų: ({{files}}, {{size}})","Crashes only":"Tik lūžimai","Create folder?":"Sukurti aplanką?","Created new limited user":"Sukurtas naujas ribotas vartotojas","Current action:":"Dabartinis veiksmas:","Current file:":"Dabartinis failas:","Current version is {{versionname}} ({{versionnumber}})":"Dabartinė versija: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Nestandartinė S3 saugykla","Custom authentication url":"Nestandartinis autorizacijos URL","Custom backup retention":"Derintas kopijų saugojimo laikas","Custom location ({{server}})":"Nestandartinė vieta ({{server}})","Custom region for creating buckets":"Nestandartinis regionas kuriamoms saugykloms","Custom region value ({{region}})":"Nestandartinio regiono reikšmė ({{region}})","Custom server url ({{server}})":"Nestandartinis serverio url ({{server}})","Custom storage class ({{class}})":"Nestandartinė saugyklos klasė ({{class}})","Days":"Dienos","Default":"Numatyta","Default ({{channelname}})":"Numatytas ({{channelname}})","Default excludes":"Numatytos išimtys","Default options":"Numatyti parametrai","Delete":"Ištrinti","Delete backup":"Ištrinti kopiją","Delete backups that are older than":"Ištrinti kopijas, kurios senesnės nei","Delete local database":"Ištrinti lokalią duombazę","Delete remote files":"Ištrinti nutolusius failus","Delete the local database":"Ištrinti lokalią duombazę","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Trinti failus {{filecount}}, ({{filesize}}) iš nutolusios saugyklos?","Desktop":"Darbastalis","Destination":"Paskirtis","Destination path":"Kelias iki paskirties","Disabled":"Išjungta","Dismiss":"Neberodyti","Dismiss all":"Neberodyti visko","Display and color theme":"Vaizdo ir spalvų tema","Do you really want to delete the backup: \"{{name}}\" ?":"Ar tikrai norite ištrinti kopiją: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Ar tikrai norite ištrinti lokalią duomenų bazę: {{name}}","Done":"Baigta","Download":"Atsisiųsti","Duplicate option {{opt}}":"Pasikartojantis parametras {{opt}}","Duplicati Website":"Duplicati svetainė","Duplicati forum":"Duplicati forumas","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Kiekviena atsarginė kopija turi su ja susietą duomenų bazę, kurioje saugoma informacija apie nuotolinę saugykla vietiniame kompiuteryje.\nTrindami kopiją galite ištrinti ir lokalią duombazę, atkurti duomenis iš nutolusių failų vis tiek galėsite.\nJei lokalią duombazę naudojate kopijoms per komandinę eilutę, tada duombazę turėtumėt palikti.","Edit as list":"Taisyti kaip sąrašą","Edit as text":"Taisyti kaip tekstą","Encrypt file":"Šifruoti failą","Encryption":"Šifravimas","Encryption changed":"Šifravimas pakeistas","Enter URL":"Įveskite URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Aprašykite saugojimo strategiją. Sutrumpinimai D/W/Y reiškai dienos/savaitės/metai, U reiškia saugoti visada. Pavyzdys: 7D:1D,4W:1W,36M:1M. Šis pavyzdys reiškia, kad bus saugoma po vieną kopiją 7 dienas, po vieną kopiją kas 4 savaites ir viena ne senesnė nei 36 mėn. Galima aprašyti ir taip: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Jei naudojama šifravimo slapta frazė, įveskite ją","Enter configuration details":"Įveskite konfigūracijos detales","Enter encryption passphrase":"Įveskite šifravimo slaptą frazę","Enter expression here":"Įveskite čia išraišką","Enter the destination path":"Įveskite paskirties kelią","Error":"Klaida","Error!":"Klaida!","Errors and crashes":"Klaidos ir lūžimai","Exclude":"Išimtys","Exclude directories whose names contain":"Neįtraukti aplankų, kurių pavadinime yra","Exclude expression":"Neįtraukti išraiškos","Exclude file":"Neįtraukti failo","Exclude file extension":"Neįtraukti failų plėtinio","Exclude files whose names contain":"Neįtraukti failų, kurių pavadinime yra","Exclude folder":"Neįtraukti aplanko","Exclude regular expression":"Neįtraukti standartinės išraiškos","Existing file found":"Rastas esamas failas","Experimental":"Eksperimentinis","Export":"Eksportas","Export backup configuration":"Eksportuoti atsarginės kopijos konfigūraciją","Export configuration":"Eksportuoti konfigūraciją","External link":"Išorinė nuoroda","FTP (Alternative)":"FTP (Alternatyva)","Failed to build temporary database: {{message}}":"Nepavyko sukurti laikinos duomenų bazės: {{message}}","Failed to connect:":"Nepavyko prisijungti:","Failed to connect: {{message}}":"Nepavyko prisijungti: {{message}}","Failed to delete:":"Nepavyko ištrinti:","Failed to fetch path information: {{message}}":"Nepavyko gauti aplanko informacijos: {{message}}","Failed to read backup defaults:":"Nepavyko nuskaityti kopijos numatytus parametrus:","Failed to restore files: {{message}}":"Failų atkūrimas nepavyko: {{message}}","Failed to save:":"Išsaugoti nepavyko:","File":"Failas","Files larger than:":"Failai didesni nei:","Filters":"Filtrai","Finished!":"Baigta!","First run setup":"Pirmojo paleidimo sąranka","Folder":"Aplankas","Folder path":"Aplanko kelias","Fri":"Pn","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS projekto ID","General":"Pagrindiniai","General backup settings":"Pagrindiniai kopijos nustatymai","General options":"Pagrindiniai parametrai","Generate":"Generuoti","Generate IAM access policy":"Generuoti IAM prieigos politiką","Group email":"Grupės el. paštas","Hidden files":"Paslėpti failai","Hide":"Paslepti","Hide hidden folders":"Nerodyti paslėptų aplankų","Home":"Pradžia","Hostnames":"Serverio vardas","Hours":"Valandos","How do you want to handle existing files?":"Kaip elgtis su esamais failais?","Hyper-V Machine":"Hyper-V mašina","Hyper-V Machine:":"Hyper-V mašina:","Hyper-V Machines":"Hyper-V mašinos","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jai kopijos laikas praleistas, užduotis bus vykdoma pirmai progai pasitaikius.","If at least one newer backup is found, all backups older than this date are deleted.":"Rasta bent viena naujesnė kopija, visos kopijos senesnės nei ši data bus ištrintos.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jei nenurodysite kelio, visi failai bus išsaugoti pagrindiniame aplanke.\nAr tikrai to norite?","If you do not enter an API Key, the tenant name is required":"Jei nurodysite API raktą, būtina nurodyti savininką","Import":"Importas","Import Destination URL":"Importo paskirties URL","Import backup configuration":"Importuoti kopijos konfigūraciją","Import from a file":"Importas iš failo","Import metadata":"Importuoti meta duomenis","Include a file?":"Įtraukti failą?","Include expression":"Įtraukti išraišką","Include regular expression":"Įtraukti standartinę išraišką","Incorrect answer, try again":"Atsakymas neteisingas, bandykite dar kartą","Individual builds for developers only. Not for use with important data.":"Individualios versijos skirtos programuotojams. Netinkamos naudoti su svarbiais duomenimis.","Information":"Informacija","Invalid characters in path":"Kelio pavadinime yra netinkamų simbolių","Invalid retention time":"Netinkamas saugojimo laikas","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Prie kai kurių FTP serverių galima prisijungti be slaptažodžio.\nAr jūs įsitikinę, kad FTP serveris leidžia prisijungimus be slaptažodžio?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Saugoti nurodyta kiekį kopijų","Keep all backups":"Saugoti visas kopijas","Keystone API version":"Keystone API versija","Language in user interface":"Kalba vartotojo interfeise","Last month":"Praeitas mėnuo","Last successful backup:":"Paskutinė sėkminga kopija:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Paskutinis sėkmingas atkūrimas: {{time}} (užtruko {{duration || '0 sek.'}})","Latest":"Naujausias","Libraries":"Bibliotekos","Live":"Gyvai","Load a configuration from an exported job or a storage provider":"Įkelti konfigūraciją iš eksportuotos užduoties arba saugojimo paslaugų tiekėjo","Load destination from an exported job or a storage provider":"Įkelti paskirtį iš eksportuotos užduoties arba saugojimo paslaugų tiekėjo","Load older data":"Įkelti senesnius duomenis","Local Repository":"Vietinė saugykla","Local database path:":"Lokalios duomenų bazės kelias:","Local repository":"Vietinė saugykla","Local storage":"Lokali saugykla","Location":"Vieta","Location where buckets are created":"Vieta, kur sukuriamos saugyklos","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}}žurnalo duomenys","Log data from the server":"Žurnalo duomenys iš serverio","Log out":"Atsijungti","MByte":"MB","MByte/s":"MB/s","Maintenance":"Priežiūra","Manually type path":"Rankiniu būdu įveskite kelią","Max download speed":"Maksimalus atsisiuntimo greitis","Max upload speed":"Maksimalus įkėlimo greitis","Menu":"Meniu","Microsoft SQL Database:":"Microsoft SQL duomenų bazė:","Microsoft SQL Databases":"Microsoft SQL duomenų bazės","Minimum redundancy":"Minimalus perteklinių kopijų kiekis","Minimum redundancy is 1.0":"Minimalus perteklinių kopijų skaičius yra 1.0","Minutes":"Minutės","Missing name":"Trūksta pavadinimo","Missing passphrase":"Trūksta slaptos frazės","Missing sources":"Trūksta šaltinių","Mon":"Pr","Months":"Mėnesiai","Move existing database":"Perkelti esamą duomenų bazę","Move failed:":"Perkelti nepavyko:","My Documents":"Mano dokumentai","My Music":"Mano muzika","My Photos":"Mano nuotraukos","My Pictures":"Mano paveikslėliai","Name":"Vardas","Never":"Niekada","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Naujas vartotojo vardas {{user}}.\nNaujo riboto vartotojo prisijungimo duomenys atnaujinti","Next":"Kitas","Next scheduled run:":"Kitas planuojamas paleidimas:","Next scheduled task:":"Kita planuojama užduotis:","Next task:":"Kita užduotis","Next time":"Kitą kartą","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Anksčiau nebuvo nurodytas sertifikatas, su serverio administratoriumi patikrinkite kad raktas teisingas: {{key}} \n\nAr patvirtinate pateiktą mazgo raktą?","No editor found for the "{{backend}}" storage type":"Saugyklos tipui "{{backend}}" nerastas redaktorius","No encryption":"Be šifravimo","No items selected":"Nieko nepasirinkta","No items to restore, please select one or more items":"Nėra ko atkurti, pasirinkite vieną ar kelis elementus","No passphrase entered":"Neįvesta slapta frazė","No scheduled tasks":"Nėra planinių užduočių","Non-matching passphrase":"Netinkama slapta frazė","None / disabled":"Nieko / išjungta","Nothing will be deleted. The backup size will grow with each change.":"Niekas nebus trinama. Kopijos dydis didės su kiekvienu pasikeitimu.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Kai bus sukurta daugiau kopijų nei nurodyta - seniausia kopija bus ištrinta.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack objekto saugykla / Swift","Operating System":"Operacinė sistema","Operations:":"Operacijos","Optional authentication password":"Neprivalomas autorizavimo slaptažodis","Optional authentication username":"Neprivalomas autorizavimo vartotojas","Options":"Parametrai","Original location":"Originali vieta","Others":"Kiti","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Senos kopijos bus šalinamos automatiškai. Bus saugoma po vieną kopiją 7 dienas, po vieną kas 4 savaites ir po vieną kas 12 mėnesių. Visada bus bent viena likusi kopija.","Overwrite":"Perrašyti","Passphrase":"Slapta frazė","Passphrase (if encrypted)":"Slapta frazė (jei šifruota)","Passphrase changed":"Slapta frazė pakeista","Passphrases are not matching":"Slaptos frazės nesutampa","Password":"Slaptažodis","Path":"Kelias","Path not found":"Kelias nerastas","Path on server":"Kelias iki serverio","Path or subfolder in the bucket":"Kelias arba pakatalogis saugykloje","Pause":"Pauzė","Pause after startup or hibernation":"Pauzė po paleidimo ar ramybės būsenos","Pause options":"Pauzės parametrai","Permissions":"Leidimai","Pick location":"Pasirinkite vietą","Point to your backup files and restore from there":"Pasirinkite atsarginės kopijos failus ir atkurkite iš jos","Port":"Portas","Prevent tray icon automatic log-in":"Neleisti automatinio prisijungimo per dėklo piktogramą","Previous":"Ankstesnis","Progress:":"Progresas:","ProjectID is optional if the bucket exist":"ProjectID yra neprivalomas, jei egzistuoja saugykla","Proprietary":"Patentuota","Recreate (delete and repair)":"Perkurti (ištrinti ir taisyti)","Relative paths not allowed":"Santykiniai keliai neleidžiami","Reload":"Užkrauti iš naujo","Remote":"Nuotolinis","Remote Path":"Kelias iki nutolusio serverio","Remote Repository":"Nutolusi saugykla","Remote path":"Kelias iki nutolusio serverio","Remote repository":"Nutolusi saugykla","Remote volume size":"Nutolusio tomo dydis","Remove":"Pašalinti","Remove option":"Pašalinti parinktį","Repair":"Remontuoti","Repeat Passphrase":"Pakartokite slaptą frazę","Reporting:":"Ataskaitų teikimas:","Reset":"Atstatyti","Restore":"Atkurti","Restore files":"Atkurti failus","Restore from":"Atkurti iš","Restore from backup configuration":"Atkurti iš atsarginės kopijos konfigūracijos","Restore options":"Atkurimo parinktis","Restore read/write permissions":"Atkurti skaitymo/rašymo leidimus","Resume":"Tęsti","Run again every":"Vykdyti dar kartą kas","Run now":"Vykdyti dabar","Running commandline entry":"Vykdoma komandų eilutės komanda","Running task:":"Vykdoma užduotis:","S3 Compatible":"Suderinamas su S3","Same as the base install version: {{channelname}}":"Ta pati, kaip pagrindinė diegimo versija: {{channelname}}","Sat":"Šešt","Save":"Įrašyti","Save and repair":"Įrašyti ir taisyti","Save different versions with timestamp in file name":"Išsaugokite kitą versiją su laiko žymoma failo pavadinime","Save immediately":"Įrašyti nedelsiant","Schedule":"Tvarkaraštis","Search":"Paieška","Search for files":"Failų paieška","Seconds":"Sekundės","Select a log level and see messages as they happen:":"Pasirinkite žurnalo lygį ir peržiūrėkite pranešimus, kaip jie įvyksta:","Select files":"Pasirinkite failus","Server":"Serveris","Server and port":"Serveris ir portas","Server hostname or IP":"Serverio pavadinimas ir IP","Server is currently paused,":"Serveris šiuo metu pristabdytas","Server is currently paused, do you want to resume now?":"Serveris šiuo metu pristabdytas, ar norite pratęsti jo darbą?","Server password":"Serverio slaptažodis","Server paused":"Serveris pristabdytas","Server state properties":"Serverio būsenos parametrai","Settings":"Nustatymai","Show":"Rodyti","Show advanced editor":"Rodyti patobulintą redaktorių","Show hidden folders":"Rodyti paslėptus aplankus","Show log":"Rodyti žurnalą","Show treeview":"Rodyti medžio vaizdą","Sia server password":"Sia serverio slaptažodis","Smart backup retention":"Išmanus kopijų saugojimas","Some OpenStack providers allow an API key instead of a password and tenant name":"Kai kurie OpenStack tiekėjai vietoj slaptažodžio pateikia API raktą ir nuomininko vardą","Source Data":"Šaltinio duomenys","Source data":"Šaltinio duomenys","Source folders":"Šaltinio aplankai","Source:":"Šaltinis:","Specific builds for developers only. Not for use with important data.":"Specifinės versijos skirtos tik programuotojams. Netinkamos naudoti su svarbiais duomenimis.","Standard protocols":"Standartiniai protokolai","Stop after the current file":"Stabdyti po dabartinio failo","Stop now":"Stabdyti dabar","Stop running backup":"Stabdyti vykdomą atsarginę kopiją","Stop running task":"Stabdyti vykdomą užduotį","Stopping task:":"Stabdoma užduotis:","Storage Type":"Saugyklos tipas","Storage class":"Saugyklos klasė","Storage class for creating a bucket":"Saugyklos klasė saugyklos kūrimui","Stored":"Išsaugota","Strong":"Stiprus","Success":"Sėkmė","Sun":"Sekm","Symbolic link":"Simbolinė nuoroda","System Files":"Sisteminiai failai","System default ({{levelname}})":"Sistemos numatytasis ({{levelname}})","System files":"Sisteminiai failai","System info":"Sistemos informacija","System properties":"Sistemos ypatybės","TByte":"TByte","TByte/s":"TByte/sek","Task is running":"Užduotis vykdoma","Temporary Files":"Laikini failai","Temporary files":"Laikini failai","Test connection":"Patikrinti prisijungimą","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"'{{fieldname}}' yra netinkamas simbolis: {{character}} (reikšmė: {{value}}, pozicija: {{pos}})","The bucket name should be all lower-case, convert automatically?":"Saugyklos pavadinimas turi būti iš mažųjų raidžių, konvertuoti automatiškai?","The dark theme (by Michal)":"Tamsi tema (nuo Michal)","The default blue on white theme (by Alex)":"Numatyta mėlyna ant balto tema (nuo Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Aplankas {{folder}} neegzistuoja.\nSukurti jį dabar?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Serverio raktas pasikeitė, su administratoriumi patikrinkite ar jis geras, priešingu atveju jūsų duomenys gali būti perimti.\n\nAr norite PAKEISTI jūsų DABARTINĮ serverio raktą \"{{prev}}\" PATEIKTU serverio raktu: {{key}}?","The path does not appear to exist, do you want to add it anyway?":"Panašu, kad toks kelias neegzistuoja, vis tiek jį pridėti?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Kelias pasibaigia ne '{{dirsep}}' simboliu, tai reiškia, kad pridėjote failą, ne aplanką.\n\nAr norite pridėti nurodytą failą?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Kelias turi būti absoliutus, tai yra turi prasidėti simboliu '/'","The region parameter is only applied when creating a new bucket":"Regiono parametras taikomas tik naujai saugyklai","The region parameter is only used when creating a bucket":"Regiono parametras panaudojamas tik kuriant saugyklą","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Serverio sertifikatas negali būti patikrintas.\nAr patvirtinate SSL sertifikatą su maiša: {{hash}}?","The storage class affects the availability and price for a stored file":"Saugyklos klasė turi įtakos failo pasiekiamumui ir kainai","The target folder contains encrypted files, please supply the passphrase":"Paskirties duomenys užšifruoti, pateikite slaptą frazę","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Naudotojas turi per daug teisių. Ar norite sukurti naują naudotoją, su prieiga tik prie pasirinkto kelio?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ši kopija buvo sukurta kitoje operacinėje sistemoje. Atkuriant failus nenurodžius paskirties vietos - jie gali atsirasti netikėtose vietose. Ar tęsti be paskirties kelio?","This month":"Šį mėnesį","This week":"Šią savaitę","Throttle settings":"Greičio nustatymai","Thu":"Ket","To File":"Į failą","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Kad patvirtintumėte visų \"{{name}}\" nutolusių failų trynimą, įveskite žodį, kurį matote žemiau","To export without a passphrase, uncheck the \"Encrypt file\" box":"Kad eksportuoti be slaptos frazės, palikite nepažymėtą varnelę \"Šifruoti failą\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Kad apsisaugoti nuo įvairių DNS atakų, Duplicati riboje galimų serverių vardus pagal nurodytą sąrašą. IP adresai ir localhost visada leidžiami. Keli serverių vardai leidžiami atskiriant kabliataškiu. Jei leidžiamas serverio vardas yra su žvaigždute (*), leidžiami visi serverių vardai ir ši savybė išjungta. Jei laukas tuščias - leidžiami tik IP adresai ir localhost.","Today":"Šiandien","Trust host certificate?":"Pasitikite saito sertifikatu?","Trust server certificate?":"Pasitikite serverio sertifikatu?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Išbandykite naujas galimybes, prie kurių šiuo metu dirbame. Šiuo metu stabiliausia versija pasiekiama. Išbadykite duomenų atkūrimą prie naudodami su svarbiais duomenimis.","Tue":"An","Type to highlight files":"Rašykite, kad paryškinti failus","Unknown backup size and versions":"Nežinomas kopijos dydis ir versijos","Until resumed":"Kol bus pratęsta","Update channel":"Atnaujinimų kanalas","Update failed:":"Atnaujinimas nepavyko:","Updating with existing database":"Atnaujinama su egzistuojančia duomenų baze","Usage statistics":"Naudojimo statistika","Usage statistics, warnings, errors, and crashes":"Naudojimo statistika, įspėjimai, klaidos ir lūžimai","Use SSL":"Naudoti SSL","Use existing database?":"Naudoti turimą duomenų bazę?","Use weak passphrase":"Naudoti silpną slaptą frazę","Useless":"Nenaudinga","User data":"Naudotojo duomenys","User domain name":"Naudotojo domeno vardas","User has too many permissions":"Naudotojas turi per daug teisių","User interface settings":"Naudotojo aplinkos nustatymai","Username":"Naudotojo vardas","Verify files":"Tikrinti failus","Verifying answer":"Tikrinamas atsakymas","Very strong":"Labai stiprus","Very weak":"Labai silpnas","Visit us on":"Aplankykite mus","WARNING: This will prevent you from restoring the data in the future.":"DĖMESIO: Tai neleis ateityje atkurti duomenis.","Waiting for task to begin":"Laukiama kol prasidės užduotis","Warnings, errors and crashes":"Įspėjimai, klaidos ir lūžimai","We recommend that you encrypt all backups stored outside your system":"Rekomenduojame šifruoti visas kopijas, kurios saugomos už jūsų sistemos ribų","Weak":"Silpna","Weak passphrase":"Silpna slapta frazė","Wed":"Tre","Weeks":"Savaitės","Where do you want to restore from?":"Iš kur norite atkurti?","Where do you want to restore the files to?":"Kur norite atkurti failus?","Years":"Metai","Yes":"Taip","Yes, I have stored the passphrase safely":"Taip, aš saugiai išsaugojau slaptą frazę","Yes, I'm brave!":"Taip, aš drąsus!","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{Item.Backup.Metadata.TargetSizeString}} / {{$ count}} versija","{{Item.Backup.Metadata.TargetSizeString}} / {{$ count}} versijos","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versijų","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versijų"]}); - gettextCatalog.setStrings('lv', {"- pick an option -":"- izvēlieties iestatījumu -","...loading...":"...notiek ielāde...","AWS Access ID":"AWS Piekļuves ID","AWS Access Key":"AWS Piekļuves atslēga","AWS IAM Policy":"AWS IAM Politika","About":"Par","About {{appname}}":"Par {{appname}}","Access Key":"Piekļuves atslēga","Access denied":"Piekļuve liegta","Access to user interface":"Piekļuve lietotāja saskarnei","Account name":"Konta nosaukums","Add a new backup":"Pievienot jaunu dublējumkopiju","Add a path directly":"Pievienot tiešo ceļu","Add advanced option":"Pievienot pielāgotu iestatījumu","Add backup":"Pievienot dublējumkopiju","Add filter":"Pievienot filtru","Add path":"Pievienot ceļu","Added":"Pievienots","Adjust bucket name?":"Precizēt spaiņa iestatījumu?","Advanced Options":"Pielāgotas Opcijas","Advanced options":"Pielāgotas opcijas","Advanced:":"Pielāgots:","All Hyper-V Machines":"Visas Hyper-V Mašīnas","All Microsoft SQL Databases":"Visas Microsoft SQL Datubāzes","Allow remote access (requires restart)":"Atļaut attālinātu piekļuvi (nepieciešams restartēt programmu)","Allowed days":"Atļautās dienas","An existing file was found at the new location":"Tika atrasts jau esošs fails jaunajā atrašanās vietā","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Tika atrasts jau esošs fails jaunajā atrašanās vietā\nVai esat pārliecināts, ka vēlaties datubāzi novirzīt uz jau esošo failu?","Anonymous usage reports":"Anonīmas lietošanas atskaites","Applications":"Lietotnes","As Command-line":"Kā Komand-rinda","Authentication password":"Autentifikācijas parole","Authentication username":"Autentifikācijas lietotājvārds","Autogenerated passphrase":"Automātiski izveidota piekļuves frāze","Back":"Atpakaļ","Backup complete!":"Dublējumkopijas veidošana pabeigta!","Backup destination":"Dublējumkopijas mērķa atrašanās vieta","Backup location":"Dublējumkopijas atrašanās vieta","Backup retention":"Dublējumkopiju saglabāšanas ilgums","Backup:":"Dublējumkopija:","Beta":"Beta versija","Browse":"Pārlūkot","Browser default":"Pārlūka noklusējums","Bucket name":"Spaiņa nosaukums","Bucket storage class":"Spaiņa uzglabāšanas klase","Canary":"Canary","Cancel":"Atcelt","Changelog":"Izmaiņu žurnāls","Check failed:":"Pārbaude neizdevās:","Check for updates now":"Pārbaudīt atjauninājumus tagad","Click to set throttle options":"Uzklikšķiniet, lai uzstādītu ierobežojumus","Compact now":"Saspiest tagad","Computer":"Dators","Configuration file:":"Konfigurācijas fails:","Configuration:":"Konfigurācija:","Configure a new backup":"Konfigurēt jaunu dublējumkopiju","Confirm delete":"Apstiprināt dzēšanu","Confirmation required":"Nepieciešams apstiprinājums","Connect":"Pieslēgties","Connect now":"Pieslēgties tagad","Connecting to server …":"Pieslēdzas serverim...","Connection lost":"Savienojums ir zudis","Connection worked!":"Savienojums strādā!","Continue":"Turpināt","Continue without encryption":"Turpināt bez šifrēšanas","Copied!":"Nokopēts!","Core options":"Pamata opcijas","Crashes only":"Tikai avārijas","Create folder?":"Izveidot mapi?","Custom region for creating buckets":"Specifiskais reģions spaiņu izveidei","Days":"Dienas","Default":"Noklusējums","Default options":"Noklusējuma iestatījumi","Delete":"Izdzēst","Delete backup":"Izdzēst dublējumkopiju","Delete local database":"Izdzēst lokālo datubāzi","Delete remote files":"Dzēst attālinātos failus","Delete the local database":"Izdzēst lokālo datubāzi","Desktop":"Darbavirsma","Destination":"Mērķis","Disabled":"Atspējots","Dismiss":"Atmest","Display and color theme":"Displeja un krāsu motīvs","Done":"Pabeigts","Download":"Lejupielādēt","Duplicati Website":"Duplicati tīmekļa vietne","Duplicati forum":"Duplicati forums","Edit as list":"Rediģēt kā sarakstu","Edit as text":"Rediģēt kā tekstu","Encrypt file":"Šifrēt failu","Encryption":"Šifrēšana","Encryption changed":"Šifrēšana mainīta","Enter URL":"Ievadiet URL","Enter backup passphrase, if any":"Ievadiet dublējumkopijas pieejas frāzi, ja tāda eksistē","Enter configuration details":"Ievadiet konfigurācijas detaļas","Enter encryption passphrase":"Ievadiet pieejas frāzi šifrēšanai","Enter the destination path":"Ievadiet mērķa atrašanās vietu","Error":"Kļūda","Error!":"Kļūda!","Errors and crashes":"Kļūdas un avārijas","Experimental":"Eksperimentāls","Export":"Eksportēt","Export configuration":"Eksportēt konfigurāciju","FTP (Alternative)":"FTP (Alternatīvs)","Failed to connect:":"Neizdevās izveidot savienojumu:","File":"Fails","Files larger than:":"Faili lielāki par:","Filters":"Filtrs","Finished!":"Pabeigts!","Folder":"Mape","General":"Vispārīgi","General backup settings":"Vispārīgie dublējumkopiju iestatījumi","General options":"Vispārīgie iestatījumi","Generate":"Izveidot","Hidden files":"Paslēptie faili","Hide":"Paslēpt","Hide hidden folders":"Paslēpt paslēptās mapes","Home":"Mājas","Hours":"Stundas","How do you want to handle existing files?":"Kā jūs vēlaties rīkoties ar jau esošajiem failiem?","Hyper-V Machine":"Hyper-V Mašīna","Hyper-V Machine:":"Hyper-V Mašīna:","Hyper-V Machines":"Hyper-V Mašīnas","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ja tika nokavēts datums, uzdevums tiks palaists cik ātri vien iespējams.","Import":"Importēt","Import from a file":"Pievienot no faila","Incorrect answer, try again":"Nepareiza atbilde, mēģiniet vēlreiz","Information":"Informācija","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Ir iespējams pievienoties pie kāda FTP servera bez paroles.\nVai esat pārliecināts, ka jūsu FTP serveris atbalsta bez-paroles pieslēgšanos?","Language in user interface":"Lietotāja saskarnes valoda:","Last month":"Pagājušais mēnesis","Latest":"Pēdējais","Libraries":"Bibliotēkas","Load older data":"Ielādēt vecākus datus","Local database path:":"Ceļš uz lokālo datubāzi:","Local storage":"Lokālā krātuve","Location":"Atrašanās vieta","Log out":"Izrakstīties","Maintenance":"Apkope","Max download speed":"Maksimālais lejupielādes ātrums","Max upload speed":"Maksimālais augšupielādes ātrums","Menu":"Izvēlne","Minutes":"Minūtes","Missing passphrase":"Trūkst pieejas frāze","Modified":"Modificēts","Mon":"Pirm","Months":"Mēneši","Move existing database":"Pārvietot esošo datubāzi","Move failed:":"Pārvietošana neizdevās:","My Documents":"Mani dokumenti","My Music":"Mana mūzika","My Photos":"Mani fotoattēli","My Pictures":"Mani attēli","Never":"Nekad","Next":"Nākamais","Next scheduled run:":"Nākamā plānotā norise","Next scheduled task:":"Nākamais plānotais uzdevums:","Next task:":"Nākamais uzdevums:","Next time":"Nākamreiz","No":"Nē","No encryption":"Nav šifrešanas","No items selected":"Nav izvēlētu vienību","No items to restore, please select one or more items":"Nav vienību ko atjaunot, lūdzu izvēlieties vienu vai vairākas vienības","No passphrase entered":"Pieejas frāze nav ievadīta","No scheduled tasks":"Nav ieplānotu uzdevumu","Non-matching passphrase":"Nesakrītoša pieejas frāze","None / disabled":"Nav / Atspējots","OK":"Labi","Operations:":"Darbības:","Optional authentication password":"Neobligāta autentifikācijas parole","Options":"Iestatījumi","Original location":"Sākotnējā atrašanās vieta","Others":"Citi","Overwrite":"Pārrakstīt","Passphrase":"Pieejas frāze","Passphrase (if encrypted)":"Pieejas frāze (ja šifrēts)","Passphrase changed":"Pieejas frāze nomainīta","Passphrases are not matching":"Pieejas frāzes nesakrīt","Password":"Parole","Path not found":"Ceļš nav atrasts","Path on server":"Ceļs uz servera","Pause":"Pauzēt","Pause options":"Pauzēt opcijas","Permissions":"Atļaujas","Port":"Ports","Reload":"Pārlādēt","Remote":"Attālināts","Remove":"Noņemt","Remove option":"Noņemt iestatījumu","Repair":"Salabot","Repeat Passphrase":"Atkārtot pieejas frāzi","Reset":"Attiestatīt","Restore":"Atgūt","Restore files":"Atgūt failus","Restore options":"Atjaunot opcijas","Restore read/write permissions":"Atjaunot lasīšanas/rakstīšanas atļaujas","Resume":"Turpināt","Run again every":"Palaist atkal katru","Run now":"Palaist tagad","Sat":"Sest","Save":"Saglabāt","Save and repair":"Saglabāt un salabot","Save immediately":"Saglabāt uzreiz","Search":"Meklēt","Search for files":"Meklēt failus","Seconds":"sekundes","Select files":"Izvēlēties failus","Server":"Serveris","Server and port":"Serveris un ports","Server hostname or IP":"Resursdatora nosaukums vai IP adrese","Server password":"Servera parole","Settings":"Iestatījumi","Show":"Parādīt","Show hidden folders":"Parādīt paslēptās mapes","Show log":"Parādīt žurnālu","Sia server password":"Sia servera parole","Source Data":"Avota Dati","Source data":"Avota dati","Source folders":"Avota mapes","Source:":"Avots:","Stop now":"Pātraukt tagad","Stop running task":"Pārtraukt uzdevuma izpildi","Stopping task:":"Aptur uzdevumu:","Storage Type":"Krātuves Tips","Strong":"Spēcīgs","Sun":"Svēt","Symbolic link":"Simboliskā saite","System files":"Sistēmas faili","System info":"Sistēmas informācija","System properties":"Sistēmas īpašības","Task is running":"Uzdevums ir palaists","Temporary files":"Pagaidu faili","Test connection":"Pārbaudīt savienojumu","The dark theme (by Michal)":"Tumšais motīvs (veidoja Michal)","The default blue on white theme (by Alex)":"Noklusējuma zils uz balta motīvs (veidoja Alex)","This month":"Šis mēnesis","This week":"Šī diena","Thu":"Cetr","Today":"Šodien","Tue":"Otr","Update channel":"Atjauninājumu kanāls","Update failed:":"Atjaunināšana neizdevās:","Usage statistics":"Izmantošanas statistika","Use SSL":"Izmantot SSL","Use weak passphrase":"Lietot vāju pieejas frāzi","Useless":"Bezjēdzīgs","User data":"Lietotāja dati","User interface settings":"Lietotāja saskarnes iestatījumi","Username":"Lietotājvārds","Verify files":"Pārbaudīt failus","Very strong":"Ļoti stiprs","Very weak":"Ļoti vājš","Warnings, errors and crashes":"Brīdinājumi, kļūdas un avārijas","We recommend that you encrypt all backups stored outside your system":"Mēs iesakām jums šifrēt visas dublējumkopijas, kuras tiek uzglabātas ārpus jūsu sistēmas","Weak":"Vājš","Weak passphrase":"Vāja pieejas frāze","Wed":"Treš","Weeks":"Nedēļas","Years":"Gadi","Yes":"Jā","Yes, I have stored the passphrase safely":"Jā, esmu noglabājais pieejas frāzi droši","Yes, I'm brave!":"Jā, esmu drosmīgs!","Yes, please break my backup!":"Jā, lūdzu salauziet manu dublējumkopiju!","Yesterday":"Vakardiena","You must enter a name for the backup":"Nepieciešams ievadīt dublējumkopijas nosaukumu","You must enter a passphrase or disable encryption":"Jums nepieciešams ievadīt pieejas frāzi vai atspējot šifrēšanu","You must fill in the password":"Nepieciešams ievadīt paroli!","You must specify a path":"Jums jānorāda ceļš","Your passphrase is easy to guess. Consider changing passphrase.":"Jūsu pieejas frāzi ir vienkārsi uzminēt. Apdomājiet pieejas frāzes nomaiņu.","bucket/folder/subfolder":"spainis/mape/apakšmape","byte":"baits","byte/s":"baiti/sekundē","resume now":"turpināt tagad","{{number}} Hour":"{{number}} Stunda","{{number}} Minutes":"{{number}} Minūtes"}); - gettextCatalog.setStrings('nl_NL', {"(1 error{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["({{$count}} errors{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} errors{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(1 warning{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["({{$count}} warnings{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} warnings{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(interrupted)":"(onderbroken)","- pick an option -":" - kies een optie -","...loading...":"...laden...","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"Let op: Sia zal later nog steeds de redundantie verhogen zolang u verbonden bent met uw hosts."," Edit as text":" Bewerk als tekst"," Edit as text":" Bewerk als tekst","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

Verbinding met server is afgewezen vanwege ongeldige authenticatie.

\n

Meld u opnieuw aan of open de pagina opnieuw vanuit het Systeemvak (indien van toepassing)

","API key":"API sleutel","AWS Access ID":"AWS Toegangs ID","AWS Access Key":"AWS Toegangssleutel","AWS IAM Policy":"AWS IAM Beleid","About":"Over","About {{appname}}":"Over {{appname}}","Access Key":"Toegangssleutel","Access Key ID":"Toegangssleutel-ID","Access Key Secret":"Toegangssleutel Geheim","Access denied":"Toegang geweigerd","Access grant":"Toegang verleend","Access key":"Toegangssleutel","Access to user interface":"Toegang tot gebruikersomgeving","Account name":"Accountnaam","Add a new backup":"Nieuwe back-up toevoegen","Add a path directly":"Voeg een pad rechtstreeks toe","Add advanced option":"Voeg geavanceerde optie toe","Add backup":"Back-up toevoegen","Add filter":"Voeg filter toe","Add path":"Voeg pad toe","Added":"Toegevoegd","Adjust bucket name?":"Bucket naam aanpassen?","Advanced Options":"Geavanceerde Opties","Advanced options":"Geavanceerde opties","Advanced:":"Geavanceerd:","Aliyun OSS Endpoint":"Aliyun OSS Eindpunt","Aliyun OSS documents and resources":"Aliyun OSS documenten en bronnen","All Hyper-V Machines":"Alle Hyper-V Machines","All Microsoft SQL Databases":"Alle Microsoft SQL Databases","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle gebruiksrapporten worden anoniem verstuurd en bevatten geen enkele persoonlijke informatie. Ze bevatten informatie over hardware en besturingssysteem, het type backend, back-up tijdsduur, totale grootte van brongegevens en soortgelijke gegevens. Ze bevatten geen paden, bestandsnamen, gebruikersnamen, wachtwoorden of soortgelijke gevoelige informatie.","Allow remote access (requires restart)":"Remote toegang toestaan (herstart vereist)","Allowed days":"Alleen op deze dagen","An existing file was found at the new location":"Een bestaand bestand was gevonden op de nieuwe locatie","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Een bestaand bestand was gevonden op de nieuwe locatie. Weet u zeker dat de database moet verwijzen naar een bestaand bestand?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Een bestaande lokale database voor de opslag is gevonden.\nHergebruik van de database zal toestaan dat de opdrachtregel- en server instances werken op dezelfde remote opslag.\n\nWilt u de bestaande database gebruiken?","Anonymous usage reports":"Anonieme gebruiksrapporten","Applications":"Toepassingen","As Command-line":"Als Opdrachtregel","AuthID":"AuthID","Authentication method":"Authenticatiemethode","Authentication method ({{auth_method}})":"Authenticatiemethode ({{auth_method}})","Authentication password":"Authenticatie wachtwoord","Authentication username":"Authenticatie gebruikersnaam","Autogenerated passphrase":"Automatisch gegenereerde wachtwoordzin","Automatically run backups":"Automatisch back-ups uitvoeren","B2 Application ID":"B2 Applicatie ID","B2 Application Key":"B2 Applicatiesleutel","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Applicatie ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Applicatiesleutel","Back":"Vorige","Backend modules:

{{item.Key}}

":"Backend modules:

{{item.Key}}

","Backup complete!":"Back-up compleet!","Backup destination":"Back-updoel","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"Back-up is gecodeerd maar er is geen wachtwoordzin beschikbaar. Typ hieronder een wachtwoordzin om te gebruiken voor het herstellen van uw bestanden, of, in het geval van GPG-codering, laat dit leeg om de gpg-code de wachtwoordzin op te laten halen door een beroep te doen op de keychain van uw systeem.","Backup location":"Back-up locatie","Backup retention":"Back-up retentie","Backup:":"Back-up:","Beta":"Beta","Broken access":"Verbroken toegang","Browse":"Bladeren","Browser default":"Browser standaard","Bucket create location":"Bucket aanmaaklocatie","Bucket name":"Bucketnaam","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Bucket-naam kan alleen tussen 3 en 63 tekens lang zijn en mag alleen kleine letters, cijfers, punten en mintekens bevatten","Bucket region":"Bucket-regio","Bucket region ap-guangzhou":"Bucket-regio ap-guangzhou","Bucket storage class":"Bucket opslagklasse","Bucket, format: BucketName-APPID":"Bucket, formaat: BucketNaam-APPID","Building list of files to restore …":"Opbouwen lijst te herstellen bestanden ...","Building partial temporary database …":"Opbouwen gedeeltelijke tijdelijke database ...","Busy …":"Bezig …","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Door remote toegang toe te staan, luistert de server naar aanvragen van een willekeurige machine op het netwerk. Verzeker u ervan dat de computer wordt gebruikt op een netwerk dat wordt beschermd door een veilig ingestelde firewall als u deze optie wilt inschakelen.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Standaard opent het systeemvak-pictogram de gebruikersomgeving met een token dat de gebruikersomgeving ontgrendelt. Dit zorgt ervoor dat u toegang heeft tot de gebruikersomgeving vanaf het systeemvak-pictogram, zonder dat u anderen hoeft te vragen het wachtwoord in te voeren. Schakel deze optie in als u er de voorkeur aan geeft zelf het wachtwoord in te voeren, zelfs wanneer de gebruikersomgeving wordt geopend vanuit het systeemvak-pictogram.","COS App ID":"COS App ID","COS Path or subfolder in the bucket":"COS Pad of submap in de bucket","COS Secret ID":"COS Geheim ID","COS Secret Key":"COS Geheime Sleutel","Cache Files":"Cache bestanden","Canary":"Canary","Cancel":"Annuleren","Cannot include \"{{text}}\"":"Mag \"{{text}}\" niet bevatten","Cannot move to existing file":"Kan niet verplaatsen naar bestaand bestand","Cannot specify filter include or excludes in extra options":"Kan geen in- of uitsluitingsfilters opnemen in extra opties","Change server passphrase":"Wijzig server wachtwoordzin","Changelog":"Aanpassingen-log","Changelog for {{appname}} {{version}}":"Aanpassingen-log voor {{appname}} {{version}}","Check failed:":"Controle mislukt:","Check for updates now":"Controleer nu op updates","Checking for updates …":"Controleren op updates ...","Checking …":"Controleren …","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"Kies 1.0 voor snelle back-up, 1.5 voor redelijke betrouwbaarheid, 2.0 voor veiliger uploaden maar trage back-up.","Chose a storage type to get started":"Kies een opslagtype om aan de slag te gaan","Click the AuthID link to create an AuthID":"Klik op de AuthID link om een AuthID aan te maken","Click to set throttle options":"Klik om bandbreedte-opties in te stellen","Client library to use":"Te gebruiken client-blibliotheek","Cloud API Secret ID":"Cloud API Geheim ID","Cloud API Secret Key":"Cloud API Geheime Sleutel","Command":"Commando","Commandline arguments":"Opdrachtregel-argumenten","Commandline …":"Opdrachtregel ...","Compact Phase":"Opruimen Subtaak","Compact now":"Nu opruimen","Compacting remote data …":"Opschonen remote gegevens ...","Complete log":"Compleet log","Completing backup …":"Afronden back-up ...","Completing previous backup …":"Afronden vorige back-up ...","Compression modules:

{{item.Key}}

":"Compressiemodules:

{{item.Key}}

","Computer":"Computer","Configuration file:":"Configuratiebestand","Configuration:":"Configuratie:","Configure a new backup":"Een nieuwe back-up instellen","Confirm delete":"Bevestig verwijderen","Confirm encryption passphrase":"Bevestig wachtwoordzin voor versleuteling","Confirm new password":"Bevestig nieuw wachtwoord","Confirm passphrase":"Bevestig wachtwoordzin","Confirmation required":"Bevestiging vereist","Connect":"Verbind","Connect now":"Verbind nu","Connecting to server …":"Verbinden met server ...","Connecting to task …":"Verbinden met taak …","Connecting …":"Verbinden …","Connection lost":"Verbinding verbroken","Connection worked!":"Verbinding werkt!","Container name":"Containernaam","Container region":"Container-regio","Continue":"Volgende","Continue without encryption":"Ga verder zonder versleuteling","Copied!":"Gekopieerd!","Copy":"Kopie","Copy Destination URL to Clipboard":"Kopieer doel URL naar Klembord","Copy URL":"Kopie URL","Copy failed. Please manually copy the URL":"Kopiëren mislukt. Kopieer de URL handmatig","Copy log":"Kopie log","Core options":"Kern-opties","Counting ({{files}} files found, {{size}})":"Tellen ({{files}} bestanden gevonden, {{size}})","Crashes only":"Alleen crashes","Create bug report …":"Bug rapport maken ...","Create folder?":"Map aanmaken?","Created new limited user":"Nieuwe beperkte gebruiker aangemaakt","Creating bug report …":"Bug rapport maken ...","Creating new user with limited access …":"Nieuwe gebruiker met beperkte toegang aanmaken ...","Creating target folders …":"Doelmappen aanmaken ...","Creating temporary backup …":"Tijdelijke back-up aanmaken ...","Creating user …":"Gebruiker aanmaken …","Current action:":"Huidige actie:","Current file:":"Huidig bestand:","Current version is {{versionname}} ({{versionnumber}})":"Huidige versie is {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Aangepaste S3 endpoint","Custom Satellite":"Aangepaste Satellite","Custom Satellite ({{satellite}})":"Aangepaste Satellite ({{satellite}})","Custom authentication url":"Aangepaste authenticatie url","Custom backup retention":"Aangepaste back-up retentie","Custom bucket storage class":"Aangepaste bucket-opslagklasse","Custom location ({{server}})":"Aangepaste locatie ({{server}})","Custom region for creating buckets":"Aangepaste regio voor het aanmaken van buckets","Custom region value ({{region}})":"Aangepaste regio waarde ({{region}})","Custom server url ({{server}})":"Aangepaste server url ({{server}})","Custom storage class ({{class}})":"Aangepaste opslagklasse ({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"VEROUDERD: {{getDeprecationMessage(item)}}","Database …":"Database ...","Days":"Dagen","Default":"Standaard","Default ({{channelname}})":"Standaard ({{channelname}})","Default excludes":"Standaard uitsluitingen","Default options":"Standaard opties","Default value: \"{{getDefaultValue(item)}}\"":"Standaardwaarde: \"{{getDefaultValue(item)}}\"","Delete":"Verwijderen","Delete Phase (Old Backup Versions)":"Verwijderen Subtaak (Oude Back-upversies)","Delete backup":"Verwijder back-up","Delete backups that are older than":"Verwijder back-ups die ouder zijn dan","Delete local database":"Verwijder lokale database","Delete remote files":"Verwijder remote bestanden","Delete the local database":"Verwijder de lokale database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} bestanden ({{filesize}}) van de remote opslag verwijderen?","Delete …":"Verwijderen ...","Deleted":"Verwijderd","Deleted Versions":"Verwijderde versies","Deleted files":"Verwijderde bestanden","Deleting remote files …":"Remote bestanden verwijderen ...","Deleting unwanted files …":"Ongewenste bestanden verwijderen ...","Description (optional)":"Omschrijving (optioneel)","Description:":"Omschrijving:","Desktop":"Desktop","Destination":"Doel","Destination path":"Doelpad","Direct restore from backup files …":"Direct herstellen vanuit back-upbestanden …","Directory path":"Directory-pad","Disabled":"Uitgeschakeld","Dismiss":"Afwijzen","Dismiss all":"Alles afwijzen","Display and color theme":"Weergave en kleurenschema","Do you really want to delete the backup: \"{{name}}\" ?":"Wilt u de back-up \"{{name}}\" echt verwijderen?","Do you really want to delete the local database for: {{name}}":"Wilt u de lokale database voor: {{name}} echt verwijderen?","Domain name":"Domeinnaam","Done":"Klaar","Download":"Download","Downloaded files":"Gedownloade bestanden","Downloading files …":"Bestanden downloaden ...","Downloading update…":"Update downloaden ...","Duplicate option {{opt}}":"Dubbele optie {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati forum","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicati moet worden beveiligd met een wachtwoordzin en er is een willekeurige wachtwoordzin voor u gegenereerd.\nAls u Duplicati opent via het systeemvakpictogram, heeft u geen wachtwoordzin nodig, maar als u van plan bent het te openen vanaf een andere locatie moet u een wachtwoordzin instellen die u kent.\nWilt u nu een wachtwoordzin instellen?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati zal bij het starten worden uitgevoerd, maar zolang als opgegeven gepauzeerd blijven. Duplicati zal een minimale hoeveelheid systeembronnen gebruiken en er zullen geen back-ups gestart worden.","Duration":"Tijdsduur","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Iedere back-up heeft een lokale database waarmee het geassocieerd is, die informatie opslaat over de remote back-up op de lokale machine.\nBij het verwijderen van een back-up kan eveneens de lokale database verwijderd worden, zonder dat dit invloed heeft op de mogelijkheid van het terugzetten van de remote bestanden.\nAls de lokale database gebruikt wordt voor back-ups vanaf de opdrachtregel, moet de database behouden blijven.","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"Aan elke back-up is een lokale database gekoppeld, waarin informatie over de externe back-up wordt opgeslagen op de lokale machine. Dit maakt het sneller om veel bewerkingen uit te voeren en vermindert de hoeveelheid gegevens die voor elke bewerking moet worden gedownload.","Edit as list":"Bewerk als lijst","Edit as text":"Bewerk als tekst","Edit …":"Bewerken ...","Email address of the Office 365 group":"E-mailadres van de Office 365-groep","Encrypt file":"Versleutel bestand","Encryption":"Versleuteling","Encryption changed":"Versleuteling aangepast","Encryption modules:

{{item.Key}}

":"Coderingsmodules:

{{item.Key}}

","Encryption passphrase":"Encryptie wachtwoordzin","Encryption passphrase (for verification)":"Coderings-wachtwoordzin (voor verificatie)","End":"Einde","Enter URL":"Geef URL in","Enter a backup destination URL:":"Voer de URL van een back-updoel in:","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Geef handmatig een retentie-strategie op. Tijdelijke aanduidingen zijn D/W/Y voor dagen/weken/jaren en U voor onbeperkt. De syntaxis is: 7D:1D,4W:1W,36M:1M. Dit voorbeeld bewaart één back-up voor elk van de volgende 7 dagen, één voor elk van de volgende 4 weken, en één voor elk van de volgende 36 maanden. Dit kan eveneens worden geschreven als 1W:1D,1M:1W,3Y:1M.","Enter a url, or click the "Target URL >" link":"Geef een URL in, of klik de "Doel-URL >" link","Enter backup passphrase, if any":"Geef eventueel back-up wachtwoordzin in","Enter configuration details":"Voer configuratie-details in","Enter encryption passphrase":"Geef een wachtwoordzin in voor versleuteling","Enter expression here":"Geef uitdrukking hier in","Enter one argument per line without quotes, e.g. *.txt":"Geef één argument per regel op zonder aanhalingstekens, bijv. *.txt","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"Geef één optie op in opdrachtregelformaat, bijv. --dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"Geef één optie op in opdrachtregelformaat, bijv. {0}","Enter the destination path":"Geef het doelpad in","Error":"Fout","Error!":"Fout!","Errors and crashes":"Fouten en crashes","Examined":"Onderzocht","Exclude":"Uitsluiten","Exclude directories whose names contain":"Sluit mappen uit waarvan de naam bevat:","Exclude expression":"Sluit uitdrukking uit","Exclude file":"Sluit bestand uit","Exclude file extension":"Sluit bestandsextensie uit","Exclude files whose names contain":"Sluit bestanden uit waarvan de naam bevat:","Exclude filter group":"Sluit filtergroep uit","Exclude folder":"Sluit map uit","Exclude regular expression":"Sluit reguliere expressie uit","Existing file found":"Bestaand bestand gevonden","Experimental":"Experimenteel","Export":"Exporteer","Export backup configuration":"Exporteer back-upconfiguratie","Export configuration":"Exporteer configuratie","Export passwords":"Exporteer wachtwoorden","Export …":"Exporteren ...","Exporting …":"Exporteren ...","External link":"Externe link","FTP (Alternative)":"FTP (Alternatief)","Failed to build temporary database: {{message}}":"Opbouwen tijdelijke database mislukt: {{message}}","Failed to connect:":"Verbinden mislukt:","Failed to connect: {{message}}":"Verbinden mislukt: {{message}}","Failed to delete:":"Verwijderen mislukt:","Failed to fetch path information: {{message}}":"Ophalen pad-informatie mislukt: {{message}}","Failed to find backup:":"Back-up kon niet worden gevonden:","Failed to get bug report URL: {{message}}":"Kan de URL van het bugrapport niet ophalen: {{message}}","Failed to import: {{message}}":"Kan niet importeren: {{message}}","Failed to read backup defaults:":"Standaard instellingen voor back-up inlezen mislukt:","Failed to read file: {{message}}":"Kan bestand niet lezen: {{message}}","Failed to restore files: {{message}}":"Herstellen bestanden mislukt: {{message}}","Failed to save:":"Opslaan mislukt:","Fatal error, no statistics collected":"Fatale fout, geen statistieken verzameld","Fetching path information …":"Ophalen pad-informatie ...","File":"Bestand","Files larger than:":"Bestanden groter dan:","Filters":"Filters","Finished!":"Klaar!","First run setup":"Instellen voor eerste gebruik","Folder":"Map","Folder in the bucket":"Map in de bucket","Folder path":"Map-pad","Folder path name":"Map-padnaam","Fri":"Vrijdag","Full destination path, including the server name, but without https":"Volledig bestemmingspad, inclusief de servernaam, maar zonder https","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"Algemeen","General backup settings":"Algemene back-upinstellingen","General options":"Algemene opties","Generate":"Genereer","Generate IAM access policy":"Genereer IAM toegangsbeleid","Getting file versions …":"Bestandsversies ophalen ...","Group email":"Groep e-mail","Hidden files":"Verborgen bestanden","Hide":"Verberg","Hide hidden folders":"Verberg verborgen bestanden","Home":"Start","Hostnames":"hostnamen","Hours":"Uur","How do you want to handle existing files?":"Hoe wilt u omgaan met bestaande bestanden?","Hyper-V Machine":"Hyper-V Machine","Hyper-V Machine:":"Hyper-V Machine:","Hyper-V Machines":"Hyper-V Machines","ID:":"ID:","IDrive Sync directory path":"IDrive Sync directory-pad","IDrive e2 Access Key ID":"IDrive e2 Toegangssleutel-ID","IDrive e2 Access Key Secret":"IDrive e2 Toegangssleutel-geheim","If a date was missed, the job will run as soon as possible.":"Als een geplande taak werd overgeslagen, zal de taak zo snel mogelijk na het geplande tijdstip starten.","If at least one newer backup is found, all backups older than this date are deleted.":"Als tenminste één nieuwere back-up is gevonden, zullen alle back-ups die ouder zijn dan deze datum worden verwijderd.","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"Als de back-up en de externe opslag niet volledig gesynchroniseerd zijn, vereist Duplicati dat u een reparatiebewerking uitvoert om de database te synchroniseren. Als de reparatie mislukt, kunt u de lokale database verwijderen en opnieuw genereren.","If the backup file was not downloaded automatically, right click and choose "Save as …".":"Als het back-upbestand niet automatisch is gedownload, klik met rechts en kies "Opslaan als ...".","If the backup file was not downloaded automatically, right click and choose "Save as …".":"Als het back-upbestand niet automatisch is gedownload, klik met rechts en kies "Opslaan als ...".","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Als u geen pad ingeeft, zullen alle bestanden opgeslagen worden in de login map.\nWeet u zeker dat dit is wat u wilt?","If you do not enter an API Key, the tenant name is required":"Als u geen API sleutel ingeeft, is een tenant naam vereist","If you want to use the backup later, you can export the configuration before deleting it.":"Als u de back-up later wilt gebruiken, kunt u de configuratie exporteren alvorens hem te verwijderen.","Import":"Importeer","Import Destination URL":"Importeer Doel URL","Import URL":"Import URL","Import backup configuration":"Importeer back-upconfiguratie","Import from a file":"Importeer vanuit een bestand","Import metadata":"Importeer metadata","Importing …":"Importeren ...","Include a file?":"Een bestand opnemen?","Include expression":"Uitdrukking opnemen","Include regular expression":"Reguliere expressie opnemen","Incorrect answer, try again":"Incorrect antwoord, probeer opnieuw","Individual builds for developers only. Not for use with important data.":"Individuele builds alleen voor ontwikkelaars. Niet voor gebruik met belangrijke gegevens.","Information":"Informatie","Interrupted, no statistics collected":"Onderbroken, geen statistieken verzameld","Invalid characters in path":"Ongeldige tekens in pad","Invalid retention time":"Ongeldige retentietijd","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Het is mogelijk te verbinden met sommige FTP servers zonder een wachtwoord.\nWeet u zeker dat uw FTP server aanmelden zonder wachtwoord ondersteunt?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Behoud een specifiek aantal back-ups","Keep all backups":"Behoud alle back-ups","Keystone API version":"Keystone API versie","Language in user interface":"Taal in gebruikersomgeving","Last month":"Vorige maand","Last successful backup:":"Laatste succesvolle back-up:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Laatste succesvolle hersteloperatie: {{time}} (duurde {{duration || '0 seconden'}})","Latest":"Laatste","Libraries":"Bibliotheken","Listing backup dates …":"Back-updatums weergeven ...","Listing remote files for purge …":"Remote bestanden tonen voor wissen ...","Listing remote files …":"Remote bestanden weergeven ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Laad een configuratie vanuit een geëxporteerde taak of een opslagprovider","Load destination from an exported job or a storage provider":"Laad doel vanuit een geëxporteerde taak of een opslagprovider","Load older data":"Laad oudere gegevens","Loading remote storage usage …":"Gebruik van externe opslag laden …","Loading …":"Laden ...","Local Repository":"Lokale Opslagplaats","Local database for {{Backup.Backup.Name}}…loading…":"Lokale database voor {{Backup.Backup.Name}}…laden…","Local database path:":"Lokaal database-pad:","Local repository":"Lokale opslagplaats","Local storage":"Lokale opslag","Location":"Locatie","Location where buckets are created":"Locatie waar buckets gemaakt worden","Log data for {{Backup.Backup.Name}}":"Log gegevens voor {{Backup.Backup.Name}}","Log data from the server":"Log gegevens van de server","Log in":"Inloggen","Log out":"Uitloggen","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Onderhoud","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"Zorg ervoor vat rclone zich in uw pad bevindt, of voeg de locatie van rclone toe via de geavanceerde opties.","Manual":"Handmatig","Manual update found:":"Handmatige update gevonden:","Manually type path":"Voer pad handmatig in","Max download speed":"Max downloadsnelheid","Max upload speed":"Max Uploadsnelheid","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database","Microsoft SQL Databases":"Microsoft SQL Databases","Minimum redundancy":"Minimale redundantie","Minimum redundancy is 1.0":"Minimale redundantie is 1.0","Minutes":"Minuten","Missing name":"Ontbrekende naam","Missing passphrase":"Ontbrekende wachtwoordzin","Missing sources":"Ontbrekende bronnen","Modified":"Gewijzigd","Mon":"Maandag","Months":"Maanden","Move existing database":"Verplaats bestaande database","Move failed:":"Verplaatsen mislukt:","My Documents":"Mijn Documenten","My Music":"Mijn Muziek","My Photos":"Mijn Foto's","My Pictures":"Mijn Afbeeldingen","Name":"Naam","Never":"Nooit","New Password":"Nieuw Wachtwoord","New update found: {{message}}":"Nieuwe update gevonden: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nieuwe gebruikersnaam is {{user}}.\nGebruikersreferenties bijgewerkt om de nieuwe beperkte gebruiker te gebruiken","Next":"Volgende","Next scheduled run:":"Volgende geplande uitvoering:","Next scheduled task:":"Volgende geplande taak:","Next task:":"Volgende taak:","Next time":"Volgende keer","No":"Nee","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Er is eerder geen certificaat opgegeven, controleer svp met de serverbeheerder of de sleutel correct is: {{key}}\n\nWilt u de gerapporteerde host-sleutel goedkeuren?","No editor found for the "{{backend}}" storage type":"Geen bewerkingsprogramma gevonden voor het "{{backend}}" opslagtype","No encryption":"Geen versleuteling","No items selected":"Geen items geselecteerd","No items to restore, please select one or more items":"Geen items om te herstellen, selecteer één of meer items","No passphrase entered":"Geen wachtwoordzin ingegeven","No scheduled tasks":"Geen geplande taken","Non-matching passphrase":"Niet-bijbehorende wachtwoordzin","None / disabled":"Geen / uitgeschakeld","Not using encryption":"Zonder versleuteling","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"Houd er rekening mee dat snelheden in bytes worden opgegeven, en dat lijnsnelheden doorgaans in bits worden gerapporteerd. Gebruik bij de conversie een factor 8, zodat een lijn van 8 mbit/s gelijkstaat aan 1 MByte/s.","Nothing will be deleted. The backup size will grow with each change.":"Er zal niets verwijderd worden. De back-upgrootte zal toenemen met iedere verandering.","OK":"OK","OSS Access Key ID":"OSS Toegangssleutel-ID","OSS Access Key Secret":"OSS Toegangssleutel Geheim","OSS Bucket Region":"OSS Bucket-regio","OSS Bucket name":"OSS Bucket-naam","OSS Endpoint":"OSS Eindpunt","OSS Path or subfolder in the bucket":"OSS Pad of submap in de bucket","OSS Region":"OSS-Regio","Official releases":"Officiële releases","Once there are more backups than the specified number, the oldest backups are deleted.":"Zodra er meer back-ups zijn dan het opgegeven aantal, zullen de oudste back-ups worden verwijderd.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Geopend","Openstack API key are not supported in v3 keystone API":"Openstack API Sleutels worden niet ondersteund in v3 keystone API","Operating System":"Besturingssysteem","Operation":"Bewerking","Operations:":"Bewerkingen:","Optional API key":"Optionele API-sleutel","Optional authentication password":"Optioneel authenticatie wachtwoord","Optional authentication username":"Optionele authenticatie gebruikersnaam","Optional region":"Optionele regio","Optional tenant name":"Optionele tenant-naam","Options":"Opties","Options added here are applied to all backups, but can be overridden in each individual backup.":"Opties die hier worden toegevoegd, worden toegepast op alle back-ups, maar kunnen worden overschreven in iedere afzonderlijke back-up.","Original location":"Originele locatie","Others":"Anderen","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Na verloop van tijd zullen back-ups automatisch verwijderd worden. Er zal één back-up overblijven voor elk van de laatste 7 dagen, voor elk van de laatste 4 weken, en voor elk van de laatste 12 maanden. Er zal altijd tenminste één back-up overblijven.","Overwrite":"Overschrijven","Passphrase":"Wachtwoordzin","Passphrase (if encrypted)":"Wachtwoordzin (indien versleuteld)","Passphrase changed":"Wachtwoordzin veranderd","Passphrases are not matching":"Wachtwoordzinnen komen niet overeen","Passphrases do not match":"Wachtwoordzinnen komen niet overeen","Password":"Wachtwoord","Patching files with local blocks …":"Bestanden bijwerken met lokale blokken ...","Path":"Pad","Path not found":"Pad niet gevonden","Path on server":"Pad op server","Path or subfolder in the bucket":"Pad of submap in de bucket","Pause":"Pauze","Pause after startup or hibernation":"Pauzeer na opstarten of slaapmodus","Pause options":"Pauzeer-opties","Permissions":"Permissies","Pick location":"Kies locatie","Please select a file to import":"Selecteer een bestand om te importeren","Point to your backup files and restore from there":"Verwijs naar de back-up bestanden en herstel daar vandaan","Port":"Poort","Prevent tray icon automatic log-in":"Voorkom automatisch inloggen door systeemvak-pictogram","Previous":"Vorige","Progress:":"Voortgang:","ProjectID is optional if the bucket exist":"ProjectID is optioneel als de bucket bestaat","Proprietary":"Fabrikantgebonden","Purge Phase":"Uitwissen Subtaak","Purging files complete!":"Wissen van bestanden compleet!","Purging files …":"Bestanden wissen ...","Rebuilding local database …":"Opnieuw opbouwen van lokale database ...","Recreate (delete and repair)":"Opnieuw aanmaken (verwijderen en repareren)","Recreate Database Phase":"Opnieuw aanmaken Database Subtaak","Recreating database …":"Opnieuw aanmaken van de database ...","Region":"Regio","Registering temporary backup …":"Registreren tijdelijke back-up ...","Relative paths not allowed":"Relatieve paden zijn niet toegestaan","Reload":"Andere code","Remote":"Remote","Remote Path":"Remote Pad","Remote Repository":"Remote Opslagplaats","Remote path":"Remote pad","Remote repository":"Remote opslagplaats","Remote volume size":"Remote volume grootte","Remove":"Verwijderen","Remove option":"Verwijder optie","Removed files":"Verwijderde bestanden","Repair":"Repareren","Repair Phase":"Repareren Subtaak","Repairing database …":"Database repareren ...","Repeat Passphrase":"Herhaal wachtwoordzin","Reporting:":"Rapportage:","Reset":"Reset","Restore":"Herstellen","Restore complete!":"Herstellen compleet!","Restore files":"Herstel bestanden","Restore files from:":"Herstel bestanden van:","Restore files …":"Bestanden herstellen ...","Restore from":"Herstellen vanaf","Restore from backup configuration":"Herstel vanuit back-up configuratie","Restore from configuration …":"Herstellen vanuit configuratie …","Restore options":"Herstelopties","Restore read/write permissions":"Herstel lees/schrijfpermissies","Restored Files":"Herstelde Bestanden","Restored Folders":"Herstelde Mappen","Restored Symlinks":"Herstelde Symbolische Links","Restoring files …":"Bestanden worden hersteld ...","Resume":"Hervat","Rewritten File Lists":"Herschreven bestandslijsten","Run again every":"Voer opnieuw uit iedere","Run now":"Nu uitvoeren","Running commandline entry":"Opdrachtregelinvoer in uitvoering","Running task:":"Taak in uitvoering:","Running …":"In uitvoering ...","Running … stop now":"In uitvoering … nu stoppen","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Zelfde als de basis installatie versie: {{channelname}}","Sat":"Zaterdag","Satellite":"Satellite","Save":"Opslaan","Save and repair":"Opslaan en repareren","Save different versions with timestamp in file name":"Sla verschillende versies op met tijdstempel in de bestandsnaam","Save immediately":"Onmiddellijk opslaan","Scanning existing files …":"Scannen bestaande bestanden ...","Scanning for local blocks …":"Scannen op lokale blokken ...","Schedule":"Planning","Search":"Zoek","Search for files":"Zoek bestanden","Seconds":"Seconden","Select a log level and see messages as they happen:":"Selecteer een logniveau en bekijk meldingen zodra ze zich voordoen:","Select files":"Selecteer bestanden","Server":"Server","Server and port":"Server en poort","Server hostname or IP":"Server hostnaam of IP","Server is currently paused,":"Server is momenteel gepauzeerd,","Server is currently paused, resume now":"Server is momenteel gepauzeerd, nu hervatten","Server is currently paused, do you want to resume now?":"Server is momenteel gepauzeerd, wilt u nu hervatten?","Server password":"Server wachtwoord","Server paused":"Server gepauzeerd","Server state properties":"Server status eigenschappen","Settings":"Instellingen","Show":"Tonen","Show advanced editor":"Toon geavanceerde editor","Show hidden folders":"Toon verborgen mappen","Show log":"Log weergeven","Show log …":"Log weergeven ...","Show treeview":"Toon boomstructuur","Sia server password":"Sia server wachtwoord","Smart backup retention":"Slimme back-up retentie","Some OpenStack providers allow an API key instead of a password and tenant name":"Sommige OpenStack providers staan een API key toe in plaats van een wachtwoord en tenant naam","Some S3 providers might only be compatible with a certain client library":"Sommige S3 providers zouden alleen compatible kunnen zijn met een specifieke client-bibliotheek","Source Data":"Bron","Source Files":"Bronbestanden","Source data":"Brongegevens","Source folders":"Bronmappen","Source:":"Bron:","Specific builds for developers only. Not for use with important data.":"Specifieke builds alleen voor ontwikkelaars. Niet voor gebruik met belangrijke gegevens.","Stable":"Stabiel","Standard protocols":"Standaard protocollen","Start":"Start","Starting backup …":"Back-up wordt gestart ...","Starting restore …":"Herstellen wordt gestart ...","Starting the restore process …":"Starten van het herstelproces ...","Stop after current file":"Stop na het huidige bestand","Stop after the current file":"Stop na het huidige bestand","Stop now":"Nu stoppen","Stop running backup":"Stop de back-up in uitvoering","Stop running task":"Stop de taak in uitvoering","Stopping after the current file:":"Stoppen na het huidige bestand:","Stopping task:":"Taak wordt gestopt:","Storage Type":"Opslagtype","Storage class":"Opslagklasse","Storage class for creating a bucket":"Opslagklasse voor het aanmaken van een bucket","Stored":"Opgeslagen","Strong":"Sterk","Success":"Succes","Sun":"Zondag","Symbolic link":"Symbolische link","System Files":"Systeembestanden","System default ({{levelname}})":"Systeem standaard ({{levelname}})","System files":"Systeembestanden","System info":"Systeeminformatie","System properties":"Systeemeigenschappen","TByte":"TByte","TByte/s":"TByte/s","Target URL >":"Doel-URL >","Target path. Example: /backup":"Doelpad. Voorbeeld: /backup","Task is running":"Taak is in uitvoering","Temporary Files":"Tijdelijke bestanden","Temporary files":"Tijdelijke bestanden","Tenant name":"Tenant-naam","Tencent Cloud Account APPID":"Tencent Cloud Account APPID","Tencent Cloud COS documents and resources":"Tencent Cloud COS documenten en bronnen","Test Phase":"Testen Subtaak","Test connection":"Test verbinding","Testing connection …":"Testen van de verbinding …","Testing permissions …":"Testen van de permissies ...","Testing …":"Testen ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Het '{{fieldname}}' veld bevat een ongeldig teken: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"De back-up ontbreekt, is deze verwijderd?","The backup was temporary and does not exist anymore, so the log data is lost":"De back-up was tijdelijk en bestaat niet meer, waardoor de log-gegevens verloren zijn gegaan","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"De back-ups worden opgesplitst in meerdere bestanden die volumes worden genoemd. Hier kunt u de maximale grootte van de individuele volumebestanden instellen. Zie deze pagina voor meer informatie.","The bucket name should be all lower-case, convert automatically?":"De bucket-naam hoort in kleine letters te zijn, automatisch converteren?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"De configuratie moet op een veilige plaats bewaard worden. Weet u zeker dat u een onversleuteld bestand wilt opslaan dat uw wachtwoorden bevat?","The connection to the server is lost, attempting again in {{time}} …":"De verbinding met de server is verbroken, opnieuw proberen over {{time}} …","The dark theme (by Michal)":"Het donkere thema (door Michal)","The default blue on white theme (by Alex)":"Het standaard blauw op wit thema (door Alex)","The encryption passphrases do not match":"De coderings-wachtwoordzinnen komen niet overeen","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"De bestandsgrootte is {{size}}, groter dan de maximaal opgegeven grootte. Als de bestandsgrootte afneemt, zal het worden opgenomen in toekomstige back-ups.","The folder {{folder}} does not exist.\nCreate it now?":"De map {{folder}} bestaat niet.\nNu aanmaken?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"De host sleutel is veranderd, controleer met uw server beheerder of dit correct is, in het andere geval zou u het slachtoffer kunnen zijn van een MAN-IN-THE-MIDDLE aanval.\n\nWilt u de HUIDIGE host sleutel \"{prev}\" VERVANGEN door de GERAPPORTEERDE host sleutel: {{key}}?","The passwords do not match":"De wachtwoorden komen niet overeen","The path does not appear to exist, do you want to add it anyway?":"Het pad lijkt niet te bestaan, wilt u het desondanks toevoegen?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Het pad eindigt niet met een '{{dirsep}}' teken, wat betekent dat u een bestand opneemt, niet een map.\n\nWilt u het aangegeven bestand opnemen?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Het pad moet een absoluut pad zijn, bijvoorbeeld het moet beginnen met een forward slash '/'","The region parameter is only applied when creating a new bucket":"De regio parameter wordt alleen toegepast bij het aanmaken van een bucket","The region parameter is only used when creating a bucket":"De regio parameter wordt alleen gebruikt bij het aanmaken van een bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Het servercertificaat kon niet gevalideerd worden.\nWilt u het certificaat goedkeuren met deze hash: {{hash}}?","The storage class affects the availability and price for a stored file":"De opslagklasse beïnvloedt de beschikbaarheid en prijs van een opgeslagen bestand","The target folder contains encrypted files, please supply the passphrase":"De doelmap bevat versleutelde bestanden, geef alstublieft de wachtwoordzin","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"De gebruiker heeft teveel permmissies. Wilt u een nieuwe beperkte gebruiker aanmaken, met enkel permissies tot het aangegeven pad?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"De back-up werd aangemaakt op een ander besturingssysteem. Bestanden terugzetten zonder een doelmap op te geven kan tot gevolg hebben dat bestanden worden teruggezet naar onverwachte plaatsen. Bent u er zeker van dat u wilt doorgaan zonder een doelmap te kiezen?","This month":"Afgelopen maand","This week":"Afgelopen week","Throttle settings":"Bandbreedte-instellingen","Thu":"Donderdag","Time":"Tijd","To File":"Naar Bestand","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Om te bevestigen dat u alle remote bestanden wilt verwijderen voor \"{{name}}\", geef svp het woord in dat u hieronder ziet","To export without a passphrase, uncheck the \"Encrypt file\" box":"Om te exporteren zonder een wachtwoordzin, deselecteer het \"Versleutel bestand\" vakje","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"Om problemen met de bucketnaamgeving te voorkomen, wordt aanbevolen om het account-ID vooraf te laten gaan door de bucketnaam. Automatisch vooraf laten gaan?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Om verschillende op DNS gebaseerde aanvallen te voorkomen, beperkt Duplicati de toegestane hostnamen tot de hier genoemde. Directe IP-toegang en localhost zijn altijd toegestaan. Meerdere hostnamen kunnen worden opgegeven met een puntkomma als scheidingsteken. Als één van de toegestane hostnamen een asterisk (*) is, zijn alle hostnamen toegestaan en is deze functie uitgeschakeld. Als het veld leeg is, is toegang alleen toegestaan via het IP adres en localhost.","Today":"Vandaag","Trust host certificate?":"Vertrouw host certificaat?","Trust server certificate?":"Vertrouw server certificaat?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probeer de nieuwste functies waar we aan werken. Momenteel de meest stabiele beschikbare versie. Test Herstellen van bestanden alvorens te gebruiken in productie-omgevingen.","Tue":"Dinsdag","Type passphrase here.":"Type hier de wachtwoordzin.","Type to highlight files":"Typ om bestanden uit te lichten","Unknown backup size and versions":"Onbekende back-up grootte en versies","Until resumed":"Tot hervatting","Update {{state.updatedVersion}} is available. Download now":"Update {{state.updatedVersion}} is beschikbaar. Download nu","Update channel":"Updatekanaal","Update failed:":"Update mislukt:","Updating with existing database":"Updaten met bestaande database","Uploaded files":"Geüploade bestanden","Uploading verification file …":"Uploaden controlebestand ...","Usage statistics":"Gebruikstatistieken","Usage statistics, warnings, errors, and crashes":"Gebruikstatistieken, waarschuwingen, fouten en crashes","Use SSL":"Gebruik SSL","Use existing database?":"Gebruik bestaande database?","Use weak passphrase":"Gebruik zwakke wachtwoordzin","Useless":"Waardeloos","User data":"Gebruikersgegevens","User domain name":"Gebruikers domeinnaam","User has too many permissions":"Gebruiker heeft teveel permissies","User interface settings":"Gebruikersomgeving-instellingen","Username":"Gebruikersnaam","Vacuuming database …":"Database opschonen ...","Validating …":"Valideren ...","Verifications":"Controles","Verify encryption passphrase":"Verifieer coderings-wachtwoordzin","Verify files":"Bestanden controleren","Verifying answer":"Antwoord controleren","Verifying backend data …":"Controleren van backend gegevens ...","Verifying files …":"Controleren bestanden ...","Verifying remote data …":"Controleren remote gegevens ...","Verifying restored files …":"Controleren herstelde bestanden ...","Verifying …":"Controleren ...","Version ID":"Versie ID","Very strong":"Erg sterk","Very weak":"Erg zwak","Visit us on":"Bezoek ons op","WARNING: The remote database is found to be in use by the commandline library.":"WAARSCHUWING: De remote database blijkt in gebruik te zijn door de opdrachtregel bibliotheek.","WARNING: This will prevent you from restoring the data in the future.":"WAARSCHUWING: Dit zal het onmogelijk maken om in de toekomst bestanden te herstellen.","Waiting for task to begin":"Wachten op het starten van de taak","Waiting for task to start …":"Wachten tot een taak begint …","Waiting for upload to finish …":"Wachten op voltooien van upload ...","Warnings, errors and crashes":"Waarschuwingen, fouten en crashes","We recommend that you encrypt all backups stored outside your system":"We raden aan dat u alle back-ups die buiten uw systeem worden opgeslagen versleutelt","Weak":"Zwak","Weak passphrase":"Zwakke wachtwoordzin","Wed":"Woensdag","Weeks":"Weken","Where do you want to restore from?":"Waar vandaan wilt u herstellen?","Where do you want to restore the files to?":"Waarheen wilt u de bestanden herstellen?","Years":"Jaren","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, ik heb de wachtwoordzin op een veilige plaats opgeborgen","Yes, I understand the risk":"Ja, ik begrijp het risico","Yes, I'm brave!":"Ja, ik ben dapper!","Yes, please break my backup!":"Ja, help mijn back-up om zeep!","Yesterday":"Gisteren","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"U verandert het database pad weg van een bestaande database.\nWeet u zeker dat dit is wat u wilt?","You are currently running {{appname}} {{version}}":"U werkt momenteel met {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"De back-up kan worden gestopt nadat de upload van alle bestanden die momenteel in behandeling zijn, is voltooid.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"De taak kan onmiddellijk worden gestopt, of het proces toestaan om door te gaan met het huidige bestand en dan stoppen.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"U hebt de versleutelingsmodus veranderd. Dit kan dingen kapotmaken. U wordt daarom aangemoedigd een nieuwe back-up aan te maken","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"U hebt de wachtwoordzin aangepast, wat niet wordt ondersteund. U wordt daarom aangemoedigd een nieuwe back-up aan te maken.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"U hebt ervoor gekozen de back-up niet te versleutelen. Encryptie is aanbevolen voor alle gegevens die worden opgeslagen op een remote server.","You have chosen to restore to a new location, but not entered one":"U koos voor terugzetten naar een nieuwe locatie, maar hebt geen locatie opgegeven","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"U hebt een sterke wachtwoordzin gegenereerd. Verzeker u ervan dat u een veilige kopie heeft van de wachtwoordzin, omdat de gegevens niet hersteld kunnen worden als u de wachtwoordzin verliest.","You must choose at least one source folder":"U moet tenminste één bronmap kiezen","You must enter a domain name to use v3 API":"Een domeinnaam moet worden opgegeven om v3 API te gebruiken","You must enter a name for the backup":"U moet een naam ingeven voor de back-up","You must enter a passphrase or disable encryption":"U moet een wachtwoordzin ingeven of versleuteling uitschakelen","You must enter a password to use v3 API":"Een wachtwoord moet worden opgegeven om v3 API te gebruiken","You must enter a positive number of backups to keep":"U moet een positief getal opgeven voor de hoeveelheid te bewaren back-ups","You must enter a tenant (aka project) name to use v3 API":"Een tenant (ofwel project) naam moet worden opgegeven om v3 API te gebruiken ","You must enter a tenant name if you do not provide an API key":"U moet een tenant naam ingeven als u de API sleutel niet verstrekt","You must enter a valid duration for the time to keep backups":"U moet een geldige tijdsduur ingeven voor de tijd dat back-ups bewaard moeten worden","You must enter a valid retention policy string":"Er moet een geldige waarde voor retentiebeleid worden opgegeven","You must enter either a password or an API key":"U moet òf een wachtwoord, òf een API sleutel ingeven","You must enter either a password or an API key, not both":"U moet òf een wachtwoord, òf een API sleutel ingeven, niet beide","You must fill in the password":"U moet het wachtwoord invullen","You must fill in the server name or address":"U moet de servernaam of -adres invullen","You must fill in the username":"U moet de gebruikersnaam invullen","You must fill in {{field}}":"U moet {{field}} invullen","You must select or fill in the AuthURI":"U moet de AuthURI selecteren of invullen","You must select or fill in the server":"U moet de server selecteren of invullen","You must specify a path":"U moet een pad opgeven","You should fill in {{field}} {{reason}}":"U moet {{field}} {{reason}} invullen","Your files and folders have been restored successfully.":"Uw bestanden en mappen zijn succesvol hersteld","Your passphrase is easy to guess. Consider changing passphrase.":"Uw wachtwoordzin is eenvoudig te raden. Overweeg de wachtwoordzin te veranderen.","bucket/folder/subfolder":"bucket/map/submap","byte":"byte","byte/s":"byte/s","cos_app_id":"cos_app_id","cos_bucket":"cos_bucket","cos_region":"cos_region","cos_secret_id":"cos_secret_id","cos_secret_key":"cos_secret_key","custom":"aangepast","failed":"mislukt","local repository, e.g. local":"lokale opslagplaats, bijv. local","oss_access_key_id":"oss_access_key_id","oss_access_key_secret":"oss_access_key_secret","oss_bucket_name":"oss_bucket_name","oss_endpoint":"oss_endpoint","oss_region":"oss_region","remote path, e.g. backup":"extern pad, bijv. backup","remote repository, e.g. remote":"externe opslagplaats, bijv. remote","resume now":"nu hervatten","storj_shared_access":"storj_shared_access","unless you are explicitly specifying --group-id":"tenzij u expliciet --group-id opgeeft","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} werd in eerste instantie ontwikkeld door {{dev1}} en {{dev2}}. {{appname}} kan gedownload worden van {{websitename}}. {{appname}} is gelicenseerd onder de {{licensename}}.","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}} gebruikt de volgende bibliotheken van derden:","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} bestanden ({{size}}) te gaan {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versie","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versies"],"{{number}} Hour":"{{number}} Uur","{{number}} Hours":"{{number}} Uur","{{number}} Minutes":"{{number}} Minuten","{{time}} (took {{duration}})":"{{time}} (duurde {{duration}})"}); - gettextCatalog.setStrings('pl', {"- pick an option -":"- wybierz opcję -","...loading...":"...ładowanie...","API key":"klucz API","AWS Access ID":"Identyfikator dostępu AWS","AWS Access Key":"Klucz dostepu AWS","AWS IAM Policy":"Polityka AWS IAM","About":"O programie","About {{appname}}":"O programie {{appname}}","Access Key":"Klucz dostępu","Access denied":"Dostęp zabroniony","Access grant":"Dostęp przyznany","Access to user interface":"Dostęp do interfejsu użytkownika","Account name":"Nazwa konta","Add a new backup":"Dodaj nową kopię","Add a path directly":"Dodaj ścieżkę bezpośrednio","Add advanced option":"Dodaj opcję zaawansowaną","Add backup":"Dodaj kopię","Add filter":"Dodaj filtr","Add path":"Dodaj ścieżkę","Added":"Dodano","Adjust bucket name?":"Poprawić nazwę zasobnika?","Advanced Options":"Opcje Zaawansowane","Advanced options":"Opcje zaawansowane","Advanced:":"Zaawansowane:","All Hyper-V Machines":"Wszystkie Maszyny Hyper-V","All Microsoft SQL Databases":"Wszystkie Bazy Danych Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Wszystkie raporty użycia są wysyłane anonimowo i nie zawierają żadnych danych osobistych. Raporty zawierają informacje o sprzęcie i systemie operacyjnym, rodzaju kopii zapasowej, czasie trwania, ogólnej ilości danych źródłowych i tym podobne. Raporty nie zawierają ścieżek, nazw plików, nazw użytkowników, haseł i tym podobnych danych wrażliwych.","Allow remote access (requires restart)":"Zezwalaj na dostęp zdalny (wymaga restartu)","Allowed days":"Dozwolone dni","An existing file was found at the new location":"Znaleziono istniejący plik w nowym położeniu","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Istniejący plik został znaleziony w nowej lokalizacji\nCzy na pewno chcesz skierować bazę danych do istniejącego pliku?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Znaleziono istniejącą, lokalną bazę danych dla magazynu.\nPonowne użycie tej bazy pozwoli pracować instancji wiersza poleceń oraz serwerowej z tym samym zdalnym magazynem.\n\nCzy chcesz użyć istniejącej bazy danych?","Anonymous usage reports":"Anonimowy raport użycia","Applications":"Aplikacje","As Command-line":"Jako Linia poleceń","AuthID":"AuthID","Authentication method":"Metoda uwierzytelnienia","Authentication method ({{auth_method}})":"Metoda uwierzytelnienia ({{auth_method}})","Authentication password":"Hasło uwierzytenienia","Authentication username":"Nazwa uwierzytelnienia","Autogenerated passphrase":"Automatycznie wygenerowane długie hasło","B2 Application ID":"ID aplikacji B2","B2 Application Key":"Klucz aplikacji B2","B2 Cloud Storage Account ID":"ID konta magazynu w chmurze B2","B2 Cloud Storage Application ID":"ID aplikacji magazynu w chmurze B2","B2 Cloud Storage Application Key":"Klucz aplikacji B2 magazynu w chmurze","Back":"Wstecz","Backup complete!":"Backup zakończony!","Backup destination":"Miejsce docelowe kopii","Backup location":"Lokalizacja kopii","Backup retention":"Retencja kopii zapasowej","Backup:":"Kopia:","Beta":"Beta","Broken access":"Przerwany dostęp","Browse":"Przeglądaj","Browser default":"Domyślna przeglądarka","Bucket create location":"Miejsce tworzenia zasobnika","Bucket name":"Nazwa zasobnika","Bucket storage class":"Klasa przechowywania zasobnika","Building list of files to restore …":"Tworzenie listy plików do przywrócenia ...","Building partial temporary database …":"Tworzenie tymczasowej częściowej bazy danych ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Po umożliwieniu zdalnego dostępu, serwer nasłuchuje żądań z każdego urządzenia w twojej sieci. Jeśli aktywujesz tę opcję, upewnij się, że używasz komputera w bezpiecznej, zabezpieczonej firewallem sieci.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Domyślnie, z ikony w zasobniku można otworzyć interfejs użytkownika dzięki tokenowi który odblokowuje interfejs. To zapewnia że masz dostęp do interfejsu użytkownika bezpośrednio z ikony w zasobniku, podczas gdy od innych będzie wymagane wprowadzenie hasła. Jeśli wolisz konieczność wprowadzenia hasła nawet przy otwieraniu interfejsu użytkownika z ikony w zasobniku, aktywuj tę funkcję.","Cache Files":"Pliki pamięci podręcznej","Canary":"Robocze","Cancel":"Anuluj","Cannot move to existing file":"Nie można przenieść do istniejącego plku","Changelog":"Lista zmian","Changelog for {{appname}} {{version}}":"Lista zmian dla {{appname}} {{version}}","Check failed:":"Sprawdzenie nieudane:","Check for updates now":"Sprawdź uaktualnienia ","Checking for updates …":"Sprawdzanie uaktualnień ...","Chose a storage type to get started":"Wybierz typ magazynu by rozpocząć","Click the AuthID link to create an AuthID":"Kliknij link AuthID by utworzyć AuthID","Click to set throttle options":"Kliknij, aby ustawić limity prędkości","Client library to use":"Biblioteka klienta do użycia","Commandline …":"Linia poleceń ...","Compact Phase":"Faza kompaktowania","Compact now":"Kompaktuj teraz","Compacting remote data …":"Kompaktowanie zdalnych danych ...","Complete log":"Log kompletny","Completing backup …":"Kończenie kopii ...","Completing previous backup …":"Kończenie poprzedniej kopii ...","Computer":"Komputer","Configuration file:":"Plik konfiguracyjny:","Configuration:":"Konfiguracja:","Configure a new backup":"Skonfiguruj nową kopię","Confirm delete":"Potwierdź usunięcie","Confirm encryption passphrase":"Potwierdź hasło szyfrowania","Confirm passphrase":"Potwierdź hasło","Confirmation required":"Potwierdzenie wymagane","Connect":"Połącz","Connect now":"Połącz teraz","Connecting to server …":"Łączenie z serwerem ...","Connection lost":"Utracono połączenie","Connection worked!":"Połączenie działa!","Container name":"Nazwa zasobnika","Container region":"Region zasobnika","Continue":"Kontynuuj","Continue without encryption":"Kontynuuj bez szyfrowania","Copied!":"Skopiowane!","Copy":"Kopiuj","Copy Destination URL to Clipboard":"Kopiuj Docelowy URL do Schowka","Copy failed. Please manually copy the URL":"Niepowodzenie kopiowania. Proszę skopiować URL ręcznie","Core options":"Opcje podstawowe","Counting ({{files}} files found, {{size}})":"Liczenie ({{files}} znaleziono plików, {{size}})","Crashes only":"Tylko awarie","Create bug report …":"Utwórz raport o błędach ...","Create folder?":"Utworzyć folder","Created new limited user":"Utwórz nowego użytkownika z ograniczeniami","Creating bug report …":"Tworzenie raportu o błędach ...","Creating new user with limited access …":"Tworzenie nowego użytkownika z ograniczonym dostępem ...","Creating target folders …":"Tworzenie folderów docelowych ...","Creating temporary backup …":"Tworzenie kopii tymczasowej ...","Current action:":"Bieżące działanie:","Current file:":"Aktualny plik:","Current version is {{versionname}} ({{versionnumber}})":"Bieżąca wersja to {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Niestandardowy węzeł końcowy S3","Custom Satellite":"Niestandardowy satelita","Custom Satellite ({{satellite}})":"Niestandardowy satelita ({{satellite}})","Custom authentication url":"Niestandardowy URL uwierzytelniania","Custom backup retention":"Niestandardowa retencja kopii","Custom location ({{server}})":"Niestandardowa lokalizacja ({{serwer}})","Custom region for creating buckets":"Niestandardowy region do tworzenia zasobników","Custom region value ({{region}})":"Niestandardowa wartość regionu ({{region}})","Custom server url ({{server}})":"Niestandardowy adres url serwera ({{serwer}})","Custom storage class ({{class}})":"Niestandardowa klasa magazynu ({{Klasa}})","Database …":"Baza danych ...","Days":"Dni","Default":"Domyślny","Default ({{channelname}})":"Domyślny ({{channelname}})","Default excludes":"Domyślne wykluczenia","Default options":"Opcje domyślne","Delete":"Usuń","Delete Phase (Old Backup Versions)":"Faza usuwania (stare wersje kopii)","Delete backup":"Usuń kopię","Delete backups that are older than":"Usuń kopie zapasowe starsze niż","Delete local database":"Usuń lokalną bazę danych","Delete remote files":"Usuń zdalne pliki","Delete the local database":"Usuń lokalną bazę danych","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Usunąć {{filecount}} plików ({{filesize}}) ze zdalnego magazynu?","Delete …":"Usuń ...","Deleted":"Usunięto","Deleted Versions":"Usunięte wersje","Deleted files":"Usunięte pliki","Deleting remote files …":"Usuwanie zdalnych plików ...","Deleting unwanted files …":"Usuwanie niepotrzebnych plików ...","Description (optional)":"Opis (opcjonalnie)","Description:":"Opis:","Desktop":"Pulpit","Destination":"Lokalizacja docelowa","Destination path":"Ścieżka docelowa","Disabled":"Wyłączone","Dismiss":"Odrzuć","Dismiss all":"Odrzucić wszystkie","Display and color theme":"Schemat ekranu i kolorystyki","Do you really want to delete the backup: \"{{name}}\" ?":"Naprawdę chcesz usunąć kopię: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Czy naprawdę chcesz usunąć lokalna bazę danych: {{name}}","Done":"Wykonane","Download":"Pobranie","Downloaded files":"Pobrane pliki","Downloading files …":"Pobieranie plików ...","Downloading update…":"Pobieranie uaktualnienia ...","Duplicate option {{opt}}":"Powielenie opcji {{opt}}","Duplicati Website":"Strona Duplicati","Duplicati forum":"Forum Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplikati będzie działać po uruchomieniu, ale pozostanie w stanie wstrzymania na wskazany czas. Duplikati będzie używać minimalne zasoby systemowe i nie będą wykonywane żadne kopie zapasowe.","Duration":"Czas trwania","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Każda skonfigurowana kopia posiada powiązaną z nią lokalną bazę danych, w której przechowuje na komputerze lokalnym informacje o zdalnej kopii.\rKiedy konfiguracja kopii jest usuwana, można również usunąć lokalną bazę danych bez wpływu na możliwość odtworzenia plików zdalnych.\rJeśli używasz lokalnej bazy danych do kopii zapasowych z wiersza poleceń, powinieneś zachować bazę danych.","Edit as list":"Edytuj jako listę","Edit as text":"Edytuj jako tekst","Edit …":"Edycja ...","Encrypt file":"Zaszyfruj plik","Encryption":"Szyfrowanie","Encryption changed":"Szyfrowanie zmienione","Encryption passphrase":"Hasło szyfrowania","End":"Zakończono","Enter URL":"Podaj URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Wprowadź strategię przechowywania ręcznie. Symbole D/W/Y oznaczają dni/tygodnie/lata oraz U - nieograniczony. Schemat składni: 7D:1D,4W:1W,36M:1M. Ten przykład zachowuje kopię dla każdego z 7 kolejnych dni, kopię dla kolejnych 4 tygodni i jedną dla kolejnych 36 miesięcy. Może to być zapisane także jako: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Podaj długie hasło, jeśli jest","Enter configuration details":"Wprowadź szczegóły konfiguracji","Enter encryption passphrase":"Podaj długie hasło szyfrowania","Enter expression here":"Tutaj wprowadź wyrażenie","Enter the destination path":"Wprowadź ścieżkę docelową","Error":"Błąd","Error!":"Błąd!","Errors and crashes":"Błędy i awarie","Examined":"Sprawdzono","Exclude":"Wyklucz","Exclude directories whose names contain":"Wyklucz katalogi z nazwą zawierającą","Exclude expression":"Wyklucz wyrażenie","Exclude file":"Wyklucz plik","Exclude file extension":"Wyklucz rozszerzenie pliku","Exclude files whose names contain":"Wyklucz pliki z nazwą zawierającą","Exclude filter group":"Grupa filtrów wykluczajacych","Exclude folder":"Wyklucz folder","Exclude regular expression":"Wyklucz wyrażenie regularne","Existing file found":"Znaleziono istniejący plik","Experimental":"Eksperymentalne","Export":"Eksport","Export backup configuration":"Eksportuj konfigurację kopii","Export configuration":"Eksportuj konfigurację","Export passwords":"Eksportuj hasła","Export …":"Eksport ...","Exporting …":"Eksportowanie ...","External link":"Link zewnętrzny","FTP (Alternative)":"FTP (Alternatywny)","Failed to build temporary database: {{message}}":"Nie udało się utworzyć tymczasowej bazy danych: {{message}}","Failed to connect:":"Nie udało się połączyć:","Failed to connect: {{message}}":"Nie udało się połączyć: {{message}}","Failed to delete:":"Nie udało się usunąć:","Failed to fetch path information: {{message}}":"Nie udało się pobrać informacji o ścieżce: {{message}}","Failed to find backup:":"Nie udało się znaleźć kopii zapasowej:","Failed to read backup defaults:":"Nie udało się odczytać domyślnych danych kopii:","Failed to restore files: {{message}}":"Nie udało się odtworzyć plików: {{message}}","Failed to save:":"Nie udało się zapisać:","Fetching path information …":"Pobieranie informacji o ścieżce ...","File":"Plik","Files larger than:":"Pliki większe niż:","Filters":"Filtry","Finished!":"Zakończono!","First run setup":"Konfiguracja początkowa","Folder":"Katalog","Folder path":"Ścieżka katalogu","Fri":"Pt","GByte":"GBajt","GByte/s":"GBajt/s","GCS Project ID":"GCS Project ID","General":"Ogólne","General backup settings":"Ogólne ustawienia kopii","General options":"Opcje ogólne","Generate":"Generuj","Generate IAM access policy":"Wygeneruj politykę dostępu IAM","Getting file versions …":"Pobieranie wersji plików ...","Group email":"E-mail grupowy","Hidden files":"Ukryte pliki","Hide":"Ukryj","Hide hidden folders":"Ukryj ukryte foldery","Home":"Strona główna","Hostnames":"Nazwy hostów","Hours":"Godziny","How do you want to handle existing files?":"Jak chcesz potraktować istniejące pliki?","Hyper-V Machine":"Maszyna Hyper-V","Hyper-V Machine:":"Maszyna Hyper-V:","Hyper-V Machines":"Maszyny Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jeśli brak daty, zadanie zostanie uruchomione najwcześniej gdy to możliwe.","If at least one newer backup is found, all backups older than this date are deleted.":"Jeśli znajdzie się przynajmniej jedna nowa kopia, wszystkie kopie starsze od niej zostaną skasowane.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jeśli ścieżka nie zostanie wprowadzona, to wszystkie pliki będą przechowywane w katalogu logowania. Czy na pewno tak właśnie ma być?","If you do not enter an API Key, the tenant name is required":"Jeśli nie podasz Klucza API, nawa dzierżawcy jest wymagana","Import":"Import","Import Destination URL":"Import Docelowego URL","Import backup configuration":"Importuj konfigurację kopii","Import from a file":"Zaimportuj z pliku","Import metadata":"Importuj metadane","Importing …":"Importowanie ...","Include a file?":"Dołaczyć plik?","Include expression":"Dołącz wyrażenie","Include regular expression":"Dołącz wyrażenie regularne","Incorrect answer, try again":"Nieprawidłowa odpowiedź, spróbuj ponownie","Individual builds for developers only. Not for use with important data.":"Indywidualne kompilacje tylko dla programistów. Nie do użytku z ważnymi danymi.","Information":"Informacja","Invalid characters in path":"Nieprawidłowe znaki w ścieżce","Invalid retention time":"Nieprawidłowy czas przechowywania","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Do niektórych serwerów FTP można łączyć się bez hasła.\nCzy na pewno Twój serwer FTP obsługuje logowanie bez hasła?","KByte":"KBajty","KByte/s":"KBajty/s","Keep a specific number of backups":"Zachowaj określoną ilość kopii","Keep all backups":"Zachowaj wszystkie kopie","Keystone API version":"Wersja Keystone API","Language in user interface":"Język w interfejsie użytkownika","Last month":"Ostatni miesiąc","Last successful backup:":"Ostatnia prawidłowa kopia zapasowa:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Ostatnie udane odtworzenie: {{time}} (zajęło {{duration || '0 sekund'}})","Latest":"Ostatni","Libraries":"Biblioteki","Listing backup dates …":"Listowanie dat kopii ...","Listing remote files for purge …":"Listowanie zdalnych plików do wyczyszczenia ...","Listing remote files …":"Listowanie zdalnych plików ...","Live":"Aktywne","Load a configuration from an exported job or a storage provider":"Wczytaj konfigurację z wyeksportowanego zadania lub magazynu","Load destination from an exported job or a storage provider":"Wczytaj miejsce docelowe z wyeksportowanego zadania lub magazynu","Load older data":"Załaduj starsze dane","Loading …":"Ładowanie ...","Local Repository":"Magazyn lokalny","Local database path:":"Ścieżka lokalnej bazy danych:","Local repository":"Magazyn lokalny","Local storage":"Magazyn lokalny","Location":"Położenie","Location where buckets are created":"Położenie, gdzie będą utworzone zasobniki","Log data for {{Backup.Backup.Name}}":"Logi dla {{Backup.Backup.Name}}","Log data from the server":"Logi z serwera","Log out":"Wyloguj","MByte":"MBajt","MByte/s":"MBajty/s","Maintenance":"Konserwacja","Manually type path":"Podaj ścieżkę ręcznie ","Max download speed":"Maksymalna szybkość pobierania","Max upload speed":"Maksymalna szybkość wysyłania","Menu":"Menu","Microsoft SQL Database:":"Baza danych Microsoft SQL:","Microsoft SQL Databases":"Bazy danych Microsoft SQL:","Minimum redundancy":"Minimalna redundancja","Minimum redundancy is 1.0":"Minimalna redundancja wynosi 1,0","Minutes":"Minuty","Missing name":"Brak nazwy","Missing passphrase":"Brak długiego hasła","Missing sources":"Brak źródła","Modified":"Zmodyfikowano","Mon":"Pn","Months":"Miesiące","Move existing database":"Przenieś istniejącą bazę danych","Move failed:":"Nie udało się przenieść:","My Documents":"Moje Dokumenty","My Music":"Moja Muzyka","My Photos":"Moje Zdjęcia","My Pictures":"Moje Obrazy","Name":"Nazwa","Never":"Nigdy","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nowa nazwa użytkownika to {{user}}.\nUaktualniono uwierzytelnienia dla użytkownika o ograniczonym dostępie","Next":"Następny","Next scheduled run:":"Następne zaplanowane uruchomienie:","Next scheduled task:":"Następne zaplanowane zadanie:","Next task:":"Następne zadanie","Next time":"Następny raz","No":"Nie","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Certyfikat nie został wcześniej określony, należy sprawdzić u administratora serwera czy klucz jest poprawny: {{key}} \n\nCzy akceptujesz podany klucz?","No editor found for the "{{backend}}" storage type":"Nie znaleziono edytora dla magazynu typu "{{backend}}"","No encryption":"Bez szyfrowania","No items selected":"Nie wybrano pozycji","No items to restore, please select one or more items":"Brak pozycji do odtworzenia, proszę wybrać jedną lub więcej pozycji.","No passphrase entered":"Nie wprowadzono długiego hasła","No scheduled tasks":"Brak zaplanowanych zadań","Non-matching passphrase":"Niepasujące długie hasła","None / disabled":"Żaden / wyłączone","Not using encryption":"Bez użycia szyfrowania","Nothing will be deleted. The backup size will grow with each change.":"Nic nie będzie kasowane. Kopia będzie zwiększała rozmiar z każdą zmianą.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Kiedy wystąpi więcej kopii niż określona ilość, najstarsze kopie zostaną skasowane.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Otwarto","Operating System":"System operacyjny","Operation":"Operacja","Operations:":"Operacje:","Optional authentication password":"Opcjonalne hasło uwierzytelnienia","Optional authentication username":"Opcjonalny użytkownik uwierzytelnienia","Options":"Opcje","Original location":"Położenie oryginalne","Others":"Inne","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Z biegiem czasu kopie będą usuwane automatycznie. Pozostanie jedna kopia dla każdego z ostatnich 7 dni, dla każdego z 4 ostatnich tygodni, dla każdego z 12 ostatnich miesięcy. Zawsze będzie zachowana przynajmniej jedna kopia.","Overwrite":"Nadpisz","Passphrase":"Długie hasło","Passphrase (if encrypted)":"Długie hasło (jeśli zaszyfrowane)","Passphrase changed":"Zmieniono hasło","Passphrases are not matching":"Hasła różnią się od siebie","Passphrases do not match":"Hasła różnią się od siebie","Password":"Hasło","Patching files with local blocks …":"Poprawianie plików za pomocą lokalnych bloków ...","Path":"Ścieżka","Path not found":"Ścieżka nie znaleziona","Path on server":"Ścieżka na serwerze","Path or subfolder in the bucket":"Ścieżka lub podkatalog w zasobniku","Pause":"Wstrzymaj","Pause after startup or hibernation":"Wstrzymaj po uruchomieniu lub hibernacji","Pause options":"Opcje wstrzymania","Permissions":"Uprawnienia","Pick location":"Wybierz położenie","Point to your backup files and restore from there":"Wskaż pliki kopii zapasowej i odtwórz z nich","Port":"Port","Prevent tray icon automatic log-in":"Zapobiegaj automatycznemu logowaniu z ikony w trayu","Previous":"Poprzedni","Progress:":"Postęp:","ProjectID is optional if the bucket exist":"ProjectID jest opcjonalne jeśli zasobnik istnieje","Proprietary":"Własny","Purge Phase":"Faza czyszczenia","Purging files complete!":"Czyszczenie plików zakończone!","Purging files …":"Czyszczenie plików ...","Rebuilding local database …":"Odbudowa lokalnej bazy danych ...","Recreate (delete and repair)":"Odtworzenie (usunięcie i naprawienie)","Recreate Database Phase":"Faza odtwarzania bazy danych","Recreating database …":"Odtwarzanie bazy danych ...","Registering temporary backup …":"Rejestrowanie kopii tymczasowej ...","Relative paths not allowed":"Ścieżki względne nie są dopuszczalne","Reload":"Przeładuj","Remote":"Zdalny","Remote Path":"Ścieżka zdalna","Remote Repository":"Magazyn zdalny","Remote path":"Ścieżka zdalna","Remote repository":"Magazyn zdalny","Remote volume size":"Rozmiar wolumenu zdalnego","Remove":"Usuń","Remove option":"Usuń opcję","Removed files":"Usunięte pliki","Repair":"Napraw","Repair Phase":"Faza naprawiania","Repairing database …":"Naprawianie bazy danych ...","Repeat Passphrase":"Powtórz długie hasło","Reporting:":"Raportowanie:","Reset":"Resetuj","Restore":"Odtwórz","Restore complete!":"Odtwarzanie zakończone!","Restore files":"Odtwórz pliki","Restore files …":"Odtwórz pliki ...","Restore from":"Odtwórz z","Restore from backup configuration":"Odtwórz z konfiguracji kopii","Restore options":"Opcje odtwarzania","Restore read/write permissions":"Odtwórz uprawnienia odczytu/zapisu","Restored Files":"Odtworzone pliki","Restored Folders":"Odtworzone foldery","Restored Symlinks":"Odtworzone linki symboliczne","Restoring files …":"Odtworzone pliki ...","Resume":"Wznów","Rewritten File Lists":"Przepisana lista plików","Run again every":"Uruchom ponownie co","Run now":"Uruchom teraz","Running commandline entry":"Uruchamianie komend z linii poleceń","Running task:":"Działające zadania:","Running …":"Działanie ...","S3 Compatible":"Kompatybilny z S3","Same as the base install version: {{channelname}}":"Zgodny z bazową wersją instalacji: {{channelname}}","Sat":"So","Satellite":"Satelita","Save":"Zapisz","Save and repair":"Zapisz i napraw","Save different versions with timestamp in file name":"Zapisz różne wersje z sygnaturą czasową w nazwie","Save immediately":"Zapisz niezwłocznie","Scanning existing files …":"Przeglądanie istniejących plików ...","Scanning for local blocks …":"Szukanie lokalnych bloków ...","Schedule":"Harmonogram","Search":"Szukaj","Search for files":"Szukaj plików","Seconds":"Sekundy","Select a log level and see messages as they happen:":"Wybierz zakres dziennika i zobacz co się wydarzyło:","Select files":"Wybierz pliki","Server":"Serwer","Server and port":"Serwer i port","Server hostname or IP":"Nazwa serwera lub IP","Server is currently paused,":"Serwer jest obecnie wstrzymany,","Server is currently paused, do you want to resume now?":"Serwer jest obecnie wstrzymany, czy chcesz teraz wznowić jego pracę?","Server password":"Hasło serwera","Server paused":"Serwer wstrzymany","Server state properties":"Właściwości stanu serwera","Settings":"Ustawienia","Show":"Pokaż","Show advanced editor":"Pokaż edytor zaawansowany","Show hidden folders":"Pokaż ukryte foldery","Show log":"Pokaż dziennik","Show log …":"Pokaż dziennik ...","Show treeview":"Pokaż drzewo widoku","Sia server password":"Hasło serwera Sia","Smart backup retention":"Inteligentna retencja kopii","Some OpenStack providers allow an API key instead of a password and tenant name":"Niektórzy dostawcy OpenStack dopuszczają klucz API zamiast hasła i nazwy najemcy","Some S3 providers might only be compatible with a certain client library":"Niektórzy dostawcy S3, mogą być zgodni tylko z określoną biblioteką klienta","Source Data":"Dane źródłowe","Source Files":"Pliki źródłowe","Source data":"Dane źródłowe","Source folders":"Foldery źródłowe","Source:":"Źródło:","Specific builds for developers only. Not for use with important data.":"Szczególne kompilacje tylko dla programistów. Nie do użytku z ważnymi danymi.","Standard protocols":"Protokoły standardowe","Start":"Rozpoczęto","Starting backup …":"Rozpoczynanie kopii ...","Starting restore …":"Uruchamianie odzyskiwania ...","Starting the restore process …":"Uruchamianie procesu odzyskiwania ...","Stop after current file":"Zatrzymaj po bieżącym pliku","Stop after the current file":"Zatrzymaj po bieżącym pliku","Stop now":"Zatrzymaj teraz","Stop running backup":"Zatrzymaj wykonywaną kopię","Stop running task":"Zatrzymaj wykonywane zadanie","Stopping after the current file:":"Zatrzymywanie po bieżącym pliku:","Stopping task:":"Zatrzymywanie zadania:","Storage Type":"Typ Magazynu","Storage class":"Klasa magazynu","Storage class for creating a bucket":"Klasa magazynu dla utworzenia zasobnika","Stored":"Zachowane","Strong":"Silne","Success":"Powodzenie","Sun":"Nie","Symbolic link":"Link symboliczny","System Files":"Pliki systemowe","System default ({{levelname}})":"System domyślny ({{levelname}})","System files":"Pliki systemowe","System info":"Informacja systemowa","System properties":"Właściwości systemowe","TByte":"TBajty","TByte/s":"TBajty/s","Task is running":"Zadanie jest wykonywane","Temporary Files":"Pliki tymczasowe","Temporary files":"Pliki tymczasowe","Test Phase":"Faza testu","Test connection":"Sprawdź połączenie","Testing permissions …":"Sprawdzanie uprawnień ...","Testing …":"Testowanie ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Pole '{{fieldname}}' zawiera niedozwolony znak: {{character}} (value: {{value}}, indeks: {{pos}})","The backup is missing, has it been deleted?":"Kopia nie istnieje, czy została usunięta?","The backup was temporary and does not exist anymore, so the log data is lost":"Kopia była tymczasowa i nie istnieje, stąd dane dziennika są utracone","The bucket name should be all lower-case, convert automatically?":"Nazwa zasobnika powinna być pisana wersalikami, zmienić automatycznie ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfiguracja powinna być przetrzymywana bezpiecznie. Jesteś pewien, że chcesz zapisać niezaszyfrowany plik zawierający twoje hasła?","The dark theme (by Michal)":"Ciemny schemat (wyk. Michal)","The default blue on white theme (by Alex)":"Domyślny schemat niebieski na białym (wyk. Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Folder {{folder}} nie istnieje.\nUtworzyć go teraz?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Klucz komputera został zmieniony, proszę sprawdzić z administratorem serwera czy jest to poprawne, w przeciwnym razie możesz zostać ofiarą ataku typu MAN-IN--MIDDLE.\n\nCzy chcesz ZASTĄPIĆ twój BIEŻĄCY klucz komputera \"{{prev}}\" na PODANY klucz: {{klucz}}?","The passwords do not match":"Hasła różnią się od siebie","The path does not appear to exist, do you want to add it anyway?":"Wygląda, że ścieżka nie istnieje, czy mimo to chcesz ją dodać?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Ścieżka nie kończy się znakiem \"{{dirsep}}\", co oznacza, że dołączasz plik, a nie folder.\n\nCzy chcesz dołączyć określony plik?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Ścieżka musi być ścieżką bezwzględną, tzn. musi rozpoczynać się prawym ukośnikiem '/'","The region parameter is only applied when creating a new bucket":"Parametr regionu jest stosowany tylko podczas tworzenia nowego zasobnika","The region parameter is only used when creating a bucket":"Parametr regionu jest używany tylko podczas tworzenia zasobnika","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certyfikat serwera nie może być zweryfikowany.\nCzy aprobujesz certyfikat SSL z sygnaturą: {{hash}}?","The storage class affects the availability and price for a stored file":"Klasa magazynu danych ma wpływ na dostępność i cenę za przechowywany plik","The target folder contains encrypted files, please supply the passphrase":"Docelowy folder zawiera zaszyfrowane pliki, proszę podać długie hasło","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Użytkownik ma za duże uprawnienia. Czy chcesz stworzyć nowego użytkownika z uprawnieniami ograniczonymi do wybranej ścieżki?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ta kopia zapasowa została utworzona na innym systemie operacyjnym. Odzyskiwanie plików bez określania folderu docelowego może spowodować, że pliki zostaną przywrócone w nieoczekiwanych miejscach. Czy na pewno chcesz kontynuować bez wyboru folderu docelowego?","This month":"Bieżący miesiąc","This week":"Bieżący tydzień","Throttle settings":"Limity prędkości","Thu":"Czw","Time":"Czas","To File":"Do Pliku","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Aby potwierdzić, że chcesz skasować wszystkie zdalne pliki dla \"{{name}}\", proszę wprowadzić słowo zamieszczone poniżej","To export without a passphrase, uncheck the \"Encrypt file\" box":"Aby wyeksportować bez hasła, odznacz pole \"Szyfruj plik\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"By zapobiec różnym atakom bazujących na DNS, Duplicati limituje dozwolone nazwy hostów do tu wymienionych. Bezpośredni dostęp z IP i localhost zawsze są dozwolone. Wiele nazw hostów może być wpisane i rozdzielone średnikiem. Jeśli któraś z podanych nazw hosta jest gwiazdką (*), wszystkie nazwy hostów są dozwolone i ta funkcja jest wyłączona. Jeśli pole jest puste, tylko dostęp z IP i localhost jest dozwolony.","Today":"Dzisiaj","Trust host certificate?":"Certyfikat zaufanego hosta?","Trust server certificate?":"Certyfikat zaufanego serwera?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Wypróbuj nowe funkcje nad którymi pracujemy. Obecnie najbardziej stabilna dostępna wersja. Przetestuj przywracanie danych przed ich użyciem w środowiskach produkcyjnych.","Tue":"Wt","Type passphrase here.":"Wpisz tutaj hasło.","Type to highlight files":"Napisz by podświetlić pliki","Unknown backup size and versions":"Nieznany rozmiar kopii i wersje","Until resumed":"Do wznowienia","Update channel":"Kanał uaktualnień","Update failed:":"Nie udało się uaktualnić","Updating with existing database":"Uaktualnij z istniejącą bazą danych","Uploaded files":"Przesłane pliki","Uploading verification file …":"Przesyłanie pliku weryfikującego ...","Usage statistics":"Statystyki użycia","Usage statistics, warnings, errors, and crashes":"Statystyki użycia , ostrzeżenia, błędy i awarie","Use SSL":"Użyj SSL","Use existing database?":"Użyj istniejącej bazy danych","Use weak passphrase":"Użyj słabego długiego hasła","Useless":"Bezużyteczne","User data":"Dane użytkownika","User domain name":"Nazwa domeny użytkownika","User has too many permissions":"Użytkownik ma za duże uprawnienia","User interface settings":"Ustawienia interfejsu użytkownika","Username":"Nazwa użytkownika","Vacuuming database …":"Oczyszczanie bazy danych ...","Validating …":"Walidacja ...","Verifications":"Weryfikacje","Verify files":"Sprawdź pliki","Verifying answer":"Weryfikacja odpowiedzi","Verifying backend data …":"Weryfikowanie danych silnika ...","Verifying files …":"Weryfikacja plików ...","Verifying remote data …":"Weryfikacja zdalnych danych ...","Verifying restored files …":"Weryfikowanie odzyskanych plików ...","Verifying …":"Weryfikowanie ...","Version ID":"ID wersji","Very strong":"Bardzo silne","Very weak":"Bardzo słabe","Visit us on":"Odwiedź nas na","WARNING: This will prevent you from restoring the data in the future.":"UWAGA: To uniemożliwi odtworzenie danych w przyszłości.","Waiting for task to begin":"Oczekiwanie na rozpoczęcie zadania","Waiting for upload to finish …":"Oczekiwanie na zakończenie przesyłania ...","Warnings, errors and crashes":"Ostrzeżenia, błędy i awarie","We recommend that you encrypt all backups stored outside your system":"Zalecamy szyfrowanie wszystkich kopii przechowywanych poza twoim systemem","Weak":"Słabe","Weak passphrase":"Słabe długie hasło","Wed":"Śr","Weeks":"Tygodnie","Where do you want to restore from?":"Gdzie chcesz odtworzyć?","Where do you want to restore the files to?":"Gdzie chcesz odtworzyć pliki?","Years":"Lata","Yes":"Tak","Yes, I have stored the passphrase safely":"Tak, długie hasło zostało bezpiecznie zachowane.","Yes, I understand the risk":"Tak, rozumiem ryzyko","Yes, I'm brave!":"Tak. Jestem dzielny!","Yes, please break my backup!":"Tak, proszę zepsuj moją kopię!","Yesterday":"Wczoraj","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Zmieniłeś ścieżkę na nie prowadzącą do istniejącej bazy danych.\nCzy jesteś pewny, że takie było twoje rzeczywiste zamierzenie?","You are currently running {{appname}} {{version}}":"Aktualnie używasz {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Możesz zatrzymać wykonywanie kopii po zakończeniu wysyłania dowolnego aktualnie przetwarzanego pliku.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Możesz przerwać zadanie natychmiast lub pozwolić kontynuować z bieżącym plikiem i następnie przerwać.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Zmieniłeś tryb szyfrowania. Może to spowodować uszkodzenie zawartości. Zamiast tego zachęcamy do utworzenia nowej kopii zapasowej.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Zmieniono hasło - zmiana hasła nie jest obsługiwana. Zachęcamy Cię zamiast tego do utworzenia nowej kopii zapasowej.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Wybrałeś opcję nieszyfrowania kopii zapasowej. Szyfrowanie jest zalecane dla wszystkich danych przechowywanych na serwerze zdalnym.","You have chosen to restore to a new location, but not entered one":"Możesz wybrać odtworzenie do nowej lokalizacji, ale nie tej wprowadzonej","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Wygenerowałeś silne hasło. Upewnij się, że wykonałeś bezpieczną kopię hasła, ponieważ danych nie będzie można odzyskać, jeśli utracisz hasło.","You must choose at least one source folder":"Musisz wybrać co najmniej jeden folder źródłowy","You must enter a domain name to use v3 API":"Musisz podać domenę aby użyć v3 API","You must enter a name for the backup":"Musisz podać nazwę kopii zapasowej","You must enter a passphrase or disable encryption":"Musisz podać długie hasło lub wyłączyć szyfrowanie","You must enter a password to use v3 API":"Musisz podać hasło aby użyć v3 API","You must enter a positive number of backups to keep":"Musisz podać dodatnią liczbę kopii do zachowania","You must enter a tenant (aka project) name to use v3 API":"Musisz podać nazwę dzierżawcy (znanego jako projekt) aby użyć v3 API","You must enter a valid duration for the time to keep backups":"Musisz podać prawidłowy okres przechowywania kopii zapasowych","You must enter a valid retention policy string":"Musisz wprowadzić prawidłowy ciąg zasad przechowywania","You must fill in the password":"Musisz wypełnić pole hasło","You must fill in the server name or address":"Musisz wypełnić pole nazwa serwera lub adres","You must fill in the username":"Musisz wypełnić pole użytkownik","You must fill in {{field}}":"Musisz wypełnić pole {{field}}","You must select or fill in the AuthURI":"Musisz wybrać lub wypełnić pole AuthURI","You must select or fill in the server":"Musisz wybrać lub wypełnić pole serwer","You must specify a path":"Musisz podać ścieżkę","Your files and folders have been restored successfully.":"Twoje pliki i foldery zostały pomyślnie odtworzone.","Your passphrase is easy to guess. Consider changing passphrase.":"Twoje długie hasło jest łatwe do odgadnięcia. Rozważ zmianę długiego hasła.","bucket/folder/subfolder":"zasobnik/folder/podfolder","byte":"bajtów","byte/s":"bajtów/s","custom":"dostosowany","resume now":"wznów teraz","unless you are explicitly specifying --group-id":"chyba że wyraźnie określisz --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} zostało opracowane głównie przez {{dev1}} i {{dev2}}. {{appname}} można pobrać z {{websitename}}. {{appname}} podlega licencji {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} pliki ({{size}}), do zakończenia {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersja","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersji","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersji","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersje"],"{{number}} Hour":"{{number}} Godzin","{{number}} Hours":"{{number}} godzin","{{number}} Minutes":"{{number}} Minut","{{time}} (took {{duration}})":"{{time}} (trwało {{duration}})"}); - gettextCatalog.setStrings('pt_BR', {"- pick an option -":"- selecione uma opção -","...loading...":"...carregando...","API key":"Chave API","AWS Access ID":"ID de acesso do AWS","AWS Access Key":"Chave de acesso do AWS","AWS IAM Policy":"Política de IAM do AWS","About":"Sobre","About {{appname}}":"Sobre {{appname}}","Access Key":"Chave de acesso","Access denied":"Acesso negado","Access grant":"Concessão de acesso","Access to user interface":"Acesso à interface do usuário","Account name":"Nome do usuário","Add a new backup":"Adicionar um novo backup","Add a path directly":"Adicione um caminho diretamente","Add advanced option":"Adicionar opção avançada","Add backup":"Adicionar backup","Add filter":"Adicionar filtro","Add path":"Adicionar caminho","Added":"Adicionado","Adjust bucket name?":"Ajustar o nome do bucket?","Advanced Options":"Opções avançadas","Advanced options":"Opções avançadas","Advanced:":"Avançado:","All Hyper-V Machines":"Todas as máquinas Hyper-V","All Microsoft SQL Databases":"Todas as bases Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos os relatórios de uso são enviados de forma anônima e não contêm dados pessoais. As informações contidas são sobre o hardware e o Sistema Operacional, o backend utilizado, a duração do backup, o tamanho total dos dados de origem e dados similares. Os relatórios não contêm caminhos, nomes de arquivos, usuários, senhas ou informações similares.","Allow remote access (requires restart)":"Permitir acesso remoto (restart necessário)","Allowed days":"Dias permitidos","An existing file was found at the new location":"Um arquivo foi encontrado no local escolhido","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Um arquivo foi encontrado no local escolhido\nVocê tem certeza que quer apontar a database para um arquivo existente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Uma base local foi encontrada.\nReutilizar a basa permitirá que as ferramentas de linha de comando e as instâncias trabalhem no mesmo armazenamento remoto.\nGostaria de utilizar a base existente?","Anonymous usage reports":"Relatório anônimo de uso","Applications":"Aplicações","As Command-line":"Como linha de comando","AuthID":"AuthID","Authentication method":"Método de autenticação","Authentication method ({{auth_method}})":"Método de autenticação ({{auth_method}})","Authentication password":"Senha de autenticação","Authentication username":"Usuário de autenticação","Autogenerated passphrase":"Senha gerada automaticamente","B2 Application ID":"ID da aplicação B2","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"ID da aplicação B2 armazenagem em nuvem","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Voltar","Backup complete!":"Backup concluído!","Backup destination":"Destino do backup","Backup location":"Localização do backup","Backup retention":"Retenção de backup","Backup:":"Backup:","Beta":"Beta","Broken access":"Acesso quebrado","Browse":"Navegar","Browser default":"Navegador padrão","Bucket create location":"Localização do Bucket","Bucket name":"Nome do Bucket","Bucket storage class":"Classe de storage do Bucket","Building list of files to restore …":"Criando lista de arquivos para restauração ...","Building partial temporary database …":"Construindo um banco de dados parcial temporário ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ao permitir o acesso remoto, o servidor escuta solicitações de qualquer máquina em sua rede. Se você habilitar essa opção, verifique se está sempre usando o computador em uma rede protegida por firewall seguro.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Por padrão, o ícone da bandeja abrirá a interface do usuário com um token que desbloqueia a interface do usuário. Isso garante que você possa acessar a interface do usuário a partir do ícone da bandeja, exigindo que outras pessoas insiram uma senha. Se você preferir digitar a senha, mesmo ao acessar a interface do usuário no ícone da bandeja, ative essa opção. ","Cache Files":"Arquivos de Cache","Canary":"Canary","Cancel":"Cancelar","Cannot move to existing file":"Não permitido mover para um arquivo existente","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog para {{appname}} {{version}}","Check failed:":"Falha na verificação:","Check for updates now":"Buscar atualizações","Checking for updates …":"Procurando atualizações ... ","Chose a storage type to get started":"Para iniciar, escolha o tipo de armazenamento","Click the AuthID link to create an AuthID":"Clique no link AuthID para criar uma AuthID","Click to set throttle options":"Clique para definir opções de limite","Client library to use":"Biblioteca cliente para ser usada","Commandline …":"Linha de comando ...","Compact Phase":"Fase Compacta","Compact now":"Compactar agora","Compacting remote data …":"Compactando dados remotos","Complete log":"Log completo","Completing backup …":"Finalizando backup... ","Completing previous backup …":"Completando o backup anterior ...","Computer":"Computador","Configuration file:":"Arquivo de configuração:","Configuration:":"Configuração:","Configure a new backup":"Configurar novo backup","Confirm delete":"Confirmar remoção","Confirm encryption passphrase":"Confirma frase de segurança encriptada","Confirm passphrase":"Confirmar frase-senha","Confirmation required":"Confirmação necessária","Connect":"Conectar","Connect now":"Conectar agora","Connecting to server …":"Conectando ao servidor ...","Connection lost":"Conexão perdida","Connection worked!":"Conexão estabelecida!","Container name":"Nome do Container","Container region":"Região do Container","Continue":"Continuar","Continue without encryption":"Continuar sem utilizar criptografia","Copied!":"Copiado!","Copy":"Copiar","Copy Destination URL to Clipboard":"Copiar URL do destino","Copy failed. Please manually copy the URL":"Falha na cópia. Copie a URL manualmente","Core options":"Opções básicas","Counting ({{files}} files found, {{size}})":"Contabilizando ({{files}} arquivos encontrados, {{size}})","Crashes only":"Somente falhas","Create bug report …":"Criar relatório de errors ...","Create folder?":"Criar diretório?","Created new limited user":"Criar novo usuário com limitações no acesso","Creating bug report …":"Criando relatório de erros ...","Creating new user with limited access …":"Criando novo usuário com acesso limitado ...","Creating target folders …":"Criando diretórios de destino…","Creating temporary backup …":"Criando backup temporário ...","Current action:":"Ação atual:","Current file:":"Arquivo atual:","Current version is {{versionname}} ({{versionnumber}})":"A versão atual é {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Endpoint S3 modificado","Custom Satellite":"Satélite customizado","Custom Satellite ({{satellite}})":"Satélite customizado ({{satellite}})","Custom authentication url":"URL de autenticação modificada","Custom backup retention":"Retenção de backup personalizada","Custom location ({{server}})":"Localização personalizada ({{server}})","Custom region for creating buckets":"Região personalizada para a criação dos buckets","Custom region value ({{region}})":"Valor personalizado da region ({{region}})","Custom server url ({{server}})":"URL personalizada do servidor ({{server}})","Custom storage class ({{class}})":"Classe de armazenamento personalizada ({{class}})","Database …":"Banco de dados","Days":"Dias","Default":"Padrão","Default ({{channelname}})":"Padrão ({{channelname}})","Default excludes":"Exclusões padrão","Default options":"Opções padrão","Delete":"Remover","Delete Phase (Old Backup Versions)":"Fase de Exclusão (Versões de Backup Antigas)","Delete backup":"Remover backup","Delete backups that are older than":"Excluir backups mais antigos que","Delete local database":"Remover base local","Delete remote files":"Remover arquivos remotos","Delete the local database":"Remover a base local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Remover {{filecount}} arquivos ({{filesize}}) do armazenamento remoto?","Delete …":"Remover ","Deleted":"Deletado","Deleted Versions":"Versões Deletadas","Deleted files":"Arquivos deletados","Deleting remote files …":"Removendo arquivos remotos ...","Deleting unwanted files …":"Removendo arquivos indesejados ...","Description (optional)":"Descrição (opcional)","Description:":"Descrição:","Desktop":"Área de Trabalho","Destination":"Destino","Destination path":"Caminho de destino","Disabled":"Desabilitado","Dismiss":"Ok","Dismiss all":"Ignorar tudo","Display and color theme":"Tela e cores do tema","Do you really want to delete the backup: \"{{name}}\" ?":"Deseja realmente remover o backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Deseja realmente remover a base local para: {{name}}","Done":"Finalizado","Download":"Baixar","Downloaded files":"Arquivos baixados","Downloading files …":"Baixando arquivos ... ","Downloading update…":"Baixando atualização... ","Duplicate option {{opt}}":"Duplicar opção {{opt}}","Duplicati Website":"Site do Duplicati","Duplicati forum":"Fórum do Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati será executado quando iniciado, mas permanecerá em um estado pausado pela duração. O Duplicati ocupará recursos mínimos do sistema e nenhum backup será executado.","Duration":"Duração","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada backup tem um banco de dados local associado a ele, que armazena informações sobre o backup remoto na máquina local.\n Ao excluir um backup, você também pode excluir o banco de dados local sem afetar a capacidade de restaurar os arquivos remotos.\n Se você estiver usando o banco de dados local para backups a partir da linha de comando, deverá manter o banco de dados.","Edit as list":"Editar como lista","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Criptografar arquivo","Encryption":"Criptografia","Encryption changed":"A criptografia mudou","Encryption passphrase":"Frase-senha de criptografia ","End":"Fim","Enter URL":"Informe a URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Insira uma estratégia de retenção. Os espaços reservados são D / W / Y para dias / semanas / anos e U para ilimitado. A sintaxe é: 7D: 1D, 4W: 1W, 36M: 1M. Este exemplo mantém um backup para cada um dos próximos 7 dias, um para cada uma das próximas 4 semanas e um para cada um dos próximos 36 meses. Isso também pode ser escrito como 1W: 1D, 1M: 1W, 3Y: 1M.","Enter backup passphrase, if any":"Informe a senha do backup, caso exista","Enter configuration details":"Inserir detalhes da configuração","Enter encryption passphrase":"Informe a senha de criptografia","Enter expression here":"Informe a expressão aqui","Enter the destination path":"Informe o caminho no destino","Error":"Erro","Error!":"Erro!","Errors and crashes":"Erros e problemas","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir diretórios que contenham","Exclude expression":"Excluir utilizando expressão","Exclude file":"Excluir arquivo","Exclude file extension":"Excluir arquivos com extensão","Exclude files whose names contain":"Excluir arquivos que contenham","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Excluir diretório","Exclude regular expression":"Excluir utilizando expressão regular","Existing file found":"Excluir arquivo encontrado","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuração do backup","Export configuration":"Exportar configuração","Export passwords":"Exportar senhas","Export …":"Exportar ...","Exporting …":"Exportando ...","External link":"Link externo","FTP (Alternative)":"FTP (alternativo)","Failed to build temporary database: {{message}}":"Falha ao construir base temporária: {{message}}","Failed to connect:":"Falha ao conectar:","Failed to connect: {{message}}":"Falha ao conectar: {{message}}","Failed to delete:":"Falha ao remover:","Failed to fetch path information: {{message}}":"Falha ao obter informação do caminho: {{message}}","Failed to find backup:":"Falha ao encontrar backup:","Failed to read backup defaults:":"Falha ao ler os padrões do backup","Failed to restore files: {{message}}":"Falha ao restaurar arquivos: {{message}}","Failed to save:":"Falha ao salvar:","Fetching path information …":"Buscando informações do caminho …","File":"Arquivo","Files larger than:":"Arquivos maiores que:","Filters":"Filtros","Finished!":"Finalizado!","First run setup":"Configuração inicial","Folder":"Diretório","Folder path":"Caminho do diretório","Fri":"Sex","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID do Projeto GCS","General":"Geral","General backup settings":"Configurações gerais de backup","General options":"Opções gerais","Generate":"Gerar","Generate IAM access policy":"Gerar política de acesso IAM","Getting file versions …":"Obtendo versões do arquivo ... ","Group email":"E-mail do grupo","Hidden files":"Arquivos ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar diretórios ocultos","Home":"Home","Hostnames":"Hostnames","Hours":"Horas","How do you want to handle existing files?":"Como você quer lidar com arquivos existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Caso um backup não ocorra na data específica, ele executará assim que possível.","If at least one newer backup is found, all backups older than this date are deleted.":"Se um novo backup for encontrado, todos os backups anteriores a esta data são excluídos.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se você não inserir um caminho, todos os arquivos serão armazenados na pasta de login.\nTem certeza de que isso é o que quer?","If you do not enter an API Key, the tenant name is required":"Se você não inserir uma chave de API, o nome do projeto é necessário","Import":"Importar","Import Destination URL":"Importar URL de destino","Import backup configuration":"Importar configuração de backup","Import from a file":"Importar de um arquivo","Import metadata":"Importar metadados","Importing …":"Importando ...","Include a file?":"Incluir um arquivo?","Include expression":"Incluir expressão","Include regular expression":"Incluir expressão regular","Incorrect answer, try again":"Resposta incorreta, tente novamente","Individual builds for developers only. Not for use with important data.":"Versões apenas para desenvolvedores. Não para uso com dados importantes.","Information":"Informação","Invalid characters in path":"Caracteres inválidos no caminho","Invalid retention time":"Tempo de retenção inválido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"É possível conectar em alguns servidores FTP sem utilizar senha.\nTem certeza que o seu servidor FTP suporta autenticação sem senha?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Manter um número específico de backups","Keep all backups":"Manter todos os backups","Keystone API version":"Versão da API Keystone","Language in user interface":"Idioma da interface do usuário","Last month":"Último mês","Last successful backup:":"Último backup bem-sucedido:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauração bem-sucedida: {{time}} (demorou {{duration || '0 segundos'}})","Latest":"Mais recentes","Libraries":"Bibliotecas","Listing backup dates …":"Listando datas de backup ... ","Listing remote files for purge …":"Listando arquivos remotos para limpeza…","Listing remote files …":"Listando arquivos remotos…","Live":"Ao vivo","Load a configuration from an exported job or a storage provider":"Carregar uma configuração de um trabalho exportado ou de um provedor de armazenamento","Load destination from an exported job or a storage provider":"Carregar destino a partir de um trabalho exportado ou de um provedor de armazenamento","Load older data":"Abrir dados antigos","Loading …":"Carregando …","Local Repository":"Repositório Local","Local database path:":"Caminho do banco de dados local:","Local repository":"Repositório local","Local storage":"Armazenamento local","Location":"Localização","Location where buckets are created":"Local onde os compartimentos são criados","Log data for {{Backup.Backup.Name}}":"Grave log para {{Backup.Backup.Name}} ","Log data from the server":"Registrar dados do servidor","Log out":"Sair","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Manutenção","Manually type path":"Digite manualmente o caminho","Max download speed":"Velocidade de download máxima","Max upload speed":"Velocidade de upload máxima","Menu":"Menu","Microsoft SQL Database:":"Banco de dados Microsoft SQL:","Microsoft SQL Databases":"Banco de Dados Microsoft SQL","Minimum redundancy":"Redundância mínima","Minimum redundancy is 1.0":"Redundância mínima é 1.0","Minutes":"Minutos","Missing name":"Faltando o nome","Missing passphrase":"Faltando a frase de senha","Missing sources":"Faltando as origens","Modified":"Modificado","Mon":"Seg","Months":"Meses","Move existing database":"Mover o banco de dados existente","Move failed:":"Falha ao mover:","My Documents":"Meus Documentos","My Music":"Minhas Músicas","My Photos":"Minhas Fotos","My Pictures":"Minhas Imagens","Name":"Nome","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nome nome de usuário é {{user}}\nAutorizações atualizadas para uso de um novo usuário limitado","Next":"Próximo","Next scheduled run:":"Próxima execução agendada:","Next scheduled task:":"Próxima tarefa agendada:","Next task:":"Próxima tarefa:","Next time":"Próxima vez","No":"Não","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nenhum certificado foi especificado anteriormente, verifique com o administrador do servidor se a chave está correta: {{key}}\n\nDeseja aprovar a chave de host relatada?","No editor found for the "{{backend}}" storage type":"Editor não encontrado para o tipo de armazenamento "{{backend}}"","No encryption":"Sem criptografia","No items selected":"Itens não selecionados","No items to restore, please select one or more items":"Sem itens para restaurar. por favor selecione um ou mais itens","No passphrase entered":"Nenhuma senha inserida","No scheduled tasks":"Sem tarefas agendadas","Non-matching passphrase":"Senha não correspondente","None / disabled":"Nenhum / desabilitado","Not using encryption":"Sem criptografia","Nothing will be deleted. The backup size will grow with each change.":"Nada será excluído. O tamanho do backup crescerá com cada mudança.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Se existir mais backups do que o número especificado, os backups mais antigos serão excluídos.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aberto","Operating System":"Sistema operacional","Operation":"Operações:","Operations:":"Operações:","Optional authentication password":"Senha opcional de autenticação","Optional authentication username":"Usuário opcional de autenticação","Options":"Opções","Original location":"Localização original","Others":"Outros","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Com o tempo, as versões de backup serão excluídas automaticamente. Permanecerá um backup dos últimos 7 dias, das últimas 4 semanas e cada um dos últimos 12 meses. Sempre haverá pelo menos um backup.","Overwrite":"Sobrescrever","Passphrase":"Frase de segurança","Passphrase (if encrypted)":"Senha (se criptografado)","Passphrase changed":"Senha alterada","Passphrases are not matching":"Senhas não correspondem","Passphrases do not match":"As senhas não correspondem","Password":"Senha","Patching files with local blocks …":"Aplicando patch nos arquivos com blocos locais ...","Path":"Caminho","Path not found":"Caminho não encontrado","Path on server":"Caminho do servidor","Path or subfolder in the bucket":"Caminho ou subpasta no bucket","Pause":"Parar","Pause after startup or hibernation":"Pausa após a inicialização ou a hibernação","Pause options":"Interromper opções","Permissions":"Permissões","Pick location":"Escolher localização","Point to your backup files and restore from there":"Aponte para os arquivos de backup e restaure de lá","Port":"Porta","Prevent tray icon automatic log-in":"Impedir login automático no ícone da bandeja","Previous":"Anterior","Progress:":"Progresso:","ProjectID is optional if the bucket exist":"ProjectID é opcional se o bucket já existe","Proprietary":"Proprietário","Purge Phase":"Estágio deleção","Purging files complete!":"Deleção de arquivos completo!","Purging files …":"Limpando arquivos ...","Rebuilding local database …":"Reconstruindo banco de dados local ...","Recreate (delete and repair)":"Recriar (excluir e reparar)","Recreate Database Phase":"Recriar banco de dados","Recreating database …":"Recriaando banco de dados ...","Registering temporary backup …":"Registrando backup temporário ...","Relative paths not allowed":"Caminhos relativos não são permitidos","Reload":"Recarregar","Remote":"Remoto","Remote Path":"Caminho remoto","Remote Repository":"Repositório remoto","Remote path":"Caminho remoto","Remote repository":"Repositório remoto","Remote volume size":"Tamanho do volume remoto","Remove":"Remover","Remove option":"Remover opção","Removed files":"Arquivos Removidos","Repair":"Reparar","Repair Phase":"Reparar","Repairing database …":"Reparando banco de dados ...","Repeat Passphrase":"Repetir frase de segurança","Reporting:":"Relatórios:","Reset":"Redefinir","Restore":"Restaurar","Restore complete!":"Restauração Completa!","Restore files":"Restaurar arquivos","Restore files …":"Restaurar arquivos ...","Restore from":"Restaurar de","Restore from backup configuration":"Restaurar a partir da configuração de backup","Restore options":"Restaurar opções","Restore read/write permissions":"Restaurar permissões leitura/escrita","Restored Files":"Arquivos Restaurados","Restored Folders":"Diretórios Restaurados","Restored Symlinks":"Links Simbólicos Restaurados","Restoring files …":"Restaurando arquivos ...","Resume":"Continuar","Rewritten File Lists":"Listas de arquivos reescritos","Run again every":"Executar novamente a cada","Run now":"Executar agora","Running commandline entry":"Executando entrada de linha de comando","Running task:":"Executando tarefa:","Running …":"Executando ...","S3 Compatible":"S3 Compatível","Same as the base install version: {{channelname}}":"Igual à versão de instalação base: {{channelname}}","Sat":"Sáb","Satellite":"Satélite","Save":"Salvar","Save and repair":"Salvar e reparar","Save different versions with timestamp in file name":"Salve diferentes versões com marcas de horário no nome do arquivo","Save immediately":"Salvar imediatamente","Scanning existing files …":"Procurando arquivos existentes ...","Scanning for local blocks …":"Procurando por blocos locais ...","Schedule":"Agendar","Search":"Buscar","Search for files":"Procurar por arquivos","Seconds":"Segundos","Select a log level and see messages as they happen:":"Selecione um nível de log e veja as mensagens conforme elas aparecem:","Select files":"Selecionar arquivos","Server":"Servidor","Server and port":"Servidor e porta","Server hostname or IP":"Nome do servidor ou IP","Server is currently paused,":"Servidor está atualmente parado,","Server is currently paused, do you want to resume now?":"Servidor está atualmente parado, você quer recomeçar agora?","Server password":"Senha do servidor","Server paused":"Servidor parado","Server state properties":"Propriedades do estado do servidor","Settings":"Configurações","Show":"Exibir","Show advanced editor":"Mostrar editor avançado","Show hidden folders":"Exibir pastas ocultas","Show log":"Exibir log","Show log …":"Exibir log ...","Show treeview":"Mostrar hierarquia","Sia server password":"Senha do servidor Sia","Smart backup retention":"Retenção de backup inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns provedores OpenStack permitem uma chave de API em vez de uma senha e nome de projeto","Some S3 providers might only be compatible with a certain client library":"Alguns provedores S3 podem ser compatíveis apenas com uma determinada biblioteca cliente","Source Data":"Dados de origem","Source Files":"Arquivos de Origem","Source data":"Dados de origem","Source folders":"Pasta de origem","Source:":"Origem:","Specific builds for developers only. Not for use with important data.":"Versão apenas para desenvolvedores. Não para uso com dados importantes.","Standard protocols":"Protocolos padrão","Start":"Inicio","Starting backup …":"Iniciando backup ...","Starting restore …":"Iniciando restauração ...","Starting the restore process …":"Iniciando o processo de restauração ...","Stop after current file":"Parar após o arquivo atual","Stop after the current file":"Parar após o arquivo atual","Stop now":"Parar agora","Stop running backup":"Parar de executar o backup","Stop running task":"Parar de executar a tarefa","Stopping after the current file:":"Parando após o arquivo atual:","Stopping task:":"Tarefa de parada:","Storage Type":"Tipo de armazenamento","Storage class":"Classe de armazenamento","Storage class for creating a bucket":"Classe de armazenamento para criar um bucket","Stored":"Armazenado","Strong":"Forte","Success":"Sucesso","Sun":"Dom","Symbolic link":"Link simbólico","System Files":"Arquivos do sistema","System default ({{levelname}})":"Sistema padrão ({{levelname}})","System files":"Arquivos do sistema","System info":"Informação do sistema","System properties":"Propriedades do sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Tarefa está executando","Temporary Files":"Arquivos temporários","Temporary files":"Arquivos temporários","Test Phase":"Fase de teste","Test connection":"Teste de conexão","Testing permissions …":"Testando permissões ...","Testing …":"Testando ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"O campo '{{fieldname}}' contém um caractere inválido: {{character}} (valor: {{value}}, índice: {{pos}})","The backup is missing, has it been deleted?":"O backup está faltando, foi excluído?","The backup was temporary and does not exist anymore, so the log data is lost":"O backup era temporário e não existe mais, portanto, os dados de log serão perdidos","The bucket name should be all lower-case, convert automatically?":"O nome do bucket deve ser todo em minúsculas. Converter automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"A configuração deve ser mantida segura. Tem certeza de que deseja salvar um arquivo não criptografado contendo suas senhas?","The dark theme (by Michal)":"O tema escuro (por Michal)","The default blue on white theme (by Alex)":"O tema padrão azul sobre branco (por Alex)","The folder {{folder}} does not exist.\nCreate it now?":"O diretório {{folder}} não existe.\nDeseja cria-lo agora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"A chave do host mudou, verifique com o administrador do servidor se está correta, caso contrário você poderia ser vítima de um ataque MAN-IN-THE MIDDLE.\n\nDeseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" com a chave do host REPORTADA: {{key}}?","The passwords do not match":"Senhas não conferem","The path does not appear to exist, do you want to add it anyway?":"O caminho não parece existir, você deseja adicioná-lo de qualquer maneira?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"O caminho não termina com um caractere '{{dirsep}}, o que significa que você inclui um arquivo, não uma pasta.\n\nDeseja incluir o arquivo especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"O caminho deve ser um caminho absoluto, ou seja, deve começar com uma barra progressiva '/'","The region parameter is only applied when creating a new bucket":"O parâmetro de região só é aplicado ao criar um novo bucket","The region parameter is only used when creating a bucket":"O parâmetro de região só é usado na criação de um bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"O certificado do servidor não pôde ser validado.\nDeseja aprovar o certificado SSL com o hash: {{hash}}?","The storage class affects the availability and price for a stored file":"A classe de armazenamento afeta a disponibilidade e o preço de um arquivo armazenado","The target folder contains encrypted files, please supply the passphrase":"A pasta de destino contém arquivos criptografados. Forneça a senha","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"O usuário tem muitas permissões. Deseja criar um novo usuário limitado, com apenas permissões para o caminho selecionado?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Este backup foi criado em outro sistema operacional. A restauração de arquivos sem especificar uma pasta de destino pode fazer com que os arquivos sejam restaurados em locais inesperados. Tem certeza de que deseja continuar sem escolher uma pasta de destino?","This month":"Este mês","This week":"Esta semana","Throttle settings":"Configurações de limitação","Thu":"Qui","Time":"Tempo","To File":"Para o arquivo","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que deseja excluir todos os arquivos remotos para \"{{nome}}\", insira a palavra abaixo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sem uma senha, desmarque a caixa \"Criptografar arquivo\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar vários ataques baseados em DNS, o Duplicati limita os nomes de host permitidos aos listados aqui. O acesso IP direto e o host local sempre são permitidos. Vários nomes de host podem ser fornecidos com um separador de ponto-e-vírgula. Se qualquer um dos nomes de host permitidos for um asterisco (*), todos os nomes de host serão permitidos e esse recurso será desativado. Se o campo estiver vazio, somente o endereço IP e o acesso ao host local serão permitidos.","Today":"Hoje","Trust host certificate?":"Confiar no certificado de host?","Trust server certificate?":"Confiar no certificado de servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Experimente os novos recursos em que estamos trabalhando. Atualmente, a versão mais estável disponível. Teste Restaurar dados antes de usar isso em ambientes de produção.","Tue":"Ter","Type passphrase here.":"Nenhuma senha inserida","Type to highlight files":"Tipo para destacar arquivos","Unknown backup size and versions":"Tamanho do backup e versões desconhecidos","Until resumed":"Até retomar","Update channel":"Canal de atualização","Update failed:":"Atualização falhou:","Updating with existing database":"Atualizando com o banco de dados existente","Uploaded files":"Arquivos enviados","Uploading verification file …":"Enviando arquivo de verificação ...","Usage statistics":"Estatísticas de uso","Usage statistics, warnings, errors, and crashes":"Estatísticas de uso, avisos, erros e falhas","Use SSL":"Utilizar SSL","Use existing database?":"Usar um banco de dados existente?","Use weak passphrase":"Usar uma senha fraca","Useless":"Sem utilidade","User data":"Dados do usuário","User domain name":"Nome de domínio do usuário","User has too many permissions":"O usuário tem muitas permissões","User interface settings":"Configurações da interface do usuário","Username":"Nome de usuário","Vacuuming database …":"Limpando banco de dados ...","Validating …":"Validando ...","Verifications":"Verificações","Verify files":"Verificar arquivos","Verifying answer":"Verificando pergunta","Verifying backend data …":"Verificando dados do backend ...","Verifying files …":"Verificando arquivos ...","Verifying remote data …":"Verificando dados remotos ...","Verifying restored files …":"Verificando arquivos restaurados ...","Verifying …":"Verificando ...","Version ID":"ID da versão","Very strong":"Muito forte","Very weak":"Muito fraca","Visit us on":"Visite-nos em","WARNING: This will prevent you from restoring the data in the future.":"AVISO: isso impedirá que você restaure os dados no futuro.","Waiting for task to begin":"Aguardando o início da tarefa","Waiting for upload to finish …":"Aguardando o upload terminar ...","Warnings, errors and crashes":"Avisos, erros e falhas","We recommend that you encrypt all backups stored outside your system":"Recomendamos que criptografe todos os backups armazenados fora do seu sistema","Weak":"Fraca","Weak passphrase":"Frase de segurança fraca","Wed":"Qua","Weeks":"Semanas","Where do you want to restore from?":"De onde você deseja restaurar?","Where do you want to restore the files to?":"Para onde você deseja restaurar os arquivos?","Years":"Anos","Yes":"Sim","Yes, I have stored the passphrase safely":"Sim, eu tenho armazenado uma frase de acesso segura","Yes, I understand the risk":"Sim, entendo o risco","Yes, I'm brave!":"Sim, sou corajoso!","Yes, please break my backup!":"Sim, corrompa meu backup!","Yesterday":"Ontem","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Você está mudando o caminho do banco de dados para longe de um banco de dados existente.\nTem certeza de que isso é o que deseja?","You are currently running {{appname}} {{version}}":"Você está atualmente executando {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Você pode interromper o backup após a conclusão de qualquer upload de arquivo em andamento.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Você pode interromper a tarefa imediatamente ou permitir que o processo continue seu arquivo atual e então pare.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Você mudou o modo de criptografia. Isso pode estragar algo. É aconselhado criar um novo backup em vez disso","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Você alterou a senha, o que não é suportado. É aconselhado criar um novo backup.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Você escolheu não criptografar o backup. Encriptação é recomendada para todos dados armazenados em um servidor remoto.","You have chosen to restore to a new location, but not entered one":"Você escolheu restaurar para um novo local, mas não inseriu um","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Você gerou uma senha segura. Certifique-se de fazer um cópia da mesma, pois os dados não podem ser recuperados se você perder a senha.","You must choose at least one source folder":"Você deve escolher pelo menos uma pasta de origem","You must enter a domain name to use v3 API":"Você deve inserir um nome de domínio para usar a API v3","You must enter a name for the backup":"Você deve inserir um nome para o backup","You must enter a passphrase or disable encryption":"Você deve inserir uma senha ou desativar a criptografia","You must enter a password to use v3 API":"Você deve digitar uma senha para usar a API v3","You must enter a positive number of backups to keep":"Você deve inserir um número positivo de backups para manter.","You must enter a tenant (aka project) name to use v3 API":"Você deve inserir um nome de inquilino (aka project) para usar a API v3","You must enter a valid duration for the time to keep backups":"Você deve inserir uma duração válida de tempo para manter os backups","You must enter a valid retention policy string":"Você tem que inserir uma string de política de retenção válida","You must fill in the password":"Você deve preencher a senha","You must fill in the server name or address":"Você deve preencher o nome do servidor ou endereço","You must fill in the username":"Você deve preencher o usuário","You must fill in {{field}}":"Você deve preencher {{field}}","You must select or fill in the AuthURI":"Você deve selecionar ou preencher a AuthURI","You must select or fill in the server":"Você deve selecionar ou preencher o servidor","You must specify a path":"Você deve especificar um caminho","Your files and folders have been restored successfully.":"Seus arquivos e pastas foram restaurados com êxito.","Your passphrase is easy to guess. Consider changing passphrase.":"Sua senha é fácil de adivinhar. Considere alterá-la.","bucket/folder/subfolder":"bucket/pasta/subpasta","byte":"byte","byte/s":"byte/s","custom":"personalizado","resume now":"continuar agora","unless you are explicitly specifying --group-id":"a menos que você esteja explicitamente especificando --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} foi desenvolvido inicialmente por {{dev1}} e{{dev2}}. {{appname}} pode ser baixado em {{websitename}}. {{appname}} é licenciado sob a {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} arquivos ({{size}}) restantes {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versão","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versões","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versões"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} horas","{{number}} Minutes":"{{number}} Minutos","{{time}} (took {{duration}})":"{{time}} (demorou {{duration}})"}); - gettextCatalog.setStrings('pt', {"- pick an option -":"- escolha uma opção -","...loading...":"...a carregar...","API key":"Chave API","AWS Access ID":"ID do acesso AWS","AWS Access Key":"Chave do acesso AWS","AWS IAM Policy":"Política de acesso e identidade AWS","About":"Sobre","About {{appname}}":"Sobre o {{appname}}","Access Key":"Chave de acesso","Access denied":"Acesso recusado","Access grant":"Acesso concedido","Access to user interface":"Acesso à interface","Account name":"Nome da conta","Add a new backup":"Adicionar nova cópia de segurança","Add a path directly":"Digitar caminho","Add advanced option":"Adicionar opção avançada","Add backup":"Adicionar cópia de segurança","Add filter":"Adicionar filtro","Add path":"Adicionar caminho","Added":"Adicionado","Adjust bucket name?":"Ajustar nome do 'bucket'?","Advanced Options":"Opções avançadas","Advanced options":"Opções avançadas","Advanced:":"Avançado:","All Hyper-V Machines":"Todas as máquinas Hyper-V","All Microsoft SQL Databases":"Todas as bases de dados Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos os relatórios de utilização são enviados de forma anónima. Contêm informação sobre o hardware, sobre o sistema operativo, o tipo de 'backend', a duração da cópia de segurança, o tamanho dos dados e informações similares. Não contêm caminhos, ficheiros, utilizadores, palavras-passe ou quaisquer outras informações pessoais.","Allow remote access (requires restart)":"Permitir acesso remoto (tem que reiniciar)","Allowed days":"Dias permitidos","An existing file was found at the new location":"Encontrado um ficheiro na nova localização","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Foi encontrado um ficheiro na nova localização.\nTem a certeza de que deseja que a base de dados aponte para este ficheiro?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Foi encontrada uma base de dados local para o armazenamento.\nA reutilização da base de dados permite o funcionamento das instâncias do servidor e da linha de comandos no mesmo armazenamento remoto.\n\nDeseja reutilizar a base de dados existente?","Anonymous usage reports":"Relatório anónimos de utilização","Applications":"Aplicações","As Command-line":"Como linha de comandos","AuthID":"AuthID","Authentication method":"Método de autenticação","Authentication method ({{auth_method}})":"Método de autenticação ({{auth_method}})","Authentication password":"Palavra-passe de autenticação","Authentication username":"Nome de utilizador de autenticação","Autogenerated passphrase":"Frase-passe gerada automaticamente","B2 Application ID":"ID Aplicação B2","B2 Application Key":"Chave da aplicação B2","B2 Cloud Storage Account ID":"ID da conta B2 Cloud Storage","B2 Cloud Storage Application ID":"ID Aplicação B2 Cloud Storage","B2 Cloud Storage Application Key":"Chave da aplicação B2 Cloud Storage","Back":"Recuar","Backup complete!":"Cópia de segurança terminada!","Backup destination":"Destino da cópia de segurança","Backup location":"Localização da cópia de segurança","Backup retention":"Retenção de cópias de segurança","Backup:":"Cópia de segurança:","Beta":"Beta","Broken access":"Acesso danificado","Browse":"Explorar","Browser default":"Navegador padrão","Bucket create location":"Localização de criação do 'bucket'","Bucket name":"Nome do 'bucket'","Bucket storage class":"Classe de armazenamento do 'bucket'","Building list of files to restore …":"A criar a lista de ficheiros a restaurar ...","Building partial temporary database …":"A criar a base de dados parcial temporária ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ao permitir o acesso remoto, o servidor escuta solicitações de qualquer máquina na sua rede. Se ativar esta opção, certifique-se que está a usar sempre o computador numa rede protegida por uma firewall segura.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Por pré-definição, o ícone da barra de tarefas abrirá a interface do utilizador com um token que desbloqueia a mesma. Isto permite-lhe que consegue aceder à interface do utilizador a partir do ícone da barra de tarefas, garantindo que terceiros tenham de introduzir uma palavra-passe. Se preferir introduzir a palavra-passe ao aceder a partir do ícone da barra de tarefas, ative esta opção.","Cache Files":"Ficheiros em cache","Canary":"Canary","Cancel":"Cancelar","Cannot move to existing file":"Não foi possível mover o ficheiro existente","Changelog":"Registo de alterações","Changelog for {{appname}} {{version}}":"Registo de alterações para {{appname}} {{version}}","Check failed:":"Falha de verificação:","Check for updates now":"Procurar atualizações agora","Checking for updates …":"A procurar atualizações ...","Chose a storage type to get started":"Escolha o tipo de armazenamento para iniciar","Click the AuthID link to create an AuthID":"Clique na ligação para criar uma AuthID","Click to set throttle options":"Clique para definir as opções de velocidade","Client library to use":"Biblioteca do cliente a utilizar","Commandline …":"Linha de comandos ...","Compact Phase":"Fase de compactar","Compact now":"Compactar agora","Compacting remote data …":"A compactar dados remotos ...","Complete log":"Registo completo","Completing backup …":"A terminar a cópia de segurança ...","Completing previous backup …":"A completar a cópia de segurança anterior ...","Computer":"Computador","Configuration file:":"Ficheiro de configuração:","Configuration:":"Configuração:","Configure a new backup":"Configurar nova cópia de segurança","Confirm delete":"Confirmação de eliminação","Confirm encryption passphrase":"Confirme a chave de encriptação","Confirm passphrase":"Confirme a chave","Confirmation required":"Requer confirmação","Connect":"Estabelecer ligação","Connect now":"Estabelecer ligação agora","Connecting to server …":"A ligar ao servidor ...","Connection lost":"Ligação perdida","Connection worked!":"Ligação funcional!","Container name":"Nome do 'container'","Container region":"Região do 'container'","Continue":"Continuar","Continue without encryption":"Continuar sem encriptação","Copied!":"Copiada!","Copy":"Copiar","Copy Destination URL to Clipboard":"Copiar URL para a área de transferência","Copy failed. Please manually copy the URL":"Falha ao copiar. Copie o URL manualmente.","Core options":"Opções de core","Counting ({{files}} files found, {{size}})":"Encontrados ({{files}} ficheiros, {{size}})","Crashes only":"Apenas términos","Create bug report …":"Criar relatório de erros ...","Create folder?":"Criar pasta?","Created new limited user":"Criar utilizador com restrições","Creating bug report …":"A criar relatório de erros ...","Creating new user with limited access …":"A criar novo utilizador com acesso limitado ...","Creating target folders …":"A criar pastas de destino ...","Creating temporary backup …":"A criar cópia de segurança temporária ...","Current action:":"Ação atual:","Current file:":"Ficheiro atual:","Current version is {{versionname}} ({{versionnumber}})":"A versão atual é a {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"URL S3 personalizado","Custom Satellite":"Satélite personalizado","Custom Satellite ({{satellite}})":"Satélite personalizado ({{satellite}})","Custom authentication url":"URL personalizado de autenticação","Custom backup retention":"Retenção de cópias de segurança personalizada","Custom location ({{server}})":"Localização personalizada ({{server}})","Custom region for creating buckets":"Região personalizada para a criação de 'buckets'","Custom region value ({{region}})":"Valor personalizado da região ({{region}})","Custom server url ({{server}})":"URL personalizado do servidor ({{server}})","Custom storage class ({{class}})":"Classe personalizada do armazenamento ({{class}})","Database …":"Base de dados ...","Days":"Dias","Default":"Padrão","Default ({{channelname}})":"Padrão ({{channelname}})","Default excludes":"Exclusões padrão","Default options":"Opções padrão","Delete":"Eliminar","Delete Phase (Old Backup Versions)":"Fase de eliminar (versões de cópias de segurança antigas)","Delete backup":"Eliminar cópia de segurança","Delete backups that are older than":"Eliminar cópias de segurança mais antigas do que","Delete local database":"Eliminar base de dados local","Delete remote files":"Eliminar ficheiros remotos","Delete the local database":"Eliminar base de dados local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Eliminar {{filecount}} ficheiros ({{filesize}}) do armazenamento remoto?","Delete …":"A apagar ...","Deleted":"Eliminado","Deleted Versions":"Versões eliminadas","Deleted files":"Ficheiros eliminados","Deleting remote files …":"A apagar ficheiros remotos ...","Deleting unwanted files …":"A apagar ficheiros desnecessários ...","Description (optional)":"Descrição (opcional)","Description:":"Descrição:","Desktop":"Ambiente de trabalho","Destination":"Destino","Destination path":"Caminho de destino","Disabled":"Desativada","Dismiss":"Descartar","Dismiss all":"Descartar tudo","Display and color theme":"Visualização e cor do tema","Do you really want to delete the backup: \"{{name}}\" ?":"Tem a certeza de que deseja eliminar a cópia de segurança: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Tem a certeza de que deseja eliminar a base de dados local para: {{name}}?","Done":"Terminado","Download":"Descarregar","Downloaded files":"Descarregar ficheiros","Downloading files …":"A transferir ficheiros ...","Downloading update…":"A transferir atualizações ...","Duplicate option {{opt}}":"Opção duplicada {{opt}}","Duplicati Website":"Site do Duplicati","Duplicati forum":"Fórum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"O Duplicati será executado quando iniciado, mas permanecerá no estado pausado pela duração. O Duplicati ocupará recursos mínimos do sistema e não será executada nenhuma cópia de segurança.","Duration":"Duração","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada cópia de segurança tem uma base de dados local associada e que armazena as informações sobre a cópia de segurança remota na sua máquina local.\nAo eliminar uma cópia de segurança, também elimina a base de dados local e afetará a possibilidade de restaurar os ficheiros remotos.\nSe estiver a utilizar uma base de dados local para cópias de segurança a partir da linha de comandos deve manter esta base de dados.","Edit as list":"Editar como lista...","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Encriptar ficheiro","Encryption":"Encriptação","Encryption changed":"Encriptação alterada","Encryption passphrase":"Frase-passe de encriptação","End":"Fim","Enter URL":"Digite o URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Introduza uma estratégia de retenção. Os espaços reservados são D/W/Y para dias / semanas / anos e U para ilimitado. A sintaxe é: 7D:1D,4W:1W,36M:1M. Este exemplo mantém uma cópia de segurança para cada um dos próximos 7 dias, uma para cada uma das próximas 4 semanas e uma para cada um dos próximos 36 meses. Isso também pode ser escrito como 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Digite a frase-passe da cópia de segurança, se existente","Enter configuration details":"Digite os detalhes da configuração","Enter encryption passphrase":"Digite a frase-passe de encriptação","Enter expression here":"Digite aqui a expressão","Enter the destination path":"Digite o caminho do destino","Error":"Erro","Error!":"Erro!","Errors and crashes":"Erros e términos","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir diretórios cujo nome contém","Exclude expression":"Expressão de exclusão","Exclude file":"Ficheiro de exclusão","Exclude file extension":"Tipo de ficheiro de exclusão","Exclude files whose names contain":"Excluir ficheiros cujo nome contém","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Pasta de exclusão","Exclude regular expression":"Expressão regular de exclusão","Existing file found":"Encontrado ficheiro","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuração de cópia de segurança","Export configuration":"Exportar configuração","Export passwords":"Exportar palavras-passe","Export …":"Exportar ...","Exporting …":"A Exportar ...","External link":"Ligação externa","FTP (Alternative)":"FTP (Alternativo)","Failed to build temporary database: {{message}}":"Falha ao criar a base de dados temporária: {{message}}","Failed to connect:":"Falha ao estabelecer ligação:","Failed to connect: {{message}}":"Falha ao estabelecer ligação: {{message}}","Failed to delete:":"Falha ao eliminar:","Failed to fetch path information: {{message}}":"Falha ao obter a informação do caminho: {{message}}","Failed to find backup:":"Falha ao encontrar a cópia de segurança:","Failed to read backup defaults:":"Falha ao ler as definições da cópia de segurança:","Failed to restore files: {{message}}":"Falha ao restaurar os ficheiros: {{message}}","Failed to save:":"Falha ao guardar:","Fetching path information …":"A obter informação do caminho ...","File":"Ficheiro","Files larger than:":"Ficheiros maiores do que:","Filters":"Filtros","Finished!":"Terminado!","First run setup":"Configuração de primeira utilização","Folder":"Pasta","Folder path":"Caminho da pasta","Fri":"Sex","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID do projeto GSC","General":"Geral","General backup settings":"Definições gerias de cópia de segurança","General options":"Opções gerais","Generate":"Gerar","Generate IAM access policy":"Gerar política de acesso IAM","Getting file versions …":"A obter versão dos ficheiros ...","Group email":"E-mail do grupo","Hidden files":"Ficheiros ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar ficheiros ocultos","Home":"Página inicial","Hostnames":"Nomes de hosts","Hours":"Horas","How do you want to handle existing files?":"Como deseja gerir os ficheiros existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Se não existir data, a tarefa será executada assim que possível.","If at least one newer backup is found, all backups older than this date are deleted.":"Se for encontrada uma cópia de segurança mais recente, todas as cópias de segurança anteriores a esta data serão eliminadas.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se não digitar o cominho, todos os ficheiros serão guardados na pasta raiz.\nTem a certeza de que é isto que deseja?","If you do not enter an API Key, the tenant name is required":"Se não digitar a chave API, será necessário o nome do 'tenant' (projeto).","Import":"Importar","Import Destination URL":"Importar URL do destino","Import backup configuration":"Importar configuração da cópia de segurança","Import from a file":"Importar de um ficheiro","Import metadata":"Importar meta-dados","Importing …":"A importar ...","Include a file?":"Incluir um ficheiro?","Include expression":"Expressão de inclusão","Include regular expression":"Expressão regular de exclusão","Incorrect answer, try again":"Resposta errada, tente novamente.","Individual builds for developers only. Not for use with important data.":"Versões apenas para programadores. Não destinadas a serem utilizadas com dados importantes.","Information":"Informação","Invalid characters in path":"Caracteres inválidos no caminho","Invalid retention time":"Tempo de retenção inválido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"É possível estabelecer ligação a servidores FTP sem palavra-passe.\nTem a certeza de que o servidor FTP possui suporte a sessões no modo anónimo?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Manter um número específico","Keep all backups":"Manter todas as cópias de segurança","Keystone API version":"Versão da API Keystone","Language in user interface":"Idioma da interface de utilizador","Last month":"Último mês","Last successful backup:":"Última cópia de segurança com sucesso:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Último restauro bem-sucedido: {{time}} (demorou {{duration || '0 segundos'}})","Latest":"Último","Libraries":"Bibliotecas","Listing backup dates …":"A listar datas das cópias de segurança ...","Listing remote files for purge …":"A listar ficheiros remotos para apagar ...","Listing remote files …":"A listar ficheiros remotos ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Carregar uma configuração de uma tarefa exportada ou de um fornecedor de armazenamento","Load destination from an exported job or a storage provider":"Carregar um destino de uma tarefa exportada ou de um fornecedor de armazenamento","Load older data":"Carregar dados antigos","Loading …":"A carregar ...","Local Repository":"Repositório local","Local database path:":"Caminho da base de dados local:","Local repository":"Repositório local","Local storage":"Armazenamento local","Location":"Localização","Location where buckets are created":"Localização para a criação dos 'buckets'","Log data for {{Backup.Backup.Name}}":"Registo para {{Backup.Backup.Name}}","Log data from the server":"Registo a partir do servidor","Log out":"Terminar sessão","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Manutenção","Manually type path":"Digitar caminho manualmente","Max download speed":"Velocidade máxima para descargas","Max upload speed":"Velocidade máxima para envios","Menu":"Menu","Microsoft SQL Database:":"Base de dados Microsoft SQL:","Microsoft SQL Databases":"Bases de dados Microsoft SQL","Minimum redundancy":"Redundância mínima","Minimum redundancy is 1.0":"A redundância mínima é 1.0","Minutes":"Minutos","Missing name":"Nome em falta","Missing passphrase":"Frase-passe inexistente","Missing sources":"Fontes em falta","Modified":"Modificado","Mon":"Seg","Months":"Meses","Move existing database":"Mover base de dados existente","Move failed:":"Falha ao mover:","My Documents":"Meus documentos","My Music":"Minhas músicas","My Photos":"Minhas fotos","My Pictures":"Minhas imagens","Name":"Nome","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"O novo nome de utilizador é {{user}}.\nAs credenciais foram atualizadas para usar o utilizador limitado","Next":"Seguinte","Next scheduled run:":"Próximo agendamento:","Next scheduled task:":"Próxima tarefa agendada:","Next task:":"Próxima tarefa:","Next time":"Próxima hora","No":"Não","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Não foi especificado nenhum certificado anteriormente, verifique com o administrador do servidor se a chave está correta: {{key}}\n\nQuer aprovar a chave de host reportada?","No editor found for the "{{backend}}" storage type":"Não foi encontrado nenhum editor para o tipo de armazenamento "{{backend}}"","No encryption":"Sem encriptação","No items selected":"Nenhum item selecionado","No items to restore, please select one or more items":"Não existem itens a restaurar, selecione um ou mais itens","No passphrase entered":"Frase-passe não introduzida","No scheduled tasks":"Nenhuma tarefa agendada","Non-matching passphrase":"Disparidade de frases-passe","None / disabled":"Nenhum / desativado","Not using encryption":"Não usando encriptação","Nothing will be deleted. The backup size will grow with each change.":"Nada será eliminado. O tamanho da cópia de segurança crescerá com cada alteração.","OK":"Aceitar","Once there are more backups than the specified number, the oldest backups are deleted.":"Se existirem mais cópias de segurança do que o número especificado, as cópias de segurança mais antigas serão eliminadas.","OpenStack AuthURI":"URI de autenticação do OpenStack ","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aberto","Operating System":"Sistema operativo","Operation":"Operação","Operations:":"Operações:","Optional authentication password":"Palavra-passe opcional para autenticação","Optional authentication username":"Nome de utilizador opcional para autenticação","Options":"Opções","Original location":"Localização original","Others":"Outras","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Com o tempo, as versões das cópias de segurança serão eliminadas automaticamente. Permanecerá uma cópia de segurança dos últimos 7 dias, das últimas 4 semanas e cada um dos últimos 12 meses. Haverá sempre pelo menos uma cópia de segurança.","Overwrite":"Substituir","Passphrase":"Frase-passe","Passphrase (if encrypted)":"Frase-passe (se encriptado)","Passphrase changed":"Frase-passe alterada","Passphrases are not matching":"Disparidade de frases-passe","Passphrases do not match":"As frases-passe não coincidem","Password":"Palavra-passe","Patching files with local blocks …":"A aplicar correcções aos ficheiros com blocos locais ...","Path":"Caminho","Path not found":"Caminho não encontrado","Path on server":"Caminho no servidor","Path or subfolder in the bucket":"Caminho ou sub-pasta no 'bucket'","Pause":"Pausa","Pause after startup or hibernation":"Pausa após o arranque ou hibernação","Pause options":"Opções de pausa","Permissions":"Permissões","Pick location":"Escolher localização","Point to your backup files and restore from there":"Apontar para os ficheiros da cópia de segurança e restaurar a partir de lá","Port":"Porta","Prevent tray icon automatic log-in":"Impedir autenticação automática com o ícone da barra de tarefas","Previous":"Anterior","Progress:":"Progresso:","ProjectID is optional if the bucket exist":"ID do projeto é opcional se o 'bucket' já existir","Proprietary":"Proprietário","Purge Phase":"Fase de purgar","Purging files complete!":"A purga dos ficheiros está terminada!","Purging files …":"A eliminar ficheiros ...","Rebuilding local database …":"A recriar a base de dados local ...","Recreate (delete and repair)":"Recriar (eliminar e reparar)","Recreate Database Phase":"Fase de recriar base de dados","Recreating database …":"A recriar a base de dados","Registering temporary backup …":"A registar a cópia de segurança emporária ...","Relative paths not allowed":"Caminhos relativos não são permitidos","Reload":"Recarregar","Remote":"Remoto","Remote Path":"Caminho remoto","Remote Repository":"Repositório remoto","Remote path":"Caminho remoto","Remote repository":"Repositório remoto","Remote volume size":"Remover tamanho do volume","Remove":"Remover","Remove option":"Remover opção","Removed files":"Ficheiros removidos","Repair":"Reparar","Repair Phase":"Fase de reparar","Repairing database …":"A reparar a base de dados ...","Repeat Passphrase":"Repetição de frase-passe","Reporting:":"Reporte:","Reset":"Repor","Restore":"Restaurar","Restore complete!":"Restauro terminado!","Restore files":"Restaurar ficheiros","Restore files …":"Restaurar ficheiros ...","Restore from":"Restaurar de","Restore from backup configuration":"Restaurar de uma configuração de cópia de segurança","Restore options":"Opções de restauro","Restore read/write permissions":"Restaurar permissões de leitura/escrita","Restored Files":"Ficheiros restaurados","Restored Folders":"Pastas restauradas","Restored Symlinks":"Ligações de ficheiros restauradas","Restoring files …":"A restaurar ficheiros ...","Resume":"Retomar","Rewritten File Lists":"Listas de ficheiros reescritos","Run again every":"Executar a cada","Run now":"Executar agora","Running commandline entry":"A executar a entrada na linha de comandos","Running task:":"Tarefa em execução:","Running …":"A executar ...","S3 Compatible":"Compatível com S3","Same as the base install version: {{channelname}}":"Igual à versão de instalação base: {{channelname}}","Sat":"Sáb","Satellite":"Satélite","Save":"Guardar","Save and repair":"Guardar e reparar","Save different versions with timestamp in file name":"Guardar versões diferentes com marcas de hora no nome do ficheiro","Save immediately":"Guardar imediatamente","Scanning existing files …":"A analisar ficheiros existentes ...","Scanning for local blocks …":"A analisar blocos locais ...","Schedule":"Agendamento","Search":"Pesquisa","Search for files":"Pesquisar ficheiros","Seconds":"Segundos","Select a log level and see messages as they happen:":"Selecione um nível de registos e veja as mensagens conforme elas aparecem:","Select files":"Selecionar ficheiros","Server":"Servidor","Server and port":"Servidor e porta","Server hostname or IP":"Nome ou IP do servidor","Server is currently paused,":"O servidor está em pausa,","Server is currently paused, do you want to resume now?":"O servidor está em pausa, deseja continuar agora?","Server password":"Palavra-passe do servidor","Server paused":"Servidor em pausa","Server state properties":"Propriedades do estado do servidor","Settings":"Definições","Show":"Mostrar","Show advanced editor":"Mostrar editor avançado","Show hidden folders":"Mostrar pastas ocultas","Show log":"Mostrar registo","Show log …":"Mostrar registo ...","Show treeview":"Mostrar em árvore","Sia server password":"Palavra-passe do servidor Sia","Smart backup retention":"Retenção de cópia de segurança inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns fornecedores OpenStack permitem uma chave de API em vez de uma palavra-passe e o tenant (projeto)","Some S3 providers might only be compatible with a certain client library":"Alguns fornecedores de S3 podem ser compatíveis apenas com uma determinada biblioteca de clientesSome S3 providers might only be compatible with a certain client library","Source Data":"Dados de origem","Source Files":"Ficheiros de origem","Source data":"Dados de origem","Source folders":"Pastas de origem","Source:":"Origem:","Specific builds for developers only. Not for use with important data.":"Versões específicas apenas para programadores. Não destinadas a serem utilizadas com dados importantes.","Standard protocols":"Protocolos padrão","Start":"Iniciar","Starting backup …":"A iniciar a cópia de segurança ...","Starting restore …":"A iniciar o restauro ...","Starting the restore process …":"A iniciar o processo de restauro ...","Stop after current file":"Parar após o ficheiro atual","Stop after the current file":"Parar após o ficheiro atual","Stop now":"Parar agora","Stop running backup":"Parar cópia de segurança em execução","Stop running task":"Parar tarefa em execução","Stopping after the current file:":"A parar após o ficheiro atual:","Stopping task:":"Parar tarefa:","Storage Type":"Tipo de armazenamento","Storage class":"Classe de armazenamento","Storage class for creating a bucket":"Classe de armazenamento para criar um 'bucket'","Stored":"Guardado","Strong":"Forte","Success":"Sucesso","Sun":"Dom","Symbolic link":"Ligação simbólica","System Files":"Ficheiros de sistema","System default ({{levelname}})":"Predefinição ({{levelname}})","System files":"Ficheiros do sistema","System info":"Informações do sistema","System properties":"Propriedades do sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Tarefa em execução","Temporary Files":"Ficheiros temporários","Temporary files":"Ficheiros temporários","Test Phase":"Fase de teste","Test connection":"Testar ligação","Testing permissions …":"A verificar permissões ...","Testing …":"A verificar ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"O campo '{{fieldname}}' contém um carácter inválido: {{character}} (valor: {{value}}, índice: {{pos}})","The backup is missing, has it been deleted?":"Falta a cópia de segurança. Será que foi eliminada?","The backup was temporary and does not exist anymore, so the log data is lost":"A cópia de segurança era temporária e já não existe, por isso os dados de registo foram perdidos","The bucket name should be all lower-case, convert automatically?":"O nome do 'bucket' deve ser todo em minúsculas. Converter automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"A configuração deve ser mantida de forma segura. Tem a certeza de que quer guardar um ficheiro não encriptado contendo as suas palavras-passe?","The dark theme (by Michal)":"Tema escuro (por Michal)","The default blue on white theme (by Alex)":"Azul em tema claro (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"A pasta {{folder}} não existe.\nCriar agora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"A chave do host foi alterada, verifique com o administrador do servidor se está correta, caso contrário pode ter sido vítima de um ataque MAN-IN-THE MIDDLE.\n\nDeseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" pela chave do host REPORTADA: {{key}}?","The passwords do not match":"As palavras-passe não coincidem","The path does not appear to exist, do you want to add it anyway?":"Parece que o caminho não existe, quer adicioná-lo mesmo assim?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"O caminho não termina com um caractere '{{dirsep}}, o que significa que incluiu um ficheiro e não uma pasta.\n\nQuer incluir o ficheiro especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"O caminho deve ser um caminho absoluto, ou seja, deve começar com uma barra inclinada '/'","The region parameter is only applied when creating a new bucket":"O parâmetro de região só é aplicado ao criar um novo 'bucket'","The region parameter is only used when creating a bucket":"O parâmetro de região só é usado na criação de um 'bucket'","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Não foi possível validar o certificado do servidor.\nQuer aprovar o certificado SSL com o hash: {{hash}}?","The storage class affects the availability and price for a stored file":"A classe de armazenamento afeta a disponibilidade e o preço de um ficheiro armazenado","The target folder contains encrypted files, please supply the passphrase":"A pasta de destino contém ficheiros encriptados. Forneça a frase-passe","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"O utilizador tem muitas permissões. Quer criar um novo utilizador limitado, com permissões apenas para o caminho selecionado?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Esta cópia de segurança foi criada noutro sistema operativo. A restauração dos ficheiros sem especificar uma pasta de destino pode fazer com que os ficheiros sejam restaurados em locais inesperados. Tem a certeza que quer continuar sem escolher uma pasta de destino?","This month":"Este mês","This week":"Esta semana","Throttle settings":"Definições de velocidade","Thu":"Qui","Time":"Hora","To File":"Para ficheiro","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que deseja eliminar todos os ficheiros remotos para \"{{nome}}\", insira a palavra abaixo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sem uma frase-passe, desmarque a caixa \"Encriptar ficheiro\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar vários ataques baseados em DNS, o Duplicati limita os nomes de host permitidos aos que estão listados aqui. O acesso IP direto e o host local são sempre permitidos. Podem ser fornecidos vários nomes de host com um separador de ponto-e-vírgula. Se qualquer um dos nomes de host permitidos for um asterisco (*), todos os nomes de host serão permitidos e esse recurso será desativado. Se o campo estiver vazio, apenas o endereço IP e o acesso ao host local serão permitidos.","Today":"Hoje","Trust host certificate?":"Confiar no certificado do host?","Trust server certificate?":"Confiar no certificado do servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Experimente as novas funcionalidades em que estamos a trabalhar. Atualmente, a versão mais estável disponível. Teste restaurar dados antes de usar isto em ambientes de produção.","Tue":"Terça","Type passphrase here.":"Digite a frase-passe aqui.","Type to highlight files":"Digite para destacar ficheiros","Unknown backup size and versions":"Tamanho e versões da cópia de segurança desconhecidos","Until resumed":"Até retormar","Update channel":"Canal de atualização","Update failed:":"Falha ao atualizar:","Updating with existing database":"A atualizar base de dados existente","Uploaded files":"Ficheiros enviados","Uploading verification file …":"A enviar ficheiro de verificação ...","Usage statistics":"Estatísticas de utilização","Usage statistics, warnings, errors, and crashes":"Estatísticas de utilização, avisos e erros","Use SSL":"Usar SSL","Use existing database?":"Usar base de dados existente?","Use weak passphrase":"Utilizar frase-passe fraca","Useless":"Inútil","User data":"Dados do utilizador","User domain name":"Nome do domínio do utilizador","User has too many permissions":"Utilizador com demasiadas permissões","User interface settings":"Definições da interface","Username":"Nome de utilizador","Vacuuming database …":"A limpar a base de dados ...","Validating …":"A validar ...","Verifications":"Verificações","Verify files":"A verificar ficheiros","Verifying answer":"A verificar resposta","Verifying backend data …":"A verificar dados remotos ...","Verifying files …":"A verificar ficheiros ...","Verifying remote data …":"A verificar dados remotos ...","Verifying restored files …":"A verificar ficheiros restaurados ...","Verifying …":"A verificar ...","Version ID":"ID da versão","Very strong":"Muito forte","Very weak":"Muito fraca","Visit us on":"Visite-nos em","WARNING: This will prevent you from restoring the data in the future.":"AVISO: isto impedirá que possa restaurar os dados no futuro.","Waiting for task to begin":"À espera para iniciar a tarefa","Waiting for upload to finish …":"A aguardar que o envio termine ...","Warnings, errors and crashes":"Avisos e erros","We recommend that you encrypt all backups stored outside your system":"Recomendamos que encripte todas as cópias de segurança armazenadas fora do seu sistema","Weak":"Fraca","Weak passphrase":"Frase-passe fraca","Wed":"Qua","Weeks":"Semanas","Where do you want to restore from?":"De onde quer restaurar?","Where do you want to restore the files to?":"Para onde quer restaurar os ficheiros?","Years":"Anos","Yes":"Sim","Yes, I have stored the passphrase safely":"Sim, eu armazenei a frase-passe de forma segura","Yes, I understand the risk":"Sim, eu entendo os riscos","Yes, I'm brave!":"Sim, sou valente!","Yes, please break my backup!":"Sim, por favor estraga a minha cópia de segurança!","Yesterday":"Ontem","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Está a alterar o caminho da base de dados para longe de uma base de dados existente.\nTem a certeza que quer isso?","You are currently running {{appname}} {{version}}":"Está a executar o {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Pode parar a cópia de segurança após o envio de qualquer ficheiro em curso terminar.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Pode parar a tarefa imediatamente ou parar a tarefa após o processo do ficheiro atual.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Mudou o modo de encriptação. Isso pode estragar algo. Em vez disso é recomendável fazer uma cópia de segurança.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Alterou a frase-passe, que não é suportada. Em vez disso é recomendável criar uma cópia de segurança.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Escolheu não encriptar a cópia de segurança. É recomendável encriptar todos os dados armazenados num servidor remoto.","You have chosen to restore to a new location, but not entered one":"Escolheu restaurar para uma localização distinta mas não a indicou","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Gerou uma frase-passe segura. Certifique-se que fez uma cópia da frase-passe, uma vez que os dados não podem ser recuperados se perder a frase-passe.","You must choose at least one source folder":"Tem que escolher, pelo menos, uma pasta de origem","You must enter a domain name to use v3 API":"Tem de introduzir um nome de domínio para usar a API v3","You must enter a name for the backup":"Tem que introduzir o nome para a cópia de segurança","You must enter a passphrase or disable encryption":"Tem de introduzir uma frase-passe ou desativar a encriptação","You must enter a password to use v3 API":"Tem de introduzir uma palavra-passe para usar a API v3","You must enter a positive number of backups to keep":"Tem que introduzir um número positivo para as cópias de segurança a manter","You must enter a tenant (aka project) name to use v3 API":"Te de introduzir um tenant (ou seja projeto) para usar a API v3","You must enter a valid duration for the time to keep backups":"Tem de introduzir uma duração de tempo válida durante a qual deve manter as cópias de segurança","You must enter a valid retention policy string":"Tem de inserir uma cadeia de política de retenção válida","You must fill in the password":"Tem que preencher uma palavra-passe","You must fill in the server name or address":"Tem que preencher o nome ou endereço do servidor","You must fill in the username":"Tem que preencher o nome de utilizador","You must fill in {{field}}":"Tem que preencher {{field}}","You must select or fill in the AuthURI":"Tem que selecionar ou preencher o AuthURI","You must select or fill in the server":"Tem que selecionar ou preencher o servidor","You must specify a path":"Tem que especificar o caminho","Your files and folders have been restored successfully.":"Os seus ficheiros e pastas foram restaurados com sucesso.","Your passphrase is easy to guess. Consider changing passphrase.":"A sua frase-passe é muito fraca. Deve alterar para uma mais forte.","bucket/folder/subfolder":"'bucket'/pasta/sub-pasta","byte":"byte","byte/s":"byte/s","custom":"personalizado","resume now":"retomar agora","unless you are explicitly specifying --group-id":"a não ser que esteja a especificar explicitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} foi inicialmente desenvolvido por {{dev1}} e {{dev2}}. {{appname}} pode ser descarregado em {{websitename}}. {{appname}} é licenciado nos termos da {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} ficheiros ({{size}}) por enviar {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versão","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versões","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versões"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} horas","{{number}} Minutes":"{{number}} minutos","{{time}} (took {{duration}})":"{{time}} (demorou {{duration}})"}); - gettextCatalog.setStrings('ro', {"- pick an option -":"- alegeți o opțiune -","...loading...":"...se încarcă...","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"Politica AWS IAM","About":"Despre","About {{appname}}":"Despre {{appname}}","Access Key":"Cheie de acces","Access denied":"Acces interzis","Access to user interface":"Accesul la interfața cu utilizatorul","Account name":"Nume de cont","Add a new backup":"Adăugați o copie de rezervă nouă","Add a path directly":"Adăugați direct o cale","Add advanced option":"Adăugați opțiunea avansată","Add backup":"Adăugați o copie de rezervă","Add filter":"Adăugați un filtru","Add path":"Adaugă calea","Added":"Adăugat","Adjust bucket name?":"Modificați numele găleții?","Advanced Options":"Opțiuni avansate","Advanced options":"Opțiuni avansate","Advanced:":"Avansat:","All Hyper-V Machines":"Toate mașinile Hyper-V","All Microsoft SQL Databases":"Toate bazele de date Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Toate rapoartele de utilizare sunt trimise anonim și nu conțin informații personale. Acestea conțin informații despre hardware și sistemul de operare, tipul de backend, durata de copiere, dimensiunea generală a datelor sursă și datele similare. Ele nu conțin căi, nume de fișiere, nume de utilizator, parole sau alte informații sensibile similare.","Allow remote access (requires restart)":"Permiteți accesul de la distanță (necesită repornire)","Allowed days":"Zile permise","An existing file was found at the new location":"Un fișier existent a fost găsit la noua locație","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fișier existent a fost găsit la noua locație\nSigur doriți ca baza de date să indice un fișier existent?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"O bază de date locală existentă pentru stocare a fost găsită.\nReutilizarea bazei de date va permite instanțelor de linie de comandă și server să funcționeze pe aceeași stocare la distanță.\n\n Doriți să utilizați baza de date existentă?","Anonymous usage reports":"Rapoarte de utilizare anonime","Applications":"Aplicații","As Command-line":"Ca linie de comandă","AuthID":"authId","Authentication password":"Parola de autentificare","Authentication username":"Numele de utilizator de autentificare","Autogenerated passphrase":"Fraza de acces generată automat","B2 Application Key":"B2 cheie de aplicație","B2 Cloud Storage Account ID":"B2 ID-ul contului de stocare în cloud","B2 Cloud Storage Application Key":"B2 Cheia aplicației de stocare cloud","Back":"Înapoi","Backup destination":"Destinație de rezervă","Backup location":"Locație de rezervă","Backup:":"Copie de rezervă:","Beta":"Beta","Broken access":"Accesul spart","Browse":"Naviga","Browser default":"Browser default","Bucket create location":"Locația unde va fi creată găleata","Bucket name":"Numele găleții","Bucket storage class":"Clasa de stocare a găleții","Building list of files to restore …":"Creez lista de fișiere de restaurat ...","Building partial temporary database …":"Creez o bază de date parțială temporară ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Prin permiterea accesului de la distanță, se configurează serverul sa asculte cererile oricăror mașini din rețeaua ta. Dacă activezi această opțiune, asigură-te că folosești mereu calculatorul într-o rețea protejată de firewall.","Cache Files":"Încarcă fișierele în avans","Canary":"Canar","Cancel":"Anulare","Cannot move to existing file":"Nu se poate muta la fișierul existent","Changelog":"Jurnal de modificări","Changelog for {{appname}} {{version}}":"Jurnal de modificări pentru {{appname}} {{version}}","Check failed:":"Verificarea a eșuat:","Check for updates now":"Verifică acum actualizările","Checking for updates …":"Caut versiuni noi ...","Chose a storage type to get started":"Alege un tip de stocare pentru a începe","Click the AuthID link to create an AuthID":"Faceți clic pe linkul AuthID pentru a crea un AuthID","Click to set throttle options":"Faceți clic pentru a seta opțiunile de accelerație","Commandline …":"Linie de comandă ...","Compact Phase":"Etapa de compactare","Compact now":"Compactează acum","Compacting remote data …":"Se compactează datele de la distanță ...","Complete log":"Jurnal complet","Completing backup …":"Se finalizează copia de rezervă ...","Completing previous backup …":"Se finalizează copia de rezervă anterioară ...","Computer":"Calculator","Configuration file:":"Fișier de configurare:","Configuration:":"Configurare:","Configure a new backup":"Configurați o copie de rezervă nouă","Confirm delete":"Confirmă ștergerea","Confirm encryption passphrase":"Confirmă parola de criptare","Confirm passphrase":"Confirmă parola","Confirmation required":"Confirmare Necesară","Connect":"Conectează","Connect now":"Conectează acum","Connecting to server …":"Se conectează la server ...","Connection lost":"Conexiunea a fost pierdută","Connection worked!":"Conexiunea a funcționat!","Container name":"Numele containerului","Container region":"Zona containerului","Continue":"Continuă","Continue without encryption":"Continuă fără criptare","Copied!":"Copiată!","Copy":"Copiază","Copy Destination URL to Clipboard":"Copiați adresa URL de destinație în Clipboard","Copy failed. Please manually copy the URL":"Copierea a eșuat. Copiați manual adresa URL","Core options":"Opțiuni centrale","Counting ({{files}} files found, {{size}})":"Numărătoare ({{fișiere}} fișiere găsite, {{size}})","Crashes only":"Doar eșecuri","Create bug report …":"Creează un raport de defecțiune","Create folder?":"Creează director?","Created new limited user":"S-a creat un nou utilizator cu drepturi limitate","Creating bug report …":"Se creează un raport de defecțiuni ...","Creating new user with limited access …":"Se creează un nou utilizator cu acces limitat ...","Creating target folders …":"Se creează directoarele destinație ...","Creating temporary backup …":"Se creează o copie de rezervă temporară ...","Current action:":"Acțiunea curentă:","Current file:":"Fișierul curent:","Current version is {{versionname}} ({{versionnumber}})":"Versiunea curentă este {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Conector S3 personalizat","Custom authentication url":"Adresă de autentificare personalizată","Custom backup retention":"Durată de retenție a copiei de rezervă personalizată","Custom location ({{server}})":"Locația particularizată ({{server}})","Custom region for creating buckets":"Regiunea personalizată pentru crearea de cupe","Custom region value ({{region}})":"Valoarea pentru regiunea particularizată ({{region}})","Custom server url ({{server}})":"Adresa URL a serverului personalizat ({{server}})","Custom storage class ({{class}})":"Clase de stocare personalizate ({{class}})","Database …":"Bază de date ...","Days":"Zile","Default":"Mod implicit","Default ({{channelname}})":"Implicit ({{nume_canal}})","Default excludes":"Excluderi implicite","Default options":"Opțiunile prestabilite","Delete":"Șterge","Delete Phase (Old Backup Versions)":"Etapa de ștergere (Versiuni Vechi ale Copiei de Rezervă)","Delete backup":"Șterge copie de rezervă","Delete backups that are older than":"Șterge copiile de rezervă mai vechi de:","Delete local database":"Șterge baza de date locală","Delete remote files":"Șterge fișierele la distanță","Delete the local database":"Ștergeți baza de date locală","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Ștergeți fișierele {{filecount}} ({{file size}}) din spațiul de stocare de la distanță?","Delete …":"Șterge ...","Deleted":"Șters","Deleted Versions":"Versiuni șterse","Deleted files":"Fișiere șterse","Deleting remote files …":"Se șterg fișierele de la distanță ...","Deleting unwanted files …":"Se șterg fișierele nedorite ...","Description (optional)":"Descriere (opțional)","Description:":"Descriere:","Desktop":"Spațiul de lucru","Destination":"Destinaţie","Destination path":"Calea destinație","Disabled":"Inactiv","Dismiss":"Închide","Dismiss all":"Închide tot","Display and color theme":"Afișare și temă de culoare","Do you really want to delete the backup: \"{{name}}\" ?":"Chiar vrei să ștergi copia de rezervă: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Chiar vrei să ștergi baza de date locală pentru: {{name}}","Done":"Terminat","Download":"Descarcă","Downloaded files":"Fișierele descărcate","Downloading files …":"Se descarcă fișierele ...","Downloading update…":"Se descarcă actualizarea ...","Duplicate option {{opt}}":"Opțiunea de duplicare {{opt}}","Duplicati Website":"Site-ul web al Duplicati","Duplicati forum":"Forum-ul Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati va rula la pornire, dar va rămâne pe pauză pentru durata specificată. Duplicati va folosi resurse minime și nu va fi creată nici o copie de rezervă.","Duration":"Durată","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Fiecare copie de rezervă are o bază de date locală asociată cu aceasta, care stochează informații despre copia de siguranță la distanță de pe aparatul local.\n            Când ștergeți o copie de rezervă, puteți șterge și baza de date locală fără a afecta capacitatea de a restabili fișierele la distanță.\n            Dacă utilizați baza de date locală pentru copii de rezervă din linia de comandă, ar trebui să păstrați baza de date.","Edit as list":"Editați ca listă","Edit as text":"Editați ca text","Encrypt file":"Criptați fișierul","Encryption":"Criptarea","Encryption changed":"Criptarea a fost modificată","End":"Sfârșit","Enter URL":"Introdu URL-ul","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Înregistrează manual o strategie de retenție. Literele sunt D/W/Y oentru zile/săptămâni/ani și U pentru nelimitat. Sintaxa este: 7D:1D,4W:1W,36M:1M. Acest exemplu păstreză o copie de rezervă pentru fiecare zi din următoarele 7 zile, una pentru următoarele 4 săptămâni și una pentru fiecare din următoarele 36 de luni. Acest lucru poate fi scris astfel 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduceți fraza de acces, dacă există","Enter configuration details":"Introduceți detaliile de configurare","Enter encryption passphrase":"Introduceți expresia de acces pentru criptare","Enter expression here":"Introduceți expresia aici","Enter the destination path":"Introduceți calea de destinație","Error":"Eroare","Error!":"Eroare!","Errors and crashes":"Erori și accidente","Examined":"Examinat","Exclude":"Exclude","Exclude directories whose names contain":"Excludeți directoarele ale căror nume conțin","Exclude expression":"Excludeți expresia","Exclude file":"Excludeți fișierul","Exclude file extension":"Excludeți extensia de fișier","Exclude files whose names contain":"Excludeți fișierele ale căror nume conțin","Exclude folder":"Excludeți dosarul","Exclude regular expression":"Excludeți expresia regulată","Existing file found":"Fișierul existent găsit","Experimental":"Experimental","Export":"Export","Export backup configuration":"Exportați configurația de backup","Export configuration":"Exportați configurația","FTP (Alternative)":"FTP (alternativă)","Failed to build temporary database: {{message}}":"Eroare la crearea bazei de date temporare: {{message}}","Failed to connect:":"Eroare de conexiune:","Failed to connect: {{message}}":"Nu s-a putut conecta: {{message}}","Failed to delete:":"Nu sa șters:","Failed to fetch path information: {{message}}":"Nu s-a putut obține informații despre cale: {{message}}","Failed to read backup defaults:":"Nu au putut fi citite valorile implicite de rezervă:","Failed to restore files: {{message}}":"Nu sa reușit restaurarea fișierelor: {{message}}","Failed to save:":"Salvarea nu a reușit:","File":"Fişier","Files larger than:":"Fișiere mai mari decât:","Filters":"Filtre","Finished!":"Terminat!","First run setup":"Prima configurare","Folder":"Pliant","Folder path":"Dosarul de cale","Fri":"Vi","GByte":"GByte","GByte/s":"GByte / s","GCS Project ID":"ID de proiect GCS","General":"General","General backup settings":"Setări de rezervă generale","General options":"Optiuni generale","Generate":"Genera","Hidden files":"Fișiere ascunse","Hide":"Ascunde","Hide hidden folders":"Ascundeți folderele ascunse","Home":"Acasă","Hours":"ore","How do you want to handle existing files?":"Cum doriți să gestionați fișierele existente?","Hyper-V Machine":"Mașină Hyper-V","Hyper-V Machine:":"Mașina Hyper-V:","Hyper-V Machines":"Mașini Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Dacă o dată a fost ratată, lucrarea va funcționa cât mai curând posibil.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Dacă nu introduceți o cale, toate fișierele vor fi stocate în dosarul de conectare.\nEști sigur că asta vrei?","If you do not enter an API Key, the tenant name is required":"Dacă nu introduceți o cheie API, este necesar numele locatarului","Import":"Import","Import Destination URL":"Importați adresa URL de destinație","Import backup configuration":"Importați configurația de rezervă","Import from a file":"Importați dintr-un fișier","Include a file?":"Includeți un fișier?","Include expression":"Includeți expresia","Include regular expression":"Includeți expresia regulată","Incorrect answer, try again":"Răspuns incorect, încercați din nou","Information":"informație","Invalid characters in path":"Caractere nevalide în cale","Invalid retention time":"Timp de retenție nevalid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Este posibil să vă conectați la un FTP fără o parolă.\nSunteți sigur că serverul FTP acceptă login-urile fără parolă?","KByte":"kByte","KByte/s":"KByte / s","Language in user interface":"Limba în interfața cu utilizatorul","Last month":"Luna trecuta","Latest":"Cele mai recente","Libraries":"Biblioteci","Load a configuration from an exported job or a storage provider":"Încărcați o configurație dintr-o lucrare exportată sau dintr-un furnizor de stocare","Load destination from an exported job or a storage provider":"Încărcați destinația dintr-o lucrare exportată sau dintr-un furnizor de stocare","Load older data":"Încărcați date mai vechi","Local database path:":"Calea bazei de date locale:","Local storage":"Depozit local","Location":"Locație","Location where buckets are created":"Locația în care sunt create găleți","Log data for {{Backup.Backup.Name}}":"Date din jurnal pentru {{Backup.Backup.Name}} ","Log data from the server":"Datele din jurnal de pe server","Log out":"Deconectați-vă","MByte":"MByte","MByte/s":"MByte / s","Maintenance":"întreținere","Manually type path":"Trasează manual calea","Max download speed":"Viteză maximă de descărcare","Max upload speed":"Viteză maximă de încărcare","Menu":"Meniul","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Baze de date Microsoft SQL","Minimum redundancy":"Redundanță minimă","Minimum redundancy is 1.0":"Redundanța minimă este de 1,0","Minutes":"Minute","Missing name":"Lipsește numele","Missing passphrase":"Fraza de acces lipsă","Missing sources":"Sursa lipsă","Mon":"Mon","Months":"Luni","Move existing database":"Mutați baza de date existentă","Move failed:":"Mutarea a eșuat:","My Documents":"Documentele mele","My Music":"Muzica mea","My Photos":"Fotografiile mele","My Pictures":"Pozele mele","Name":"Nume","Never":"Nu","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Numele noului utilizator este {{user}}.\nAu fost aprobate informațiile pentru a utiliza noul utilizator limitat","Next":"Următor →","Next scheduled run:":"Următorul programat:","Next scheduled task:":"Următoarea sarcină programată:","Next task:":"Următoarea sarcină:","Next time":"Data viitoare","No":"Nu","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Niciun certificat nu a fost specificat anterior, verificați cu administratorul serverului că cheia este corectă: {{key}}\n\nDoriți să aprobați cheia de gazdă raportată?","No editor found for the "{{backend}}" storage type":"Nu a fost găsit un editor pentru tipul de stocare 6118489 _ {{backend}} "","No encryption":"Nu există criptare","No items selected":"Nu au fost selectate elemente","No items to restore, please select one or more items":"Nu există elemente pentru restaurare, selectați unul sau mai multe elemente","No passphrase entered":"Nu a fost introdusă nici o expresie de acces","No scheduled tasks":"Nu există sarcini programate","Non-matching passphrase":"Fraza de acces fără potrivire","None / disabled":"Nici unul / dezactivat","OK":"O.K","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Operations:":"Operații:","Optional authentication password":"Parola de autentificare opțională","Optional authentication username":"Nume de utilizator opțional de autentificare","Options":"Opțiuni","Original location":"Locația originală","Others":"Alții","Overwrite":"Suprascriere","Passphrase":"o expresie de acces","Passphrase (if encrypted)":"Fraza de acces (dacă este criptată)","Passphrase changed":"Fraza de acces a fost modificată","Passphrases are not matching":"Frazele de acces nu se potrivesc","Password":"Parola","Path not found":"Calea nu a fost găsită","Path on server":"Cale pe server","Path or subfolder in the bucket":"Cale sau subfolder în găleată","Pause":"Pauză","Pause after startup or hibernation":"Întrerupeți după pornire sau hibernare","Pause options":"Opțiunile de întrerupere","Permissions":"Permisiuni","Pick location":"Alegeți locația","Point to your backup files and restore from there":"Indicați fișierele de rezervă și restaurați-le de acolo","Port":"Port","Previous":"Anterior","ProjectID is optional if the bucket exist":"ID-ul proiectului este opțional dacă există o cupă","Proprietary":"Proprietate","Recreate (delete and repair)":"Refaceți (ștergeți și reparați)","Relative paths not allowed":"Căile relative nu sunt permise","Reload":"Reîncarcă","Remote":"la distanta","Remove":"Elimina","Remove option":"Eliminați opțiunea","Repair":"Reparație","Repeat Passphrase":"Repetați expresia de acces","Reporting:":"Raportarea:","Reset":"restabili","Restore":"Restabili","Restore files":"Restaurați fișierele","Restore from":"Restaurați de la","Restore from backup configuration":"Restabiliți din configurația de backup","Restore options":"Restaurați opțiunile","Restore read/write permissions":"Restaurați permisiunile de citire / scriere","Resume":"Relua","Run again every":"Rulați din nou fiecare","Run now":"Fugiți acum","Running commandline entry":"Rulează intrarea în linia de comandă","Running task:":"Sarcina de funcționare:","S3 Compatible":"S3 Compatibil","Same as the base install version: {{channelname}}":"La fel ca versiunea de instalare de bază: {{channelname}}","Sat":"Sat","Save":"Salvați","Save and repair":"Salvați și reparați","Save different versions with timestamp in file name":"Salvați diferite versiuni cu marca de timp în numele fișierului","Save immediately":"Salvați imediat","Schedule":"Programa","Search":"Căutare","Search for files":"Căutați fișiere","Seconds":"secunde","Select a log level and see messages as they happen:":"Selectați un nivel de jurnal și vedeți mesajele așa cum se întâmplă:","Select files":"Selectati fisierele","Server":"Server","Server and port":"Server și port","Server hostname or IP":"Server hostname sau IP","Server is currently paused,":"Serverul este în prezent întrerupt,","Server is currently paused, do you want to resume now?":"Serverul este în prezent întrerupt, doriți să îl reluați acum?","Server password":"Parola serverului","Server paused":"Serverul a fost întrerupt","Server state properties":"Proprietăți stare server","Settings":"Setări","Show":"Spectacol","Show advanced editor":"Afișați editorul avansat","Show hidden folders":"Afișați dosarele ascunse","Show log":"Arată jurnal","Show treeview":"Afișați arborele","Sia server password":"Parola serverului Sia","Some OpenStack providers allow an API key instead of a password and tenant name":"Unii furnizori OpenStack permit o cheie API în locul unei parole și a unui nume de chiriaș","Source Data":"Datele sursă","Source data":"Datele sursă","Source folders":"Sursă de directoare","Source:":"Sursă:","Standard protocols":"Protocoale standard","Stop after the current file":"Opriți după fișierul curent","Stop now":"Opreste-te acum","Stop running backup":"Nu mai rulați backupul","Stop running task":"Opriți executarea sarcinii","Stopping task:":"Oprire:","Storage Type":"Tip de stocare","Storage class":"Clasă de stocare","Storage class for creating a bucket":"Clasă de stocare pentru crearea unei găleți","Stored":"stocate","Strong":"Puternic","Success":"Succes","Sun":"Soare","Symbolic link":"Link-uri simbolice","System default ({{levelname}})":"Implicit în sistem ({{levelname}})","System files":"Fișiere de sistem","System info":"Informatie de sistem","System properties":"Proprietatile sistemului","TByte":"TByte","TByte/s":"TByte / s","Task is running":"Sarcina se execută","Temporary files":"Fișiere temporare","Test connection":"Test de conexiune","The bucket name should be all lower-case, convert automatically?":"Numele găleții ar trebui să fie toate literele mici, să se convertească automat?","The dark theme (by Michal)":"Tema intunecata (de Michal)","The default blue on white theme (by Alex)":"Culoarea albastră implicită pe alb (de Alex)","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Cheia gazdă a fost modificată, verificați-vă cu administratorul serverului dacă aceasta este corectă, altfel ați putea fi victima unui atac MAN-IN-THE-MIDDLE.\n\nDoriți să ÎNLOCUIți cheia gazdă CURRENT \"{{prev}}\" cu cheia gazdă REPORTED: {{key}}?","The path does not appear to exist, do you want to add it anyway?":"Calea nu pare să existe, vreți să o adăugați oricum?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Calea nu se termină cu un caracter {{dirsep}}, ceea ce înseamnă că includeți un fișier, nu un dosar.\n\nDoriți să includeți fișierul specificat?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Calea trebuie să fie o cale absolută, adică trebuie să pornească cu o slash '/'","The region parameter is only applied when creating a new bucket":"Parametrul regiune se aplică numai când se creează o nouă găleată","The region parameter is only used when creating a bucket":"Parametrul regiune este utilizat numai când creați o găleată","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certificatul de server nu a putut fi validat.\nDoriți să aprobați certificatul SSL cu hash: {{hash}}?","The storage class affects the availability and price for a stored file":"Clasa de stocare afectează disponibilitatea și prețul unui fișier stocat","The target folder contains encrypted files, please supply the passphrase":"Dosarul țintă conține fișiere criptate, furnizați expresia de acces","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Utilizatorul are prea multe permisiuni. Doriți să creați un nou utilizator limitat, cu permisiuni numai pentru calea selectată?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Această copie de siguranță a fost creată pe un alt sistem de operare. Restaurarea fișierelor fără specificarea unui dosar de destinație poate determina refacerea fișierelor în locuri neașteptate. Sigur doriți să continuați fără a alege un dosar de destinație?","This month":"Luna aceasta","This week":"Săptămâna aceasta","Throttle settings":"Setările clapetei","Thu":"Thu","To File":"La dosar","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pentru a confirma că doriți să ștergeți toate fișierele la distanță pentru \"{{name}}\", introduceți cuvântul pe care îl vedeți mai jos","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pentru a exporta fără o expresie de acces, debifați caseta \"Criptare fișier\"","Today":"Astăzi","Trust host certificate?":"Trust gazdă certificat?","Trust server certificate?":"Certificat de server de încredere?","Tue":"Marti","Type to highlight files":"Tastați pentru a evidenția fișierele","Unknown backup size and versions":"Mărimea și versiunile de rezervă necunoscute","Until resumed":"Până la reluare","Update channel":"Actualizați canalul","Update failed:":"Actualizare esuata:","Updating with existing database":"Actualizarea cu baza de date existentă","Usage statistics":"Statistica utilizării","Usage statistics, warnings, errors, and crashes":"Statistici de utilizare, avertismente, erori și accidente","Use SSL":"Utilizați SSL","Use existing database?":"Utilizați baza de date existentă?","Use weak passphrase":"Utilizați fraza de acces slabă","Useless":"Inutil","User data":"Datele utilizatorului","User has too many permissions":"Utilizatorul are prea multe permisiuni","User interface settings":"Setările interfeței utilizatorului","Username":"Nume de utilizator","Verify files":"Verificați fișierele","Verifying answer":"Verificarea răspunsului","Very strong":"Foarte puternic","Very weak":"Foarte slab","Visit us on":"Vizitați-ne","WARNING: This will prevent you from restoring the data in the future.":"AVERTISMENT: Acest lucru vă va împiedica să restaurați datele în viitor.","Waiting for task to begin":"Se așteaptă ca sarcina să înceapă","Warnings, errors and crashes":"Avertizări, erori și accidente","We recommend that you encrypt all backups stored outside your system":"Vă recomandăm să criptați toate copiile de rezervă stocate în afara sistemului dvs.","Weak":"Slab","Weak passphrase":"Frază de acces slabă","Wed":"însura","Weeks":"săptămâni","Where do you want to restore from?":"De unde doriți să restaurați?","Where do you want to restore the files to?":"Unde doriți să restaurați fișierele?","Years":"Ani","Yes":"da","Yes, I have stored the passphrase safely":"Da, am stocat expresia de acces în siguranță","Yes, I'm brave!":"Da, sunt curajos!","Yes, please break my backup!":"Da, vă rog să întrerupeți backupul!","Yesterday":"Ieri","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Schimbați calea bazei de date departe de o bază de date existentă.\nEști sigur că asta vrei?","You are currently running {{appname}} {{version}}":"În prezent, executați {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Ați schimbat modul de criptare. Acest lucru poate sparge lucrurile. Sunteți încurajați să creați în schimb o copie de siguranță nouă","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Ați schimbat fraza de acces, care nu este acceptată. Sunteți încurajați să creați în schimb o copie de siguranță nouă.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Ați ales să nu criptați copia de rezervă. Criptarea este recomandată pentru toate datele stocate pe un server de la distanță.","You have chosen to restore to a new location, but not entered one":"Ați ales să restaurați o locație nouă, dar nu ați introdus una","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Ați generat o expresie de acces puternică. Asigurați-vă că ați făcut o copie sigură a expresiei de acces, deoarece datele nu pot fi recuperate dacă pierdeți expresia de acces.","You must choose at least one source folder":"Trebuie să alegeți cel puțin un dosar sursă","You must enter a name for the backup":"Trebuie să introduceți un nume pentru copia de rezervă","You must enter a passphrase or disable encryption":"Trebuie să introduceți o expresie de acces sau să dezactivați criptarea","You must enter a positive number of backups to keep":"Trebuie să introduceți un număr pozitiv de copii de rezervă pe care să le păstrați","You must enter a valid duration for the time to keep backups":"Trebuie să introduceți o durată valabilă pentru timpul necesar pentru a păstra copii de rezervă","You must fill in the password":"Trebuie să completați parola","You must fill in the server name or address":"Trebuie să completați numele sau adresa serverului","You must fill in the username":"Trebuie să completați numele de utilizator","You must fill in {{field}}":"Trebuie să completați {{field}}","You must select or fill in the AuthURI":"Trebuie să selectați sau să completați AuthURI","You must select or fill in the server":"Trebuie să selectați sau să completați serverul","You must specify a path":"Trebuie să specificați o cale","Your files and folders have been restored successfully.":"Fișierele și folderele dvs. au fost restaurate cu succes.","Your passphrase is easy to guess. Consider changing passphrase.":"Fraza de acces este ușor de ghicit. Luați în considerare schimbarea expresiei de acces.","bucket/folder/subfolder":"cupă pentru excavat / folder / subfolder","byte":"octet","byte/s":"byte / s","custom":"personalizat","resume now":"reluați acum","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a fost dezvoltat în primul rând prin {{dev1}} și {{dev2}} . {{appname}} poate fi descărcat de la {{sitename}} . {{appname}} este licențiat sub {{licensename}} .","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fișiere ({{size}}) pentru a merge {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} versiune","{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} Versiuni","{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} Versiuni"],"{{number}} Hour":"{{număr}} oră","{{number}} Minutes":"{{număr}} Minute","{{time}} (took {{duration}})":"{{time}} (a luat {{duration}})"}); - gettextCatalog.setStrings('ru', {"- pick an option -":"- выберите параметр -","...loading...":"...загрузка...","API key":"Ключ API","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"О программе","About {{appname}}":"О {{appname}}","Access Key":"Ключ доступа","Access denied":"Доступ запрещен","Access grant":"Разрешение на доступ","Access to user interface":"Доступ в веб-интерфейс","Account name":"Имя учётной записи","Add a new backup":"Создать новую резервную копию","Add a path directly":"Добавить путь непосредственно","Add advanced option":"Добавить расширенный параметр","Add backup":"Добавить резервную копию","Add filter":"Добавить фильтр","Add path":"Добавить путь","Added":"Добавлено","Adjust bucket name?":"Изменить имя блока?","Advanced Options":"Расширенные параметры","Advanced options":"Расширенные параметры","Advanced:":"Дополнительно:","All Hyper-V Machines":"Все виртуальные машины Hyper-V","All Microsoft SQL Databases":"Все базы данных Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Все отчеты отправляются анонимно и не включают каких-либо персональных данных. Они содержат информацию об аппаратной конфигурации и операционной системе, типе бэкэнда, продолжительности резервного копирования, а также общий размер резервируемых данных и другие подобные данные. Они не включают пути или имена файлов, имена пользователей, пароли или любую другую конфиденциальную информацию.","Allow remote access (requires restart)":"Разрешить удалённый доступ (потребуется перезапуск)","Allowed days":"Разрешенные дни","An existing file was found at the new location":"Существующий файл был найден по новому пути","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Существующий файл был найден по новому пути\nВы точно хотите, чтобы база данных указывала на существующий файл?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Была обнаружена локальная база данных для хранилища.\nПовторное использование базы данных позволит экземплярам командной строки и сервера работать на одном и том же удаленном хранилище.\n\n Вы хотите использовать существующую базу данных?","Anonymous usage reports":"Анонимные отчёты об использовании","Applications":"Приложения","As Command-line":"Как командная строка","AuthID":"AuthID","Authentication method":"Метод аутентификации","Authentication method ({{auth_method}})":"Метод аутентификации ({{auth_method}})","Authentication password":"Пароль для аутентификации","Authentication username":"Имя пользователя для аутентификации","Autogenerated passphrase":"Сгенерированный пароль","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Назад","Backup complete!":"Резервное копирование завершено!","Backup destination":"Хранение резервной копии","Backup location":"Расположение резервной копии","Backup retention":"Хранение копий","Backup:":"Резервная копия:","Beta":"Beta","Broken access":"Битый доступ","Browse":"Обзор","Browser default":"Браузер по-умолчанию","Bucket create location":"Место создания блока","Bucket name":"Имя блока","Bucket storage class":"Класс хранения блока","Building list of files to restore …":"Создание списка файлов для восстановления…","Building partial temporary database …":"Создание временной базы данных…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Разрешая удаленный доступ, сервер видит запросы от любого компьютера в вашей сети. Если Вы включили эту опцию, убедитесь, что используете компьютер в защищенной сети, где есть надежный Файрвол.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"По умолчанию значок в трее открывает пользовательский интерфейс сразу без ввода каких либо данных. Это удобно для быстрого доступа к интерфейсу, но не безопасно, так как любой может получить доступ к зашифрованным резервным копиям. Если вам такое не нравится, включите эту опцию, предварительно указав пароль выше. ","Cache Files":"Кеш файлы","Canary":"Canary","Cancel":"Отмена","Cannot move to existing file":"Не могу переместить в существующий файл","Changelog":"История изменений","Changelog for {{appname}} {{version}}":"Список изменений для {{appname}} {{version}}","Check failed:":"Проверка не удалась:","Check for updates now":"Проверить наличие обновлений","Checking for updates …":"Проверка обновлений...","Chose a storage type to get started":"Для начала выберите тип хранилища","Click the AuthID link to create an AuthID":"Нажмите на ссылку AuthID для создания AuthID","Click to set throttle options":"Нажмите, чтобы установить параметры ограничения скорости","Client library to use":"Использовать клиентскую библиотеку","Commandline …":"Командная строка...","Compact Phase":"Компактная фаза","Compact now":"Уплотнить сейчас","Compacting remote data …":"Сжатие удаленных данных…","Complete log":"Полный отчёт","Completing backup …":"Завершение резервного копирования…","Completing previous backup …":"Завершение предыдущего резервного копирования…","Computer":"Компьютер","Configuration file:":"Файл конфигурации:","Configuration:":"Настройка:","Configure a new backup":"Настройка новой резервной копии","Confirm delete":"Подтвердите удаление","Confirm encryption passphrase":"Подтвердите кодовую фразу шифрования","Confirm new password":"Подтверждение пароля","Confirm passphrase":"Подтвердите кодовую фразу","Confirmation required":"Необходимо подтверждение","Connect":"Подключение","Connect now":"Подключиться сейчас","Connecting to server …":"Подключение к серверу…","Connection lost":"Потеряно соединение","Connection worked!":"Подключение работает!","Container name":"Имя контейнера","Container region":"Регион контейнера","Continue":"Продолжить","Continue without encryption":"Продолжить без шифрования","Copied!":"Скопировано!","Copy":"Копировать","Copy Destination URL to Clipboard":"Скопировать URL-адрес назначения в буфер обмена","Copy failed. Please manually copy the URL":"Копирование не удалось. Скопируйте URL-адрес вручную","Core options":"Основные параметры","Counting ({{files}} files found, {{size}})":"Сканирование (найдено {{files}} файлов, {{size}})","Crashes only":"Только падения","Create bug report …":"Создать отчет об ошибке…","Create folder?":"Создать папку?","Created new limited user":"Создан новый ограниченный пользователь","Creating bug report …":"Создание отчета об ошибке…","Creating new user with limited access …":"Создание нового пользователя с ограниченным доступом…","Creating target folders …":"Создание целевых папок…","Creating temporary backup …":"Создание временной резервной копии…","Current action:":"Текущая операция:","Current file:":"Текущий файл:","Current version is {{versionname}} ({{versionnumber}})":"Текущая версия — {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Пользовательский S3 endpoint","Custom Satellite":"Пользовательский спутник","Custom Satellite ({{satellite}})":"Пользовательский спутник ({{satellite}})","Custom authentication url":"Пользовательский URL-адрес аутентификации","Custom backup retention":"Пользовательское","Custom location ({{server}})":"Пользовательское местоположение ({{server}})","Custom region for creating buckets":"Пользовательский регион для создания buckets","Custom region value ({{region}})":"Пользовательское значение региона ({{region}})","Custom server url ({{server}})":"Пользовательский URL-адрес сервера ({{server}})","Custom storage class ({{class}})":"Пользовательский класс хранения ({{class}})","Database …":"База данных…","Days":"Дней","Default":"По умолчанию","Default ({{channelname}})":"По умолчанию ({{channelname}})","Default excludes":"Исключения по-умолчанию","Default options":"Параметры по умолчанию","Delete":"Удалить","Delete Phase (Old Backup Versions)":"Этап удаления (старые версии резервного копирования)","Delete backup":"Удалить резервную копию","Delete backups that are older than":"Удалить копии старше","Delete local database":"Удалить локальную базу данных","Delete remote files":"Удалить файлы с диска","Delete the local database":"Удалить локальную базу данных","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Удалить {{filecount}} файлов ({{filesize}}) из удаленного хранилища?","Delete …":"Удалить…","Deleted":"Удалено","Deleted Versions":"Удалённые версии","Deleted files":"Удалённые файлы","Deleting remote files …":"Удаление \"удаленных\" файлов…","Deleting unwanted files …":"Удаление ненужных файлов…","Description (optional)":"Описание (опционально)","Description:":"Описание:","Desktop":"Рабочий стол","Destination":"Хранение","Destination path":"Путь назначения","Disabled":"Отключено","Dismiss":"Скрыть","Dismiss all":"Отклонить все","Display and color theme":"Отображение и цветовая тема","Do you really want to delete the backup: \"{{name}}\" ?":"Подтверждаете удаление плана резервного копирования: «{{name}}» ?","Do you really want to delete the local database for: {{name}}":"Вы действительно хотите удалить локальную базу данных для: {{name}}","Done":"Готово","Download":"Скачать","Downloaded files":"Загруженные файлы","Downloading files …":"Загрузка файлов…","Downloading update…":"Загрузка обновления…","Duplicate option {{opt}}":"Дублировать параметр {{opt}}","Duplicati Website":"Сайт Duplicati ","Duplicati forum":"Форум Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati будет запускаться при старте системы, но останется приостановленным, используя минимум ресурсов и не выполняя резервное копирование.","Duration":"Продолжительность","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Каждый план резервного копирования создаёт локальную базу данных, в которой содержится информация о резервируемых файлах.\nУдаление плана резервного копирования и его локальной базы данных не влияет на возможность восстановления уже зарезервированных файлов.\nЕсли Вы планируете воспользоваться удаляемым планом в будущем через командную строку, то не рекомендуется удалять локальную базу данных.","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"Каждая резервная копия имеет локальную базу данных, которая хранит информацию о ней. Это ускоряет выполнение многих операций и сокращает объём передаваемых данных с удалённых серверов.","Edit as list":"Редактировать как список","Edit as text":"Редактировать как текст","Edit …":"Изменить... ","Encrypt file":"Шифровать файл","Encryption":"Шифрование","Encryption changed":"Шифрование изменено","Encryption passphrase":"Кодовая фраза для шифрования","End":"Конец","Enter URL":"Введите URL-адрес","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Схема такая. Есть заполнители D/W/Y/U соответсвенно день (D), неделя (W), год (Y), без ограничений (U). Например: 7D:1D,4W:1W,36M:1M\nВ этом примере сохраняется одна копия за каждые 7 дней, одна копия за 4 недели и одна копия за 36 месяцев. ","Enter backup passphrase, if any":"Введите пароль резервной копии, если таковой имеется","Enter configuration details":"Ввод сведений конфигурации","Enter encryption passphrase":"Введите пароль шифрования","Enter expression here":"Введите выражение здесь","Enter the destination path":"Введите путь назначения","Error":"Ошибка","Error!":"Ошибка!","Errors and crashes":"Ошибки и падения","Examined":"Проверено","Exclude":"Исключить","Exclude directories whose names contain":"Исключить каталоги, имена которых содержат","Exclude expression":"Выражение для исключения","Exclude file":"Исключить файл","Exclude file extension":"Исключить файловое расширение","Exclude files whose names contain":"Исключить файлы, имена которых содержат","Exclude filter group":"Исключить группу фильтров","Exclude folder":"Исключить папку","Exclude regular expression":"Регулярное выражение для исключения","Existing file found":"Найден существующий файл","Experimental":"Experimental","Export":"Экспорт","Export backup configuration":"Экспорт конфигурации резервного копирования","Export configuration":"Экспорт конфигурации","Export passwords":"Экспорт паролей","Export …":"Экспорт...","Exporting …":"Экспортирование...","External link":"Внешняя ссылка","FTP (Alternative)":"FTP (Альтернативный)","Failed to build temporary database: {{message}}":"Не удалось построить временную базу данных: {{message}}","Failed to connect:":"Не удается подключиться:","Failed to connect: {{message}}":"Не удается подключиться: {{message}}","Failed to delete:":"Не удалось удалить:","Failed to fetch path information: {{message}}":"Не удалось получить сведения о пути: {{message}}","Failed to find backup:":"Не удалось найти резервную копию:","Failed to read backup defaults:":"Не удалось прочитать настройки по умолчанию для резервной копии:","Failed to restore files: {{message}}":"Не удалось восстановить файлы: {{message}}","Failed to save:":"Не удалось сохранить:","Fetching path information …":"Получение информации о пути…","File":"Файл","Files larger than:":"Файлы размером более:","Filters":"Фильтры","Finished!":"Готово!","First run setup":"Настройка при первом запуске","Folder":"Папка","Folder path":"Путь к папке","Fri":"Пт","GByte":"ГБ","GByte/s":"ГБ/сек","GCS Project ID":"GCS Project ID","General":"Общие","General backup settings":"Общие параметры резервного копирования","General options":"Основные параметры","Generate":"Сгенерировать","Generate IAM access policy":"Сгенерировать политики доступа IAM","Getting file versions …":"Получение версий файлов…","Group email":"Электронная почта группы","Hidden files":"Скрытые файлы","Hide":"Скрыть","Hide hidden folders":"Скрыть скрытые папки","Home":"Главная","Hostnames":"Имя хоста","Hours":"часов","How do you want to handle existing files?":"Как вы хотите обрабатывать существующие файлы?","Hyper-V Machine":"Hyper-V Машина","Hyper-V Machine:":"Hyper-V Машина:","Hyper-V Machines":"Hyper-V Машины","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Если дата была пропущена, задание будет выполнено как можно скорее.","If at least one newer backup is found, all backups older than this date are deleted.":"Если найдена резервная копия старше, чем указанное количество дней, недель и т.д., то они будут удалятся. ","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Если вы не введете путь, все файлы будут храниться в папке логина.\nВы уверены, что это то, что вы хотите?","If you do not enter an API Key, the tenant name is required":"Если вы не вводите ключ API, требуется имя арендатора","Import":"Импорт","Import Destination URL":"Импортировать URL-адрес назначения","Import backup configuration":"Импорт настройки резервной копии","Import from a file":"Импортировать из файла","Import metadata":"Импортировать метаданные","Importing …":"Импорт...","Include a file?":"Включить файл?","Include expression":"Выражение для включения","Include regular expression":"Регулярное выражение для включения","Incorrect answer, try again":"Неправильный ответ, попробуйте еще раз","Individual builds for developers only. Not for use with important data.":"Индивидуальные сборки только для разработчиков. Не рекомендуется использовать для сохранения важных данных.","Information":"Информация","Invalid characters in path":"Недопустимые символы в пути","Invalid retention time":"Недопустимое время хранения","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"К некоторым FTP возможно подключиться без пароля.\nВы уверены, что ваш FTP-сервер поддерживает вход без пароля?","KByte":"КБайт","KByte/s":"КБ/сек","Keep a specific number of backups":"Хранить в количестве","Keep all backups":"Хранить все копии","Keystone API version":"Версия Keystone API","Language in user interface":"Язык пользовательского интерфейса","Last month":"Последний месяц","Last successful backup:":"Последнее успешное резервное копирование:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Последнее успешное восстановление: {{time}} (took {{duration || '0 seconds'}})","Latest":"Последнее","Libraries":"Библиотеки","Listing backup dates …":"Отображать дату резервного копирования…","Listing remote files for purge …":"Показать список удаленных файлов после очистки…","Listing remote files …":"Вывод списка \"удаленных\" файлов…","Live":"Текущие","Load a configuration from an exported job or a storage provider":"Загрузить настройки из экспортированного задания или поставщика хранилища","Load destination from an exported job or a storage provider":"Загрузить назначение из экспортированного задания или поставщика хранилища","Load older data":"Загрузить ещё...","Loading …":"Загрузка...","Local Repository":"Локальный репозиторий","Local database path:":"Путь локальной базы данных:","Local repository":"Локальный репозиторий","Local storage":"Локальное хранилище","Location":"Местоположение","Location where buckets are created":"Место где создаются buckets","Log data for {{Backup.Backup.Name}}":"Данные журнала для {{Backup.Backup.Name}}","Log data from the server":"Сообщения журнала сервера","Log out":"Выход","MByte":"Мбайт","MByte/s":"Мбайт/с","Maintenance":"Техническое обслуживание","Manually type path":"Ввести путь вручную","Max download speed":"Максимальная скорость загрузки","Max upload speed":"Максимальная скорость выгрузки","Menu":"Меню","Microsoft SQL Database:":"База данных Microsoft SQL:","Microsoft SQL Databases":"Баз данных Microsoft SQL","Minimum redundancy":"Минимальная избыточность","Minimum redundancy is 1.0":"Минимальная избыточность - 1.0","Minutes":"минут","Missing name":"Отсутствует имя","Missing passphrase":"Отсутствующие парольная фраза","Missing sources":"Отсутствуют источники","Modified":"Изменено","Mon":"Пн","Months":"Месяцев","Move existing database":"Перемещение существующей базы данных","Move failed:":"Перемещение не удалось:","My Documents":"Мои документы","My Music":"Моя музыка","My Photos":"Мои фотографии","My Pictures":"Мои Картинки","Name":"Имя","Never":"Никогда","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Новое имя пользователя — {{user}}.\nОбновлены учетные данные для использования нового пользователя с ограниченными правами","Next":"Далее","Next scheduled run:":"Следующий запуск:","Next scheduled task:":"Следующий запуск:","Next task:":"Следующая задача:","Next time":"В следующий раз","No":"Нет","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Сертификат не был указан ранее, пожалуйста проверьте с администратором сервера ключ: {{key}} \n\nВы хотите утвердить полученный ключ сервера?","No editor found for the "{{backend}}" storage type":"Не найден редактор для хранилища типа "{{backend}}"","No encryption":"Без шифрования","No items selected":"Элементы не выбраны","No items to restore, please select one or more items":"Нет элементов для восстановления, выберите один или несколько элементов","No passphrase entered":"Не введена кодовая фраза","No scheduled tasks":"Нет запланированных задач","Non-matching passphrase":"Кодовые фразы не совпадают","None / disabled":"Нет / отключено","Not using encryption":"Без шифрования","Nothing will be deleted. The backup size will grow with each change.":"Ничего не будет удалено. Размер резервной копии будет расти с каждым изменением.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Когда количество резервных копий превышает указанное количество, самые старые резервные копии удаляются.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Открыто","Operating System":"Операционная Система","Operation":"Операция","Operations:":"Операции:","Optional authentication password":"Необязательный пароль аутентификации","Optional authentication username":"Необязательное имя пользователя","Options":"Параметры","Options added here are applied to all backups, but can be overridden in each individual backup.":"Указанные настройки будут применяться ко всем резервным копиям, но могут быть переопределены для каждой отдельной резервной копии.","Original location":"Исходное местоположение","Others":"Другие","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Со временем резервные копии будут удаляться автоматически. Останется по одной резервной копии за последние 7 дней, за последние 4 недели, за последний 12 месяцев. Всегда будет как минимум одна оставшаяся резервная копия.","Overwrite":"Перезаписать","Passphrase":"Кодовая фраза","Passphrase (if encrypted)":"Кодовая фраза (если зашифрован)","Passphrase changed":"Кодовая фраза изменена","Passphrases are not matching":"Кодовые фразы не совпадают","Passphrases do not match":"Парольные фразы не совпадают","Password":"Пароль","Patching files with local blocks …":"Исправление файлов локальными блоками…","Path":"Путь","Path not found":"Путь не найден","Path on server":"Путь на сервере","Path or subfolder in the bucket":"Путь или подпапка в bucket","Pause":"Пауза","Pause after startup or hibernation":"Отложенный запуск после включения или выхода из спящего режима","Pause options":"Параметры паузы","Permissions":"Разрешения","Pick location":"Выберите местоположение","Point to your backup files and restore from there":"Укажите место хранения резервной копии и восстановите данные из неё","Port":"Порт","Prevent tray icon automatic log-in":"Запретить автоматический вход из значка в трее","Previous":"Назад","Progress:":"Прогресс:","ProjectID is optional if the bucket exist":"ProjectID необязателен, если существует bucket","Proprietary":"Проприетарное","Purge Phase":"Стадия очистки","Purging files complete!":"Очистка файлов завершена!","Purging files …":"Очистка файлов...","Rebuilding local database …":"Восстановление локальной базы данных…","Recreate (delete and repair)":"Пересоздать (удалить и исправить)","Recreate Database Phase":"Этап восстановления базы данных","Recreating database …":"Восстановление базы данных…","Registering temporary backup …":"Регистрация временной резервной копии…","Relative paths not allowed":"Относительные пути не допускаются","Reload":"Обновить","Remote":"Удаленный","Remote Path":"Удаленный путь","Remote Repository":"Удаленный Репозиторий","Remote path":"Удаленный путь","Remote repository":"Удаленный репозиторий","Remote volume size":"Размер удаленного тома","Remove":"Удалить","Remove option":"Удалить параметр","Removed files":"Удаленные файлы","Repair":"Исправить","Repair Phase":"Период исправления","Repairing database …":"Восстановление базы данных…","Repeat Passphrase":"Повторить кодовую фразу","Reporting:":"Отчетность:","Reset":"Сбросить","Restore":"Восстановление","Restore complete!":"Восстановление завершено!","Restore files":"Восстановить файлы","Restore files …":"Восстановить файлы...","Restore from":"Восстановить из","Restore from backup configuration":"Восстановить из конфигурации резервной копии","Restore options":"Параметры восстановления","Restore read/write permissions":"Восстановить разрешения чтения/записи","Restored Files":"Восстановленные Файлы","Restored Folders":"Восстановленные Папки","Restored Symlinks":"Восстановленные Символические ссылки","Restoring files …":"Восстановление файлов…","Resume":"Продолжить","Rewritten File Lists":"Перезаписанные списки файлов","Run again every":"Запускать каждый","Run now":"Запустить сейчас","Running commandline entry":"Выполнение записи командной строки","Running task:":"Выполняемая задача:","Running …":"Запуск...","S3 Compatible":"S3 совместимый","Same as the base install version: {{channelname}}":"Такой же как в базовой версии: {{channelname}}","Sat":"Сб","Satellite":"Спутник","Save":"Сохранить","Save and repair":"Сохранить и исправить","Save different versions with timestamp in file name":"Сохранить различные версии с отметкой времени в имени файла","Save immediately":"Немедленно сохранить","Scanning existing files …":"Сканирование существующих файлов…","Scanning for local blocks …":"Сканирование локальных блоков…","Schedule":"Расписание","Search":"Поиск","Search for files":"Поиск файлов","Seconds":"Секунд","Select a log level and see messages as they happen:":"Выберите уровень журналирования для просмотра сообщений по мере их возникновения:","Select files":"Выбор файлов","Server":"Сервер","Server and port":"Сервер и порт","Server hostname or IP":"Имя сервера или IP","Server is currently paused,":"Сервер приостановлен,","Server is currently paused, do you want to resume now?":"Сервер в настоящее время приостановлен, вы хотите возобновить сейчас?","Server password":"Пароль сервера","Server paused":"Сервер приостановлен","Server state properties":"Свойства состояния сервера","Settings":"Настройки","Show":"Показать","Show advanced editor":"Текстовое отображение","Show hidden folders":"Показать скрытые папки","Show log":"Журнал","Show log …":"Показать журнал …","Show treeview":"Древовидное отображение","Sia server password":"Пароль сервера Sia","Smart backup retention":"Умное хранение копий","Some OpenStack providers allow an API key instead of a password and tenant name":"Некоторые провайдеры OpenStack позволяют использовать ключ API вместо имени клиента и пароля","Some S3 providers might only be compatible with a certain client library":"Некоторые поставщики S3 могут быть совместимы только с определенной клиентской библиотекой.","Source Data":"Исходные данные","Source Files":"Исходные Файлы","Source data":"Данные для резервирования","Source folders":"Исходные папки","Source:":"Источник:","Specific builds for developers only. Not for use with important data.":"Специальные сборки только для разработчиков. Не рекомендуется использовать для сохранения важных данных.","Standard protocols":"Стандартные протоколы","Start":"Начало","Starting backup …":"Запуск резервного копирования…","Starting restore …":"Начало восстановления…","Starting the restore process …":"Запуск процесса восстановления…","Stop after current file":"Остановить после текущего файла","Stop after the current file":"Остановиться после текущего файла","Stop now":"Остановить сейчас","Stop running backup":"Остановить резервное копирование","Stop running task":"Остановить задачу","Stopping after the current file:":"Остановка после текущего файла:","Stopping task:":"Остановка задачи:","Storage Type":"Тип хранилища","Storage class":"Класс хранилища","Storage class for creating a bucket":"Класс хранения для создания bucket","Stored":"Сохраненные","Strong":"Сильный","Success":"Успех","Sun":"Вс","Symbolic link":"Символическая ссылка","System Files":"Системные Файлы","System default ({{levelname}})":"По умолчанию ({{levelname}})","System files":"Системные файлы","System info":"Информация о системе","System properties":"Свойства системы","TByte":"ТБайт","TByte/s":"ТБайт/s","Task is running":"Выполняется задача","Temporary Files":"Временные Файлы","Temporary files":"Временные файлы","Test Phase":"Этап проверки","Test connection":"Проверить доступ","Testing permissions …":"Проверка разрешений…","Testing …":"Тестирование…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Поле '{{fieldname}}' содержит недопустимый символ: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Резервная копия не найдена. Возможно удалена.","The backup was temporary and does not exist anymore, so the log data is lost":"Резервная копия была временной и больше не существует, поэтому данные журнала отсутствуют.","The bucket name should be all lower-case, convert automatically?":"Имя bucket должно быть строчным, преобразовать автоматически?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Конфигурация должна быть защищена. Вы уверены, что хотите сохранить незашифрованным файл, в котором содержатся ваши пароли?","The dark theme (by Michal)":"Тёмная тема (от Michael)","The default blue on white theme (by Alex)":"Стандартная тема синий на белом (от Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Папка {{folder}} не существует. \nСоздать сейчас?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Ключ узла изменился, пожалуйста, проверьте у администратора сервера так ли это, в противном случае вы можете быть жертвой атаки MAN-IN-THE-MIDDLE.\n\nВы хотите ЗАМЕНИТЬ ваш ТЕКУЩИЙ ключ узла «{{prev}}» ПОЛУЧЕННЫМ ключом хоста: {{key}}?","The passwords do not match":"Пароли не совпадают","The path does not appear to exist, do you want to add it anyway?":"Путь, по-видимому, не существует, вы всё равно хотите его добавить?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Путь не заканчивается символом «{{dirsep}}», что означает, что вы включаете файл, а не папку.\n\nВы хотите включить указанный файл?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Путь должен быть абсолютным, то есть он должен начинаться с косой черты «/»","The region parameter is only applied when creating a new bucket":"Параметр «регион» применяется только при создании нового bucket","The region parameter is only used when creating a bucket":"Параметр «регион» используется только при создании bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Не удалось проверить сертификат сервера.\nВы хотите утвердить SSL-сертификат с хэшом: {{hash}}?","The storage class affects the availability and price for a stored file":"Класс хранилища влияет на доступность и цену сохраненного файла","The target folder contains encrypted files, please supply the passphrase":"Целевая папка содержит зашифрованные файлы, пожалуйста, укажите кодовую фразу","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Пользователь имеет слишком много прав. Вы хотите создать нового пользователя с ограниченными правами, с разрешениями только на выбранный путь?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Эта резервная копия была создана в другой операционной системе. Восстановление файлов без указания папки назначения может повлечь восстановление файлов в неожиданных местах. Вы уверены, что вы хотите продолжить без выбора папки назначения?","This month":"В этом месяце","This week":"На этой неделе","Throttle settings":"Параметры ограничения скорости","Thu":"Чт","Time":"Время","To File":"В файл","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Чтобы подтвердить, что вы хотите удалить все дистанционные файлы для «{{name}}», введите слово, которое вы видите ниже","To export without a passphrase, uncheck the \"Encrypt file\" box":"Чтобы экспортировать без кодовой фразы, снимите флажок «Зашифровать файл»","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Чтобы предотвратить различные атаки на основе DNS, Duplicati ограничивает допустимые имена хостов перечисленными здесь. Всегда разрешен прямой IP-доступ и localhost. Несколько имен хостов могут быть указаны через точку с запятой. Для доступа с любого хоста, указываем звездочку (*). Если оставить поле пустым, разрешен только IP-адрес и доступ к локальному хосту.","Today":"Сегодня","Trust host certificate?":"Доверять сертификату хоста?","Trust server certificate?":"Доверять сертификату сервера?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Попробуйте новые функции, над которыми мы работаем. На данный момент самая стабильная из доступных версий. Проведите тестовое восстановление данных перед использованием в производственной или в корпоративной сфере.","Tue":"Вт","Type passphrase here.":"Введите здесь кодовую фразу.","Type to highlight files":"Напишите для выделения файлов","Unknown backup size and versions":"Неизвестные размер резервной копии и версии","Until resumed":"До возобновления","Update channel":"Канал обновлений","Update failed:":"Обновление не удалось:","Updating with existing database":"Обновление с существующей базой данных","Uploaded files":"Загруженные файлы","Uploading verification file …":"Загрузить проверочный файл…","Usage statistics":"Статистика использования","Usage statistics, warnings, errors, and crashes":"Статистика использования, предупреждения, ошибки и падения","Use SSL":"Использовать SSL","Use existing database?":"Использовать существующую базу данных?","Use weak passphrase":"Использовать слабую кодовую фразу","Useless":"Бесполезно","User data":"Данные пользователя","User domain name":"Доменное имя пользователя","User has too many permissions":"Пользователь имеет слишком много разрешений","User interface settings":"Настройки интерфейса","Username":"Имя пользователя","Vacuuming database …":"Очистка базы данных…","Validating …":"Проверка…","Verifications":"Проверено","Verify files":"Проверить файлы","Verifying answer":"Проверка ответа","Verifying backend data …":"Проверка внутренних данных …","Verifying files …":"Проверка файлов…","Verifying remote data …":"Проверка удаленных данных…","Verifying restored files …":"Проверка восстановленных файлов…","Verifying …":"Проверка…","Version ID":"Version ID","Very strong":"Очень надёжный","Very weak":"Очень слабый","Visit us on":"Посетите нас на","WARNING: This will prevent you from restoring the data in the future.":"ВНИМАНИЕ: Файлы с диска удаляются навсегда в обход корзины!","Waiting for task to begin":"Ожидание начала задачи","Waiting for upload to finish …":"Ожидание завершения выгрузки…","Warnings, errors and crashes":"Предупреждения, ошибки и падения","We recommend that you encrypt all backups stored outside your system":"Мы рекомендуем зашифровать все резервные копии, хранящиеся вне вашей системы","Weak":"Слабый","Weak passphrase":"Слабая кодовая фраза","Wed":"Ср","Weeks":"Недель","Where do you want to restore from?":"Откуда вы хотите восстановить данные?","Where do you want to restore the files to?":"Куда вы хотите восстановить файлы?","Years":"Лет","Yes":"Да","Yes, I have stored the passphrase safely":"Да, я надёжно сохранил кодовую фразу","Yes, I understand the risk":"Да, я принимаю риск","Yes, I'm brave!":"Да, я смелый!","Yes, please break my backup!":"Да, пожалуйста, сломайте мою резервную копию!","Yesterday":"Вчера","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Вы меняете путь базы данных отличный от существующей базы данных.\nВы уверены, что это то, что вы хотите?","You are currently running {{appname}} {{version}}":"Вы используете {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Вы можете остановить резервное копирование после завершения загрузки всех текущих файлов.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Вы можете немедленно остановить задачу или позволить процессу продолжить работу с текущим файлом, а затем остановить.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Вы изменили режим шифрования. Это может что-нибудь сломать. Вместо этого вам лучше создать новую резервную копию","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Вы изменили кодовую фразу, но это не поддерживается. Вместо этого вам стоит создать новую резервную копию.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Вы выбрали не шифровать резервную копию. Шифрование рекомендовано для всех данных, хранящихся на удаленном сервере.","You have chosen to restore to a new location, but not entered one":"Вы выбрали новое место для восстановления, но не ввели его","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Вы использовали сильную парольную фразу. Пожалуйста, убедитесь, что вы надёжно сохранили парольную фразу, ибо восстановление данных невозможно в случае её утраты.","You must choose at least one source folder":"Вы должны выбрать по крайней мере одну исходную папку","You must enter a domain name to use v3 API":"Вы должны ввести доменное имя, чтобы использовать v3 API","You must enter a name for the backup":"Вам необходимо ввести имя резервной копии","You must enter a passphrase or disable encryption":"Вы должны ввести кодовую фразу или отключить шифрование","You must enter a password to use v3 API":"Вы должны ввести пароль, чтобы использовать v3 API","You must enter a positive number of backups to keep":"Необходимо ввести положительное число резервных копий для хранения","You must enter a tenant (aka project) name to use v3 API":"Вы должны ввести имя проекта, чтобы использовать v3 API","You must enter a valid duration for the time to keep backups":"Необходимо ввести допустимый срок времени хранения резервных копий","You must enter a valid retention policy string":"Необходимо ввести допустимое значение политики хранения","You must fill in the password":"Вы должны заполнить пароль","You must fill in the server name or address":"Вы должны заполнить имя сервера или адрес","You must fill in the username":"Вы должны заполнить имя пользователя","You must fill in {{field}}":"Вы должны заполнить {{field}}","You must select or fill in the AuthURI":"Вы должны выбрать или заполнить AuthURI","You must select or fill in the server":"Вы должны выбрать или заполнить сервер","You must specify a path":"Вы должны указать путь","Your files and folders have been restored successfully.":"Ваши файлы и папки были восстановлены успешно.","Your passphrase is easy to guess. Consider changing passphrase.":"Вашу кодовую фразу легко отгадать. Подумайте об изменении кодовой фразы.","bucket/folder/subfolder":"bucket/папка/подпапка","byte":"байт","byte/s":"байт/сек","custom":"пользовательские","resume now":"возобновить сейчас","unless you are explicitly specifying --group-id":"если вы явно не указываете --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"Основными разработчиками {{appname}} являются {{dev1}} и {{dev2}}. Последняя версия {{appname}} может быть загружена с сайта {{websitename}}. {{appname}} распространяется под лицензией {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} файлов ({{size}}) впереди {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версия","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версии","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версий","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версий"],"{{number}} Hour":"{{number}} Часов","{{number}} Hours":"{{number}} Часов","{{number}} Minutes":"{{number}} минут","{{time}} (took {{duration}})":"{{time}} (заняло {{duration}})"}); + gettextCatalog.setStrings('ca', {"- pick an option -":"- trieu una opció -","...loading...":"S'està carregant...","AWS Access ID":"ID d'accés d'AWS","AWS Access Key":"Clau d'accés d'AWS","AWS IAM Policy":"Política IAM d'AWS","About":"Quant a","About {{appname}}":"Quant al {{appname}}","Access Key":"Clau d'accés","Access denied":"S'ha denegat l'accés","Access to user interface":"Accés a la interfície d'usuari","Account name":"Nom del compte","Add a new backup":"Afegeix una nova còpia de seguretat","Add a path directly":"Afegeix una ruta directament","Add advanced option":"Afegeix una opció avançada","Add backup":"Afegeix una còpia de seguretat","Add filter":"Afegeix un filtre","Add path":"Afegeix una ruta","Added":"Afegits","Adjust bucket name?":"Voleu modificar el nom del contenidor?","Advanced Options":"Opcions avançades","Advanced options":"Opcions avançades","Advanced:":"Avançat:","All Hyper-V Machines":"Totes les màquines de l'Hyper-V","All Microsoft SQL Databases":"Totes les bases de dades SQL de Microsoft","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tots els informes d'ús s'envien anònimament i no contenen cap informació personal. Contenen informació sobre el maquinari i el sistema operatiu, el tipus de capa d'accés de dades, la durada de la còpia de seguretat, la mida general de les dades d'origen i dades similars. No contenen rutes, noms de fitxers, noms d'usuari, contrasenyes o dades sensibles similars.","Allow remote access (requires restart)":"Permet l'accés remot (cal reiniciar el programa)","Allowed days":"Dies permesos","An existing file was found at the new location":"S'ha trobat un fitxer existent a la nova ubicació","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"S'ha trobat un fitxer existent a la nova ubicació.\nSegur que voleu que la base de dades apunti a un fitxer existent?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"S'ha trobat una base de dades local existent per a l'emmagatzematge.\nSi reaprofiteu la base de dades, permetreu que les instàncies de la línia d'ordres i del servidor funcionin amb el mateix emmagatzematge remot.\n\n Voleu fer servir la base de dades existent?","Anonymous usage reports":"Informes d'ús anònims","Applications":"Aplicacions","As Command-line":"Com a línia d'ordres","AuthID":"AuthID","Authentication password":"Contrasenya per a l'autenticació","Authentication username":"Nom d'usuari per a l'autenticació","Autogenerated passphrase":"Contrasenya generada automàticament","B2 Application Key":"Clau d'aplicació de B2","B2 Cloud Storage Account ID":"ID del compte de B2 Cloud Storage","B2 Cloud Storage Application Key":"Clau d'aplicació de B2 Cloud Storage","Back":"Enrere","Backup complete!":"S'ha completat la còpia de seguretat!","Backup destination":"Destinació de la còpia de seguretat","Backup location":"Ubicació de la còpia de seguretat","Backup retention":"Preservació de la còpia de seguretat","Backup:":"Còpia de seguretat:","Beta":"Beta","Broken access":"L'accés està trencat","Browse":"Navega","Browser default":"Valor per defecte del navegador","Bucket create location":"Ubicació de creació del contenidor","Bucket name":"Nom del contenidor","Bucket storage class":"Classe d'emmagatzematge del contenidor","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Si permeteu l'accés remot, el servidor escolta les peticions de qualsevol ordinador de la xarxa. Si activeu aquesta opció, assegureu-vos que sempre feu servir l'ordinador en una xarxa protegida amb un tallafoc.","Cache Files":"Fitxers de memòria cau","Canary":"Canary","Cancel":"Cancel·la","Cannot move to existing file":"No s'ha pogut canviar al fitxer existent","Changelog":"Registre de canvis","Changelog for {{appname}} {{version}}":"Registre de canvis del {{appname}} {{version}}","Check failed:":"Ha fallat la comprovació:","Check for updates now":"Comprova ara si hi ha actualitzacions","Chose a storage type to get started":"Trieu un tipus d'emmagatzematge per començar","Click the AuthID link to create an AuthID":"Feu clic a l'enllaç d'AuthID per crear una AuthID","Click to set throttle options":"Feu clic per definir les opcions de velocitat","Compact Phase":"Fase de compactació","Compact now":"Compacta ara","Computer":"Ordinador","Configuration file:":"Fitxer de configuració:","Configuration:":"Configuració:","Configure a new backup":"Configura una nova còpia de seguretat","Confirm delete":"Confirma l'eliminació","Confirmation required":"Es requereix una confirmació","Connect":"Connecta","Connect now":"Connecta ara","Connection lost":"S'ha perdut la connexió","Connection worked!":"Ha funcionat la connexió!","Container name":"Nom del contenidor","Container region":"Regió del contenidor","Continue":"Continua","Continue without encryption":"Continua sense xifratge","Copied!":"S'ha copiat!","Copy":"Copia","Copy Destination URL to Clipboard":"Copia l'URL de destinació al porta-retalls","Copy failed. Please manually copy the URL":"Ha fallat la còpia. Copieu l'URL manualment","Core options":"Opcions principals","Counting ({{files}} files found, {{size}})":"S'està comptant (s'han trobat {{files}} fitxers, {{size}})","Crashes only":"Només fallades","Create folder?":"Voleu crear una carpeta?","Created new limited user":"S'ha creat un nou usuari limitat","Current action:":"Acció actual:","Current file:":"Fitxer actual:","Current version is {{versionname}} ({{versionnumber}})":"La versió actual és {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Extrem d'S3 personalitzat","Custom authentication url":"URL d'autenticació personalitzat","Custom backup retention":"Preservació de còpies de seguretat personalitzada","Custom location ({{server}})":"Ubicació personalitzada ({{server}})","Custom region for creating buckets":"Regió de creació de contenidors personalitzada","Custom region value ({{region}})":"Valor de regió personalitzat ({{region}})","Custom server url ({{server}})":"URL del servidor personalitzat ({{server}})","Custom storage class ({{class}})":"Classe d'emmagatzematge personalitzada ({{class}})","Days":"Dies","Default":"Per defecte","Default ({{channelname}})":"Per defecte ({{channelname}})","Default excludes":"Exclusions per defecte","Default options":"Opcions per defecte","Delete":"Elimina","Delete Phase (Old Backup Versions)":"Fase d'eliminació (versions antigues de la còpia de seguretat)","Delete backup":"Elimina la còpia de seguretat","Delete backups that are older than":"Elimina les còpies de seguretat anteriors a","Delete local database":"Elimina la base de dades local","Delete remote files":"Elimina els fitxers remots","Delete the local database":"Elimina la base de dades local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Voleu eliminar {{filecount}} fitxers ({{filesize}}) de l'emmagatzematge remot?","Deleted":"Eliminats","Deleted Versions":"Versions eliminades","Deleted files":"Fitxers eliminats","Description (optional)":"Descripció (opcional)","Description:":"Descripció:","Desktop":"Escriptori","Destination":"Destinació","Destination path":"Ruta de destinació","Disabled":"Desactivat","Dismiss":"Ignora","Dismiss all":"Ignora-ho tot","Display and color theme":"Visualització i tema de color","Do you really want to delete the backup: \"{{name}}\" ?":"Segur que voleu eliminar la còpia de seguretat «{{name}}»?","Do you really want to delete the local database for: {{name}}":"Segur que voleu eliminar la base de dades local de «{{name}}»?","Done":"Fet","Download":"Baixa","Downloaded files":"Fitxers baixats","Duplicate option {{opt}}":"Opció duplicada {{opt}}","Duplicati Website":"Lloc web del Duplicati","Duplicati forum":"Fòrum del Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"El Duplicati s'executarà quan arrenqui, però es mantindrà pausat durant el període especificat. El Duplicati ocuparà els recursos del sistema mínims i no s'executaran còpies de seguretat.","Duration":"Durada","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada còpia de seguretat té una base de dades local associada que emmagatzema informació sobre la còpia de seguretat remota a l'ordinador local.\n Quan elimineu una còpia de seguretat, també podeu eliminar la base de dades local sense que això afecti la possibilitat de restaurar els fitxers remots.\n Si feu servir la base de dades local per a còpies de seguretat des de la línia d'ordres, hauríeu de mantenir la base de dades.","Edit as list":"Edita com a llista","Edit as text":"Edita com a text","Encrypt file":"Xifra el fitxer","Encryption":"Xifratge","Encryption changed":"S'ha canviat el xifratge","End":"Final","Enter URL":"Introduïu l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Introduïu un pla de preservació manualment. Les expressions són D/W/Y per a dies/setmanes/anys i U per a il·limitat. La sintaxi és: 7D:1D,4W:1W,36M:1M. Aquest exemple preserva una còpia de seguretat per a cadascun dels pròxims 7 dies, per a cadascuna de les pròximes 4 setmanes, i per a cadascun dels pròxims 36 mesos. Això també es pot escriure així: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduïu la contrasenya de la còpia de seguretat, si en té","Enter configuration details":"Introduïu els detalls de configuració","Enter encryption passphrase":"Introduïu la contrasenya de xifratge","Enter expression here":"Introduïu l'expressió aquí","Enter the destination path":"Introduïu la ruta de destinació","Error":"Error","Error!":"S'ha produït un error!","Errors and crashes":"Errors i fallades","Examined":"Examinats","Exclude":"Exclusions","Exclude directories whose names contain":"Exclou carpetes amb un nom que contingui","Exclude expression":"Exclou una expressió","Exclude file":"Exclou un fitxer","Exclude file extension":"Exclou una extensió de fitxer","Exclude files whose names contain":"Exclou fitxers amb un nom que contingui","Exclude filter group":"Exclou un grup de filtres","Exclude folder":"Exclou una carpeta","Exclude regular expression":"Exclou una expressió regular","Existing file found":"S'ha trobat un fitxer existent","Experimental":"Experimental","Export":"Exporta","Export backup configuration":"Exporta la configuració de la còpia de seguretat","Export configuration":"Exporta la configuració","Export passwords":"Exporta les contrasenyes","External link":"Enllaç extern","FTP (Alternative)":"FTP (alternatiu)","Failed to build temporary database: {{message}}":"No s'ha pogut crear la base de dades temporal: {{message}}","Failed to connect:":"No s'ha pogut connectar:","Failed to connect: {{message}}":"No s'ha pogut connectar: {{message}}","Failed to delete:":"No s'ha pogut eliminar:","Failed to fetch path information: {{message}}":"No s'ha pogut recollir la informació de les rutes: {{message}}","Failed to find backup:":"No s'ha pogut trobar la còpia de seguretat:","Failed to read backup defaults:":"No s'han pogut llegir els valors per defecte de la còpia de seguretat:","Failed to restore files: {{message}}":"No s'han pogut restaurar els fitxers: {{message}}","Failed to save:":"No s'ha pogut desar:","File":"Fitxer","Files larger than:":"Fitxers més grans que:","Filters":"Filtres","Finished!":"S'ha acabat!","First run setup":"Configuració inicial","Folder":"Carpeta","Folder path":"Ruta de la carpeta","Fri":"Divendres","GByte":"GBytes","GByte/s":"GByte/s","GCS Project ID":"ID del projecte de GCS","General":"General","General backup settings":"Paràmetres generals de la còpia de seguretat","General options":"Opcions generals","Generate":"Genera","Generate IAM access policy":"Genera una política d'accés IAM","Group email":"Adreça electrònica del grup","Hidden files":"Fitxers ocults","Hide":"Amaga","Hide hidden folders":"Amaga les carpetes ocultes","Home":"Inici","Hostnames":"Noms","Hours":"Hores","How do you want to handle existing files?":"Què voleu fer amb els fitxers existents?","Hyper-V Machine":"Màquina de l'Hyper-V","Hyper-V Machine:":"Màquina de l'Hyper-V:","Hyper-V Machines":"Màquines de l'Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si s'ha sobrepassat una data, la tasca s'executarà tan aviat com sigui possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si es troba com a mínim una còpia de seguretat més recent, s'eliminaran totes les còpies de seguretat anteriors a aquesta data.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si no introduïu una ruta, s'emmagatzemaran tots els fitxers a la carpeta d'inici de sessió.\nSegur que voleu fer això?","If you do not enter an API Key, the tenant name is required":"Si no introduïu una clau API, heu d'indicar el nom d'inquilí","Import":"Importa","Import Destination URL":"Importa un URL de destinació","Import backup configuration":"Importa una configuració de còpia de seguretat","Import from a file":"Importa des d'un fitxer","Import metadata":"Importa les metadades","Include a file?":"Voleu incloure un fitxer?","Include expression":"Inclou una expressió","Include regular expression":"Inclou una expressió regular","Incorrect answer, try again":"La resposta és incorrecta, torneu-ho a provar","Individual builds for developers only. Not for use with important data.":"Compilacions individuals només per a desenvolupadors. No ho feu servir amb dades importants.","Information":"Informació","Invalid characters in path":"La ruta conté caràcters no vàlids","Invalid retention time":"El període de preservació no és vàlid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"És possible connectar-se a alguns servidors FTP sense contrasenya.\nSegur que el vostre servidor FTP suporta l'accés sense contrasenya?","KByte":"KBytes","KByte/s":"KByte/s","Keep a specific number of backups":"Preserva un nombre específic de còpies de seguretat","Keep all backups":"Preserva totes les còpies de seguretat","Keystone API version":"Versió de l'API de Keystone","Language in user interface":"Idioma de la interfície d'usuari","Last month":"El mes passat","Last successful backup:":"Última còpia de seguretat completada:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauració completada: {{time}} (durada: {{duration || '0 segons'}})","Latest":"Versió més recent","Libraries":"Biblioteques","Live":"En viu","Load a configuration from an exported job or a storage provider":"Importeu una configuració des d'una tasca exportada o des d'un proveïdor d'emmagatzematge","Load destination from an exported job or a storage provider":"Importeu una destinació des d'una tasca exportada o des d'un proveïdor d'emmagatzematge","Load older data":"Carrega dades més antigues","Local database path:":"Ruta de la base de dades local:","Local repository":"Dipòsit local","Local storage":"Emmagatzematge local","Location":"Ubicació","Location where buckets are created":"Ubicació on es creen els contenidors","Log data for {{Backup.Backup.Name}}":"Dades de registre de {{Backup.Backup.Name}}","Log data from the server":"Dades de registre del servidor","Log out":"Surt","MByte":"MBytes","MByte/s":"MByte/s","Maintenance":"Manteniment","Manually type path":"Escriviu la ruta manualment","Max download speed":"Velocitat màxima de baixada","Max upload speed":"Velocitat màxima de càrrega","Menu":"Menú","Microsoft SQL Database:":"Base de dades SQL de Microsoft:","Microsoft SQL Databases":"Bases de dades SQL de Microsoft","Minimum redundancy":"Redundància mínima","Minimum redundancy is 1.0":"La redundància mínima és de 1.0","Minutes":"Minuts","Missing name":"No s'ha definit un nom","Missing passphrase":"No s'ha definit una contrasenya","Missing sources":"No s'ha definit un origen","Modified":"Modificats","Mon":"Dilluns","Months":"Mesos","Move existing database":"Mou una base de dades existent","Move failed:":"No s'ha pogut moure:","My Documents":"Documents","My Music":"Música","My Photos":"Fotografies","My Pictures":"Imatges","Name":"Nom","Never":"Mai","New user name is {{user}}.\nUpdated credentials to use the new limited user":"El nou nom d'usuari és {{user}}.\nS'han actualitzat les credencials per fer servir el nou usuari limitat","Next":"Següent","Next scheduled run:":"Pròxima execució programada:","Next scheduled task:":"Pròxima tasca programada:","Next task:":"Pròxima tasca:","Next time":"La pròxima vegada","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No s'ha especificat cap certificat anteriorment, comproveu amb l'administrador del servidor que la clau és correcta: {{key}} \n\nVoleu aprovar aquesta clau d'amfitrió?","No editor found for the "{{backend}}" storage type":"No s'ha trobat cap editor per a l'emmagatzematge del tipus «{{backend}}»","No encryption":"Sense xifratge","No items selected":"No s'ha seleccionat cap element","No items to restore, please select one or more items":"No hi ha elements per restaurar, seleccioneu-ne un o més","No passphrase entered":"No s'ha introduït cap contrasenya","No scheduled tasks":"No hi ha tasques planificades","Non-matching passphrase":"La contrasenya no coincideix","None / disabled":"Cap / desactivat","Not using encryption":"El xifratge està desactivat","Nothing will be deleted. The backup size will grow with each change.":"No s'eliminarà res. La mida de la còpia de seguretat augmentarà després de cada canvi.","OK":"D'acord","Once there are more backups than the specified number, the oldest backups are deleted.":"Una vegada hi ha més còpies de seguretat que el nombre especificat, s'eliminen les còpies de seguretat més antigues.","OpenStack AuthURI":"AuthURI de l'OpenStack","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Oberts","Operating System":"Sistema operatiu","Operation":"Operació","Operations:":"Operacions:","Optional authentication password":"Contrasenya per a l'autenticació (opcional)","Optional authentication username":"Nom d'usuari per a l'autenticació (opcional)","Options":"Opcions","Original location":"Ubicació original","Others":"Altres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Al llarg del temps, les còpies de seguretat s'eliminaran automàticament. Es conservarà una còpia de seguretat per a cadascun dels darrers 7 dies, les darreres 4 setmanes i els darrers 12 mesos. Sempre hi haurà com a mínim una còpia de seguretat restant.","Overwrite":"Sobreescriu-los","Passphrase":"Contrasenya","Passphrase (if encrypted)":"Contrasenya (si el fitxer està xifrat)","Passphrase changed":"S'ha canviat la contrasenya","Passphrases are not matching":"Les contrasenyes no coincideixen","Passphrases do not match":"Les contrasenyes no coincideixen","Password":"Contrasenya","Path":"Ruta","Path not found":"No s'ha trobat la ruta","Path on server":"Ruta al servidor","Path or subfolder in the bucket":"Ruta o subcarpeta al contenidor","Pause":"Pausa","Pause after startup or hibernation":"Pausa després de l'arrencada o la hibernació","Pause options":"Opcions de pausa","Permissions":"Permisos","Pick location":"Trieu una ubicació","Point to your backup files and restore from there":"Indiqueu on són els vostres fitxers de còpia de seguretat i feu una restauració des d'allà","Port":"Port","Prevent tray icon automatic log-in":"Impedeix l'inici de sessió automàtic de la safata del sistema","Previous":"Enrere","Progress:":"Progrés:","ProjectID is optional if the bucket exist":"La ProjectID és opcional si el contenidor existeix","Proprietary":"De propietat","Purge Phase":"Fase de purga","Purging files complete!":"S'ha completat la purga de fitxers!","Recreate (delete and repair)":"Recrea (elimina i repara)","Recreate Database Phase":"Fase de recreació de la base de dades","Relative paths not allowed":"No es permet l'ús de rutes relatives","Reload":"Actualitza","Remote":"Remot","Remote Path":"Ruta remota","Remote Repository":"Dipòsit remot","Remote path":"Ruta remota","Remote repository":"Dipòsit remot","Remote volume size":"Mida dels volums remots","Remove":"Elimina","Remove option":"Elimina l'opció","Removed files":"Fitxers eliminats","Repair":"Repara","Repair Phase":"Fase de reparació","Repeat Passphrase":"Repetiu la contrasenya","Reporting:":"S'està informant:","Reset":"Reinicialitza","Restore":"Restaura","Restore complete!":"S'ha completat la restauració!","Restore files":"Restaura fitxers","Restore from":"Restaura des de","Restore from backup configuration":"Restaura des d'una configuració de còpia de seguretat","Restore options":"Opcions de restauració","Restore read/write permissions":"Restaura els permisos de lectura/escriptura","Resume":"Reprèn","Rewritten File Lists":"Llistes de fitxers reescrits","Run again every":"Torna a executar cada","Run now":"Executa ara","Running commandline entry":"S'està executant una entrada de la línia d'ordres","Running task:":"Tasca en execució:","S3 Compatible":"Compatible amb S3","Same as the base install version: {{channelname}}":"La mateixa que la versió base d'instal·lació: {{channelname}}","Sat":"Dissabte","Save":"Desa","Save and repair":"Desa i repara","Save different versions with timestamp in file name":"Desa les versions diferents amb una marca horària al nom del fitxer","Save immediately":"Desa immediatament","Schedule":"Planificació","Search":"Cerca","Search for files":"Cerca fitxers","Seconds":"Segons","Select a log level and see messages as they happen:":"Trieu un nivell de registre i vegeu els nous missatges al moment:","Select files":"Seleccioneu els fitxers","Server":"Servidor","Server and port":"Servidor i port","Server hostname or IP":"Nom del servidor o IP","Server is currently paused,":"El servidor està pausat actualment,","Server is currently paused, do you want to resume now?":"El servidor està pausat actualment, voleu reprendre la tasca ara?","Server password":"Contrasenya del servidor","Server paused":"S'ha pausat el servidor","Server state properties":"Propietats de l'estat del servidor","Settings":"Configuració","Show":"Mostra","Show advanced editor":"Mostra l'editor avançat","Show hidden folders":"Mostra les carpetes ocultes","Show log":"Mostra el registre","Show treeview":"Mostra la vista en arbre","Sia server password":"Contrasenya del servidor de Sia","Smart backup retention":"Preservació de còpies de seguretat intel·ligent","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns proveïdors de l'OpenStack permeten fer servir una clau API en comptes d'una contrasenya i un nom d'inquilí","Source Data":"Dades d'origen","Source data":"Dades d'origen","Source folders":"Carpetes d'origen","Source:":"Origen:","Specific builds for developers only. Not for use with important data.":"Compilacions específiques només per a desenvolupadors. No ho feu servir amb dades importants.","Standard protocols":"Protocols estàndard","Start":"Inici","Stop after the current file":"Atura després del fitxer actual","Stop running backup":"Atura la còpia de seguretat en execució","Stop running task":"Atura la tasca en execució","Stopping task:":"S'està aturant la tasca:","Storage Type":"Tipus d'emmagatzematge","Storage class":"Classe d'emmagatzematge","Storage class for creating a bucket":"Classe d'emmagatzematge per crear un contenidor","Stored":"Emmagatzemat","Strong":"Forta","Success":"Èxit","Sun":"Diumenge","Symbolic link":"Enllaç simbòlic","System Files":"Fitxers del sistema","System default ({{levelname}})":"Valor per defecte del sistema ({{levelname}})","System files":"Fitxers del sistema","System info":"Informació del sistema","System properties":"Propietats del sistema","TByte":"TBytes","TByte/s":"TByte/s","Task is running":"La tasca s'està executant","Temporary Files":"Fitxers temporals","Temporary files":"Fitxers temporals","Test Phase":"Fase de comprovació","Test connection":"Comprova la connexió","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"El camp «{{fieldname}}» conté un caràcter no vàlid: {{character}} (valor: {{value}}, índex: {{pos}})","The backup is missing, has it been deleted?":"No s'ha trobat la còpia de seguretat; l'heu eliminat?","The backup was temporary and does not exist anymore, so the log data is lost":"La còpia de seguretat era temporal i ja no existeix, per la qual cosa s'han perdut les dades del registre","The bucket name should be all lower-case, convert automatically?":"El nom del contenidor ha d'estar en minúscules; voleu convertir-lo automàticament?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"És recomanable que deseu la configuració en un lloc segur. Segur que voleu desar un fitxer sense xifrar amb les vostres contrasenyes?","The dark theme (by Michal)":"Tema fosc (per Michal)","The default blue on white theme (by Alex)":"Tema per defecte, blau sobre blanc (per Alex)","The folder {{folder}} does not exist.\nCreate it now?":"La carpeta {{folder}} no existeix.\nVoleu crear-la ara?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clau de l'amfitrió ha canviat, comproveu amb l'administrador del servidor que això és correcte, o podríeu ser víctima d'un atac d'intermediari.\n\nVoleu substituir la clau d'amfitrió actual («{{prev}}») amb la clau d'amfitrió «{{key}}»?","The passwords do not match":"Les contrasenyes no coincideixen","The path does not appear to exist, do you want to add it anyway?":"Sembla que la ruta no existeix, voleu afegir-la igualment?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"La ruta no acaba amb un caràcter «{{dirsep}}», la qual cosa vol dir que heu triat un fitxer, no una carpeta.\n\nVoleu incloure el fitxer especificat?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"La ruta ha de ser absoluta, és a dir, ha de començar amb una barra «/»","The region parameter is only applied when creating a new bucket":"El paràmetre de regió només s'aplica quan es crea un contenidor","The region parameter is only used when creating a bucket":"El paràmetre de regió només es fa servir quan es crea un contenidor","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"No s'ha pogut validar el certificat del servidor.\nVoleu aprovar el certificat SSL amb la suma «{{hash}}»?","The storage class affects the availability and price for a stored file":"La classe d'emmagatzematge afecta la disponibilitat i el preu dels fitxers emmagatzemats","The target folder contains encrypted files, please supply the passphrase":"La carpeta de destinació conté fitxers encriptats; introduïu-ne la contrasenya","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'usuari té massa permisos. Voleu crear un nou usuari limitat, amb permisos només per a la ruta seleccionada?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Aquesta còpia de seguretat s'ha creat en un altre sistema operatiu. Si restaureu fitxers sense especificar una carpeta de destinació, pot ser que es restaurin fitxers en llocs inesperats. Segur que voleu continuar sense seleccionar una carpeta de destinació?","This month":"Aquest mes","This week":"Aquesta setmana","Throttle settings":"Opcions de velocitat","Thu":"Dijous","Time":"Hora","To File":"A un fitxer","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Per confirmar que voleu eliminar tots els fitxers remots de «{{name}}, escriviu la paraula que veieu a continuació","To export without a passphrase, uncheck the \"Encrypt file\" box":"Per fer una exportació sense contrasenya, desactiveu la casella «Xifra el fitxer»","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Per evitar diversos atacs basats en el DNS, el Duplicati limita els noms de servidor permesos als d'aquesta llista. Sempre es permet l'accés directe a localhost o per IP. Podeu indicar diversos noms de servidor separant-los amb un punt i coma. Si cap dels noms d'ordinador permesos és un asterisc (*), es permeten tots els noms d'ordinador i es desactiva aquesta característica. Si el camp és buit, només es permet l'accés a localhost o per adreça IP.","Today":"Avui","Trust host certificate?":"Voleu confiar en el certificat de l'amfitrió?","Trust server certificate?":"Voleu confiar en el certificat del servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Proveu les noves característiques que estem preparant. És la versió més estable disponible actualment. Proveu la funció de restauració abans de fer-ho servir en entorns de producció.","Tue":"Dimarts","Type passphrase here.":"Escriviu la contrasenya aquí.","Type to highlight files":"Escriviu per ressaltar fitxers","Unknown backup size and versions":"No s'han pogut determinar la mida de la còpia de seguretat i les versions","Until resumed":"Fins que es reprengui","Update channel":"Canal d'actualitzacions","Update failed:":"Ha fallat l'actualització:","Updating with existing database":"S'està actualitzant amb una base de dades existent","Uploaded files":"Fitxers carregats","Usage statistics":"Estadístiques d'ús","Usage statistics, warnings, errors, and crashes":"Estadístiques d'ús, avisos, errors i fallades","Use SSL":"Fes servir SSL","Use existing database?":"Voleu fer servir la base de dades existent?","Use weak passphrase":"Fes servir una contrasenya dèbil","Useless":"Inútil","User data":"Dades d'usuari","User domain name":"Nom de domini de l'usuari","User has too many permissions":"L'usuari té massa permisos","User interface settings":"Paràmetres de la interfície d'usuari","Username":"Nom d'usuari","Verifications":"Verificacions","Verify files":"Verifica els fitxers","Verifying answer":"S'està verificant la resposta","Version ID":"ID de la versió","Very strong":"Molt forta","Very weak":"Molt dèbil","Visit us on":"Visiteu-nos a","WARNING: This will prevent you from restoring the data in the future.":"AVÍS: Això impedirà que restaureu les dades més endavant.","Waiting for task to begin":"S'està esperant que la tasca comenci","Warnings, errors and crashes":"Avisos, errors i fallades","We recommend that you encrypt all backups stored outside your system":"És recomanable que xifreu totes les còpies de seguretat emmagatzemades fora del vostre ordinador","Weak":"Dèbil","Weak passphrase":"Contrasenya dèbil","Wed":"Dimecres","Weeks":"Setmanes","Where do you want to restore from?":"Des d'on voleu fer la restauració?","Where do you want to restore the files to?":"On voleu restaurar els fitxers?","Years":"Anys","Yes":"Sí","Yes, I have stored the passphrase safely":"Sí, he desat la contrasenya en un lloc segur","Yes, I understand the risk":"Sí, entenc els riscos","Yes, I'm brave!":"Sí, no tinc por!","Yes, please break my backup!":"Sí, destrossa'm la còpia de seguretat!","Yesterday":"Ahir","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Esteu canviant la ruta d'una base de dades existent.\nSegur que voleu fer això?","You are currently running {{appname}} {{version}}":"Actualment esteu executant el {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Heu canviat el mode de xifratge. Pot ser que això trenqui alguna cosa. És recomanable que creeu una nova còpia de seguretat en comptes de fer això","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Heu canviat la contrasenya, i això no està implementat. És recomanable que creeu una nova còpia de seguretat en comptes de fer això.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Heu decidit no xifrar la còpia de seguretat. És recomanable que xifreu totes les dades emmagatzemades en un servidor remot.","You have chosen to restore to a new location, but not entered one":"Heu decidit fer la restauració en una nova ubicació, però no n'heu indicat cap","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Heu generat una contrasenya forta. Assegureu-vos que heu copiat la contrasenya en un lloc segur, perquè no podreu recuperar les dades si la perdeu.","You must choose at least one source folder":"Heu de triar com a mínim una carpeta d'origen","You must enter a domain name to use v3 API":"Heu d'introduir un nom de domini per fer servir l'API v3","You must enter a name for the backup":"Heu d'introduir un nom per a la còpia de seguretat","You must enter a passphrase or disable encryption":"Heu d'introduir una contrasenya o desactivar el xifratge","You must enter a password to use v3 API":"Heu d'introduir una contrasenya per fer servir l'API v3","You must enter a positive number of backups to keep":"Heu d'introduir un nombre positiu de còpies de seguretat que voleu preservar","You must enter a tenant (aka project) name to use v3 API":"Heu d'introduir un nom d'inquilí (projecte) per fer servir l'API v3","You must enter a valid duration for the time to keep backups":"Heu d'introduir una durada vàlida de preservació de les còpies de seguretat","You must fill in the password":"Heu d'introduir la contrasenya","You must fill in the server name or address":"Heu d'introduir el nom o l'adreça del servidor","You must fill in the username":"Heu d'introduir el nom d'usuari","You must fill in {{field}}":"Heu d'introduir el camp «{{field}}»","You must select or fill in the AuthURI":"Heu de triar o introduir l'AuthURI","You must select or fill in the server":"Heu de triar o introduir el servidor","You must specify a path":"Heu d'especificar una ruta","Your files and folders have been restored successfully.":"S'han restaurat els fitxers i carpetes correctament.","Your passphrase is easy to guess. Consider changing passphrase.":"La contrasenya és fàcil d'endevinar. Penseu a canviar la contrasenya.","bucket/folder/subfolder":"contenidor/carpeta/subcarpeta","byte":"bytes","byte/s":"byte/s","custom":"personalitzat","resume now":"reprèn ara","unless you are explicitly specifying --group-id":"excepte si especifiqueu explícitament el paràmetre --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"El {{appname}} ha estat desenvolupat principalment per {{dev1}} i {{dev2}}. Podeu baixar-vos el {{appname}} des de {{websitename}}. El {{appname}} està publicat sota la {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"Queden {{files}} fitxers ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versió","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versions"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} hores","{{number}} Minutes":"{{number}} minuts","{{time}} (took {{duration}})":"{{time}} (ha tardat {{duration}})"}); + gettextCatalog.setStrings('cs', {"- pick an option -":"- vyberte jednu z možností -","...loading...":"…načítání…","API key":"Klíč k aplikačnímu programovému rozhraní (API)","AWS Access ID":"Přístupový identifikátor ke službě AWS","AWS Access Key":"Přístupový klíč ke službě AWS","AWS IAM Policy":"Zásady IAM služby AWS","About":"O aplikaci","About {{appname}}":"O aplikaci {{appname}}","Access Key":"Přístupový klíč","Access denied":"Přístup odepřen","Access grant":"Udělení přístupu","Access to user interface":"Přístup k uživatelskému rozhraní","Account name":"Název účtu","Add a new backup":"Přidat novou zálohu","Add a path directly":"Přidat popis umístění přímo","Add advanced option":"Přidat pokročilou volbu","Add backup":"Přidat zálohu","Add filter":"Přidat filtr","Add path":"Přidat popis umístění","Added":"Přidáno","Adjust bucket name?":"Přizpůsobit název „nádoby“ (bucket)?","Advanced Options":"Pokročilé volby","Advanced options":"Pokročilé volby","Advanced:":"Pokročilé:","All Hyper-V Machines":"Všechny Hyper-V stroje","All Microsoft SQL Databases":"Všechny Microsoft SQL databáze","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Veškerá hlášení o využívání jsou posílána anonymně a neobsahují žádné osobní údaje. Obsahují informace o hardware a operačním systému, typu podpůrné vrstvy (backend), trvání zálohy, celkové velikosti zdrojových dat a podobně.\nNeobsahují popisy umístění, názvy souborů, uživatelská jména, hesla nebo podobné citlivé údaje.","Allow remote access (requires restart)":"Umožnit přístup na dálku (vyžaduje restart)","Allowed days":"Dny, ve které je přístup umožněn","An existing file was found at the new location":"V novém umístění byl nalezen už existující soubor","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"V novém umístění byl nalezen už existující soubor\nOpravdu chcete nasměrovat databázi do existujícího souboru?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Byla nalezena existující místní databáze pro ukládání.\nOpětovné využití databáze umožní, aby instance pro příkazový řádek a server fungovaly na stejném vzdáleném úložišti.\n\nChcete použít existující databázi?","Anonymous usage reports":"Anonymní hlášení o použití","Applications":"Aplikace","As Command-line":"Jako příkazový řádek","AuthID":"AuthID","Authentication method":"Způsob autentizace","Authentication method ({{auth_method}})":"Způsob autentizace ({{auth_method}})","Authentication password":"Ověřovací heslo","Authentication username":"Ověřovací uživatelské jméno","Autogenerated passphrase":"Automaticky vytvořená heslová fráze","B2 Application ID":"B2 Aplikační ID","B2 Application Key":"Aplikační klíč ke službě B2","B2 Cloud Storage Account ID":"Identifikátor účtu u cloudového úložiště B2","B2 Cloud Storage Application ID":"Aplikační klíč ke cloudovému úložišti B2","B2 Cloud Storage Application Key":"Aplikační klíč ke cloudovému úložišti B2","Back":"Zpět","Backup complete!":"Záloha dokončena!","Backup destination":"Cíl zálohy","Backup location":"Umístění zálohy","Backup retention":"Doba uchovávání záloh","Backup:":"Záloha:","Beta":"Vývojová testovací (beta)","Broken access":"Nefunkční přístup","Browse":"Procházet","Browser default":"Výchozí nastavení webového prohlížeče","Bucket create location":"Umístění ve kterém „nádobu“ (bucket) vytvořit","Bucket name":"Název „nádoby“ (bucket)","Bucket storage class":"Třída úložiště nesoucí „nádobu“ (bucket)","Building list of files to restore …":"Vytváření seznamu souborů k obnovení…","Building partial temporary database …":"Vytváření částečné dočasné databáze…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Umožněním přístupu na dálku, server očekává požadavky z libovolného stroje na síti. Pokud tuto volbu zapnete, počítač používejte pouze na síti, zabezpečené bránou firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Ve výchozím stavu ikona v oznamovací oblasti otevře uživatelské rozhraní s tokenem, který ho odemkne. To zajistí že můžete přistupovat k uživatelskému rozhraní z ikony v oznamovací oblasti, zatímco po ostatních bude vyžadovat zadání hesla. Pokud upřednostňujete zadávání hesla i při přístupu k uživatelskému rozhraní z ikony v oznamovací oblasti, zapněte tuto volbu.","Cache Files":"Soubory mezipaměti","Canary":"Kanárek","Cancel":"Storno","Cannot move to existing file":"Nelze přesunout do existujícího souboru","Changelog":"Seznam změn","Changelog for {{appname}} {{version}}":"Seznam změn v {{appname}} {{version}}","Check failed:":"Zjištění se nezdařilo:","Check for updates now":"Zjistit dostupnost případných aktualizací nyní","Checking for updates …":"Zjišťování dostupnosti případných aktualizací…","Chose a storage type to get started":"Pro začátek vyberte typ úložiště","Click the AuthID link to create an AuthID":"AuthID vytvoříte kliknutím na odkaz AuthID","Click to set throttle options":"Kliknutím nastavte předvolby přiškrcování","Client library to use":"Používaná klientská knihovna","Commandline …":"Příkazový řádek…","Compact Phase":"Fáze zkompaktňování","Compact now":"Zkompaktnit nyní","Compacting remote data …":"Zkompaktňování dat na protějšku…","Complete log":"Úplný záznam událostí","Completing backup …":"Dokončování zálohy…","Completing previous backup …":"Dokončování předchozí zálohy…","Computer":"Počítač","Configuration file:":"Soubor s nastaveními:","Configuration:":"Nastavení:","Configure a new backup":"Nastavit novou zálohu","Confirm delete":"Potvrzení smazání","Confirm encryption passphrase":"Potvrzení zadání šifrovací heslové fráze","Confirm passphrase":"Zopakování zadání heslové fráze","Confirmation required":"Vyžadováno potvrzení","Connect":"Připojit","Connect now":"Připojit nyní","Connecting to server …":"Připojování k serveru…","Connection lost":"Spojení ztraceno","Connection worked!":"Spojení funguje!","Container name":"Název kontejneru","Container region":"Region umístění kontejneru","Continue":"Pokračovat","Continue without encryption":"Pokračovat bez šifrování","Copied!":"Zkopírováno!","Copy":"Kopírovat","Copy Destination URL to Clipboard":"Zkopírovat URL adresu cíle do schránky","Copy failed. Please manually copy the URL":"Kopie se nezdařila. Zkopírujte URL adresu ručně","Core options":"Základní volby","Counting ({{files}} files found, {{size}})":"Počítání ({{files}} souborů nalezeno, {{size}})","Crashes only":"Pouze pády","Create bug report …":"Vytvořit hlášení chyby…","Create folder?":"Vytvořit složku?","Created new limited user":"Vytvořen nový uživatelský účet s omezenými oprávněními","Creating bug report …":"Vytvořit hlášení chyby…","Creating new user with limited access …":"Vytváření nového uživatele s omezeným přístupem…","Creating target folders …":"Vytváření cílových složek…","Creating temporary backup …":"Vytváření dočasné zálohy…","Current action:":"Stávající akce:","Current file:":"Stávající soubor:","Current version is {{versionname}} ({{versionnumber}})":"Stávající verze je {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Vlastní S3 koncový bod","Custom Satellite":"Vlastní satelit","Custom Satellite ({{satellite}})":"Vlastní satelit ({{satellite}})","Custom authentication url":"Vlastní ověřovací URL adresa","Custom backup retention":"Uživatelem určená doba uchovávání záloh","Custom location ({{server}})":"Vlastní umístění ({{server}})","Custom region for creating buckets":"Vlastní region pro vytváření „nádob“ (bucket)","Custom region value ({{region}})":"Hodnota pro vlastní region ({{region}})","Custom server url ({{server}})":"Vlastní URL adresa serveru ({{server}})","Custom storage class ({{class}})":"Vlastní třída úložiště ({{class}})","Database …":"Databáze…","Days":"Dnů","Default":"Výchozí","Default ({{channelname}})":"Výchozí ({{channelname}})","Default excludes":"Ve výchozím stavu vynecháno","Default options":"Výchozí volby","Delete":"Smazat","Delete Phase (Old Backup Versions)":"Fáze mazání (staré verze zálohy)","Delete backup":"Smazat zálohu","Delete backups that are older than":"Smazat zálohy starší než","Delete local database":"Smazat místní databázi","Delete remote files":"Smazat soubory na protějšku","Delete the local database":"Smazat místní databázi","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Smazat {{filecount}} souborů ({{filesize}}) ze vzdáleného úložiště?","Delete …":"Smazat…","Deleted":"Smazáno","Deleted Versions":"Smazané verze","Deleted files":"Smazané soubory","Deleting remote files …":"Mazání souborů na protějšku…","Deleting unwanted files …":"Mazání nepotřebných souborů…","Description (optional)":"Popis (volitelné)","Description:":"Popis:","Desktop":"Osobní počítač","Destination":"Cíl","Destination path":"Cílové umístění","Disabled":"Vypnuto","Dismiss":"Zavřít","Dismiss all":"Zavřít vše","Display and color theme":"Motiv vzhledu zobrazení a barev","Do you really want to delete the backup: \"{{name}}\" ?":"Opravdu chcete smazat zálohu: „{{name}}“?","Do you really want to delete the local database for: {{name}}":"Opravdu chcete smazat místní databázi pro: {{name}}","Done":"Hotovo","Download":"Stáhnout","Downloaded files":"Stažené soubory","Downloading files …":"Stahování souborů…","Downloading update…":"Stahování aktualizace…","Duplicate option {{opt}}":"Volba duplikace {{opt}}","Duplicati Website":"Webové stránky projektu Duplicati","Duplicati forum":"Diskuzní fórum o Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati se zahájí při spuštění, ale po dobu průběhu zůstane v pozastaveném stavu. Bude zabírat co nejméně systémových prostředků a nebudou spouštěny žádné zálohy.","Duration":"Doba trvání","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Ke každé záloze je přiřazena místní databáze, která uchovává informace o vzdálené záloze na místním stroji.\n Při mazání zálohy je také možné smazat lokální databázi aniž by tím byla postižena schopnost obnovovat vzdálené soubory.\n Pokud používáte místní databáze pro zálohy z příkazového řádku, měli byste databázi ponechat.","Edit as list":"Upravit jako seznam","Edit as text":"Upravit jako text","Edit …":"Upravit…","Encrypt file":"Zašifrovat soubor","Encryption":"Šifrování","Encryption changed":"Šifrování změněno","Encryption passphrase":"Šifrovací heslová fráze","End":"Konec","Enter URL":"Zadejte URL adresu","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Zadejte strategii uchovávání záloh ručně. Výplň je D/W/Y pro dny/týdny/roky a U pro neomezené. Forma zápisu je: 7D:1D,4W:1W,36M:1M. V tomto příkladu je ponechána jedna záloha z každého dne po dobu příštích 7 dnů, jedna z každého týdne po dobu příštích 4 týdnů a jedna z každého měsíce po dobu příštích 36 měsíců. Je možné zapsat také jako 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Zadejte záložní heslovou frázi, pokud existuje","Enter configuration details":"Zadejte podrobnosti nastavení","Enter encryption passphrase":"Zadejte šifrovací heslovou frázi","Enter expression here":"Sem zadejte výraz","Enter the destination path":"Zadejte popis cílového umístění ","Error":"Chyba","Error!":"Chyba!","Errors and crashes":"Chyby a pády","Examined":"Prozkoumáno","Exclude":"Vynechat","Exclude directories whose names contain":"Vynechat složky jejichž názvy obsahují","Exclude expression":"Výraz pro vynechané","Exclude file":"Vynechat soubor","Exclude file extension":"Vynechat soubory s příponami","Exclude files whose names contain":"Vynechat soubory jejichž názvy obsahují","Exclude filter group":"Skupina filtru vynechání","Exclude folder":"Vynechat složku","Exclude regular expression":"Regulární výraz pro vynechávané","Existing file found":"Nalezen existující soubor","Experimental":"Experimentální","Export":"Exportovat","Export backup configuration":"Exportovat zálohu nastavení","Export configuration":"Exportovat nastavení","Export passwords":"Exportovat hesla","Export …":"Export…","Exporting …":"Exportování…","External link":"Vnější odkaz","FTP (Alternative)":"FTP (alternativní)","Failed to build temporary database: {{message}}":"Nepodařilo se vytvořit dočasnou databázi: {{message}}","Failed to connect:":"Nepodařilo se připojit:","Failed to connect: {{message}}":"Nepodařilo se připojit: {{message}}","Failed to delete:":"Nepodařilo se smazat:","Failed to fetch path information: {{message}}":"Nepodařilo se stáhnout informaci o popisu umístění: {{message}}","Failed to find backup:":"Zálohu se nepodařilo nalézt:","Failed to read backup defaults:":"Nepodařilo se načíst výchozí parametry zálohy:","Failed to restore files: {{message}}":"Nepodařilo se obnovit soubory: {{message}}","Failed to save:":"Nepodařilo se uložit:","Fetching path information …":"Získávání informací o popisu umístění…","File":"Soubor","Files larger than:":"Soubory větší než:","Filters":"Filtry","Finished!":"Dokončeno!","First run setup":"Úvodní nastavení při prvním spuštění","Folder":"Složka","Folder path":"Popis umístění složky","Fri":"Pá","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS identifikátor projektu","General":"Obecné","General backup settings":"Obecná nastavení zálohy","General options":"Obecné volby","Generate":"Vytvořit","Generate IAM access policy":"Vytvořit IAM zásady přístupu","Getting file versions …":"Získávání verzí souboru…","Group email":"E-mail skupiny","Hidden files":"Skryté soubory","Hide":"Skrýt","Hide hidden folders":"Skrýt skryté složky","Home":"Domovská složka","Hostnames":"Názvy strojů","Hours":"Hodin","How do you want to handle existing files?":"Jak chcete zacházet s existujícími soubory?","Hyper-V Machine":"Hyper-V stroj","Hyper-V Machine:":"Hyper-V stroj:","Hyper-V Machines":"Hyper-V stroje","ID:":"Identifikátor:","If a date was missed, the job will run as soon as possible.":"Pokud chybělo datum, úloha bude spuštěna co možná nejdříve.","If at least one newer backup is found, all backups older than this date are deleted.":"Pokud je nalezena alespoň jedna novější záloha, všechny zálohy starší než tento datum budou smazány.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Pokud nezadáte popis umístění, všechny soubory budou uloženy v přihlašovací složce.\nJe to to, co chcete?","If you do not enter an API Key, the tenant name is required":"Pokud nezadáte klíč k API, je vyžadováno jméno nájemníka (tenant)","Import":"Import","Import Destination URL":"Importovat URL adresu cíle","Import backup configuration":"Importovat nastavení zálohy","Import from a file":"Importovat ze souboru","Import metadata":"Importovat metadata","Importing …":"Importování…","Include a file?":"Zahrnout soubor?","Include expression":"Výraz pro zahrnutí","Include regular expression":"Regulární výraz pro zahrnutí","Incorrect answer, try again":"Nesprávná odpověď, zkuste to znovu","Individual builds for developers only. Not for use with important data.":"Jednotlivá sestavení určená pouze pro vývojáře. Nepoužívejte pro důležitá data.","Information":"Informace","Invalid characters in path":"Neplatné znaky v popisu umístění","Invalid retention time":"Neplatná doba ponechání","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"K některým FTP serverům je možné se připojit i bez hesla.\nOpravdu to tento FTP server umožňuje?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Ponechat konkrétní počet záloh","Keep all backups":"Ponechat všechny zálohy","Keystone API version":"Verze aplikačního program. rozhraní stavebního bloku","Language in user interface":"Jazyk textů v uživatelském rozhraní","Last month":"Minulý měsíc","Last successful backup:":"Minulá úspěšná záloha:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Minulé úspěšné obnovení: {{time}} (trvalo {{duration || '0 sekund'}})","Latest":"Poslední","Libraries":"Knihovny","Listing backup dates …":"Vypisování datumů záloh…","Listing remote files for purge …":"Vypisování souborů na protějšku, které trvale vymazat…","Listing remote files …":"Vypisování souborů na protějšku…","Live":"Aktuální","Load a configuration from an exported job or a storage provider":"Načíst nastavení z exportované úlohy nebo z poskytovatele úložiště","Load destination from an exported job or a storage provider":"Načíst cíl z exportované úlohy nebo poskytovatele úložiště","Load older data":"Načíst starší data","Loading …":"Načítání…","Local database path:":"Popis umístění místní databáze:","Local repository":"Místní repozitář","Local storage":"Místní úložiště","Location":"Umístění","Location where buckets are created":"Umístění, ve kterém jsou „nádoby“ (bucket) vytvářeny","Log data for {{Backup.Backup.Name}}":"Zaznamenávat (log) údaje pro {{Backup.Backup.Name}}","Log data from the server":"Zaznamenávat data ze serveru","Log out":"Odhlásit se","MByte":"MB","MByte/s":"MB/s","Maintenance":"Údržba","Manually type path":"Zadejte popis umístění ručně","Max download speed":"Nejvyšší rychlost stahování","Max upload speed":"Nejvyšší rychlost odesílání","Menu":"Nabídka","Microsoft SQL Database:":"Databáze Microsoft SQL:","Microsoft SQL Databases":"Databáze Microsoft SQL","Minimum redundancy":"Minimální redundance","Minimum redundancy is 1.0":"Minimální redundance je 1.0","Minutes":"Minut","Missing name":"Chybějící název","Missing passphrase":"Chybějící heslová fráze","Missing sources":"Chybějící zdroje","Modified":"Změněno","Mon":"Po","Months":"Měsíců","Move existing database":"Přesunout existující databázi","Move failed:":"Přesun se nezdařil:","My Documents":"Moje dokumenty","My Music":"Hudba","My Photos":"Fotografie","My Pictures":"Obrázky","Name":"Název","Never":"Nikdy","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nové uživatelské jméno je {{user}}.\nNyní budou používány přihlašovací údaje tohoto uživatele","Next":"Další","Next scheduled run:":"Příští naplánované spuštění:","Next scheduled task:":"Příští naplánovaná úloha:","Next task:":"Příští úloha:","Next time":"Příště","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Předtím nebyl určen žádný certifikát, ověřte se správcem serveru že klíč je správný: {{key}}\n\nSchvalujete tento klíč stroje?","No editor found for the "{{backend}}" storage type":"Nebyl nalezen žádný editor pro typ úložiště „{{backend}}“","No encryption":"Nešifrovat","No items selected":"Nejsou vybrané žádné položky","No items to restore, please select one or more items":"Žádné položky pro obnovení – vyberte alespoň jednu","No passphrase entered":"Není zadaná žádná heslová fráze","No scheduled tasks":"Žádné naplánované úlohy","Non-matching passphrase":"Zadání heslové fráze se neshodují","None / disabled":"Žádné / vypnuté","Not using encryption":"Nepoužívá šifrování","Nothing will be deleted. The backup size will grow with each change.":"Nic nebude smazáno. Velikost zálohy naroste při každé změně.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Jakmile je zde více záloh než zadané číslo, nejstarší zálohy budou smazané.","OpenStack AuthURI":"AuthURI pro OpenStack","OpenStack Object Storage / Swift":"Objektové úložiště OpenStack (Swift)","Opened":"Otevřeno","Operating System":"Operační systém","Operation":"Operace","Operations:":"Operace:","Optional authentication password":"Volitelné ověřovací heslo","Optional authentication username":"Volitelné uživatelské jméno pro ověření","Options":"Předvolby","Original location":"Původní umístění","Others":"Ostatní","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Po čase jsou zálohy automaticky odmazávány. Bude udržována jedna záloha z každého dne za minulých 7 dnů, jedna z každého týdne za minulé 4 týdny a jedna z každého měsíce za minulých 12 měsíců. A vždy zde bude přinejmenším jedna ponechaná záloha.","Overwrite":"Přepsat","Passphrase":"Heslová fráze","Passphrase (if encrypted)":"Heslová fráze (v případě, že je použito šifrování)","Passphrase changed":"Heslová fráze změněna","Passphrases are not matching":"Zadání heslové fráze se neshodují","Passphrases do not match":"Zadání heslové fráze se neshodují","Password":"Heslo","Patching files with local blocks …":"Opravování souborů pomocí místních bloků…","Path":"Popis umístění","Path not found":"Umístění nenalezeno","Path on server":"Popis umístění na serveru","Path or subfolder in the bucket":"Umístění nebo podsložka v „nádobě“ (bucket)","Pause":"Pozastavit","Pause after startup or hibernation":"Pozastavit po spuštění nebo hibernaci","Pause options":"Předvolby pozastavení","Permissions":"Přístupová práva","Pick location":"Vyberte umístění","Point to your backup files and restore from there":"Nasměrujte na soubory se zálohou a obnovte odsud","Port":"Port","Prevent tray icon automatic log-in":"Zabránit automatickému přihlašování ikony v oznamovací oblasti","Previous":"Předchozí","Progress:":"Postup:","ProjectID is optional if the bucket exist":"Pokud „nádoba“ (bucket) existuje, je identifikátor projektu (ProjectID) nepovinný","Proprietary":"Proprietární","Purge Phase":"Fáze trvalého mazání","Purging files complete!":"Trvalé smazání souborů dokončeno!","Purging files …":"Trvalé vymazávání souborů…","Rebuilding local database …":"Znovuvytváření místní databáze…","Recreate (delete and repair)":"Vytvořit znovu (smazat a opravit)","Recreate Database Phase":"Fáze znovuvytváření databáze","Recreating database …":"Znovuvytváření databáze…","Registering temporary backup …":"Registrace dočasné zálohy…","Relative paths not allowed":"Vztažené (relativní) popisy umístění není možné použít","Reload":"Načíst znovu","Remote":"Vzdálené","Remote Path":"Vzdálené umístění","Remote Repository":"Vzdálený repozitář","Remote path":"Vzdálené umístění","Remote repository":"Vzdálený repozitář","Remote volume size":"Velikost vzdáleného svazku","Remove":"Odebrat","Remove option":"Odebrat volbu","Removed files":"Odebrané soubory","Repair":"Opravit","Repair Phase":"Fáze oprav","Repairing database …":"Oprava databáze…","Repeat Passphrase":"Zopakování heslové fráze","Reporting:":"Hlášení:","Reset":"Resetovat","Restore":"Obnovit","Restore complete!":"Obnovení dokončeno!","Restore files":"Obnovit soubory","Restore files …":"Obnovit soubory…","Restore from":"Obnovit z","Restore from backup configuration":"Obnovit nastavení ze zálohy","Restore options":"Volby obnovení","Restore read/write permissions":"Obnovit práva pro čtení/zápis","Restored Files":"Obnovené soubory","Restored Folders":"Obnovené složky","Restored Symlinks":"Obnovené symbolické odkazy","Restoring files …":"Obnovování souborů…","Resume":"Pokračovat","Rewritten File Lists":"Seznamy přepsaných souborů","Run again every":"Spustit znovu každou","Run now":"Spustit nyní","Running commandline entry":"Spuštěná položka příkazového řádku","Running task:":"Spuštěná úloha:","Running …":"Spuštěné…","S3 Compatible":"Kompatibilní s S3","Same as the base install version: {{channelname}}":"Stejné jako základní nainstalovaná verze: {{channelname}}","Sat":"So","Satellite":"Satelit","Save":"Uložit","Save and repair":"Uložit a opravit","Save different versions with timestamp in file name":"Uložit různé verze odlišené časovou značkou v názvu souboru","Save immediately":"Okamžitě uložit","Scanning existing files …":"Skenování existujících souborů…","Scanning for local blocks …":"Skenování místních bloků…","Schedule":"Plán","Search":"Hledat","Search for files":"Hledat soubory","Seconds":"Sekund","Select a log level and see messages as they happen:":"Vyberte úroveň podrobnosti zaznamenávaných událostí a sledujte zprávy:","Select files":"Vybrat soubory","Server":"Server","Server and port":"Server a port","Server hostname or IP":"Název nebo IP adresa serveru","Server is currently paused,":"Server je nyní pozastavený,","Server is currently paused, do you want to resume now?":"Server je nyní pozastavený, chcete ho nyní znovu spustit?","Server password":"Heslo serveru","Server paused":"Server pozastaven","Server state properties":"Vlastnosti stavu serveru","Settings":"Nastavení","Show":"Zobrazit","Show advanced editor":"Zobrazit pokročilý editor","Show hidden folders":"Zobrazit skryté složky","Show log":"Zobrazit záznam událostí (log)","Show log …":"Zobrazit záznam událostí (log)…","Show treeview":"Zobrazit stromový pohled","Sia server password":"Heslo Sia serveru","Smart backup retention":"Chytrá doba uchovávání záloh","Some OpenStack providers allow an API key instead of a password and tenant name":"Někteří poskytovatelé OpenStack umožňují použití klíče k API namísto hesla a jména nájemníka (tenant)","Some S3 providers might only be compatible with a certain client library":"Někteří S3 poskytovatelé mohou být kompatibilní pouze s některými klientskými knihovnami","Source Data":"Zdrojová data","Source Files":"Zdrojové soubory","Source data":"Zdrojová data","Source folders":"Zdrojové složky","Source:":"Zdroj:","Specific builds for developers only. Not for use with important data.":"Konkrétní sestavení určená pouze pro vývojáře. Nepoužívejte pro důležitá data.","Standard protocols":"Standardní protokoly","Start":"Začátek","Starting backup …":"Spouštění zálohy…","Starting restore …":"Spouštění obnovení…","Starting the restore process …":"Spouštění procesu obnovení…","Stop after the current file":"Zastavit po stávajícím souboru","Stop running backup":"Zastavit probíhající zálohu","Stop running task":"Zastavit probíhající úlohu","Stopping after the current file:":"Zastavování pro stávajícím souboru:","Stopping task:":"Zastavování úlohy:","Storage Type":"Typ úložiště","Storage class":"Třída úložiště","Storage class for creating a bucket":"Třída úložiště pro vytváření „nádoby“ (bucket)","Stored":"Uloženo","Strong":"Silné","Success":"Úspěch","Sun":"Ne","Symbolic link":"Symbolický odkaz","System Files":"Systémové soubory","System default ({{levelname}})":"Systémové výchozí ({{levelname}})","System files":"Systémové soubory","System info":"Informace o systému","System properties":"Vlastnosti systému","TByte":"TB","TByte/s":"TB/s","Task is running":"Úloha je spuštěná","Temporary Files":"Dočasné soubory","Temporary files":"Dočasné soubory","Test Phase":"Fáze zkoušení","Test connection":"Vyzkoušet spojení","Testing permissions …":"Zkoušení přístupových práv…","Testing …":"Testování…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Kolonka „{{fieldname}}“ obsahuje neplatný znak: {{character}} (hodnota: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Záloha chybí, byla smazána?","The backup was temporary and does not exist anymore, so the log data is lost":"Záloha byla dočasná a už neexistuje, takže data záznamu událostí jsou ztracena","The bucket name should be all lower-case, convert automatically?":"Název nádoby by měl být malými písmeny, převést automaticky?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Nastavení by měla být uchovávána bezpečně. Opravdu chcete uložit nešifrovaný soubor obsahující vaše hesla?","The dark theme (by Michal)":"Tmavé téma vzhledu (od Michala)","The default blue on white theme (by Alex)":"Výchozí téma vzhledu modrá na bílé (od Alexe)","The folder {{folder}} does not exist.\nCreate it now?":"Složka {{folder}} neesxistuje.\nVytvořit nyní?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Klíč stroje se změnil, zkontrolujte se správcem serveru zda je správný, protože byste mohli být obětí útoku typu člověk uprostřed (man-in-the-midle).\n\nChcete NAHRADIT STÁVAJÍCÍ klíč stroje \"{{prev}}\" NAHLÁŠENÝM klíčem stroje: {{key}}?","The passwords do not match":"Zadání hesla se neshodují","The path does not appear to exist, do you want to add it anyway?":"Popisované umístění zdá se neexistuje, přejete si ho přidat i tak?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Dané umístění nekončí na znak „{{dirsep}}“, což znamená, že jste zahrnuli soubor, ne složku.\n\nChcete zahrnout daný soubor?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Je třeba, aby se jednalo o úplný popis umístění, tj. aby začínal dopředným lomítkem „/“","The region parameter is only applied when creating a new bucket":"Parametr region je použit pouze při vytváření nové „nádoby“ (bucket)","The region parameter is only used when creating a bucket":"Parametr region je použit pouze při vytváření „nádoby“ (bucket)","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certifikát serveru se nepodařilo ověřit.\nChcete schválit SSL certifikát s otiskem: {{hash}}?","The storage class affects the availability and price for a stored file":"Třída úložiště ovlivňuje dostupnost a cenu za uložení souboru","The target folder contains encrypted files, please supply the passphrase":"Cílová složka obsahuje zašifrované soubory, zadejte heslovou frázi","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Uživatel má příliš vysoká přístupová práva. Chcete vytvořit nového uživatele s právy omezenými pouze na vybraný popis umístění?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Tato záloha byla vytvořena na jiném operačním systému. Obnovení souborů bez zadání cílové složky může způsobit, že soubory budou obnoveny do neočekávaných míst. Opravdu chcete pokračovat bez zvolení cílové složky?","This month":"Tento měsíc","This week":"Tento týden","Throttle settings":"Nastavení přiškrcování","Thu":"Čt","Time":"Čas","To File":"Do souboru","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Svůj úmysl smazat všechny vzdálené soubory pro „{{name}}“ potvrďte opsáním níže uvedeného slova ","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pro exportování bez heslové fráze odškrtněte „Šifrovat soubor“","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Z důvodu prevence různým útokům prostřednictvím DNS, Duplicati omezuje názvy strojů, kterým je umožněn přístup na ty, vypsané zde. Přímý přístup na IP adresu a localhost je umožněn vždy. Je možné zadat vícero názvů strojů, oddělovaných středníkem. Pokud je některý z názvů povolených strojů hvězdička (*), je přístup umožněn ze všech strojů a tato funkce je vypnuta. Pokud kolonka není vyplněna, je umožněn přístup pouze na IP adresu a localhost.","Today":"Út","Trust host certificate?":"Důvěřovat certifikátu stroje?","Trust server certificate?":"Důvěřovat certifikátu serveru?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Vyzkoušejte nové funkce, na kterých pracujeme. V současnosti nejstabilnější dostupná verze. Pořádně si vyzkoušejte obnovu dat, než toto použijete v produkčních prostředích.","Tue":"Út","Type passphrase here.":"Sem zadejte heslovou frázi.","Type to highlight files":"Soubory zvýrazňujte psaním","Unknown backup size and versions":"Neznámá velikost a verze databáze","Until resumed":"Dokud není pokračováno","Update channel":"Aktualizační kanál","Update failed:":"Aktualizace se nezdařila:","Updating with existing database":"Aktualizace se stávající databází","Uploaded files":"Nahrané soubory","Uploading verification file …":"Nahrávání ověřovacího souboru…","Usage statistics":"Statistiky využití","Usage statistics, warnings, errors, and crashes":"Statistiky využití, varování, chyby a pády","Use SSL":"Použít SSL","Use existing database?":"Použít existující databázi?","Use weak passphrase":"Použít slabou heslovou frázi","Useless":"Nepoužitelné","User data":"Uživatelská data","User domain name":"Název domény uživatele","User has too many permissions":"Uživatel má příliš mnoho oprávnění","User interface settings":"Nastavení uživatelského rozhraní","Username":"Uživatelské jméno","Vacuuming database …":"Úklid v databázi…","Validating …":"Ověřování…","Verifications":"Ověřování","Verify files":"Ověřit soubory","Verifying answer":"Ověřování odpovědi","Verifying backend data …":"Ověřování dat podpůrné vrstvy (backend)…","Verifying files …":"Ověřování správnosti souborů…","Verifying remote data …":"Ověřování správnosti dat na protějšku…","Verifying restored files …":"Ověřování obnovených souborů…","Verifying …":"Ověřování…","Version ID":"Identif. verze","Very strong":"Velmi silné","Very weak":"Velmi slabé","Visit us on":"Navštivte nás na","WARNING: This will prevent you from restoring the data in the future.":"VAROVÁNÍ: toto zabrání v budoucnu obnovovat data!","Waiting for task to begin":"Čekání na zahájení úlohy","Waiting for upload to finish …":"Čeká se na dokončení nahrání…","Warnings, errors and crashes":"Varování, chyby a pády","We recommend that you encrypt all backups stored outside your system":"Doporučujeme šifrovat všechny zálohy, které jsou ukládány mimo váš stroj","Weak":"Slabé","Weak passphrase":"Slabá heslová fráze","Wed":"St","Weeks":"Týdny","Where do you want to restore from?":"Odkud chcete obnovit?","Where do you want to restore the files to?":"Kam chcete soubory obnovit?","Years":"Let","Yes":"Ano","Yes, I have stored the passphrase safely":"Ano, heslovou frázi mám bezpečně uloženou","Yes, I understand the risk":"Ano, rozumím riziku","Yes, I'm brave!":"Ano, mám odvahu!","Yes, please break my backup!":"Ano, chci rozbít své zálohy!","Yesterday":"Včera","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Měníte umístění databáze pryč z existující databáze.\nOpravdu je to to, co chcete?","You are currently running {{appname}} {{version}}":"Nyní provozujete {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Změnili jste režim šifrování. To může něco rozbít. Doporučujeme namísto toho vytvořit novou zálohu","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Změnili jste heslovou frázi, což není podporováno. Doporučujeme namísto toho vytvořit novou zálohu.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Zvolili jste že záloha nebude šifrována. Šifrování je doporučeno pro veškerá data ukládaná na vzdálený server.","You have chosen to restore to a new location, but not entered one":"Zvolili jste obnovu do nového umístění, ale nezadali jste ho","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vytvořili jste odolnou heslovou frázi. Tu si dobře uschovejte, protože v případě její ztráty data nebude možné obnovit.","You must choose at least one source folder":"Je třeba zvolit alespoň jednu zdrojovou složku","You must enter a domain name to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat doménový název","You must enter a name for the backup":"Je třeba zadat název zálohy","You must enter a passphrase or disable encryption":"Buď je třeba zadat heslovou frázi nebo šifrování vypnout","You must enter a password to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat heslo","You must enter a positive number of backups to keep":"Je třeba zadat kladný počet záloh které uchovávat","You must enter a tenant (aka project) name to use v3 API":"Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba zadat název projektu (tenant)","You must enter a valid duration for the time to keep backups":"Je třeba zadat platnou dobu po kterou ponechávat zálohy","You must enter a valid retention policy string":"Je třeba zadat platný řetězec zásady doby uchovávání záloh","You must fill in the password":"Je třeba vyplnit heslo","You must fill in the server name or address":"Je třeba vyplnit název nebo adresu serveru","You must fill in the username":"Je třeba vyplnit uživatelské jméno","You must fill in {{field}}":"Je třeba vyplnit kolonku {{field}}","You must select or fill in the AuthURI":"Je třeba vybrat nebo vyplnit AuthURI","You must select or fill in the server":"Je třeba vybrat nebo vyplnit server","You must specify a path":"Je třeba zadat popis umístění","Your files and folders have been restored successfully.":"Soubory a složky byly úspěšně obnoveny.","Your passphrase is easy to guess. Consider changing passphrase.":"Vaše heslová fráze je snadno uhodnutelná. Zvažte volbu jiné.","bucket/folder/subfolder":"nadoba/slozka/podslozka","byte":"B","byte/s":"B/s","custom":"vlastní","resume now":"pokračovat nyní","unless you are explicitly specifying --group-id":"pokud výslovně neuvedete --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} bylo vyvinuto hlavně {{dev1}} a {{dev2}}. {{appname}} je možné si stáhnout z {{websitename}}. {{appname}} je šířeno pod licencí {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} souborů ({{size}}) zbývá {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verze","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verze","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzí","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzí"],"{{number}} Hour":"{{number}} hodin","{{number}} Hours":"{{number}} hodin","{{number}} Minutes":"{{number}} minut","{{time}} (took {{duration}})":"{{time}} (trvalo {{duration}})"}); + gettextCatalog.setStrings('da', {"- pick an option -":"- vælg indstilling -","...loading...":"...indlæser...","API key":"API Key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Om","About {{appname}}":"Om {{appname}}","Access Key":"Access Key","Access denied":"Adgang nægtet","Access grant":"Adgang godkendt","Access to user interface":"Adgang til brugerflade","Account name":"Kontonavn","Add a new backup":"Tilføj en ny backup","Add a path directly":"Tilføj en sti","Add advanced option":"Tilføj en avanceret indstilling","Add backup":"Tilføj backup","Add filter":"Tilføj filter","Add path":"Tilføj sti","Added":"Tilføjet","Adjust bucket name?":"Tilpas bucket navnet?","Advanced Options":"Avancerede indstillinger","Advanced options":"Avancerede indstillinger","Advanced:":"Avanceret:","All Hyper-V Machines":"Alle Hyper-V maskiner","All Microsoft SQL Databases":"Alle Microsoft SQL databaser","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle brugsrapporter bliver sendt anonymt og indeholder ikke personlige oplysninger. De indeholder oplysninger om hardware, operativsystem, destinationstype, backup varighed, backup størrelse og lignende information. De indeholder ikke stier, filnavne, brugernavne, adgangskoder eller lignende følsom information.","Allow remote access (requires restart)":"Tillad fjernadgang (kræver genstart)","Allowed days":"Tilladte dage","An existing file was found at the new location":"En eksisterende fil blev fundet på den nye placering","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"En eksisterende fil blev funder på den nye placering.\nEr du sikker på at du vil have databasen til at pege på en eksisterende fil?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"En eksisterende lokal database for destinationen er fundet.\nHvis du genbruger databasen, kan du bruge både kommandolinie og serveren til at arbejde på samme destination.\n\nVil du bruge den eksisterende database?","Anonymous usage reports":"Anonyme brugsstatistiker","Applications":"Applikationer","As Command-line":"Som kommandolinie","AuthID":"AuthID","Authentication method":"Godkendelsesmetode","Authentication method ({{auth_method}})":"Godkendelsesmetode ({{auth_method}})","Authentication password":"Adgangskode til godkendelse","Authentication username":"Brugernavn til godkendelse","Autogenerated passphrase":"Autogenereret adgangssætning","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Tilbage","Backup complete!":"Backup fuldført!","Backup destination":"Backup destination","Backup location":"Backup placering","Backup retention":"Backup fastholdelse","Backup:":"Backup:","Beta":"Beta","Broken access":"Adgang defekt","Browse":"Gennemse","Browser default":"Browser standard","Bucket create location":"Bucket placering ved oprettelse","Bucket name":"Bucket navn","Bucket storage class":"Bucket storage class","Building list of files to restore …":"Opbygger liste af filer for gendannelse ...","Building partial temporary database …":"Bygger en midlertidig database ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ved at tillade fjernadgang vil serveren lytte efter forespørgsler fra en hver maskine på dit netværk. Hvis du slår denne indstilling til, så vær sikker på at computeren er på et sikkert netværk beskyttet af en firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Som standard vil systembakke-ikonet åbne brugerfladen med en token der låser applikationen op. Dette sikrer at du kan tilgå brugerfladen fra systembakke-ikonet, mens andre brugerkonti skal indtaste en adgangskode. Foretrækker du at skulle skrive adgangskoden, selv når du åbner via systembakke-ikonet, så slå denne indstilling til.","Cache Files":"Cache Filer","Canary":"Canary","Cancel":"Annuller","Cannot move to existing file":"Kan ikke flytte til eksisterende fil","Changelog":"Ændringslog","Changelog for {{appname}} {{version}}":"Ændringslog for {{appname}} {{version}}","Check failed:":"Kontrol fejlede:","Check for updates now":"Tjek for opdateringer nu","Checking for updates …":"Leder efter opdateringer ...","Chose a storage type to get started":"Valgte en destinationstype at komme i gang","Click the AuthID link to create an AuthID":"Click på AuthID linket for at oprettet et AuthID","Click to set throttle options":"Klik for at sætte hastigheds begrænsning","Client library to use":"Klient bibliotek som skal bruges","Commandline …":"Kommandolinie ...","Compact Phase":"Komprimeringsfase","Compact now":"Komprimer nu","Compacting remote data …":"Komprimerer data på destinationen ...","Complete log":"Samlet log","Completing backup …":"Fuldfører backup ...","Completing previous backup …":"Fuldfører forrige backup ...","Computer":"Computer","Configuration file:":"Konfigurationsfil:","Configuration:":"Konfiguration:","Configure a new backup":"Indstil en ny backup","Confirm delete":"Bekræft sletning","Confirm encryption passphrase":"Bekræft krypteringskoden","Confirm passphrase":"Bekræft adgangskode","Confirmation required":"Bekræftelse kræves","Connect":"Forbind","Connect now":"Forbind nu","Connecting to server …":"Forbinder til server ...","Connection lost":"Forbindelse mistet","Connection worked!":"Forbindelsen virkede!","Container name":"Container navn","Container region":"Container region","Continue":"Fortsæt","Continue without encryption":"Fortsæt uden kryptering","Copied!":"Kopieret!","Copy":"Kopier","Copy Destination URL to Clipboard":"Kopier URL-destinationsadressen til udklipsholder","Copy failed. Please manually copy the URL":"Kopiering mislykkedes. Kopier venligst URL-adressen manuelt","Core options":"Grund indstillinger","Counting ({{files}} files found, {{size}})":"Tæller ({{files}} filer fundet, {{size}})","Crashes only":"Kun nedbrud","Create bug report …":"Opret fejlrapport ...","Create folder?":"Opret mappe?","Created new limited user":"Opret en ny begrænset bruger","Creating bug report …":"Opretter fejlrapport ...","Creating new user with limited access …":"Opretter en ny bruger med begrænset adgang ...","Creating target folders …":"Opretter destinations mapper ...","Creating temporary backup …":"Opretter en midlertidig backup ...","Current action:":"Nuværende handling:","Current file:":"Nuværende fil:","Current version is {{versionname}} ({{versionnumber}})":"Nuværende version er {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Brugerdefineret S3 endpoint","Custom Satellite":"Brugerdefineret Satellit","Custom Satellite ({{satellite}})":"Brugerdefineret Satellit ({{satellite}})","Custom authentication url":"Brugerdefineret godkendelses url","Custom backup retention":"Brugerdefineret backup fastholdelse","Custom location ({{server}})":"Brugerdefineret placering ({{server}})","Custom region for creating buckets":"Brugerdefineret region for at oprette buckets","Custom region value ({{region}})":"Brugerdefineret regions værdi ({{region}})","Custom server url ({{server}})":"Brugerdefineret server url ({{server}})","Custom storage class ({{class}})":"Brugerdefineret storage class ({{klasse}})","Database …":"Database ...","Days":"Dage","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standard ekskluderinger","Default options":"Standardindstillinger","Delete":"Slet","Delete Phase (Old Backup Versions)":"Slettefase (Gamle backup-versioner)","Delete backup":"Slet backup","Delete backups that are older than":"Slet sikkerhedskopier, der er ældre end","Delete local database":"Slet lokal database","Delete remote files":"Slette filer fra destinationen","Delete the local database":"Slet den lokale database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Slet {{filecount}} filer ({{filesize}}) fra destinationen?","Delete …":"Slet ...","Deleted":"Slettet","Deleted Versions":"Slettede versioner","Deleted files":"Slettede filer","Deleting remote files …":"Sletter filer fra destinationen ...","Deleting unwanted files …":"Sletter uønskede filer ...","Description (optional)":"Beskrivelse (valgfrit)","Description:":"Beskrivelse:","Desktop":"Skrivebord","Destination":"Destination","Destination path":"Destinations sti","Disabled":"Deaktiveret","Dismiss":"Afvis","Dismiss all":"Afvis alle","Display and color theme":"Visning og farvevalg","Do you really want to delete the backup: \"{{name}}\" ?":"Vil du virkelig slette backupen: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Vil du virkelig slette den lokale database for: {{navn}}","Done":"Færdig","Download":"Download","Downloaded files":"Downloadede filer","Downloading files …":"Downloader filer ...","Downloading update…":"Downloader opdatering ...","Duplicate option {{opt}}":"Dublet af indstilling {{opt}}","Duplicati Website":"Duplicati hjemmeside","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati kører når startet, men forbliver i pause-tilstand. Duplicati optager minimale systemressourcer og ingen backups vil køre.","Duration":"Varighed","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Hver backup har en lokal database tilknyttet, som gemmer information om data på fjerndestinationen lokalt på maskinen.\nNår du sletter en backup kan du også slette den lokale database uden at dette påvirker muligheden for at gendanne filer.\nHvis du bruger den lokale database til at køre backup via kommandolinien skal du beholde databasen.","Edit as list":"Rediger som liste","Edit as text":"Rediger som tekst","Edit …":"Rediger ...","Encrypt file":"Krypter fil","Encryption":"Kryptering","Encryption changed":"Kryptering ændret","Encryption passphrase":"Krypteringssætning","End":"Afsluttet","Enter URL":"Indtast URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Indtast manuelt en fastholdelsesstrategi. Variablerne er D/W/Y for henholdsvis dage/uger/år or U for ubegrænset. Syntaksen er: 7D:1D,4W:1W,36M:1M. Dette eksempel fastholder én backup for hver af de næste 7 dage, én for hver af de næste 4 uger og én for hver af de næste 36 måneder. Det samme kan også opnås ved at skrive 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Indtast adgangssætning til backup, hvis defineret","Enter configuration details":"Indtast konfigurationsdetaljer","Enter encryption passphrase":"Indtast adgangssætning til kryptering","Enter expression here":"Indtast udtryk her","Enter the destination path":"Indtast destinations stien","Error":"Fejl","Error!":"Fejl!","Errors and crashes":"Fejl og nedbrud","Examined":"Undersøgt","Exclude":"Eksludér","Exclude directories whose names contain":"Ekskluder mapper hvor navnet indeholder","Exclude expression":"Excluder udtryk","Exclude file":"Excluder fil","Exclude file extension":"Ekskluder filendelse","Exclude files whose names contain":"Ekskluder filer hvor navnet indeholder","Exclude filter group":"Ekskluderings filter gruppe","Exclude folder":"Ekskluder mappe","Exclude regular expression":"Ekskluder regulært udtryk","Existing file found":"Eksisterende fil fundet","Experimental":"Eksperimental","Export":"Eksporter","Export backup configuration":"Eksporter backup konfiguration","Export configuration":"Eksporter konfiguration","Export passwords":"Eksportér adgangskoder","Export …":"Eksport ...","Exporting …":"Eksporterer ...","External link":"Eksternt link","FTP (Alternative)":"FTP (alternativ)","Failed to build temporary database: {{message}}":"Kunne ikke bygge midlertidig database: {{message}}","Failed to connect:":"Kunne ikke forbinde:","Failed to connect: {{message}}":"Kunne ikke forbinde: {{message}}","Failed to delete:":"Kunne ikke slette:","Failed to fetch path information: {{message}}":"Kunne ikke hente sti-information: {{message}}","Failed to find backup:":"Kunne ikke finde backup:","Failed to read backup defaults:":"Kunne ikke læse backup standardværdier:","Failed to restore files: {{message}}":"Kunne ikke gendanne filer: {{message}}","Failed to save:":"Kunne ikke gemme:","Fetching path information …":"Henter information om stier ...","File":"Fil","Files larger than:":"Filer større end:","Filters":"Filtre","Finished!":"Færdig!","First run setup":"Førstegangsopsætning","Folder":"Mappe","Folder path":"Mappe sti","Fri":"Fre","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Projekt ID","General":"Generelt","General backup settings":"Generelle backup indstillinger","General options":"Generelle indstillinger","Generate":"Generér","Generate IAM access policy":"Generér IAM access policy","Getting file versions …":"Henter fil versioner ...","Group email":"Gruppe email","Hidden files":"Skjulte filer","Hide":"Skjul","Hide hidden folders":"Skjul skjulte filer","Home":"Hjem","Hostnames":"Hostnavne","Hours":"Timer","How do you want to handle existing files?":"Hvordan vil du håndtere eksisterende filer?","Hyper-V Machine":"Hyper-V maskine","Hyper-V Machine:":"Hyper-V maskine:","Hyper-V Machines":"Hyper-V maskiner","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Hvis der ikke blev kørt på det angivne tidspunkt, vil jobbet køre så hurtigt som muligt.","If at least one newer backup is found, all backups older than this date are deleted.":"Hvis der findes mindst en nyere sikkerhedskopi, slettes alle backups, der er ældre end denne dato.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Hvis du ikke indtaster en sti, vil alle filer blive gemt i login mappen.\nEr du sikke på at det er det du vil gøre?","If you do not enter an API Key, the tenant name is required":"Hvis du ikke indtaster en API key, skal du angive tenant navnet","Import":"Importér","Import Destination URL":"Importer destinations URL","Import backup configuration":"Importer backup konfiguration","Import from a file":"Importer fra en fil","Import metadata":"Importer metadata","Importing …":"Importerer ...","Include a file?":"Inkluder en fil?","Include expression":"Inkluder udtryk","Include regular expression":"Inkluder regulært udtryk","Incorrect answer, try again":"Forkert svar, prøv igen","Individual builds for developers only. Not for use with important data.":"Individuelle versioner kun for udviklere. Bør ikke bruges med vigtig data.","Information":"Information","Invalid characters in path":"Ugyldige tegn i stien","Invalid retention time":"Ugyldig bevaringstid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Det er muligt at oprette forbindelse til visse FTP servere uden adgangskode.\nEr du sikker på din FTP-server understøtter login uden adgangskode?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Gem et bestemt antal backups","Keep all backups":"Gem alle backups","Keystone API version":"Keystone API version","Language in user interface":"Sprog i brugergrænsefladen","Last month":"Sidste måned","Last successful backup:":"Sidst gennemførte backup:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Sidste gennemførte gendannelse: {{time}} (took {{duration || '0 seconds'}})","Latest":"Nyeste","Libraries":"Biblioteker","Listing backup dates …":"Noterer backup datoer...","Listing remote files for purge …":"Noterer filer fra destinationen til rensning ...","Listing remote files …":"Noterer filer fra destinationen ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Indlæs konfiguration fra en eksporteret fil eller en pladsudbyder","Load destination from an exported job or a storage provider":"Indlæs destination fra en eksporteret fil eller en pladsudbyder","Load older data":"Indlæs ældre data","Loading …":"Indlæser ...","Local database path:":"Lokal database sti:","Local repository":"Lokal fortegnelse","Local storage":"Local opbevaring","Location":"Placering","Location where buckets are created":"Placering hvor buckets bliver oprettet","Log data for {{Backup.Backup.Name}}":"Logdata for {{Backup.Backup.Name}}","Log data from the server":"Logdata fra serveren","Log out":"Log ud","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Vedligehold","Manually type path":"Indtast en sti manuelt","Max download speed":"Max downloadhastighed","Max upload speed":"Maks uploadhastighed","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Databaser","Minimum redundancy":"Mindste tilladte redundans","Minimum redundancy is 1.0":"Mindste redundans er 1.0","Minutes":"Minutter","Missing name":"Navn mangler","Missing passphrase":"Adgangssætning mangler","Missing sources":"Kilder mangler","Modified":"Ændret","Mon":"Man","Months":"Måneder","Move existing database":"Flyt eksisterende database","Move failed:":"Flytning fejlede:","My Documents":"Mine dokumenter","My Music":"Min musik","My Photos":"Mine foto","My Pictures":"Mine billeder","Name":"Navn","Never":"Aldrig","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nyt bruger navn er {{user}}.\nLoginoplysninger er opdateret til den nye begrænsede bruger","Next":"Næste","Next scheduled run:":"Næste planlagte kørsel:","Next scheduled task:":"Næste planlagte opgave:","Next task:":"Næste opgave:","Next time":"Næste tidspunkt","No":"Nej","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Intet certifikat har været anvendt før, kontroller venligst at nøglen er korrekt hos serveradministratoren: {{key}} \n\nVil du godkende den angivne nøgle?","No editor found for the "{{backend}}" storage type":"Ingen editor blev fundet for "{{backend}}" destinationen","No encryption":"Ingen kryptering","No items selected":"Ingen emner valgt","No items to restore, please select one or more items":"Ingen emner er valgt til gendannelse, vælg venligst en eller flere emner","No passphrase entered":"Ingen adgangssætning angivet","No scheduled tasks":"Ingen planlagte opgaver","Non-matching passphrase":"Uoverenstemmelse mellem adgangssætninger","None / disabled":"Ingen / deaktiveret","Not using encryption":"Uden kryptering","Nothing will be deleted. The backup size will grow with each change.":"Intet vil blive slettet. Backup størrelsen vokser med hver ændring.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Når der er flere backups end det angivne antal, slettes de ældste sikkerhedskopier.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Åbnet","Operating System":"Operativ System","Operation":"Operation","Operations:":"Operationer:","Optional authentication password":"Valgfri adgangskode til godkendelse","Optional authentication username":"Valgfrit brugernavn til godkendelse","Options":"Indstillinger","Original location":"Oprindelig placering","Others":"Andre","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Over tid vil backups blive slettet automatisk. Der vil forblive en backup for hver af de sidste 7 dage, hver af de sidste 4 uger, hver af de sidste 12 måneder. Der vil altid være mindst en tilbageværende backup.","Overwrite":"Overskriv","Passphrase":"Adgangssætning","Passphrase (if encrypted)":"Adgangssætning (hvis krypteret)","Passphrase changed":"Adgangssætning ændret","Passphrases are not matching":"Adgangssætninger er ikke ens","Passphrases do not match":"Adgangssætninger er ikke identiske","Password":"Adgangskode","Patching files with local blocks …":"Opdaterer filer med lokale blokke ...","Path":"Sti","Path not found":"Stien blev ikke fundet","Path on server":"Sti på server","Path or subfolder in the bucket":"Sti eller undermappe i bucket","Pause":"Pause","Pause after startup or hibernation":"Pause efter start eller dvale","Pause options":"Pause indstillinger","Permissions":"Tilladelser","Pick location":"Vælg placering","Point to your backup files and restore from there":"Udpeg dine backup-filer og gendan fra dem","Port":"Port","Prevent tray icon automatic log-in":"Forhindre automatisk login-in fra system ikonet","Previous":"Forrige","Progress:":"Fremgang:","ProjectID is optional if the bucket exist":"ProjectID er valgfrit hvis bucket eksisterer","Proprietary":"Proprietære","Purge Phase":"Rensningsfase","Purging files complete!":"Rensning af filer gennemført!","Purging files …":"Fjerner filer ...","Rebuilding local database …":"Genopbygger lokal database ...","Recreate (delete and repair)":"Gendan (slet og reparer)","Recreate Database Phase":"Database gendannelsesfase ...","Recreating database …":"Gendanner database ...","Registering temporary backup …":"Registrerer midlertidig backup ...","Relative paths not allowed":"Relative stier er ikke tilladt","Reload":"Genindlæs","Remote":"Destination","Remote Path":"Destinations sti","Remote Repository":"Ekstern fortegnelse","Remote path":"Destinations sti","Remote repository":"Ekstern fortegnelse","Remote volume size":"Volume størrelse","Remove":"Fjern","Remove option":"Fjern indstilling","Removed files":"Fjernede filer","Repair":"Reparer","Repair Phase":"Reparationsfase","Repairing database …":"Reparere database ...","Repeat Passphrase":"Gentag adgangssætning","Reporting:":"Rapporterer:","Reset":"Nulstil","Restore":"Gendan","Restore complete!":"Gendannelse fuldført!","Restore files":"Gendan filer","Restore files …":"Gendan filer ...","Restore from":"Gendan fra","Restore from backup configuration":"Gendan fra konfiguration i backup","Restore options":"Indstillinger til gendannelse","Restore read/write permissions":"Gendan læse/skrive tilladelser","Restored Files":"Gendannede filer","Restored Folders":"Gendannede mapper","Restored Symlinks":"Gendannede Symlinks","Restoring files …":"Gendanner filer ...","Resume":"Genoptag","Rewritten File Lists":"Genskrevne fil-lister","Run again every":"Kør igen hver","Run now":"Kør nu","Running commandline entry":"Kører kommandolinie opgave","Running task:":"Kørende opgave:","Running …":"Kører ...","S3 Compatible":"S3 kompatibel","Same as the base install version: {{channelname}}":"Samme som grundinstallationsversionen: {{channelname}}","Sat":"Lør","Save":"Gem","Save and repair":"Gem og reparer","Save different versions with timestamp in file name":"Gem forskellige versioner med tidstempel i filnavnet","Save immediately":"Gem med det samme","Scanning existing files …":"Skanner eksisterende filer ...","Scanning for local blocks …":"Scanner for lokale blokke ...","Schedule":"Planlagt","Search":"Søg","Search for files":"Søg efter filer","Seconds":"Sekunder","Select a log level and see messages as they happen:":"Vælg et log niveau og se beskeder som de kommer:","Select files":"Vælg filer","Server":"Server","Server and port":"Server og port","Server hostname or IP":"Server navn eller IP","Server is currently paused,":"Serveren er sat på pause.","Server is currently paused, do you want to resume now?":"Serveren er sat på pause, vil du genoptage med det samme?","Server password":"Server adgangskode","Server paused":"Server på pause","Server state properties":"Egenskaber for serveren","Settings":"Indstillinger","Show":"Vis","Show advanced editor":"Vis avanceret redigering","Show hidden folders":"Vis skjulte mapper","Show log":"Vis log","Show log …":"Vis log ...","Show treeview":"Vis træstruktur","Sia server password":"Sia server adgangskode","Smart backup retention":"Smart backupfastholdelse","Some OpenStack providers allow an API key instead of a password and tenant name":"Visse OpenStack udbydere tillader en API nøgle istedet for en adgangskode og et tenant navn","Source Data":"Kilde data","Source Files":"Kilde filer","Source data":"Kilde data","Source folders":"Kilde mapper","Source:":"Kilde:","Specific builds for developers only. Not for use with important data.":"Specifikke versioner kun til udviklere. Bør ikke bruges med vigtig data.","Standard protocols":"Standard protokoller","Start":"Start","Starting backup …":"Starter backup ...","Starting restore …":"Starter gendannelse ...","Starting the restore process …":"Starter gendannelses processen ...","Stop after the current file":"Stop efter den nuværende fil","Stop running backup":"Stop den kørende backup","Stop running task":"Stop den kørende opgave","Stopping after the current file:":"Stopper efter den nuværende fil:","Stopping task:":"Stopper opgave:","Storage Type":"Opbevaringstype","Storage class":"Opbevaringsklasse","Storage class for creating a bucket":"Opbevaringsklasse når der oprettes en bucket","Stored":"Gemt","Strong":"Stærk","Success":"Succes","Sun":"Søn","Symbolic link":"Symbolsk kæde","System Files":"System Filer","System default ({{levelname}})":"System standard ({{levelname}})","System files":"System filer","System info":"System info","System properties":"System egenskaber","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Opgave kører","Temporary Files":"Midlertidige Filer","Temporary files":"Midlertidige filer","Test Phase":"Testfase","Test connection":"Test forbindelse","Testing permissions …":"Tester tilladelser ...","Testing …":"Tester ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"'{{fieldname}}' feltet indeholder ugyldige karakterer: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Backup mangler, er den blevet slettet?","The backup was temporary and does not exist anymore, so the log data is lost":"Backup var midlertidig og eksisterer ikke længere, log data er dermed tabt","The bucket name should be all lower-case, convert automatically?":"Bucket navnet bør være med små bogstaver, konverter automatisk?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Opsætningen bør holdes hemmelig. Er du sikker på at du vil gemme en ikke-krypteret fil der indeholder dine adgangskoder?","The dark theme (by Michal)":"Mørke farver (af Michal)","The default blue on white theme (by Alex)":"Standard blå på hvid (af Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Mappen {{folder}} eksisterer ikke.\nOpret den nu?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Nøglen fra værten er ændret, kontroller venligst med server administratoren om dette er korrekt, ellers kan du være offer for et MAN-IN-THE-MIDDLE angreb.\n\nVil du ERSTATTE din NUVÆRENDE værtsnøgle \"{{prev}}\" med den RAPPORTEREDE værtsnøgle: {{key}}?","The passwords do not match":"Adgangskoderne er ikke ens","The path does not appear to exist, do you want to add it anyway?":"Stien ser ikke ud til at findes, vil du tilføje den alligevel?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Stien slutter ikke med '{{dirsep}}' tegnet, hvilket betyder at du inkluderer en file og ikke en mappe.\n\nVil du inkludere den valgte fil?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Stien skal være en absolut sti, altså skal den starte med '/'","The region parameter is only applied when creating a new bucket":"Regionsparameteren anvendes kun når der oprettes en ny bucket","The region parameter is only used when creating a bucket":"Regionsparameteren bruges kun når der oprettes en ny bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Server certifikatet kunne ikke valideres.\nVil du godkende SSL certifikatet med dette hash: {{hash}}?","The storage class affects the availability and price for a stored file":"Opbevaringsklasen påvirker tilgængeligheden og prisen for en opbevaret fil","The target folder contains encrypted files, please supply the passphrase":"Destinationsmappen indeholder krypterede filer, angiv venligst adgangssætningen","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Brugeren har for mange tilladelser. Vil du oprette en ny begrænset bruger der kun har adgang til den valgte sti?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Denne backup blev oprettet på et andet operativsystem. Når der gendannes filer uden at angive en destination, kan disse blive oprettet på uventede placeringer. Er du sikker på at du vil fortsætte uden at vælge en destinationsmappe?","This month":"Denne måned","This week":"Denne uge","Throttle settings":"Indstillinger for hastighedsbegrænsning","Thu":"Tor","Time":"Tid","To File":"Til fil","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"For at bekræfte at du vil slette all fjernfiler til \"{{name}}\", indtast venligst det ord ud ser herunder","To export without a passphrase, uncheck the \"Encrypt file\" box":"For at eksportere uden en adgangsætning, fjern mærket ud for \"Krypter filen\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"For at forhindre forskellige DNS baserede angreb svarer Duplicati kun på hostnavne der er angivet her. Direkte adgang over IP eller localhost er altid tilladt. Flere hostnavne kan angives med en semikolonseparator. Hvis nogen af de tilladte hostnavne er en stjerne (*), vil alle hostnavne være tilladt og denne indstilling slået fra. Hvis feltet er tomt vil kun IP addresse og localhost adgangvære tilladt.","Today":"I dag","Trust host certificate?":"Stol på værtscertifikatet?","Trust server certificate?":"Stol på server certifikatet?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Prøv nye funktioner vi arbejder på. Den mest stabile version tilgængelig på nuværende tidspunkt. Test gendannelse af data før du bruger dette i produktions miljøer.","Tue":"Tir","Type passphrase here.":"Indtast adgangssætning her.","Type to highlight files":"Skriv for at markere filer","Unknown backup size and versions":"Ukendt backup størrelse og versionsantal","Until resumed":"Indtil genoptaget","Update channel":"Opdateringskanal","Update failed:":"Opdatering fejlede:","Updating with existing database":"Opdaterer med eksisterende database","Uploaded files":"Uploadede filer","Uploading verification file …":"Uploader verifikationsfil ...","Usage statistics":"Brugsstatistik","Usage statistics, warnings, errors, and crashes":"Brugsstatistik, advarsler, fejl og nedbrud","Use SSL":"Brug SSL","Use existing database?":"Brug eksisterende database?","Use weak passphrase":"Brug svag adgangssætning","Useless":"Ubrugelig","User data":"Brugerdata","User domain name":"Bruger domæne navn","User has too many permissions":"Brugeren har for mange tilladelser","User interface settings":"Indstillinger til brugergrænseflade","Username":"Brugernavn","Vacuuming database …":"Støvsuger databasen ...","Validating …":"Validerer ...","Verifications":"Verificeringer","Verify files":"Verificer filer","Verifying answer":"Verificerer svar","Verifying backend data …":"Verificerer destinations data ...","Verifying files …":"Verificerer filer ...","Version ID":"Versions-id","Very strong":"Meget stærk","Very weak":"Meget svag","Visit us on":"Besøg os på","WARNING: This will prevent you from restoring the data in the future.":"ADVARSEL: Dette vil forhindre dig i at gendanne data i fremtiden.","Waiting for task to begin":"Venter på at opgaven starter","Warnings, errors and crashes":"Advarsler, fejl og nedbrud","We recommend that you encrypt all backups stored outside your system":"Vi anbefaler at du krypterer alle backups der er gemt uden for dit system","Weak":"Svag","Weak passphrase":"Svag adgangssætning","Wed":"Ons","Weeks":"Uger","Where do you want to restore from?":"Hvor vil du gerne gendanne fra?","Where do you want to restore the files to?":"Hvor vil du gendanne filerne til?","Years":"År","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, jeg har opbevaret adgangssætningen sikkert","Yes, I understand the risk":"Ja, jeg forstår risikoen","Yes, I'm brave!":"Ja, jeg er modig!","Yes, please break my backup!":"Ja, ødelæg venligst min backup!","Yesterday":"I går","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Du er ved at ændre database stien væk fra en eksisterende database.\nEr du sikker på at det er det du vil?","You are currently running {{appname}} {{version}}":"Du kører med {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Du har skiftet krypteringsmetode. Dette kan ødelægge ting. Du opfordres til at oprette en ny backup i stedet.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Du har skiftet adgangssætningen, hvilket ikke understøttes. Du opfordres til at oprette en ny backup i stedet.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Du har valgt at undlade at kryptere din backup. Kryptering anbefales for alt data der gemmes på en fjerndestination.","You have chosen to restore to a new location, but not entered one":"Du har valgt at gendanne til en ny placering, men ikke angivet en","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Du har genereret en stærk adgangssætning. Sørg for, at du har en sikker kopi, da data ikke kan gendannes, hvis du mister adgangssætningen.","You must choose at least one source folder":"Du skal vælge mindst en kilde mappe","You must enter a domain name to use v3 API":"Du er nødt til at angive et domæne navn for at bruge v3 API'en","You must enter a name for the backup":"Du skal angive et navn for denne backup","You must enter a passphrase or disable encryption":"Du skal indtaste en adgangssætning eller fravælge kryptering","You must enter a password to use v3 API":"Du skal angive en adgangskode for at bruge v3 API'en","You must enter a positive number of backups to keep":"Du skal indtaste et positivt antal backups der skal bevares","You must enter a tenant (aka project) name to use v3 API":"Du er nødt til at angive et tenant (projekt) navn for at bruge v3 API'en","You must enter a valid duration for the time to keep backups":"Du skal angive en gyldig periode som backups gemmes i","You must fill in the password":"Du skal angive en adgangskode","You must fill in the server name or address":"Du skal angive server navnet eller adressen","You must fill in the username":"Du skal angive et brugernavn","You must fill in {{field}}":"Du skal udfylde {{field}}","You must select or fill in the AuthURI":"Du skal vælge eller udfylde AuthURI","You must select or fill in the server":"Du skal vælge eller indtaste server navnet","You must specify a path":"Du skal angive en sti","Your files and folders have been restored successfully.":"Dine filer og mapper blev gendannet korrekt.","Your passphrase is easy to guess. Consider changing passphrase.":"Din kodesætning er let at gætte. Overvej at skifte den.","bucket/folder/subfolder":"buvket/mappe/undermappe","byte":"byte","byte/s":"byte/s","custom":"tilpasset","resume now":"genoptag nu","unless you are explicitly specifying --group-id":"Medmindre du eksplicit angiver --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} er primært udviklet af {{dev1}} og {{dev2}}. {{appname}} kan downloades fra {{websitename}}. {{appname}} er licenseret med {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} filer ({{size}}) tilbage {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versioner"],"{{number}} Hour":"{{number}} Timer","{{number}} Hours":"{{number}} Timer","{{number}} Minutes":"{{number}} Minutter","{{time}} (took {{duration}})":"{{time}} (varighed: {{duration}})"}); + gettextCatalog.setStrings('de', {"- pick an option -":"- Option auswählen -","...loading...":"...laden...","API key":"API-Key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Über","About {{appname}}":"Über {{appname}}","Access Key":"Zugriffsschlüssel","Access denied":"Zugriff verweigert","Access grant":"Zugriffs-Grant","Access to user interface":"Zugriff auf die Benutzeroberfläche","Account name":"Kontoname","Add a new backup":"Neues Backup hinzufügen","Add a path directly":"Pfad direkt eingeben","Add advanced option":"Option für Profis hinzufügen","Add backup":"Sicherung hinzufügen","Add filter":"Filter hinzufügen","Add path":"Pfad hinzufügen","Added":"Hinzugefügt","Adjust bucket name?":"Bucket-Name anpassen?","Advanced Options":"Optionen für Profis","Advanced options":"Optionen für Profis","Advanced:":"Für Profis:","All Hyper-V Machines":"Alle Hyper-V Maschinen","All Microsoft SQL Databases":"Alle Microsoft SQL-Datenbanken","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle Nutzungsberichte werden anonym verschickt und enthalten keine personenbezogenen oder personenbeziehbare Daten. Sie enthalten Daten über Hardware, Betriebssystem, das verwendete Backend, die Sicherungsdauer, die Gesamtgröße der Sicherungen und ähnliche Daten. Sie enthalten NICHT Pfade, Dateinamen, Benutzernamen, Passwörter oder andere sensible Informationen.","Allow remote access (requires restart)":"Fernzugriff erlauben (Neustart notwendig)","Allowed days":"Erlaubte Tage","An existing file was found at the new location":"An dem angegebenen Ort wurde eine bereits vorhandene Datenbank gefunden.","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Eine vorhandene Datenbank wurde gefunden.\nSoll diese Datenbank von nun an verwendet werden?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Eine lokale Datenbank für den Onlinespeicher wurde gefunden.\nMit dieser Datenbank können GUI und Kommandozeile auf dem gleichen Onlinespeicher arbeiten.\n\nSoll die lokale Datenbank genutzt werden?","Anonymous usage reports":"Anonyme Nutzungsberichte","Applications":"Anwendungen","As Command-line":"als Befehl für Kommandozeile","AuthID":"AuthID","Authentication method":"Authentifizierungs-Methode","Authentication method ({{auth_method}})":"Authentifizierungs-Methode ({{auth_method}})","Authentication password":"Passwort für Anmeldung","Authentication username":"Benutzername für Anmeldung","Autogenerated passphrase":"Automatisch generierte Passphrase","B2 Application ID":"B2-Anwendungs-ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Zurück","Backup complete!":"Sicherung abgeschlossen!","Backup destination":"Sicherungsziel","Backup location":"Sicherungsort","Backup retention":"Sicherungsaufbewahrung","Backup:":"Sicherung:","Beta":"Beta","Broken access":"Defekter Zugriff","Browse":"Durchsuchen","Browser default":"Browserstandard","Bucket create location":"Bucket-Speicherort","Bucket name":"Bucket-Name","Bucket storage class":"Bucket Speicherklasse","Building list of files to restore …":"Erstellen einer Liste von wiederherzustellenden Dateien...","Building partial temporary database …":"Temporäre Datenbank wird erstellt...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Bei erlaubtem Fernzugriff wird der Server auf Anfragen von jedem Computer Ihres Netzwerks antworten. Stellen Sie bei Aktivierung dieser Option bitte sicher, dass Sie den Computer immer in einem sicheren, durch eine Firewall geschützten Netzwerk verwenden.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Standardmäßig öffnet das Taskleistensymbol den Zugriff auf die Benutzeroberfläche. Dies stellt sicher, dass Sie über das Taskleistensymbol auf die Benutzeroberfläche zugreifen können. Wenn Sie es bevorzugen, dass das Passwort auch beim Zugriff auf die Benutzeroberfläche über das Taskleistensymbol eingegeben werden muss, aktivieren Sie diese Option.","Cache Files":"Dateien cachen","Canary":"Canary","Cancel":"Abbrechen","Cannot move to existing file":"Verschieben auf bereits existierende Datei nicht möglich","Changelog":"Änderungsprotokoll","Changelog for {{appname}} {{version}}":"Änderungsprotokoll für {{appname}} {{version}}","Check failed:":"Prüfung fehlgeschlagen:","Check for updates now":"Aktualisierung suchen","Checking for updates …":"Aktualisierungen werden gesucht …","Chose a storage type to get started":"Wähle einen Speichertypen zum Starten","Click the AuthID link to create an AuthID":"Auf AuthID klicken um eine AuthID zu erstellen","Click to set throttle options":"Zum Einstellen der Drosselungsoptionen anklicken","Client library to use":"Zu benutzende Client Bibliothek","Commandline …":"Kommandozeile …","Compact Phase":"Komprimierungsphase","Compact now":"Sicherung komprimieren","Compacting remote data …":"Remotedaten verkleinern...","Complete log":"Vollständiges Protokoll","Completing backup …":"Sicherung wird abgeschlossen …","Completing previous backup …":"Vorherige Sicherung wird abgeschlossen …","Computer":"Computer","Configuration file:":"Konfigurationsdatei:","Configuration:":"Konfiguration:","Configure a new backup":"Neue Sicherung konfigurieren","Confirm delete":"Löschen bestätigen","Confirm encryption passphrase":"Verschlüsselungspassphrase bestätigen","Confirm passphrase":"Passphrase bestätigen","Confirmation required":"Bestätigung erfolderlich","Connect":"Verbinden","Connect now":"Jetzt verbinden","Connecting to server …":"Verbindung zum Server wird hergestellt …","Connection lost":"Verbindung verloren","Connection worked!":"Verbindung erfolgreich!","Container name":"Container-Name","Container region":"Container-Region","Continue":"Fortfahren","Continue without encryption":"Ohne Verschlüsselung fortfahren","Copied!":"Kopiert!","Copy":"Kopie","Copy Destination URL to Clipboard":"Ziel-URL in Zwischenablage kopieren","Copy failed. Please manually copy the URL":"Kopie fehlgeschlagen. Bitte kopiere die URL manuell","Core options":"Allgemeine Optionen","Counting ({{files}} files found, {{size}})":"Dateien ermitteln ({{files}} files found, {{size}})","Crashes only":"Nur Abstürze","Create bug report …":"Fehlerbericht erstellen...","Create folder?":"Ordner erstellen?","Created new limited user":"Nutzer mit eingeschränkten Rechten anlegen","Creating bug report …":"Fehlerbericht wird erstellt... ","Creating new user with limited access …":"Neuer Benutzer mit eingeschränktem Zugriff wird erstellt …","Creating target folders …":"Zielverzeichnisse erstellen... ","Creating temporary backup …":"Temporäre Sicherung wird erstellt …","Current action:":"Aktuelle Aktion:","Current file:":"Aktuelle Datei:","Current version is {{versionname}} ({{versionnumber}})":"Aktuelle Version: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Benutzerdefinierter S3 endpoint","Custom Satellite":"Benutzerdefinierter Satellit","Custom Satellite ({{satellite}})":"Benutzerdefinierter Satellit ({{satellite}})","Custom authentication url":"Benutzerdefinierte URL für Authentifizierung","Custom backup retention":"Benutzerdefinierte Sicherungsaufbewahrung","Custom location ({{server}})":"Benutzerdefinierter Standort ({{server}})","Custom region for creating buckets":"Benutzerdefinierte Region, um Buckets zu erstellen","Custom region value ({{region}})":"Benutzerdefinierter Wert für Region ({{region}})","Custom server url ({{server}})":"Benutzerdefinierte Server-URL ({{server}})","Custom storage class ({{class}})":"Benutzerdefinierte Speicher-Klasse ({{class}})","Database …":"Datenbank …","Days":"Tage","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standardmäßig ausgeschlossen","Default options":"Standard-Optionen","Delete":"Löschen","Delete Phase (Old Backup Versions)":"Phase Löschen (alte Sicherungsversionen)","Delete backup":"Sicherung löschen","Delete backups that are older than":"Sicherungen löschen, die älter sind als","Delete local database":"Lokale Datenbank löschen","Delete remote files":"Remote-Dateien löschen","Delete the local database":"Die lokale Datenbank löschen","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} Dateien ({{filesize}}) vom Remote-Speicher löschen?","Delete …":"Löschen …","Deleted":"Gelöscht","Deleted Versions":"Gelöschte Versionen","Deleted files":"Gelöschte Dateien","Deleting remote files …":"Remote-Dateien löschen... ","Deleting unwanted files …":"Unnötige Daten löschen... ","Description (optional)":"Beschreibung (optional)","Description:":"Beschreibung:","Desktop":"Desktop","Destination":"Ziel","Destination path":"Ziel-Pfad","Disabled":"Deaktiviert","Dismiss":"Verwerfen","Dismiss all":"Alles ausblenden","Display and color theme":"Darstellung und Farbthema","Do you really want to delete the backup: \"{{name}}\" ?":"Möchten Sie die Sicherung wirklich löschen: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Möchten Sie die lokale Datenbank wirklich löschen für: {{name}}","Done":"Fertig","Download":"Herunterladen","Downloaded files":"Heruntergeladene Dateien","Downloading files …":"Dateien werden heruntergeladen …","Downloading update…":"Aktualisierung wird heruntergeladen …","Duplicate option {{opt}}":"doppelte Option {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati Forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati wird beim Start ausgeführt und verbleibt für die angegebene Dauer im pausierten Zustand. Dabei belegt Duplicati minimale Systemressourcen und Backups werden nicht ausgeführt.","Duration":"Dauer","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Jeder Sicherung ist eine lokale Datenbank zugeordnet, die Informationen über die Fernsicherung auf dem lokalen Rechner speichert.\\nWenn Sie eine Sicherung löschen, können Sie auch die lokale Datenbank löschen, ohne die Wiederherstellbarkeit der entfernten Dateien zu beeinträchtigen.\\nWenn Sie die lokale Datenbank für Sicherungen von der Kommandozeile aus verwenden, sollten Sie die Datenbank behalten.","Edit as list":"Als Liste bearbeiten","Edit as text":"Als Text bearbeiten","Edit …":"Bearbeiten …","Encrypt file":"Datei verschlüsseln","Encryption":"Verschlüsselung","Encryption changed":"Verschlüsselung geändert","Encryption passphrase":"Verschlüsselungspassphrase","End":"Ende","Enter URL":"URL eingeben","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Gib manuell die Aufbewahrungregeln an. Platzhalter sind D/W/Y für Tag/Woche/Jahr und U für unbegrenzt. Die Syntax lautet 7D:1D,4W:1W,36M:1M. Dieses Beispiel behält eine Sicherung für jeden der nächsten 7 Tage, jede der nächsten 4 Wochen und jeden der nächsten 36 Monate. Die Schreibweise 1W:1D,1M:1W,3Y:1M ist ebenso gültig.","Enter backup passphrase, if any":"Sicherungspassphrase eingeben, falls vorhanden","Enter configuration details":"Konfigurationsdetails eingeben","Enter encryption passphrase":"Verschlüsselungpassphrase eingeben","Enter expression here":"Ausdruck hier eingeben","Enter the destination path":"Ziel-Pfad angeben","Error":"Fehler","Error!":"Fehler!","Errors and crashes":"Fehler und Abstürze","Examined":"Geprüft","Exclude":"Ausschließen","Exclude directories whose names contain":"Ordner ausschließen dessen Namen beinhaltet","Exclude expression":"Filter (ausschließen)","Exclude file":"Datei ausschließen","Exclude file extension":"Dateiendung ausschließen","Exclude files whose names contain":"Dateien ausschließen dessen Namen beinhaltet","Exclude filter group":"Filtergruppe ausschließen","Exclude folder":"Ordner ausschließen","Exclude regular expression":"Regulären Ausdruck (ausschließen)","Existing file found":"Vorhandene Datenbank gefunden","Experimental":"Experimental","Export":"Exportieren","Export backup configuration":"Sicherungskonfiguration exportieren","Export configuration":"Konfiguration exportieren","Export passwords":"Passwort exportieren","Export …":"Exportieren …","Exporting …":"Am Exportieren …","External link":"Externer Link","FTP (Alternative)":"FTP (Alternativ)","Failed to build temporary database: {{message}}":"Erstellen der temporären Datenbank fehlgeschlagen: {{message}}","Failed to connect:":"Verbindung fehlgeschlagen:","Failed to connect: {{message}}":"Verbindung fehlgeschlagen: {{message}}","Failed to delete:":"Löschen fehlgeschlagen:","Failed to fetch path information: {{message}}":"Konnte Pfadangaben nicht abrufen: {{message}}","Failed to find backup:":"Sicherung konnte nicht gefunden werden:","Failed to read backup defaults:":"Sicherungsstandardeinstellungen konnten nicht gelesen werden:","Failed to restore files: {{message}}":"Wiederherstellung der Dateien fehlgeschlagen: {{message}}","Failed to save:":"Fehler beim Speichern:","Fetching path information …":"Abrufen von Pfadinformationen...","File":"Datei","Files larger than:":"Dateien größer als:","Filters":"Filter","Finished!":"Fertiggestellt!","First run setup":"Zuerst Setup starten","Folder":"Ordner","Folder path":"Ordnerpfad","Fri":"Fr","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"Allgemein","General backup settings":"Allgemeine Sicherungseinstellungen","General options":"Allgemeine Einstellungen","Generate":"Erzeugen","Generate IAM access policy":"IAM-Zugriffsrichtlinie generieren","Getting file versions …":"Dateiversionen werden abgerufen …","Group email":"Gruppen-E-Mail","Hidden files":"Versteckte Dateien","Hide":"Ausblenden","Hide hidden folders":"versteckte Ordner ausblenden","Home":"Home","Hostnames":"Hostnamen","Hours":"Stunden","How do you want to handle existing files?":"Wie sollen bestehende Dateien behandelt werden?","Hyper-V Machine":"Hyper-V-Maschine","Hyper-V Machine:":"Hyper-V-Maschine:","Hyper-V Machines":"Hyper-V-Maschinen","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Wurde ein Zeitpunkt verpasst, startet die Sicherung so bald wie möglich.","If at least one newer backup is found, all backups older than this date are deleted.":"Falls mindestens eine neuere Sicherung gefunden wird, werden alle Sicherungen älter als dieses Datum gelöscht.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ohne Pfad werden alle Dateien im Anmeldeverzeichnis gespeichert.\\nMöchten Sie das?","If you do not enter an API Key, the tenant name is required":"Wenn kein API Schlüssel angegeben wurde, ist der Tenant-Name erforderlich.","Import":"Importieren","Import Destination URL":"Ziel-URL importieren","Import backup configuration":"Sicherungskonfiguration importieren","Import from a file":"Von einer Datei importieren","Import metadata":"Importiere Metadata","Importing …":"Am Importieren …","Include a file?":"Datei einschießen?","Include expression":"Filter (einschließen)","Include regular expression":"Regulären Ausdruck (einschließen)","Incorrect answer, try again":"Fehlerhafte Antwort, versuche es erneut","Individual builds for developers only. Not for use with important data.":"Individuelle Builds nur für Entwickler. Nicht für die Verwendung mit wichtigen Daten.","Information":"Information","Invalid characters in path":"Unzulässige Zeichen im Pfad","Invalid retention time":"Ungültige Aufbewahrungszeit","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Manche FTP-Server erlauben ein Verbinden ohne Passwort.\nSind Sie sicher, dass Ihr FTP-Server dazu gehört?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Eine bestimmte Anzahl von Sicherungen behalten","Keep all backups":"Alle Sicherungen behalten","Keystone API version":"Keystone API Version","Language in user interface":"Sprache der Benutzeroberfläche","Last month":"Letzter Monat","Last successful backup:":"Letzte erfolgreiche Sicherung:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Letzte erfolgreiche Wiederherstellung: {{time}} (dauerte {{duration || '0 Sekunden'}})","Latest":"Neueste","Libraries":"Bibliotheken","Listing backup dates …":"Sicherungsdaten werden aufgelistet …","Listing remote files for purge …":"Auflisten von Remote-Dateien fürs Löschen...","Listing remote files …":"Auflisten von Remote-Dateien...","Live":"Live","Load a configuration from an exported job or a storage provider":"Konfiguration aus einem exportierten Job oder Speicheranbieter laden","Load destination from an exported job or a storage provider":"Ziel aus einem exportierten Job oder Speicheranbieter laden","Load older data":"ältere Einträge laden","Loading …":"Laden...","Local database path:":"Lokale Datenbank:","Local repository":"Lokales Repository","Local storage":"Lokaler Speicher","Location":"Ort","Location where buckets are created":"Speicherort, wo die Buckets erstellt werden","Log data for {{Backup.Backup.Name}}":"Protokolldaten für {{Backup.Backup.Name}}","Log data from the server":"Protokolldaten vom Server","Log out":"Abmelden","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Wartung","Manually type path":"Pfad eingeben","Max download speed":"Max. Downloadgeschwindigkeit","Max upload speed":"Max. Uploadgeschwindigkeit","Menu":"Menü","Microsoft SQL Database:":"Microsoft SQL Datenbank:","Microsoft SQL Databases":"Microsoft SQL Datenbanken","Minimum redundancy":"Minimale Redundanz","Minimum redundancy is 1.0":"Die minimale Redundanz ist 1,0","Minutes":"Minuten","Missing name":"Name fehlt","Missing passphrase":"Passphrase fehlt","Missing sources":"Quelle fehlt","Modified":"Geändert","Mon":"Mo","Months":"Monate","Move existing database":"Datenbank verschieben","Move failed:":"Verschieben fehlgeschlagen:","My Documents":"Dokumente","My Music":"Musik","My Photos":"Meine Fotos","My Pictures":"Bilder","Name":"Name","Never":"Nie","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Neuer Benutzername ist {{user}}.\nZugangsdaten für eingeschränken Benutzer verwendet","Next":"Weiter","Next scheduled run:":"Nächste geplante Ausführung:","Next scheduled task:":"Nächste geplante Aufgabe:","Next task:":"Nächste Aufgabe:","Next time":"Nächstes Mal","No":"Nein","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Es wurde kein Zertifikat angegeben, bitte überprüfen Sie mit dem Serveradministrator, ob der Schlüssel korrekt ist: {{key}}\\n\\nMöchten Sie den angegebenen Host-Schlüssel bestätigen?","No editor found for the "{{backend}}" storage type":"Kein Editor für den "{{backend}}" Speichertyp gefunden","No encryption":"Keine Verschlüsselung","No items selected":"Nichts ausgewählt","No items to restore, please select one or more items":"Es wurden keine Daten für die Wiederherstellung ausgewählt. Wähle eine Datei oder einen Ordner aus.","No passphrase entered":"Keine Passphrase eingegeben","No scheduled tasks":"Keine geplanten Aufgaben","Non-matching passphrase":"Nicht übereinstimmende Passphrase","None / disabled":"Keine / deaktiviert","Not using encryption":"Verschlüsselung nicht verwenden","Nothing will be deleted. The backup size will grow with each change.":"Es wird nichts gelöscht. Die Sicherungsgröße erhöht sich mit jeder Änderung.","OK":"OK","Official releases":"Offizielle Versionen","Once there are more backups than the specified number, the oldest backups are deleted.":"Sobald mehr Sicherungen als angegeben vorhanden sind, werden die ältesten Sicherungen gelöscht.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Geöffnet","Operating System":"Betriebssystem","Operation":"Operation","Operations:":"Operationen:","Optional authentication password":"Passwort für Anmeldung (optional)","Optional authentication username":"Benutzername für Anmeldung (optional)","Options":"Optionen","Original location":"Ursprünglicher Speicherort","Others":"Weitere","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Mit der Zeit werden die Sicherungen automatisch gelöscht. Es bleibt eine Sicherung für jeden der letzten 7 Tage, jede der letzten 4 Wochen und jeden der letzten 12 Monate erhalten. Es bleibt immer mindestens eine Sicherung erhalten.","Overwrite":"Überschreiben","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (falls verschlüsselt)","Passphrase changed":"Passphrase gändert","Passphrases are not matching":"Passphrasen stimmen nicht überein","Passphrases do not match":"Passphrasen stimmen nicht überein","Password":"Passwort","Patching files with local blocks …":"Dateien mit vorhandenen Daten aufbauen...","Path":"Pfad","Path not found":"Pfad nicht gefunden","Path on server":"Pfad auf Server","Path or subfolder in the bucket":"Pfad oder Unterverzeichnis im Bucket","Pause":"Pause","Pause after startup or hibernation":"Pause nach dem Start oder Aufwachen","Pause options":"Anhalten Optionen","Permissions":"Berechtigungen","Pick location":"Speicherort auswählen","Point to your backup files and restore from there":"Sicherungsdateien auswählen und wiederherstellen","Port":"Port","Prevent tray icon automatic log-in":"Verhindert das automatische Anmelden per Taskleistensymbol","Previous":"Zurück","Progress:":"Fortschritt:","ProjectID is optional if the bucket exist":"Die Projekt-ID ist optional, wenn der Bucket existiert","Proprietary":"Proprietär","Purge Phase":"Aufräumphase","Purging files complete!":"Löschen von Dateien abgeschlossen!","Purging files …":"Dateien bereinigen...","Rebuilding local database …":"Lokale Datenbank wird neu aufgebaut …","Recreate (delete and repair)":"Wiederherstellen (löschen und reparieren)","Recreate Database Phase":"Datenbank-Wiederherstellungsphase","Recreating database …":"Datenbank wird neu erstellt …","Registering temporary backup …":"Temporäre Sicherung wird registriert …","Relative paths not allowed":"Relative Pfade sind nicht möglich","Reload":"Neu laden","Remote":"Remote","Remote Path":"Entfernter Pfad","Remote Repository":"Entferntes Repository","Remote path":"Entfernter Pfad","Remote repository":"Entferntes Repository","Remote volume size":"Remote-Volume-Größe","Remove":"Entfernen","Remove option":"Option entfernen","Removed files":"Entfernte Dateien","Repair":"Reparieren","Repair Phase":"Reparatur Phase","Repairing database …":"Datenbank wird repariert …","Repeat Passphrase":"Passphrase wiederholen","Reporting:":"Bericht:","Reset":"Zurücksetzen","Restore":"Wiederherstellen","Restore complete!":"Wiederherstellung komplett!","Restore files":"Dateien wiederherstellen","Restore files …":"Dateien wiederherstellen …","Restore from":"Wiederherstellen von","Restore from backup configuration":"Aus Sicherungskonfiguration wiederherstellen","Restore options":"Wiederherstellungsoptionen","Restore read/write permissions":"Schreib- und Leserechte wiederherstellen","Restored Files":"Dateien wiederhergestellt","Restored Folders":"Ordner wiederhergestellt","Restored Symlinks":"Symbolische Verknüpfungen wiederhergestellt","Restoring files …":"Dateien werden wiederhergestellt …","Resume":"Fortsetzen","Rewritten File Lists":"Neu geschrieben Dateiliste","Run again every":"Wiederholen alle","Run now":"Jetzt sichern","Running commandline entry":"Führe Kommandozeilenbefehl aus","Running task:":"Laufende Aufgabe:","Running …":"Läuft...","S3 Compatible":"S3 Kompatibel","Same as the base install version: {{channelname}}":"Wie die zuerst installierte Version: {{channelname}}","Sat":"Sa","Satellite":"Satellit","Save":"Speichern","Save and repair":"Speichern und reparieren","Save different versions with timestamp in file name":"Mehrere Versionen mit Zeitstempel im Dateinamen speichern","Save immediately":"Sofort speichern","Scanning existing files …":"Vorhandene Dateien werden gescannt …","Scanning for local blocks …":"Scannen nach lokalen Blöcken...","Schedule":"Zeitplan","Search":"Suche","Search for files":"Dateien suchen","Seconds":"Sekunden","Select a log level and see messages as they happen:":"Wähle eine Protokollierungsstufe aus und sehe dir die Meldungen an während sie erstellt werden:","Select files":"Wähle Dateien","Server":"Server","Server and port":"Server und Port","Server hostname or IP":"Server-Hostname oder IP","Server is currently paused,":"Server ist pausiert,","Server is currently paused, do you want to resume now?":"Server ist zurzeit pausiert, Server starten?","Server password":"Server-Passwort","Server paused":"Server pausiert","Server state properties":"Server Zustandseigenschaften","Settings":"Einstellungen","Show":"Anzeigen","Show advanced editor":"Erweiterten Editor anzeigen","Show hidden folders":"Versteckte Ordner anzeigen","Show log":"Protokolldatei anzeigen","Show log …":"Protokoll anzeigen...","Show treeview":"Baumansicht anzeigen","Sia server password":"Sia Server-Passwort","Smart backup retention":"Intelligente Sicherungsaufbewahrung","Some OpenStack providers allow an API key instead of a password and tenant name":"Einige OpenStack Anbieter erlauben einen API Schlüssel anstelle eines Passwortes und Tenant Namen","Some S3 providers might only be compatible with a certain client library":"Manche S3 Anbieter sind nur mit bestimmten Client Bibliotheken kompatibel","Source Data":"Quell-Daten","Source Files":"Quelldateien","Source data":"Quell-Daten","Source folders":"Quell-Verzeichnisse","Source:":"Quelle:","Specific builds for developers only. Not for use with important data.":"Spezifische Builds nur für Entwickler. Nicht für die Verwendung mit wichtigen Daten.","Stable":"Stabil","Standard protocols":"Standardprotokolle","Start":"Beginn","Starting backup …":"Sicherung wird gestartet …","Starting restore …":"Wiederherstellung wird gestartet …","Starting the restore process …":"Starten des Wiederherstellungsprozesses...","Stop after the current file":"Beende nach aktueller Datei","Stop running backup":"Laufende Sicherung anhalten","Stop running task":"Beende laufenden Vorgang","Stopping after the current file:":"Anhalten nach der aktuellen Datei:","Stopping task:":"Beende Vorgang","Storage Type":"Speichertyp","Storage class":"Speicherklasse","Storage class for creating a bucket":"Speicherklasse zum Erstellen eines Bucket","Stored":"Gespeichert","Strong":"Stark","Success":"Erfolgreich","Sun":"So","Symbolic link":"Symbolischer Link","System Files":"Systemdateien","System default ({{levelname}})":"System-Standard ({{levelname}})","System files":"Systemdateien","System info":"System-Informationen","System properties":"System-Eigenschaften","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Aufgabe wird ausgeführt","Temporary Files":"Temporäre Dateien","Temporary files":"Temporäre Dateien","Test Phase":"Test Phase","Test connection":"Verbindung prüfen","Testing permissions …":"Berechtigungen werden überprüft …","Testing …":"Prüfung...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Das Feld '{{fieldname}}' beinhaltet ein ungültiges Zeichen: {{character}} (Wert: {{value}}, Position: {{pos}})","The backup is missing, has it been deleted?":"Die Sicherung fehlt, wurde sie gelöscht?","The backup was temporary and does not exist anymore, so the log data is lost":"Die Sicherung war temporär und existiert nicht mehr, die Protokolldaten sind daher verloren","The bucket name should be all lower-case, convert automatically?":"Der Bucket sollte klein geschrieben sein. Jetzt klein schreiben?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Die Konfiguration sollte sicher aufbewahrt werden. Sicher, dass eine unverschlüsselte Datei mit Ihren Passwörtern gespeichert werden soll?","The dark theme (by Michal)":"Dunkles Thema (von Michal)","The default blue on white theme (by Alex)":"Blau-auf-Weiß Thema (von Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Der Ordner {{folder}} existiert nicht.\nOrdner erstellen?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Der Host-Schlüssel wurde geändert, bitte prüfen Sie mit dem Server-Administrator, ob dieser korrekt ist, sonst könnten Sie das Opfer eines MAN-IN-THE-MIDDLE-Angriffs werden.\\n\\nMöchten Sie Ihren AKTUELLEN Host-Schüssel \"{{prev}}\" durch den GEMELDETEN Host-Schüssel {{key}} ersetzen?","The passwords do not match":"Die Passwörter stimmen nicht überein","The path does not appear to exist, do you want to add it anyway?":"Der Pfad scheint nicht zu existieren. Möchten Sie ihn trotzdem hinzufügen?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Der Pfad endet nicht mit dem Zeichen \"{{dirsep}}\", was bedeutet, dass Sie eine Daten und kein Verzeichnis einschließen.\\n\\nMöchten Sie die angegebene Datei einschließen?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Der Pfad muss ein absoluter Pfad sein. Das heißt, er muss mit '/' beginnen","The region parameter is only applied when creating a new bucket":"Der Bereich Parameter wird nur angewendet, wenn ein neuer Bucket erzeugt wird","The region parameter is only used when creating a bucket":"Der Bereich Parameter wird nur angewendet, wenn ein Bucket erzeugt wird","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Das Server Zertifikat konnte nicht validiert werden.\\nMöchten Sie das SSL-Zertifikat mit dem folgenden Hash bestätigen: {{hash}}?","The storage class affects the availability and price for a stored file":"Die Speicherklasse wirkt sich auf die Verfügbarkeit und den Preis einer gespeicherten Datei aus","The target folder contains encrypted files, please supply the passphrase":"Der Zielordner enthält verschlüsselte Dateien, bitte stelle die Passphrase bereit","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Der Nutzer hat zu viele Berechtigungen. Möchten Sie einen neuen eingeschränkten Nutzer erstellen, welcher nur Zugriffsrechte für den ausgewählten Pfad hat?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Dieses Backup wurde mit einem anderen Betriebssystem erstellt. Die Wiederherstellung von Dateien ohne Angabe eines Zielordners kann dazu führen, dass Dateien an unerwarteten Stellen wiederhergestellt werden. Sind Sie sicher, dass Sie fortfahren möchten, ohne ein Zielverzeichnis zu wählen?","This month":"Dieser Monat","This week":"Diese Woche","Throttle settings":"Drosselungseinstellungen","Thu":"Do","Time":"Zeit","To File":"als Datei","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Zum Bestätigen für das Löschen der Remote-Dateien für \"{{name}}\", bitte das unten angegebene Wort eingeben","To export without a passphrase, uncheck the \"Encrypt file\" box":"Deaktiviere »Datei verschlüsseln«, um ohne eine Passphrase zu exportieren","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Um verschiedene DNS-basierte Angriffe zu verhindern, beschränkt Duplicati die erlaubten Hostnamen auf die hier aufgeführten. Direkter IP-Zugriff und localhost ist immer erlaubt. Mehrere Hostnamen können mit einem Semikolon-Trennzeichen versehen werden. Wenn einer der zulässigen Hostnamen ein Sternchen (*) ist, sind alle Hostnamen zulässig und diese Funktion ist deaktiviert. Is das Feld leer, sind nur IP-Adresse und lokaler Host-Zugriff zulässig.","Today":"Heute","Trust host certificate?":"Host Zertifikat vertrauen?","Trust server certificate?":"Server Zertifikat vertrauen?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probiere neue Funktionen aus, an denen wir gerade arbeiten. Derzeit die stabilste verfügbare Version. Vor der Verwendung im produktiven Umfeld teste bitte die Wiederherstellung der Daten.","Tue":"Di","Type passphrase here.":"Hier Passphrase eingeben.","Type to highlight files":"Tippen, um Dateien zu markieren","Unknown backup size and versions":"Unbekannte Backupgröße und -versionen","Until resumed":"Bis zur Wiederaufnahme","Update channel":"Update-Kanal","Update failed:":"Update fehlgeschlagen:","Updating with existing database":"Datenbank wird aktualisiert","Uploaded files":"Hochgeladene Dateien","Uploading verification file …":"Verifikationsdatei wird hochgeladen …","Usage statistics":"Nutzungsstatistiken","Usage statistics, warnings, errors, and crashes":"Nutzungsberichte, Warnungen, Fehler und Abstürze","Use SSL":"SSL benutzen","Use existing database?":"Bestehende Datenbank nutzen?","Use weak passphrase":"Schwache Passphrase verwenden","Useless":"Nutzlos","User data":"Benutzer Daten","User domain name":"Benutzer Domänenname ","User has too many permissions":"Nutzer hat zu viele Rechte","User interface settings":"Einstellungen der Benutzeroberfläche","Username":"Benutzername","Vacuuming database …":"Datenbank wird bereinigt …","Validating …":"Validieren...","Verifications":"Überprüfungen","Verify files":"Dateien prüfen","Verifying answer":"Antwort verifizieren","Verifying backend data …":"Verifizierung von Backend-Daten...","Verifying files …":"Dateien überprüfen... ","Verifying remote data …":"Remotedaten prüfen ...","Verifying restored files …":"Wiederhergestellte Dateien werden überprüft …","Verifying …":"Am Überprüfen …","Version ID":"Version ID","Very strong":"Sehr stark","Very weak":"Sehr schwach","Visit us on":"Besuche uns auf","WARNING: This will prevent you from restoring the data in the future.":"WARNUNG: Dadurch können Sie die Daten in Zukunft nicht wiederherstellen.","Waiting for task to begin":"Warte darauf, loslegen zu können","Waiting for upload to finish …":"Warte auf Ende des Uploads... ","Warnings, errors and crashes":"Warnungen, Fehler und Abstürze","We recommend that you encrypt all backups stored outside your system":"Wir empfehlen, dass Sie alle Backups verschlüsseln, die außerhalb Ihres Systems gespeichert werden.","Weak":"Schwach","Weak passphrase":"Schwache Passphrase","Wed":"Mi","Weeks":"Wochen","Where do you want to restore from?":"Von wo wollen Sie wiederherstellen?","Where do you want to restore the files to?":"Wohin sollen die Dateien wiederhergestellt werden?","Years":"Jahre","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, ich habe die Passphrase sicher gespeichert","Yes, I understand the risk":"Ja, ich habe die Risiken verstanden","Yes, I'm brave!":"Ja, ich bin mutig!","Yes, please break my backup!":"Ja, bitte zerstöre meine Sicherung!","Yesterday":"Gestern","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Sie ändern gerade den Datenbankpfad einer existierenden lokalen Datenbank.\nSind Sie sicher, dass Sie das wollen?","You are currently running {{appname}} {{version}}":"Aktuell wird {{appname}} {{version}} verwendet","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Sie haben die Verschlüsselungsmethode geändert. Dies könnte Daten zerstören. Wir empfehlen Ihnen, stattdessen eine neue Sicherung zu erstellen","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Sie haben die Passphrase geändert, was nicht unterstützt wird. Bitte erstellen Sie stattdessen eine neue Sicherung.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Sie haben ausgewählt, dass die Sicherung nicht verschlüsselt werden soll. Die Verschlüsselung wird für alle auf einem Remote-Server gespeicherten Daten empfohlen.","You have chosen to restore to a new location, but not entered one":"Wiederherstellen an einen neuen Ort wurde gewählt, aber kein Ort angegeben","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Sie haben eine starke Passphrase erstellt. Stellen Sie sicher, dass Sie diese an einem sicheren Ort aufbewahren, da die Daten bei Verlust der Passphrase nicht wiederhergestellt werden können.","You must choose at least one source folder":"Sie müssen mindestens ein Quellverzeichnis wählen.","You must enter a domain name to use v3 API":"Eingabe vom Domänennamens für die Verwendungder v3-API","You must enter a name for the backup":"Sie müssen einen Namen für die Sicherung eingeben.","You must enter a passphrase or disable encryption":"Sie müssen eine Passphrase eingeben oder die Verschlüsselung deaktivieren.","You must enter a password to use v3 API":"Gib ein Passwort für die Verwendungder v3-API an","You must enter a positive number of backups to keep":"Sie müssen eine positive Anzahl der zu behaltenden Sicherungen eingeben.","You must enter a tenant (aka project) name to use v3 API":"Gib einen Kundennamen (bzw. Projektnamen) für die Verwendungder v3-API","You must enter a valid duration for the time to keep backups":"Sie müssen eine gültige Aufbewahrungsdauer für die Sicherungen eingeben.","You must enter a valid retention policy string":"Sie müssen eine gültige Aufbewahrungsregel angeben.","You must fill in the password":"Sie müssen ein Passwort eintragen.","You must fill in the server name or address":"Sie müssen einen Servernamen oder eine Adresse eintragen.","You must fill in the username":"Sie müssen einen Benutzernamen eintragen.","You must fill in {{field}}":"{{field}} muss ausgefüllt sein","You must select or fill in the AuthURI":"Sie müssen die AuthURI auswählen oder eintragen.","You must select or fill in the server":"Sie müssen den Server auswählen oder eintragen.","You must specify a path":"Sie müssen einen Pfad angeben.","Your files and folders have been restored successfully.":"Dateien und Ordner erfolgreich wiederhergestellt.","Your passphrase is easy to guess. Consider changing passphrase.":"Ihre Passphrase ist leicht zu erraten. Erwägen Sie eine Änderung der Passphrase.","bucket/folder/subfolder":"Bucket/Ordner/Unterordner","byte":"Byte","byte/s":"Byte/s","custom":"benutzerdefiniert","resume now":"Jetzt starten","unless you are explicitly specifying --group-id":"es sei denn, Sie geben explizit --group-id an","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} wurde hauptsächlich von {{dev1}} und {{dev2}} entwickelt. {{appname}} kann unter folgender Adresse heruntergeladen werden: {{websitename}}. {{appname}} ist unter {{licensename}} lizenziert.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} Dateien ({{size}}) zu erledigen {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versionen"],"{{number}} Hour":"{{number}} Stunde","{{number}} Hours":"{{number}} Stunden","{{number}} Minutes":"{{number}} Minuten","{{time}} (took {{duration}})":"{{time}} (dauerte {{duration}})"}); + gettextCatalog.setStrings('en_GB', {"- pick an option -":"- pick an option -","...loading...":"...loading...","API key":"API key","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"About","About {{appname}}":"About {{appname}}","Access Key":"Access Key","Access denied":"Access denied","Access grant":"Access grant","Access to user interface":"Access to user interface","Account name":"Account name","Add a new backup":"Add a new backup","Add a path directly":"Add a path directly","Add advanced option":"Add advanced option","Add backup":"Add backup","Add filter":"Add filter","Add path":"Add path","Added":"Added","Adjust bucket name?":"Adjust bucket name?","Advanced Options":"Advanced Options","Advanced options":"Advanced options","Advanced:":"Advanced:","All Hyper-V Machines":"All Hyper-V Machines","All Microsoft SQL Databases":"All Microsoft SQL Databases","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.","Allow remote access (requires restart)":"Allow remote access (requires restart)","Allowed days":"Allowed days","An existing file was found at the new location":"An existing file was found at the new location","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"An existing file was found at the new location\nAre you sure you want the database to point to an existing file?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?","Anonymous usage reports":"Anonymous usage reports","Applications":"Applications","As Command-line":"As Command-line","AuthID":"AuthID","Authentication method":"Authentication method","Authentication method ({{auth_method}})":"Authentication method ({{auth_method}})","Authentication password":"Authentication password","Authentication username":"Authentication username","Autogenerated passphrase":"Autogenerated passphrase","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Back","Backup complete!":"Backup complete!","Backup destination":"Backup destination","Backup location":"Backup location","Backup retention":"Backup retention","Backup:":"Backup:","Beta":"Beta","Broken access":"Broken access","Browse":"Browse","Browser default":"Browser default","Bucket create location":"Bucket create location","Bucket name":"Bucket name","Bucket storage class":"Bucket storage class","Building list of files to restore …":"Building list of files to restore …","Building partial temporary database …":"Building partial temporary database …","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.","Cache Files":"Cache Files","Canary":"Canary","Cancel":"Cancel","Cannot move to existing file":"Cannot move to existing file","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog for {{appname}} {{version}}","Check failed:":"Check failed:","Check for updates now":"Check for updates now","Checking for updates …":"Checking for updates …","Chose a storage type to get started":"Chose a storage type to get started","Click the AuthID link to create an AuthID":"Click the AuthID link to create an AuthID","Click to set throttle options":"Click to set throttle options","Client library to use":"Client library to use","Commandline …":"Command Line …","Compact Phase":"Compact Phase","Compact now":"Compact now","Compacting remote data …":"Compacting remote data …","Complete log":"Complete log","Completing backup …":"Completing backup …","Completing previous backup …":"Completing previous backup …","Computer":"Computer","Configuration file:":"Configuration file:","Configuration:":"Configuration:","Configure a new backup":"Configure a new backup","Confirm delete":"Confirm delete","Confirm encryption passphrase":"Confirm encryption passphrase","Confirm passphrase":"Confirm passphrase","Confirmation required":"Confirmation required","Connect":"Connect","Connect now":"Connect now","Connecting to server …":"Connecting to server …","Connection lost":"Connection lost","Connection worked!":"Connection worked!","Container name":"Container name","Container region":"Container region","Continue":"Continue","Continue without encryption":"Continue without encryption","Copied!":"Copied!","Copy":"Copy","Copy Destination URL to Clipboard":"Copy Destination URL to Clipboard","Copy failed. Please manually copy the URL":"Copy failed. Please manually copy the URL","Core options":"Core options","Counting ({{files}} files found, {{size}})":"Counting ({{files}} files found, {{size}})","Crashes only":"Crashes only","Create bug report …":"Create bug report …","Create folder?":"Create folder?","Created new limited user":"Created new limited user","Creating bug report …":"Creating bug report …","Creating new user with limited access …":"Creating new user with limited access …","Creating target folders …":"Creating target folders …","Creating temporary backup …":"Creating temporary backup …","Current action:":"Current action:","Current file:":"Current file:","Current version is {{versionname}} ({{versionnumber}})":"Current version is {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Custom S3 endpoint","Custom Satellite":"Custom Satellite","Custom Satellite ({{satellite}})":"Custom Satellite ({{satellite}})","Custom authentication url":"Custom authentication url","Custom backup retention":"Custom backup retention","Custom location ({{server}})":"Custom location ({{server}})","Custom region for creating buckets":"Custom region for creating buckets","Custom region value ({{region}})":"Custom region value ({{region}})","Custom server url ({{server}})":"Custom server url ({{server}})","Custom storage class ({{class}})":"Custom storage class ({{class}})","Database …":"Database …","Days":"Days","Default":"Default","Default ({{channelname}})":"Default ({{channelname}})","Default excludes":"Default excludes","Default options":"Default options","Delete":"Delete","Delete Phase (Old Backup Versions)":"Delete Phase (Old Backup Versions)","Delete backup":"Delete backup","Delete backups that are older than":"Delete backups that are older than","Delete local database":"Delete local database","Delete remote files":"Delete remote files","Delete the local database":"Delete the local database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Delete {{filecount}} files ({{filesize}}) from the remote storage?","Delete …":"Delete …","Deleted":"Deleted","Deleted Versions":"Deleted Versions","Deleted files":"Deleted files","Deleting remote files …":"Deleting remote files …","Deleting unwanted files …":"Deleting unwanted files …","Description (optional)":"Description (optional)","Description:":"Description:","Desktop":"Desktop","Destination":"Destination","Destination path":"Destination path","Disabled":"Disabled","Dismiss":"Dismiss","Dismiss all":"Dismiss all","Display and color theme":"Display and color theme","Do you really want to delete the backup: \"{{name}}\" ?":"Do you really want to delete the backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Do you really want to delete the local database for: {{name}}","Done":"Done","Download":"Download","Downloaded files":"Downloaded files","Downloading files …":"Downloading files …","Downloading update…":"Downloading update…","Duplicate option {{opt}}":"Duplicate option {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.","Duration":"Duration","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.","Edit as list":"Edit as list","Edit as text":"Edit as text","Edit …":"Edit …","Encrypt file":"Encrypt file","Encryption":"Encryption","Encryption changed":"Encryption changed","Encryption passphrase":"Encryption passphrase","End":"End","Enter URL":"Enter URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Enter backup passphrase, if any","Enter configuration details":"Enter configuration details","Enter encryption passphrase":"Enter encryption passphrase","Enter expression here":"Enter expression here","Enter the destination path":"Enter the destination path","Error":"Error","Error!":"Error!","Errors and crashes":"Errors and crashes","Examined":"Examined","Exclude":"Exclude","Exclude directories whose names contain":"Exclude directories whose names contain","Exclude expression":"Exclude expression","Exclude file":"Exclude file","Exclude file extension":"Exclude file extension","Exclude files whose names contain":"Exclude files whose names contain","Exclude filter group":"Exclude filter group","Exclude folder":"Exclude folder","Exclude regular expression":"Exclude regular expression","Existing file found":"Existing file found","Experimental":"Experimental","Export":"Export","Export backup configuration":"Export backup configuration","Export configuration":"Export configuration","Export passwords":"Export passwords","Export …":"Export …","Exporting …":"Exporting …","External link":"External link","FTP (Alternative)":"FTP (Alternative)","Failed to build temporary database: {{message}}":"Failed to build temporary database: {{message}}","Failed to connect:":"Failed to connect:","Failed to connect: {{message}}":"Failed to connect: {{message}}","Failed to delete:":"Failed to delete:","Failed to fetch path information: {{message}}":"Failed to fetch path information: {{message}}","Failed to find backup:":"Failed to find backup:","Failed to read backup defaults:":"Failed to read backup defaults:","Failed to restore files: {{message}}":"Failed to restore files: {{message}}","Failed to save:":"Failed to save:","Fetching path information …":"Fetching path information …","File":"File","Files larger than:":"Files larger than:","Filters":"Filters","Finished!":"Finished!","First run setup":"First run setup","Folder":"Folder","Folder path":"Folder path","Fri":"Fri","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"General","General backup settings":"General backup settings","General options":"General options","Generate":"Generate","Getting file versions …":"Getting file versions …","Group email":"Group email","Hidden files":"Hidden files","Hide":"Hide","Hide hidden folders":"Hide hidden folders","Home":"Home","Hostnames":"Hostnames","Hours":"Hours","How do you want to handle existing files?":"How do you want to handle existing files?","Hyper-V Machine":"Hyper-V Machine","Hyper-V Machine:":"Hyper-V Machine:","Hyper-V Machines":"Hyper-V Machines","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"If a date was missed, the job will run as soon as possible.","If at least one newer backup is found, all backups older than this date are deleted.":"If at least one newer backup is found, all backups older than this date are deleted.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?","If you do not enter an API Key, the tenant name is required":"If you do not enter an API Key, the tenant name is required","Import":"Import","Import Destination URL":"Import Destination URL","Import backup configuration":"Import backup configuration","Import from a file":"Import from a file","Import metadata":"Import metadata","Importing …":"Importing …","Include a file?":"Include a file?","Include expression":"Include expression","Include regular expression":"Include regular expression","Incorrect answer, try again":"Incorrect answer, try again","Individual builds for developers only. Not for use with important data.":"Individual builds for developers only. Not for use with important data.","Information":"Information","Invalid characters in path":"Invalid characters in path","Invalid retention time":"Invalid retention time","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"It is possible to connect to some FTP servers without a password.\nAre you sure your FTP server supports password-less logins?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Keep a specific number of backups","Keep all backups":"Keep all backups","Keystone API version":"Keystone API version","Language in user interface":"Language in user interface","Last month":"Last month","Last successful backup:":"Last successful backup:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Last successful restore: {{time}} (took {{duration || '0 seconds'}})","Latest":"Latest","Libraries":"Libraries","Listing backup dates …":"Listing backup dates …","Listing remote files for purge …":"Listing remote files for purge …","Listing remote files …":"Listing remote files …","Live":"Live","Load a configuration from an exported job or a storage provider":"Load a configuration from an exported job or a storage provider","Load destination from an exported job or a storage provider":"Load destination from an exported job or a storage provider","Load older data":"Load older data","Loading …":"Loading …","Local database path:":"Local database path:","Local repository":"Local repository","Local storage":"Local storage","Location":"Location","Location where buckets are created":"Location where buckets are created","Log data for {{Backup.Backup.Name}}":"Log data for {{Backup.Backup.Name}}","Log data from the server":"Log data from the server","Log out":"Log out","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Maintenance","Manually type path":"Manually type path","Max download speed":"Max download speed","Max upload speed":"Max upload speed","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Databases","Minimum redundancy":"Minimum redundancy","Minimum redundancy is 1.0":"Minimum redundancy is 1.0","Minutes":"Minutes","Missing name":"Missing name","Missing passphrase":"Missing passphrase","Missing sources":"Missing sources","Modified":"Modified","Mon":"Mon","Months":"Months","Move existing database":"Move existing database","Move failed:":"Move failed:","My Documents":"My Documents","My Music":"My Music","My Photos":"My Photos","My Pictures":"My Pictures","Name":"Name","Never":"Never","New user name is {{user}}.\nUpdated credentials to use the new limited user":"New user name is {{user}}.\nUpdated credentials to use the new limited user","Next":"Next","Next scheduled run:":"Next scheduled run:","Next scheduled task:":"Next scheduled task:","Next task:":"Next task:","Next time":"Next time","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?","No editor found for the "{{backend}}" storage type":"No editor found for the "{{backend}}" storage type","No encryption":"No encryption","No items selected":"No items selected","No items to restore, please select one or more items":"No items to restore, please select one or more items","No passphrase entered":"No passphrase entered","No scheduled tasks":"No scheduled tasks","Non-matching passphrase":"Non-matching passphrase","None / disabled":"None / disabled","Not using encryption":"Not using encryption","Nothing will be deleted. The backup size will grow with each change.":"Nothing will be deleted. The backup size will grow with each change.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Once there are more backups than the specified number, the oldest backups are deleted.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Opened","Operating System":"Operating System","Operation":"Operation","Operations:":"Operations:","Optional authentication password":"Optional authentication password","Optional authentication username":"Optional authentication username","Options":"Options","Original location":"Original location","Others":"Others","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.","Overwrite":"Overwrite","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (if encrypted)","Passphrase changed":"Passphrase changed","Passphrases are not matching":"Passphrases are not matching","Passphrases do not match":"Passphrases do not match","Password":"Password","Patching files with local blocks …":"Patching files with local blocks …","Path":"Path","Path not found":"Path not found","Path on server":"Path on server","Path or subfolder in the bucket":"Path or subfolder in the bucket","Pause":"Pause","Pause after startup or hibernation":"Pause after startup or hibernation","Pause options":"Pause options","Permissions":"Permissions","Pick location":"Pick location","Point to your backup files and restore from there":"Point to your backup files and restore from there","Port":"Port","Prevent tray icon automatic log-in":"Prevent tray icon automatic log-in","Previous":"Previous","Progress:":"Progress:","ProjectID is optional if the bucket exist":"ProjectID is optional if the bucket exist","Proprietary":"Proprietary","Purge Phase":"Purge Phase","Purging files complete!":"Purging files complete!","Purging files …":"Purging files …","Rebuilding local database …":"Rebuilding local database …","Recreate (delete and repair)":"Recreate (delete and repair)","Recreate Database Phase":"Recreate Database Phase","Recreating database …":"Recreating database …","Registering temporary backup …":"Registering temporary backup …","Relative paths not allowed":"Relative paths not allowed","Reload":"Reload","Remote":"Remote","Remote Path":"Remote Path","Remote Repository":"Remote Repository","Remote path":"Remote path","Remote repository":"Remote repository","Remote volume size":"Remote volume size","Remove":"Remove","Remove option":"Remove option","Removed files":"Removed files","Repair":"Repair","Repair Phase":"Repair Phase","Repairing database …":"Repairing database …","Repeat Passphrase":"Repeat Passphrase","Reporting:":"Reporting:","Reset":"Reset","Restore":"Restore","Restore complete!":"Restore complete!","Restore files":"Restore files","Restore files …":"Restore files …","Restore from":"Restore from","Restore from backup configuration":"Restore from backup configuration","Restore options":"Restore options","Restore read/write permissions":"Restore read/write permissions","Restored Files":"Restored Files","Restored Folders":"Restored Folders","Restored Symlinks":"Restored Symlinks","Restoring files …":"Restoring files …","Resume":"Resume","Rewritten File Lists":"Rewritten File Lists","Run again every":"Run again every","Run now":"Run now","Running commandline entry":"Running command line entry","Running task:":"Running task:","Running …":"Running …","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Same as the base install version: {{channelname}}","Sat":"Sat","Satellite":"Satellite","Save":"Save","Save and repair":"Save and repair","Save different versions with timestamp in file name":"Save different versions with timestamp in file name","Save immediately":"Save immediately","Scanning existing files …":"Scanning existing files …","Scanning for local blocks …":"Scanning for local blocks …","Schedule":"Schedule","Search":"Search","Search for files":"Search for files","Seconds":"Seconds","Select a log level and see messages as they happen:":"Select a log level and see messages as they happen:","Select files":"Select files","Server":"Server","Server and port":"Server and port","Server hostname or IP":"Server hostname or IP","Server is currently paused,":"Server is currently paused,","Server is currently paused, do you want to resume now?":"Server is currently paused, do you want to resume now?","Server password":"Server password","Server paused":"Server paused","Server state properties":"Server state properties","Settings":"Settings","Show":"Show","Show advanced editor":"Show advanced editor","Show hidden folders":"Show hidden folders","Show log":"Show log","Show log …":"Show log …","Show treeview":"Show treeview","Sia server password":"Sia server password","Smart backup retention":"Smart backup retention","Some OpenStack providers allow an API key instead of a password and tenant name":"Some OpenStack providers allow an API key instead of a password and tenant name","Some S3 providers might only be compatible with a certain client library":"Some S3 providers might only be compatible with a certain client library","Source Data":"Source Data","Source Files":"Source Files","Source data":"Source data","Source folders":"Source folders","Source:":"Source:","Specific builds for developers only. Not for use with important data.":"Specific builds for developers only. Not for use with important data.","Standard protocols":"Standard protocols","Start":"Start","Starting backup …":"Starting backup …","Starting restore …":"Starting restore …","Starting the restore process …":"Starting the restore process …","Stop after the current file":"Stop after the current file","Stop running backup":"Stop running backup","Stop running task":"Stop running task","Stopping after the current file:":"Stopping after the current file:","Stopping task:":"Stopping task:","Storage Type":"Storage Type","Storage class":"Storage class","Storage class for creating a bucket":"Storage class for creating a bucket","Stored":"Stored","Strong":"Strong","Success":"Success","Sun":"Sun","Symbolic link":"Symbolic link","System Files":"System Files","System default ({{levelname}})":"System default ({{levelname}})","System files":"System files","System info":"System info","System properties":"System properties","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Task is running","Temporary Files":"Temporary Files","Temporary files":"Temporary files","Test Phase":"Test Phase","Test connection":"Test connection","Testing permissions …":"Testing permissions …","Testing …":"Testing …","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"The backup is missing, has it been deleted?","The backup was temporary and does not exist anymore, so the log data is lost":"The backup was temporary and does not exist anymore, so the log data is lost","The bucket name should be all lower-case, convert automatically?":"The bucket name should be all lower-case, convert automatically?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?","The dark theme (by Michal)":"The dark theme (by Michal)","The default blue on white theme (by Alex)":"The default blue on white theme (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"The folder {{folder}} does not exist.\nCreate it now?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?","The passwords do not match":"The passwords do not match","The path does not appear to exist, do you want to add it anyway?":"The path does not appear to exist, do you want to add it anyway?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"The path must be an absolute path, i.e. it must start with a forward slash '/'","The region parameter is only applied when creating a new bucket":"The region parameter is only applied when creating a new bucket","The region parameter is only used when creating a bucket":"The region parameter is only used when creating a bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?","The storage class affects the availability and price for a stored file":"The storage class affects the availability and price for a stored file","The target folder contains encrypted files, please supply the passphrase":"The target folder contains encrypted files, please supply the passphrase","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?","This month":"This month","This week":"This week","Throttle settings":"Throttle settings","Thu":"Thu","Time":"Time","To File":"To File","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below","To export without a passphrase, uncheck the \"Encrypt file\" box":"To export without a passphrase, uncheck the \"Encrypt file\" box","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost/127.0.0.1 are always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.","Today":"Today","Trust host certificate?":"Trust host certificate?","Trust server certificate?":"Trust server certificate?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.","Tue":"Tue","Type passphrase here.":"Type passphrase here.","Type to highlight files":"Type to highlight files","Unknown backup size and versions":"Unknown backup size and versions","Until resumed":"Until resumed","Update channel":"Update channel","Update failed:":"Update failed:","Updating with existing database":"Updating with existing database","Uploaded files":"Uploaded files","Uploading verification file …":"Uploading verification file …","Usage statistics":"Usage statistics","Usage statistics, warnings, errors, and crashes":"Usage statistics, warnings, errors, and crashes","Use SSL":"Use SSL","Use existing database?":"Use existing database?","Use weak passphrase":"Use weak passphrase","Useless":"Useless","User data":"User data","User domain name":"User domain name","User has too many permissions":"User has too many permissions","User interface settings":"User interface settings","Username":"Username","Vacuuming database …":"Vacuuming database …","Validating …":"Validating …","Verifications":"Verifications","Verify files":"Verify files","Verifying answer":"Verifying answer","Verifying backend data …":"Verifying backend data …","Verifying files …":"Verifying files …","Verifying remote data …":"Verifying remote data …","Verifying restored files …":"Verifying restored files …","Verifying …":"Verifying …","Version ID":"Version ID","Very strong":"Very strong","Very weak":"Very weak","Visit us on":"Visit us on","WARNING: This will prevent you from restoring the data in the future.":"WARNING: This will prevent you from restoring the data in the future.","Waiting for task to begin":"Waiting for task to begin","Waiting for upload to finish …":"Waiting for upload to finish …","Warnings, errors and crashes":"Warnings, errors and crashes","We recommend that you encrypt all backups stored outside your system":"We recommend that you encrypt all backups stored outside your system","Weak":"Weak","Weak passphrase":"Weak passphrase","Wed":"Wed","Weeks":"Weeks","Where do you want to restore from?":"Where do you want to restore from?","Where do you want to restore the files to?":"Where do you want to restore the files to?","Years":"Years","Yes":"Yes","Yes, I have stored the passphrase safely":"Yes, I have stored the passphrase safely","Yes, I understand the risk":"Yes, I understand the risk","Yes, I'm brave!":"Yes, I'm brave!","Yes, please break my backup!":"Yes, please break my backup!","Yesterday":"Yesterday","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"You are changing the database path away from an existing database.\nAre you sure this is what you want?","You are currently running {{appname}} {{version}}":"You are currently running {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.","You have chosen to restore to a new location, but not entered one":"You have chosen to restore to a new location, but not entered one","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.","You must choose at least one source folder":"You must choose at least one source folder","You must enter a domain name to use v3 API":"You must enter a domain name to use v3 API","You must enter a name for the backup":"You must enter a name for the backup","You must enter a passphrase or disable encryption":"You must enter a passphrase or disable encryption","You must enter a password to use v3 API":"You must enter a password to use v3 API","You must enter a positive number of backups to keep":"You must enter a positive number of backups to keep","You must enter a tenant (aka project) name to use v3 API":"You must enter a tenant (aka project) name to use v3 API","You must enter a valid duration for the time to keep backups":"You must enter a valid duration for the time to keep backups","You must enter a valid retention policy string":"You must enter a valid retention policy string","You must fill in the password":"You must fill in the password","You must fill in the server name or address":"You must fill in the server name or address","You must fill in the username":"You must fill in the username","You must fill in {{field}}":"You must fill in {{field}}","You must select or fill in the AuthURI":"You must select or fill in the AuthURI","You must select or fill in the server":"You must select or fill in the server","You must specify a path":"You must specify a path","Your files and folders have been restored successfully.":"Your files and folders have been restored successfully.","Your passphrase is easy to guess. Consider changing passphrase.":"Your passphrase is easy to guess. Consider changing passphrase.","bucket/folder/subfolder":"bucket/folder/subfolder","byte":"byte","byte/s":"byte/s","custom":"custom","resume now":"resume now","unless you are explicitly specifying --group-id":"unless you are explicitly specifying --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} files ({{size}}) to go {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Hour","{{number}} Hours":"{{number}} Hours","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (took {{duration}})"}); + gettextCatalog.setStrings('es', {"- pick an option -":"- escoja una opción -","...loading...":"...cargando...","API key":"Clave API","AWS Access ID":"AWS Acceso ID","AWS Access Key":"AWS Clave de aceso","AWS IAM Policy":"AWS IAM Política","About":"Acerca de","About {{appname}}":"Acerca de {{appname}}","Access Key":"Clave de acceso","Access denied":"Acceso denegado","Access grant":"Acceso concedido","Access to user interface":"Acceso a la interfaz de usuario","Account name":"Nombre de la cuenta","Add a new backup":"Añadir nueva copia de seguridad","Add a path directly":"Agregar la ruta directamente","Add advanced option":"Añadir opción avanzada","Add backup":"Añadir copia de seguridad","Add filter":"Añadir filtro","Add path":"Añadir ruta","Added":"Agregado","Adjust bucket name?":"¿Ajustar el nombre del deposito?","Advanced Options":"Opciones Avanzadas","Advanced options":"Opciones avanzadas","Advanced:":"Avanzado:","All Hyper-V Machines":"Todas las máquinas de Hyper-V","All Microsoft SQL Databases":"Las bases de datos de Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos los informes de uso son enviados anónimamente y no contienen ninguna información personal. Contiene información sobre hardware y sistema operativo, el tipo de respaldo, duración de copia de seguridad, tamaño de fuente de datos y similares. No contiene rutas, nombres de archivos, nombres de usuarios, contraseñas o información sensible similar.","Allow remote access (requires restart)":"Permitir el acceso remoto (requiere reiniciar)","Allowed days":"Días permitidos","An existing file was found at the new location":"Se encontró un archivo existente en la nueva ubicación","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Se encontró un archivo existente en la nueva ubicación\n¿Está seguro que desea que la base de datos apunte a un archivo existente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Se ha encontrado una base de datos local existente para el almacenamiento.\nVolver a utilizar la base de datos permitirá a las instancias de línea de comandos y al servidor trabajar con el mismo almacenamiento remoto.\n\n¿Desea utilizar la base de datos existente?","Anonymous usage reports":"Informes de uso anónimos","Applications":"Aplicaciones","As Command-line":"Como Línea de comandos","AuthID":"AuthID","Authentication method":"Método de autentificación","Authentication method ({{auth_method}})":"Método de autentificación ({{auth_method}})","Authentication password":"Contraseña de autenticación","Authentication username":"Nombre de usuario de autenticación","Autogenerated passphrase":"Autogenerar frase de seguridad","B2 Application ID":"ID de la aplicación B2","B2 Application Key":"B2 clave de aplicación","B2 Cloud Storage Account ID":"B2 Cuenta Cloud Storage ID","B2 Cloud Storage Application ID":"ID de la aplicación de almacenamiento en la nube B2","B2 Cloud Storage Application Key":"B2 Clave de aplicación de Cloud Storage","Back":"Volver","Backup complete!":"Respaldo completo!","Backup destination":"Destino de la copia de seguridad","Backup location":"Ubicación de la copia de seguridad","Backup retention":"Conservación de copia de respaldo","Backup:":"Copia de seguridad:","Beta":"Beta","Broken access":"Acceso roto","Browse":"Navega","Browser default":"Navegador por defecto","Bucket create location":"Crear la ubicación del depósito","Bucket name":"Nombre del depósito","Bucket storage class":"Categoría de almacenamiento del depósito","Building list of files to restore …":"Creando una lista de archivos para restaurar ...","Building partial temporary database …":"Construyendo una base de datos parcial temporal ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Permitiendo el acceso remoto, el servidor atenderá requerimientos desde\ncualquier equipo de su red. Si Ud. habilita esta opción, asegurese siempre de usar\nla computadora dentro de una red protegida por un firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"De forma predeterminada, el icono de la bandeja abrirá la interfaz de usuario con un token que desbloquea la interfaz de usuario. Esto asegura que pueda acceder a la interfaz de usuario desde el icono de la bandeja, mientras que requiere que otros ingresen una contraseña. Si prefiere tener que escribir la contraseña, incluso al acceder a la interfaz de usuario desde el icono de la bandeja, habilite esta opción.","Cache Files":"Archivos caché","Canary":"Experimental e inestable (Canary)","Cancel":"Cancelar","Cannot move to existing file":"No se puede mover al archivo existente","Changelog":"Registro de cambios","Changelog for {{appname}} {{version}}":"Registro de cambios para {{appname}} {{version}}","Check failed:":"Error en chequeo:","Check for updates now":"Comprobar actualizaciones ahora","Checking for updates …":"Buscando actualizaciones ...","Chose a storage type to get started":"Elija un tipo de almacenamiento para empezar","Click the AuthID link to create an AuthID":"Haga clic en el enlace de AuthID para crear una AuthID","Click to set throttle options":"Acceda para opciones de aceleración","Client library to use":"Biblioteca cliente para usar","Commandline …":"Línea de comandos ...","Compact Phase":"Fase de compactación","Compact now":"Compactar ahora","Compacting remote data …":"Compactando datos remotos ...","Complete log":"Registro completo","Completing backup …":"Completando copia de seguridad ...","Completing previous backup …":"Completando copia de seguridad precia ...","Computer":"Ordenador","Configuration file:":"Archivo de configuración:","Configuration:":"Configuración:","Configure a new backup":"Configurar nueva copia de seguridad","Confirm delete":"Confirmar borrado","Confirm encryption passphrase":"Confirmar frase de seguridad cifrada","Confirm passphrase":"Confirme contraseña","Confirmation required":"Confirmación necesaria","Connect":"Conectar","Connect now":"Conectar ahora","Connecting to server …":"Conectando al servidor ...","Connection lost":"Conexión perdida","Connection worked!":"¡La conexión funcionó!","Container name":"Nombre del contenedor","Container region":"Contenedor de región","Continue":"Continuar","Continue without encryption":"Continuar sin cifrado","Copied!":"¡Copiado!","Copy":"Copia","Copy Destination URL to Clipboard":"Copiar la URL de destino al portapapeles","Copy failed. Please manually copy the URL":"Copía fallida. Por favor, copia manualmente la dirección URL","Core options":"Opciones de base","Counting ({{files}} files found, {{size}})":"Contando ({{files}} archivos encontrados, {{size}})","Crashes only":"Sólo bloqueos","Create bug report …":"Crear informe de errores ...","Create folder?":"¿Crear carpeta?","Created new limited user":"Creó un nuevo usuario limitado","Creating bug report …":"Creando informe de errores ...","Creating new user with limited access …":"Creando nuevo usuario con acceso limitado ...","Creating target folders …":"Creando carpetas de destino …","Creating temporary backup …":"Creando copia de seguridad temporal ...","Current action:":"Proceso actual:","Current file:":"Archivo actual:","Current version is {{versionname}} ({{versionnumber}})":"La versión actual es {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Personalizada S3 endpoint","Custom Satellite":"Satélite personalizado","Custom Satellite ({{satellite}})":"Satélite personalizado ({{satellite}})","Custom authentication url":"Url de autenticación personalizada","Custom backup retention":"Conservación de copia de respaldo personalizada","Custom location ({{server}})":"Ubicación personalizada ({{server}})","Custom region for creating buckets":"Región personalizada para la creación de depósitos","Custom region value ({{region}})":"Personalizar el valor de la región ({{region}})","Custom server url ({{server}})":"Url del servidor personalizada ({{server}})","Custom storage class ({{class}})":"Categoría de almacenamiento personalizado ({{class}})","Database …":"Base de datos ...","Days":"Días","Default":"Por defecto","Default ({{channelname}})":"({{channelname}}) por defecto","Default excludes":"Exclusiones por defecto","Default options":"Opciones por defecto","Delete":"Eliminar","Delete Phase (Old Backup Versions)":"Elimine Fase (Versiones Antiguas del Respaldo)","Delete backup":"Eliminar copia de seguridad","Delete backups that are older than":"Eliminar copias de seguridad que tengan mas de","Delete local database":"Eliminar base de datos local","Delete remote files":"Eliminar archivos remotos","Delete the local database":"Eliminar la base de datos local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"¿Eliminar {{filecount}} archivos con ({{filesize}}) del almacenamiento remoto?","Delete …":"Eliminar ...","Deleted":"Eliminado","Deleted Versions":"Versiones eliminadas","Deleted files":"Archivos eliminados","Deleting remote files …":"Eliminando archivos remotos ...","Deleting unwanted files …":"Eliminando archivos no deseados ...","Description (optional)":"Descripción (opcional)","Description:":"Descripción:","Desktop":"Escritorio","Destination":"Destino","Destination path":"Ruta de destino","Disabled":"Desactivar","Dismiss":"Descartar","Dismiss all":"Ignorar todo","Display and color theme":"Apariencia y esquema de colores","Do you really want to delete the backup: \"{{name}}\" ?":"¿Realmente desea eliminar la copia de seguridad: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Realmente desea eliminar la base de datos local: {{name}}","Done":"Hecho","Download":"Descargar","Downloaded files":"Ficheros descargados","Downloading files …":"Descargando archivos ...","Downloading update…":"Descargando actualización ...","Duplicate option {{opt}}":"Opciones de duplicado {{opt}}","Duplicati Website":"Sitio Web Duplicati","Duplicati forum":"Foro de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati se ejecutará cuando inicie, pero permanecerá en stand-by mientras se ejecute.\nDuplicati ocupará minimos recursos del sistema y ningúna tarea de respaldo se ejectutará.","Duration":"Duración","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada copia tiene una base de datos local asociada que almacena información sobre la copia de seguridad remota en la máquina local.\nAl eliminar una copia de seguridad, también puede borrar la base de datos local sin afectar a la habilidad de restaurar los archivos remotos.\nSi está utilizando la base de datos local para copias de seguridad desde la línea de comandos, debe mantener la base de datos.","Edit as list":"Editar lista","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Cifrar archivo","Encryption":"Cifrado","Encryption changed":"Cambios de cifrado","Encryption passphrase":"Contraseña de cifrado","End":"Fin","Enter URL":"Introduzca URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ingrese una estrategia de retención en forma manual. Los campos son D/W/Y para dias/semanas/años y U para \"ilimitado\". La sintaxis es: 7D:1D,4W:1W,36M:1M. Este ejemplo mantiene una copia para cada uno de los 7 dias, una para cada una de las 4 semanas y una por cada uno de los próximos 36 meses. Esto también puede escribirse como 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduzca la frase de seguridad, si la hay","Enter configuration details":"Introduzca los detalles de configuración","Enter encryption passphrase":"Introduzca la frase de seguridad","Enter expression here":"Introduzca aquí la expresión","Enter the destination path":"Introduzca la ruta de destino","Error":"Error","Error!":"¡Error!","Errors and crashes":"Errores y bloqueos","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir directorios cuyos nombres contienen","Exclude expression":"Excluir expresión","Exclude file":"Excluir archivos","Exclude file extension":"Excluir extensión de archivo","Exclude files whose names contain":"Excluir archivos cuyos nombres contengan","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Excluir la carpeta","Exclude regular expression":"Excluir la expresión regular","Existing file found":"Archivo existente encontrado","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuración de copia de seguridad","Export configuration":"Exportar configuración","Export passwords":"Exportar contraseñas","Export …":"Exportar ...","Exporting …":"Exportando ...","External link":"Enlace externo","FTP (Alternative)":"FTP (Alternativa)","Failed to build temporary database: {{message}}":"Error al crear base de datos temporal: {{message}}","Failed to connect:":"Fallo al conectar:","Failed to connect: {{message}}":"No se pudo conectar: {{message}}","Failed to delete:":"Error al eliminar:","Failed to fetch path information: {{message}}":"Error al recuperar información de la ruta: {{message}}","Failed to find backup:":"Error para encontrar respaldo:","Failed to read backup defaults:":"Error al leer los valores predeterminados de copia de seguridad:","Failed to restore files: {{message}}":"Fallo al restaurar archivos: {{message}}","Failed to save:":"Error al guardar:","Fetching path information …":"Obteniendo información de ruta ...","File":"Archivo","Files larger than:":"Archivos que superen:","Filters":"Filtros","Finished!":"¡Terminado!","First run setup":"Configuración de primera ejecución","Folder":"Carpeta","Folder path":"Ruta de la carpeta","Fri":"Vie","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Proyecto ID","General":"General","General backup settings":"Configuración general de la copia de seguridad","General options":"Opciones generales","Generate":"Generar","Generate IAM access policy":"Generar política de acceso IAM","Getting file versions …":"Obteniendo versiones de archivos ...","Group email":"Correo del grupo","Hidden files":"Archivos ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar carpetas ocultas","Home":"Inicio","Hostnames":"Nombres de host","Hours":"Horas","How do you want to handle existing files?":"¿Cómo desea manejar los archivos existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si la fecha se paso, se ejecutará el trabajo tan pronto como sea posible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si al menos una copia mas nueva es encontrada, todas las copias anteriores\na ese día s eliminarán.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si no introduce una ruta, todos los archivos se almacenarán en la carpeta de inicio de sesión.\n¿Está seguro que es lo que quiere?","If you do not enter an API Key, the tenant name is required":"Si no introduce una clave API, requerirá el nombre de cliente","Import":"Importar","Import Destination URL":"Importar Destino URL","Import backup configuration":"Importar configuración de copias de seguridad","Import from a file":"Importar desde un archivo","Import metadata":"Importar metadatos","Importing …":"Importando ...","Include a file?":"¿Incluir un archivo?","Include expression":"Incluir una expresión","Include regular expression":"Incluir una expresión regular","Incorrect answer, try again":"Respuesta incorrecta, intente de nuevo","Individual builds for developers only. Not for use with important data.":"Compilaciones individuales solo para desarrolladores. No usar con datos importantes.","Information":"Información","Invalid characters in path":"Caracteres no válidos en la ruta","Invalid retention time":"Tiempo de retención no válido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Es posible conectar a un FTP sin contraseña.\n¿Está seguro que su servidor FTP admite los inicios de sesión sin contraseña?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Mantener un número específico de copias de seguridad","Keep all backups":"Mantener todas las copias de seguridad","Keystone API version":"Versión de la API de Keystone","Language in user interface":"Idioma de interfaz de usuario","Last month":"Mes pasado","Last successful backup:":"Última copia de seguridad exitosa","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauración exitosa: {{time}} (took {{duration || '0 seconds'}})","Latest":"Más reciente","Libraries":"Librerías","Listing backup dates …":"Listando fechas de las copias de seguridad","Listing remote files for purge …":"Listando archivos remotos para purgar ...","Listing remote files …":"Listando archivos remotos ...","Live":"En vivo","Load a configuration from an exported job or a storage provider":"Cargar una configuración desde un trabajo exportado o un proveedor de almacenamiento","Load destination from an exported job or a storage provider":"Cargar un destino desde un trabajo exportado o un proveedor de almacenamiento","Load older data":"Cargar datos anteriores","Loading …":"Cargando ...","Local database path:":"Ruta de la base de datos local:","Local repository":"Repositorio local","Local storage":"Almacenamiento local","Location":"Localización","Location where buckets are created":"La ubicación donde se crean los depósitos","Log data for {{Backup.Backup.Name}}":"Registrar datos para {{Backup.Backup.Name}}","Log data from the server":"Registrar datos desde el servidor","Log out":"Desconectar","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Mantenimiento","Manually type path":"Escribir manualmente la ruta","Max download speed":"Velocidad máxima de descarga","Max upload speed":"Velocidad máxima de carga","Menu":"Menú","Microsoft SQL Database:":"Base de datos Microsoft SQL:","Microsoft SQL Databases":"Bases de datos Microsoft SQL:","Minimum redundancy":"Redundancia mínima","Minimum redundancy is 1.0":"Redundancia mínima es 1.0","Minutes":"Minutos","Missing name":"Falta el nombre","Missing passphrase":"Falta la frase de seguridad","Missing sources":"Faltan las fuentes","Modified":"Modificado","Mon":"Lun","Months":"Meses","Move existing database":"Mover base de datos existente","Move failed:":"Fallos al mover:","My Documents":"Mis Documentos","My Music":"Mi Música","My Photos":"Mis Fotos","My Pictures":"Mis Imágenes","Name":"Nombre","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"El nuevo nombre de usuario es {{user}}.\nCredenciales actualizadas para el nuevo usuario restringido","Next":"Siguiente","Next scheduled run:":"Siguiente ejecución programada:","Next scheduled task:":"Siguiente tarea programada:","Next task:":"Siguiente tarea:","Next time":"La próxima vez","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No se especificó previamente un certificado, por favor verifica con el administrador del servidor que la llave es correcta: {{key}}\n\n¿Desea aprobar la llave del host reportada?","No editor found for the "{{backend}}" storage type":"Ningún editor para el "{{backend}}" tipo de almacenamiento","No encryption":"Sin cifrado","No items selected":"No hay artículos seleccionados","No items to restore, please select one or more items":"No hay artículos para restaurar, seleccione uno o más elementos","No passphrase entered":"No se introdujo clave de seguridad","No scheduled tasks":"No hay tareas programadas","Non-matching passphrase":"No coincide la frase de seguridad","None / disabled":"Ninguno / desactivado","Not using encryption":"Sin usar cifrado","Nothing will be deleted. The backup size will grow with each change.":"Nada será borrado. El tamaño de la copia de seguridad aumentará con cada cambio.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Una vez que haya más copias de seguridad que el número especificado, se eliminarán las copias de seguridad más antiguas.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Abierto","Operating System":"Sistema operativo","Operation":"Operación","Operations:":"Operaciones:","Optional authentication password":"Contraseña de autentificación opcional","Optional authentication username":"Nombre de usuario para autentificación opcional","Options":"Opciones","Original location":"Localización original","Others":"Otros","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Con el tiempo, las copias de seguridad se eliminarán automáticamente. Seguirá habiendo una copia de seguridad para cada uno de los últimos 7 días, cada una de las últimas 4 semanas, cada uno de los últimos 12 meses. Siempre permanecerá, al menos, una copia de seguridad.","Overwrite":"Sobrescribir","Passphrase":"Frase de seguridad","Passphrase (if encrypted)":"Frase de seguridad (con cifrado)","Passphrase changed":"Frase de seguridad cambiada","Passphrases are not matching":"Las frases de seguridad no coinciden","Passphrases do not match":"Las frases de seguridad no coinciden","Password":"Contraseña","Patching files with local blocks …":"Parchear archivos con bloques locales","Path":"Ruta","Path not found":"Ruta no encontrada","Path on server":"Ruta del servidor","Path or subfolder in the bucket":"Ruta o subcarpeta en el depósito","Pause":"Pausa","Pause after startup or hibernation":"Pausar después del arranque o de hibernación","Pause options":"Opciones de pausa","Permissions":"Permisos","Pick location":"Elegir ubicación","Point to your backup files and restore from there":"Indique sus ficheros de copia de seguridad y restáurelos desde allí","Port":"Puerto","Prevent tray icon automatic log-in":"Impedir el inicio de sesión automático con el icono de la bandeja","Previous":"Anterior","Progress:":"Progreso","ProjectID is optional if the bucket exist":"ProjectID es opcional si el depósito existe","Proprietary":"Propietario","Purge Phase":"Fase de purgado","Purging files complete!":"¡Purgado de ficheros finalizado!","Purging files …":"Purgando archivos ...","Rebuilding local database …":"Reconstruyendo base de datos local ...","Recreate (delete and repair)":"Recrear (borrar y reparar)","Recreate Database Phase":"Fase de recreación de base de datos","Recreating database …":"Recreando base de datos …","Registering temporary backup …":"Registrando copia de seguridad temporal …","Relative paths not allowed":"No se permiten rutas relativas","Reload":"Recargar","Remote":"Remoto","Remote Path":"Ruta Remota","Remote Repository":"Repositorio Remoto","Remote path":"Ruta remota","Remote repository":"Repositorio remoto","Remote volume size":"Tamaño de volumen remoto","Remove":"Quitar","Remove option":"Quitar opción","Removed files":"Ficheros borrados","Repair":"Reparar","Repair Phase":"Fase de reparación","Repairing database …":"Reparando base de datos…","Repeat Passphrase":"Repita la frase de seguridad","Reporting:":"Reportando:","Reset":"Resetear","Restore":"Restaurar","Restore complete!":"¡Restauración finalizada!","Restore files":"Restaurar archivos","Restore files …":"Restaurando archivos ...","Restore from":"Restaurar desde","Restore from backup configuration":"Restaurar desde una configuración de copia de seguridad","Restore options":"Opciones de restauración","Restore read/write permissions":"Restaurar permisos de lectura/escritura","Restored Files":"Archivos Restaurados","Restored Folders":"Carpetas Restauradas","Restored Symlinks":"Symlinks restaurados","Restoring files …":"Restaurando archivos ....","Resume":"Resumir","Rewritten File Lists":"Listas de ficheros reescritos","Run again every":"Volver a ejecutar cada","Run now":"Ejecutar ahora","Running commandline entry":"Ejecutando entrada de linea de comandos","Running task:":"Ejecutando tarea:","Running …":"Ejecutando ...","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Igual que la versión base instalada: {{channelname}}","Sat":"Sab","Satellite":"Satélite","Save":"Guardar","Save and repair":"Guardar y reparar","Save different versions with timestamp in file name":"Guardar diferentes versiones con fecha y hora en el nombre de archivo","Save immediately":"Guardar inmediatamente","Scanning existing files …":"Escaneando archivos existentes ...","Scanning for local blocks …":"Buscando bloques locales…","Schedule":"Horario","Search":"Buscar","Search for files":"Buscar archivos","Seconds":"Segundos","Select a log level and see messages as they happen:":"Seleccione un nivel de registro y vea los mensajes a medida que ocurren:","Select files":"Seleccionar ficheros","Server":"Servidor","Server and port":"Servidor y puerto","Server hostname or IP":"Nombre del servidor o IP","Server is currently paused,":"El servidor se encuentra en pausa,","Server is currently paused, do you want to resume now?":"El servidor se encuentra en pausa, ¿quiere reanudar ahora?","Server password":"Contraseña del servidor","Server paused":"Servidor pausado","Server state properties":"Propiedades del estado del servidor","Settings":"Configuraciones","Show":"Mostrar","Show advanced editor":"Mostrar el editor avanzado","Show hidden folders":"Mostrar carpetas ocultas","Show log":"Mostrar registro","Show log …":"Mostrar registro …","Show treeview":"Mostrar vista de árbol","Sia server password":"Contraseña del servidor Sia","Smart backup retention":"Retención de copias inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Algunos proveedores de OpenStack permiten una clave API en lugar de un nombre del cliente y contraseña","Some S3 providers might only be compatible with a certain client library":"Es posible que algunos proveedores de S3 solo sean compatibles con una biblioteca de cliente determinada","Source Data":"Datos de Origen","Source Files":"Archivos de origen","Source data":"Datos de origen","Source folders":"Carpetas de origen","Source:":"Origen:","Specific builds for developers only. Not for use with important data.":"Compilaciones específicas solo para desarrolladores. No usar con datos importantes.","Standard protocols":"Protocolos estándar","Start":"Comenzar","Starting backup …":"Comenzando copia de seguridad","Starting restore …":"Comenzando restauración ...","Starting the restore process …":"Comenzando el proceso de restauración ...","Stop after the current file":"Detener después del archivo actual","Stop running backup":"Detener respaldo en curso","Stop running task":"Detener tarea en ejecución","Stopping after the current file:":"Parando después del archivo actual:","Stopping task:":"Deteniendo tarea:","Storage Type":"Tipo de Almacenamiento","Storage class":"Categoría de almacenamiento","Storage class for creating a bucket":"Categoría de almacenamiento para la creación de un depósito","Stored":"Almacenados","Strong":"Fuerte","Success":"Éxito","Sun":"Dom","Symbolic link":"Enlace simbólico","System Files":"Archivos del sistema","System default ({{levelname}})":"Sistema por defecto ({{levelname}})","System files":"Archivos de sistema","System info":"Información del sistema","System properties":"Propiedades del sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"La tarea está ejecutandose","Temporary Files":"Archivos temporales","Temporary files":"Archivos temporales","Test Phase":"Fase de pruebas","Test connection":"Conexión de prueba","Testing permissions …":"Probando permisos…","Testing …":"Probando ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"El campo '{{fieldname}}' contiene un carácter no válido: {{carácter}} (valor: {{valor}}, índice: {{pos}})","The backup is missing, has it been deleted?":"Falta la copia de seguridad, ¿se ha eliminado?","The backup was temporary and does not exist anymore, so the log data is lost":"La copia de seguridad era temporal y ya no existe, por lo que los datos de registro se han perdido.","The bucket name should be all lower-case, convert automatically?":"El nombre del depósito debe ser todo en minúsculas, ¿convertir automáticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuración debe mantenerse segura. ¿Está seguro de que desea guardar un archivo sin cifrar que contenga sus contraseñas?","The dark theme (by Michal)":"Tema oscuro (por Michal)","The default blue on white theme (by Alex)":"Tema por defecto azul sobre blanco (por Alex)","The folder {{folder}} does not exist.\nCreate it now?":"La carpete {{carpeta}} no existe.\n¿La creo ahora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clave de host fue cambiada, compruebe con el administrador del servidor si esto es correcto, de lo contrario usted podría ser víctima de un ataque MAN-IN-THE-MIDDLE.\n\n¿Desea REMPALAZAR su ACTUAL clave de host \"{{prev}}\" con la clave del host REGISTRADA: {{key}}?","The passwords do not match":"Las contraseñas no coinciden","The path does not appear to exist, do you want to add it anyway?":"La ruta parece que no existe, ¿desea agregar de todos modos?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"La ruta no termina con un carácter '{{dirsep}}', que significa que incluye un archivo, no una carpeta.\n\n¿Desea incluir el archivo especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"La ruta debe ser una ruta absoluta, es decir, debe comenzar con una barra '/'","The region parameter is only applied when creating a new bucket":"El parámetro de la región sólo se aplica al crear un nuevo depósito","The region parameter is only used when creating a bucket":"El parámetro de la región sólo se utiliza al crear un depósito","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"El certificado del servidor no puede ser validado.\n¿Quieres aprobar el certificado SSL con el hash: {{hash}}?","The storage class affects the availability and price for a stored file":"La categoría de almacenamiento afecta la disponibilidad y precio de un archivo almacenado","The target folder contains encrypted files, please supply the passphrase":"La carpeta de destino contiene archivos encriptados, por favor suministra la frase de seguridad","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"El usuario tiene demasiados permisos. ¿Quieres crear un usuario nuevo, con sólo permisos para la ruta seleccionada?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Esta copia de seguridad fue creada en otro sistema operativo. Restaurar estos ficheros sin indicar una carpeta de destino puede provocar que sean restaurados en ubicaciones imprevistas ¿Está seguro de que quiere continuar sin elegir una carpeta de destino?","This month":"Este mes","This week":"Esta semana","Throttle settings":"Ajustes de aceleración.","Thu":"Jue","Time":"Hora","To File":"A archivo","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que desea eliminar todos los archivos remotos \"{{name}}\", por favor ingrese la palabra que ves abajo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sin una frase de seguridad, desactive la casilla \"Cifrar el archivo\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar varios ataques basados en DNS, Duplicati limita los nombres de anfitriones permitidos a los que se enumeran aquí. El acceso directo a IP y al anfitrión local siempre está permitido. Se pueden proporcionar varios nombres de anfitrión con un separador de punto y coma. Si alguno de los nombres de anfitrión permitidos es un asterisco (*), todos los nombres de anfitrión están permitidos y esta función está desactivada. Si el campo está vacío, solo se permite el acceso a la dirección IP y al anfitrión local.","Today":"Hoy","Trust host certificate?":"¿Confiar en el certificado del host?","Trust server certificate?":"¿Confiar en el certificado del servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probar las nuevas funciones en las que estamos trabajando. Actualmente, la versión más estable disponible. Pruebe a Restaurar los datos antes de usarlo en entornos de producción","Tue":"Mar","Type passphrase here.":"Escriba la frase de seguridad aquí.","Type to highlight files":"Tipo para seleccionar archivos","Unknown backup size and versions":"Tamaño y versiones de la copia de seguridad desconocidas","Until resumed":"Hasta reanudar","Update channel":"Canal de actualización","Update failed:":"Error de actualización:","Updating with existing database":"Actualizando la base de datos existente","Uploaded files":"Archivos subidos","Uploading verification file …":"Subiendo archivo de verificación…","Usage statistics":"Estadísticas de uso","Usage statistics, warnings, errors, and crashes":"Estadísticas de uso, advertencias, errores y bloqueos","Use SSL":"Usar SSL","Use existing database?":"¿Usar base de datos existente?","Use weak passphrase":"Uso de frase de seguridad débil","Useless":"Inútil","User data":"Datos de usuario","User domain name":"Nombre de dominio de usuario","User has too many permissions":"El usuario tiene demasiados permisos","User interface settings":"Preferencias de la interfaz de usuario","Username":"Nombre de usuario","Vacuuming database …":"Limpiando la base de datos ...","Validating …":"Validando ...","Verifications":"Verificaciones","Verify files":"Verificar archivos","Verifying answer":"Verificando respuesta","Verifying backend data …":"Verificando datos del servidor ...","Verifying files …":"Verificando archivos ...","Verifying remote data …":"Verificando datos remotos ...","Verifying restored files …":"Verificando archivos restaurados ...","Verifying …":"Verificando ...","Version ID":"ID de versión","Very strong":"Muy fuerte","Very weak":"Muy débil","Visit us on":"Visítenos en","WARNING: This will prevent you from restoring the data in the future.":"ADVERTENCIA: Esto le impedirá restaurar los datos en el futuro.","Waiting for task to begin":"Esperando que se inicie la tarea","Waiting for upload to finish …":"Esperando a que finalice la carga …","Warnings, errors and crashes":"Advertencias, errores y bloqueos","We recommend that you encrypt all backups stored outside your system":"Recomendamos cifrar todas las copias de seguridad almacenadas fuera de su sistema","Weak":"Débil","Weak passphrase":"Frase de seguridad débil","Wed":"Mié","Weeks":"Semanas","Where do you want to restore from?":"¿Desde dónde quiere restaurar?","Where do you want to restore the files to?":"¿Dónde desea restaurar los archivos?","Years":"Años","Yes":"Sí","Yes, I have stored the passphrase safely":"Sí, he guardado la frase de seguridad de forma segura","Yes, I understand the risk":"Sí, entiendo el riesgo","Yes, I'm brave!":"Sí, ¡soy valiente!","Yes, please break my backup!":"Sí, por favor, ¡rompe mi copia de seguridad!","Yesterday":"Ayer","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Está cambiando la ruta de la base de datos de una base de datos existente.\n¿Realmente es lo que quieres?","You are currently running {{appname}} {{version}}":"Actualmente está ejecutando {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Ha cambiado el modo de encriptación. Esto puede quebrar cosas. Le animamos a crear una nueva copia de seguridad en su lugar","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Ha cambiado la frase de seguridad, la cual no es compatible. Le animamos a crear una nueva copia de seguridad en su lugar.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Ha optado por no cifrar la copia de seguridad. El cifrado se recomienda para todos los datos almacenados en un servidor remoto.","You have chosen to restore to a new location, but not entered one":"Ha elegido restaurar a una nueva ubicación, pero no la ha indicado","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Ha generado una frase de contraseña segura. Asegúrese de haber hecho una copia segura de la frase de contraseña, ya que los datos no se pueden recuperar si la pierde.","You must choose at least one source folder":"Debe seleccionar al menos una carpeta de origen","You must enter a domain name to use v3 API":"Debe ingresar un nombre de dominio para usar la API v3","You must enter a name for the backup":"Debe introducir un nombre para la copia de seguridad","You must enter a passphrase or disable encryption":"Debe ingresar una frase de seguridad o deshabilitar el cifrado","You must enter a password to use v3 API":"Debe ingresar una contraseña para usar la API v3","You must enter a positive number of backups to keep":"Debe especificar un número positivo de copias de seguridad a guardar","You must enter a tenant (aka project) name to use v3 API":"Debe ingresar un nombre de cliente (también conocido como proyecto) para usar la API v3","You must enter a valid duration for the time to keep backups":"Debe introducir una duración válida para el tiempo de retención de las copias de seguridad","You must enter a valid retention policy string":"Debes ingresar una cadena de política de retención válida","You must fill in the password":"Debe rellenar la contraseña","You must fill in the server name or address":"Debe introducir el nombre del servidor o la dirección","You must fill in the username":"Debe rellenar el nombre de usuario","You must fill in {{field}}":"Debe rellenar el {{field}}","You must select or fill in the AuthURI":"Debe seleccionar o rellenar la AuthURI","You must select or fill in the server":"Debe seleccionar o rellenar en el servidor","You must specify a path":"Debe especificar una ruta de acceso","Your files and folders have been restored successfully.":"Los archivos y carpetas han sido restaurados con éxito.","Your passphrase is easy to guess. Consider changing passphrase.":"Tu frase de seguridad es fácil de adivinar. Considere cambiarla.","bucket/folder/subfolder":"depósito/carpeta/subcarpeta","byte":"byte","byte/s":"byte/s","custom":"Personalizar","resume now":"reanudar ahora","unless you are explicitly specifying --group-id":"a menos que usted haya especificando explícitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} fue desarrollado principalmente por {{dev1}} y {{dev2}}. Puede descargarse {{appname}} desde {{websitename}}. {{appname}} está licenciado bajo {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} ficheros ({{size}}) para finalizar {{speed_txt}} ","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versión","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versiones","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versiones"],"{{number}} Hour":"{{number}} Hora","{{number}} Hours":"{{número}} Horas","{{number}} Minutes":"{{number}} Minutos","{{time}} (took {{duration}})":"{{time}} (llevó {{duration}})"}); + gettextCatalog.setStrings('fi', {"- pick an option -":"- Valitse jokin vaihtoehto -","...loading...":"...ladataan...","API key":"API-avain","AWS Access ID":"Tunniste \"Access Key ID\" palveluun AWS","AWS Access Key":"Tunniste \"Access Key ID\" palveluun AWS","AWS IAM Policy":"Palvelun AWS IAM-asetukset","About":"Tietoja","About {{appname}}":"Tietoja sovelluksesta {{appname}}","Access Key":"Pääsyavain","Access denied":"Pääsy evätty","Access to user interface":"Käyttöoikeus käyttöliittymään","Account name":"Käyttäjätunnus","Add a new backup":"Lisää uusi varmuuskopio","Add a path directly":"Lisää suora polku","Add advanced option":"Anna harvoin tarvittava valitsin","Add backup":"Lisää varmuuskopio","Add filter":"Lisää suodatin","Add path":"Lisää polku","Added":"Lisätty","Adjust bucket name?":"Muuta ämpärin nimeä?","Advanced Options":"Harvoin tarvittavat valitsimet","Advanced options":"Harvoin tarvittavat valitsimet","Advanced:":"Harvoin tarvittavat asetukset","All Hyper-V Machines":"Kaikki Hyper-V-virtuaalikoneet","All Microsoft SQL Databases":"Kaikki Microsoft SQL -tietokannat","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Kaikki käyttöraportit lähetetään anonyymisti. Ne eivät sisällä mitään henkilökohtaisia tietoja. Raportit sisältävät tietoja laitteistosta ja käyttöjärjestelmästä, käytetystä etäpalvelusta, varmuuskopion kestosta, varmuuskopioitavan datan määrästä yms.Raportit eivät sisällä polkuja, tiedostonimiä, käyttäjätunnuksia, salasanoja tai vastaavia tietoja.","Allow remote access (requires restart)":"Salli etäyhteydet (Vaatii Duplicatin uudeleenkäynnistämisen)","Allowed days":"Sallitut päivät","An existing file was found at the new location":"Olemassaoleva tiedosto löydettiin uudesta paikasta","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Annettu tietokanta on jo olemassa.\nOletko varma, että haluat käyttää olemassaolevaa tietokantaa?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Löydettiin olemassaoleva paikallinen tietokanta tälle varmuuskopiolle.\nSaman tietokannan käyttäminen mahdollistaa kometorivi-ohjelman ja palvelimen käyttämisen saman varmuuskopion kanssa.\n\nHaluatko käyttää samaa tietokantaa?","Anonymous usage reports":"Anonyymit käyttöraportit","Applications":"Sovellukset","As Command-line":"Komentona","AuthID":"AuthID","Authentication method":"Tunnistautumistapa","Authentication password":"Kirjautumissalasana","Authentication username":"Käyttäjätunnus","Autogenerated passphrase":"Automaattisesti luoto salauslause","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"Tunnus B2 Cloud Storage Account ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Palaa","Backup complete!":"Varmuuskopiointi valmis!","Backup destination":"Sijainti, johon varmuuskopio tehdään","Backup location":"Varmuuskopion sijainti","Backup:":"Varmuuskopio:","Beta":"Beta","Broken access":"Pääsy epäonnistui","Browse":"Selaa","Browser default":"Selaimen oletusasetus","Bucket create location":"Luo ämpäri sijaintiin","Bucket name":"Ämpärin nimi","Bucket storage class":"Ämpärin tallennusluokka","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Sallimalla etäyhteyden ohjelmisto kuuntelee pyyntöjä miltä tahansa laitteelta verkossa. Jos sallit tämän, varmista että tietokoneesi on aina palomuurilla suojatussa verkossa.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Oletuksena huomautusalueen kuvake avaa käyttöliittymän ja poistaa käyttöliitymän lukituksen erillisellä valtuutuksella. Tämä mahdollistaa käyttöliittymän käytön huomautusalueen kuvakkeesta ilman salasanaa, vaikka muille käyttöliittymä on salasanasuojattu. Jos haluat käyttää salasanaa myös huomatusalueen kuvakkeen kanssa, valitse tämä valinta.","Cache Files":"Välimuistitiedostot","Canary":"Canary","Cancel":"Peruuta","Cannot move to existing file":"Ei voida korvata olemassaolevaa tiedostoa","Changelog":"Muutokset","Changelog for {{appname}} {{version}}":"Muutokset versiossa {{appname}} {{version}}","Check failed:":"Päivitysten haku epäonnistui:","Check for updates now":"Tarkista päivitykset","Checking for updates …":"Tarkistetaan päivityksiä ...","Chose a storage type to get started":"Valitseensin tallennustyyppi","Click the AuthID link to create an AuthID":"Klikkaa AuthID-linkkiä luodaksesi AuthID-tunnisteen","Client library to use":"Käytettävä kirjasto","Commandline …":"Komentorivi ...","Compact Phase":"Tiivistys-vaihe","Compact now":"Tiivistä nyt","Compacting remote data …":"Tiiistetään kohteen tiedostoja ...","Complete log":"Koko loki","Completing backup …":"Viimeistellään varmuuskopiota ...","Computer":"Tietokone","Configuration file:":"Asetustiedosto","Configuration:":"Asetukset:","Configure a new backup":"Määrittele uusi varmuuskopio","Confirm delete":"Vahvista poistaminen","Confirm encryption passphrase":"Vahvista salauslause","Confirm passphrase":"Vahvista salasana","Confirmation required":"Tarvitsen vahvistuksen","Connect":"Yhdistä","Connect now":"Yhdistä nyt","Connecting to server …":"Yhdistetään palvelimeen ...","Connection lost":"Yhteys katkesi","Connection worked!":"Yhteys toimi!","Container name":"Kontin nimi","Container region":"Kontin alue","Continue":"Jatka","Continue without encryption":"Jatka salaamatta","Copied!":"Kopioitu!","Copy":"Kopioi","Copy Destination URL to Clipboard":"Kopio etäpalvelimen osoite leikepöydälle","Copy failed. Please manually copy the URL":"Kopionti epäonnistui. Kopio osoite käsin","Core options":"Ydinasetukset","Counting ({{files}} files found, {{size}})":"Lasketaan tiedostoja. (Löydetty {{files}} tiedostoa, {{size}})","Crashes only":"Vain kaatumiset","Create bug report …":"Luo virheraportti ...","Create folder?":"Luo kansio?","Created new limited user":"Luotiin uusi rajoitettu käyttäjä","Creating bug report …":"Luodaan virheraporttia ...","Creating new user with limited access …":"Luodaan uusi rajoitettu käyttäjä","Creating target folders …":"Luodaan kohdekansiot ...","Creating temporary backup …":"Luodaan tilapäinen varmuuskopio ...","Current file:":"Nykyinen tiedosto:","Current version is {{versionname}} ({{versionnumber}})":"Nykyinen versio on {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Vaihtoehtoinen S3 päätepiste","Custom authentication url":"Vaihtoehtoinen autentikointiosoite","Custom location ({{server}})":"Vaihtoehtoinen sijainti ({{server}})","Custom region for creating buckets":"Vaihtoehtoinen alue ämpärin luomista varten","Custom region value ({{region}})":"Vaihtoehtoinen alue ({{region}})","Custom server url ({{server}})":"Vaihtoehtoisen palvelimen osoite ({{server}})","Custom storage class ({{class}})":"Vaihtoehtoinen tallennusluokka ({{class}})","Database …":"Tietokanta ...","Days":"Päivää","Default":"Oletus","Default ({{channelname}})":"Oletus ({{channelname}})","Default options":"Oletusasetukset","Delete":"Poista","Delete backup":"Poista varmuuskopio","Delete backups that are older than":"Poista varmuuskopiot, jotka ovat vanhempia kuin","Delete local database":"Poista paikallinen tietokanta","Delete remote files":"Poista tiedostot etäpalvelimelta","Delete the local database":"Poista paikallinen tietokanta","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Poistetaanko {{filecount}} tiedostoa ({{filesize}}) etäpalvelimelta","Delete …":"Poista ...","Deleted":"Poistettu","Deleted Versions":"Poistetut versiot","Deleted files":"Poistetut tiedostot","Deleting remote files …":"Poistetaan kohteen tiedostoja ...","Deleting unwanted files …":"Poistetaan turhia tiedostoja ...","Description (optional)":"Kuvaus (valinnainen)","Description:":"Kuvaus:","Desktop":"Työpöytä","Destination":"Kohde","Destination path":"Kohdepolku","Disabled":"Poistettu käytöstä","Dismiss":"Ohita","Dismiss all":"Hylkää kaikki","Display and color theme":"Näyttö ja väriteema","Do you really want to delete the backup: \"{{name}}\" ?":"Haluatko varmasti poistaa varmuuskopion \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Haluatko varmasti poistaa varmuuskopion {{name}} paikallisen tietokannan?","Done":"Valmis","Download":"Lataa","Downloaded files":"Ladatut tiedostot","Downloading files …":"Ladataan tiedostoja ...","Downloading update…":"Ladataan päivitystä ...","Duplicate option {{opt}}":"Sama valitsin {{opt}} annettiin kahdesti","Duplicati Website":"Duplicatin verkkosivu","Duplicati forum":"Duplicatin keskustelualue","Duration":"Kesto","Edit as list":"Muokkaa listana","Edit as text":"Muokkaa tekstinä","Edit …":"Muokkaa ...","Encrypt file":"Salaa tiedosto","Encryption":"Salaus","Encryption changed":"Salausasetukset ovat muuttuneet","Encryption passphrase":"Salausavain","End":"Loppu","Enter URL":"Anna URL","Enter backup passphrase, if any":"Anna varmuuskopion salauslause, jos käytät salausta.","Enter encryption passphrase":"Anna salauslause","Enter expression here":"Anna ilmaisu","Enter the destination path":"Anna kohdekansion polku","Error":"Virhe","Error!":"Virhe!","Errors and crashes":"Virheet ja kaatumiset","Exclude":"Ohita","Exclude directories whose names contain":"Ohita kansiot, joiden nimessä on","Exclude expression":"Ohita ilmaisu","Exclude file":"Ohita tiedosto","Exclude file extension":"Ohita tämän tyyppiset tiedostot","Exclude files whose names contain":"Ohita tiedostot, joiden nimessä on","Exclude folder":"Ohita kansio","Exclude regular expression":"Ohita säännöllistä ilmaisua vastaavat kohteet","Existing file found":"Löydettiin olemassaoleva tiedosto","Experimental":"Experimental","Export":"Vie","Export backup configuration":"Vie varmuuskopion asetukset","Export configuration":"Vie asetukset","External link":"Ulkoinen linkki","FTP (Alternative)":"FTP (vaihtoehtoinen)","Failed to build temporary database: {{message}}":"Tilapäisen tietokannan luominen epäonnistui. Virhe: {{message}}","Failed to connect:":"Yhteyden muodostaminen epäonnistui:","Failed to connect: {{message}}":"Yhteyden muodostaminen epäonnistui: {{message}}","Failed to delete:":"Poistaminen epäonnistui:","Failed to fetch path information: {{message}}":"Polkutietojen noutaminen epäonnistui: {{message}}","Failed to find backup:":"Varmuuskopiota ei löydetty:","Failed to read backup defaults:":"Varmuuskopion oletusasetusten lukeminen epäonnistui:","Failed to restore files: {{message}}":"Tiedostojen palauttaminen epäonnistui: {{message}}","Failed to save:":"Tallennus epäonnistui:","File":"Tiedosto","Files larger than:":"Tiedostot, joiden koko on suurempi kuin:","Filters":"Suodattimet","Finished!":"Valmis!","Folder":"Kansio","Folder path":"Kansion polku","Fri":"Pe","GByte":"GT","GByte/s":"GT/s","GCS Project ID":"GCS Projektin ID","General":"Yleinen","General backup settings":"Yleiset varmuuskopioasetukset","General options":"Yleiset asetukset","Generate":"Luo","Generate IAM access policy":"Luo Amazon IAM access policy","Getting file versions …":"Haetaan tiedostojen versioita ...","Group email":"Ryhmäsähköpostiosoite","Hidden files":"Piilotetut tiedostot","Hide":"Piilota","Hide hidden folders":"Älä näytä piilotettuja kansioita","Home":"Etusivu","Hostnames":"Isäntänimet","Hours":"tuntia","How do you want to handle existing files?":"Mitä tehdään olemassa oleville tiedostoille?","Hyper-V Machine":"Hyper-V-virtuaalikone","Hyper-V Machine:":"Hyper-V-virtuaalikone:","Hyper-V Machines":"Hyper-V-virtuaalikoneet","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jos ajastettu varmuuskopio jää tekemättä, se tehdään niin pian kuin mahdollista.","If at least one newer backup is found, all backups older than this date are deleted.":"Kaikki tätä päivämäärää vanhemmat varmuuskopiot poistetaan, mikäli vähintään yksi uudempi varmuuskopio löytyy.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jos et anna polkua, kaikki tiedostot tallennetaan kirjautumiskansioon.\nOletko varma, että haluat tätä?","If you do not enter an API Key, the tenant name is required":"Jos et anna tunnistetta API key, on tunniste \"tenant name\" pakollinen","Import":"Tuo","Import Destination URL":"Tuo etäpalvelimen osoite","Import backup configuration":"Tuo varmuuskopion asetukset","Import from a file":"Tuo tiedostosta","Import metadata":"Tuo metatieto","Importing …":"Tuodaan ...","Include a file?":"Sisällytä tiedosto?","Include expression":"Sisällytä ilmaisua vastaavat kohteet","Include regular expression":"Sisällytä säännöllistä ilmaisua vastaavat kohteet","Incorrect answer, try again":"Virheellinen vastaus. Yritä uudelleen.","Individual builds for developers only. Not for use with important data.":"Yksittäiset versiot, vain ohjelman kehittäjille. Älä käytä tärkeiden tietojen kanssa.","Information":"Informaatio","Invalid characters in path":"Virheellisiä merkkejä polussa","Invalid retention time":"Epäkelpo säilytysaika","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"JOtkut FTP-palvelimet sallivat yhteyden muodostamisen ilman salasanaa.\nOleko varma, että käyttämäsi FTP-palvelin sallii anonyymit kirjautumiset?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Säilytä määritelty määrä varmuuskopioita","Keep all backups":"Säilytä kaikki varmuuskopiot","Language in user interface":"Käytettävä kieli","Last month":"Viime kuussa","Last successful backup:":"Viimeisin onnistunut varmuuskopio:","Latest":"Viimesin","Libraries":"Kirjastot","Listing backup dates …":"Listataan varmuuskopioiden päivämääriä ...","Listing remote files …":"Listataan kohteen tiedostoja ...","Live":"Live","Load older data":"Lataa vanhoja tietoja","Loading …":"Ladataan ...","Local database path:":"Paikallisen tietokannan sijainti:","Local storage":"Paikallinen tilankäyttö","Location":"Sijainti","Location where buckets are created":"Alue, jolle ämpärit luodaan","Log data for {{Backup.Backup.Name}}":"Varmuuskopion {{Backup.Backup.Name}} lokitiedot","Log data from the server":"Palvelimen lokitiedot","Log out":"Kirjaudu ulos","MByte":"MB","MByte/s":"MB/s","Maintenance":"Ylläpito","Manually type path":"Anna polku","Max download speed":"Suurin latausnopeus","Max upload speed":"Suurin lähetysnopeus","Menu":"Valikko","Microsoft SQL Database:":"Microsoft SQL-tietokanta:","Microsoft SQL Databases":"Microsoft SQL -tietokannat","Minutes":"Minuuttia","Missing name":"Et antanut nimeä","Missing passphrase":"Salasana puuttuuEt antanut salasanaa","Missing sources":"Et valinnut varmuuskopioitavia tietostoja","Mon":"ma","Months":"Kuukautta","Move existing database":"Siirrä olemassa oleva tietokanta","Move failed:":"Siirto epäonnistui:","My Documents":"Tiedostot","My Music":"Musiikki","My Photos":"Kuvat","My Pictures":"Kuvat","Name":"Nimi","Never":"Ei koskaan","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Uusi käyttäjätunnus on {{user}}.\nPäivitä tunnukset käyttääksesi uutta rajoitettua käyttäjää.","Next":"Seuraava","Next scheduled run:":"Seuraava varmuuskopio tehdään:","Next scheduled task:":"Seuraava ajoitettu tehtävä:","Next task:":"Seuraava tehtävä:","Next time":"Seuraavalla kerralla","No":"Ei","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Sertifikaattia ei ole määritelty aikaisemmin. Varmista palvelimen ylläpitäjältä, että avain onn oikea: {{key}}\n\nHaluatko hyväksyä tämän avaimen?","No editor found for the "{{backend}}" storage type":"Etäpalvelimelle "{{backend}}" ei löytynyt editoria.","No encryption":"Ei salausta","No items selected":"Et valinnut yhtään kohdetta","No items to restore, please select one or more items":"Et valinnut yhtään tiedostoa palautettavaksi. Valitse yksi tai useampi tiedosto.","No passphrase entered":"Et antanut salasanaa","No scheduled tasks":"Ei ajastettuja tehtäviä","Non-matching passphrase":"Salasanat eivät ole samat","None / disabled":"Ei mitään/poistettu käytöstä","Not using encryption":"Salaus ei ole käytössä","Nothing will be deleted. The backup size will grow with each change.":"Mitään ei poisteta. Varmuuskopion koko kasvaa jokaisella muutoksella.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Vanhimmat varmuuskopiot poistetaan, kun varmuuskopioita on enemmän kuin määritelty määrä.","OpenStack AuthURI":"Openstack autentikointiosoite","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Avattu","Operating System":"Käyttöjärjestelmä","Operations:":"Toimenpiteet:","Optional authentication password":"Salasana (ei välttämätön)","Optional authentication username":"Käyttäjätunnus (ei välttämätön)","Options":"Valitsimet","Original location":"Alkuperäinen sijainti","Others":"Muut","Overwrite":"Korvaa","Passphrase":"Salauslause","Passphrase (if encrypted)":"Salauslause (jos varmuuskopio on salattu)","Passphrase changed":"Salauslause vaihdettiin","Passphrases are not matching":"Salauslauseet eivät täsmää","Passphrases do not match":"Salausavaimet eivät täsmää","Password":"Salasana","Path":"Polku","Path not found":"Polkua ei löydy","Path on server":"Polku etäpalvelimella","Path or subfolder in the bucket":"Ämpärin polku tai alikansio","Pause":"Tauko","Pause after startup or hibernation":"Tauko käynnistyksen tai lepotilasta heräämisen jälkeen","Permissions":"Oikeudet","Pick location":"Valitse sijainti","Port":"Portti","Previous":"Edellinen","Progress:":"Edistyminen: ","ProjectID is optional if the bucket exist":"Tunniste ProjectID on valinnainen, jos ämpäri on jo olemassa","Proprietary":"Suljettu","Rebuilding local database …":"Rakennetaan paikallinen tietokanta uudelleen ...","Recreate (delete and repair)":"Luo uudelleen (poista ja korjaa)","Recreating database …":"Luodaan tietokanta uudelleen ...","Registering temporary backup …":"Rekisteröidään tilapäinen varmuuskopio ...","Relative paths not allowed":"Suhteelliset polut eivät ole sallittuja","Reload":"Lataa uudelleen","Remote":"Etäpalvelimella","Remote Path":"Kohteen polku","Remote path":"Kohteen polku","Remove":"Poista","Remove option":"Poisto-asetukset","Repair":"Korjaa","Repair Phase":"Korjausvaihe","Repairing database …":"Korjataan tietokantaa ...","Repeat Passphrase":"Toista salauslause","Reporting:":"Raportoin:","Reset":"Palauta edelliset asetukset","Restore":"Palauta","Restore complete!":"Palautus valmis!","Restore files":"Palauta tiedostoja","Restore files …":"Palauta tiedostoja ...","Restore from":"Palauta etäpalvelimelta","Restore options":"Palautusasetukset","Restore read/write permissions":"Palauta luku- ja kirjoitusoikeudet","Restored Files":"Palautetut tiedostot","Restored Folders":"Palautetut kansiot","Restoring files …":"Palautetaan tiedostoja ...","Resume":"Jatka","Run again every":"Suorita uudelleen joka","Run now":"Suorita nyt","Running commandline entry":"Ajetaan komentorivin komentoa","Running task:":"Suoritettava tehtävä:","Running …":"Käynnissä ...","S3 Compatible":"S3-yhteensopiva","Same as the base install version: {{channelname}}":"Sama kuin asennettu versio: {{channelname}}","Sat":"La","Save":"Tallenna","Save and repair":"Tallenna ja korjaa","Save different versions with timestamp in file name":"Tallenna eri versiot aikaleima tiedoston nimessä","Save immediately":"Tallenna heti","Schedule":"Aikataulu","Search":"Etsi","Search for files":"Etsi tiedostoja","Seconds":"Sekuntia","Select a log level and see messages as they happen:":"Valitse lokitiedot ja näe ne heti, kun ne ilmoitetaan lokiin:","Select files":"Valitse tiedostot","Server":"Palvelin","Server and port":"Palvelin ja portti:","Server hostname or IP":"Palvelimen nimi ja IP-osoite","Server is currently paused,":"Palvelin on pysäytetty,","Server is currently paused, do you want to resume now?":"Palvelin on pysäytetty, haluatko aktivoida sen nyt?","Server password":"Palvelimen salasana","Server paused":"Palvelin on pysäytetty","Server state properties":"Palvelimen tila","Settings":"Asetukset","Show":"Näytä","Show advanced editor":"Näytä asetusten muokkain","Show hidden folders":"Näytä piilotetut tiedostot","Show log":"Näytä loki","Show treeview":"Näytä puunäkymä","Some OpenStack providers allow an API key instead of a password and tenant name":"Jotkin OpenStack-palveluntarjoajat sallivat API-avaimen käytön salasanan ja käyttäjätunnuksen sijaan","Source Data":"Lähdetiedostot","Source data":"Lähdetiedostot","Source folders":"Lähekansiot","Source:":"Varmuuskopioitavat tiedostot:","Standard protocols":"Standardinmukaiset protokollat","Stop after the current file":"Keskeytä nykyisen tiedoston jälkeen","Stop running backup":"Keskeytä käynnissä oleva varmuuskopiointi","Storage Type":"Tallennustyyppi","Storage class":"Tallennusluokka","Storage class for creating a bucket":"Tallennusluokka ämpärin luomista varten","Stored":"Tallennettu","Strong":"Vahva","Success":"Onnistui","Sun":"Su","Symbolic link":"Symbolinen linkki","System Files":"Järjestelmätiedostot","System default ({{levelname}})":"Järjestelmän oletus ({{levelname}})","System files":"Järjestelmätiedostot","System info":"Järjestelmän tiedot","System properties":"Järjestelmän ominaisuudet","TByte":"TB","TByte/s":"TB/s","Task is running":"Tehtävää suoritetaan","Temporary Files":"Väliaikaiset tiedostot","Temporary files":"Tilapäistiedostot","Test connection":"Kokeile yhteysasetuksia","The bucket name should be all lower-case, convert automatically?":"Bucketin nimen pitää olla kirjoitettu pienillä kirjaimilla. Muuta automaattisesti?","The dark theme (by Michal)":"Tumma teema (by Michal)","The default blue on white theme (by Alex)":"Oletusteema, sinistä valkoisella (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Kansiota {{folder}} ei ole olemassa. Luodaanko se nyt?","The passwords do not match":"Salasanat eivät täsmää","The path does not appear to exist, do you want to add it anyway?":"Polku ei vaikuta olevan olemassa, haluatko lisätä sen silti?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Polku ei pääty '{{dirsep}}' -merkkiin, eli olet lisäämässä tiedoston etkä kansiota. Haluatko lisätä määritellyn tiedoston?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Polun pitää olla absoluuttinen, eli sen tulee alkaa vinoviivalla \"/\"","The region parameter is only applied when creating a new bucket":"Alue -parametria käytetään vain bucketia luodessa.","The region parameter is only used when creating a bucket":"Alue -parametria käytetään vain bucketia luodessa.","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Palvelimen varmennetta ei pystytty todentamaan. Haluatko hyväksyä SSL-varmenteen, jonka tiiviste on {{hash}}?","The storage class affects the availability and price for a stored file":"Tietovaraston tyyppi vaikuttaa talennetun tiedoston saatavuuteen ja hintaan.","The target folder contains encrypted files, please supply the passphrase":"Kohdekansio sisältää salattuja tiedostoja. Anna salasana","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Käyttäjällä on liikaa oikeuksia. Haluatko luoda uuden rajoitetun käyttäjän, jolla on käyttöoikeus vain valittuun polkuun?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Tämä varmuuskopio on luotu toisessa käyttöjärjestelmässä. Tiedostojen palauttaminen ilman kohdekansion määrittelyä voi johtaa tiedostojen palauttamiseen odottamattomiin paikkoihin. Haluatko varmasti jatkaa määrittelemättä kohdekansiota?","This month":"Tässä kuussa","This week":"Tällä viikolla","Thu":"To","Time":"Aika","To File":"Tiedostoon","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Vahvistaaksesi että haluat poistaa kaikki etäkohteen tiedostot työltä \"{{name}}\", kirjoita alla näkyvä sana","To export without a passphrase, uncheck the \"Encrypt file\" box":"Viedäksesi ilmaan salasanaa poista rasti \"Salaa tiedosto\" -valinnasta","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Erilaisten DNS-hyökkäysten estämiseksi Duplicati rajaa sallitut isäntänimet tässä listattuihin. Suora yhteys IP-osoitteella ja localhost ovat aina sallittuja. Useita isäntänimia voidaan listata erottamalla ne puolipisteellä. Jos yksikin listattu isäntänimi on asteriski (*), sallitaan kaikki isäntänimet, ja tämä toiminto on pois käytöstä. Mikäli kenttä on tyhjä, ainoastaan IP-osoite- ja localhost-yhteys on sallittu.","Today":"Tänään","Trust host certificate?":"Luota palvelimen varmenteeseen?","Trust server certificate?":"Luota palvelimen varmenteeseen?","Tue":"ti","Type passphrase here.":"Kirjoita salausavain tähän.","Type to highlight files":"Kirjoita korostaaksesi tiedostoja","Until resumed":"Toistaiseksi","Update channel":"Päivityskanava","Update failed:":"Päivitys epäonnistui:","Uploading verification file …":"Lähetetään varmennustiedosto ...","Usage statistics":"Käyttötilastot","Usage statistics, warnings, errors, and crashes":"Käyttötilastot, varoitukset, virheet ja kaatumiset","Use SSL":"Käytä SSL:ää","Use existing database?":"Käytä olemassaolevaa tietokantaa?","Use weak passphrase":"Käytä heikkoa salasanaa","Useless":"Hyödytön","User data":"Käyttäjätiedot","User has too many permissions":"Käyttäjällä on liikaa oikeuksia","User interface settings":"Käyttöliittymän asetukset","Username":"Käyttäjätunnus","Vacuuming database …":"Puhdistetaan tietokanta ...","Verify files":"Tarkista tiedostot","Verifying answer":"Tarkistetaan vastausta","Very strong":"Hyvin vahva","Very weak":"Hyvin heikko","Visit us on":"Tutustu meihin","WARNING: This will prevent you from restoring the data in the future.":"VAROITUS: Tämä estää tietojen palauttamisen tulevaisuudessa","Waiting for task to begin":"Odotetaan tehtävän alkamista","Warnings, errors and crashes":"Varoitukset, virheet ja kaatumiset","We recommend that you encrypt all backups stored outside your system":"Suosittelemme salausta varmuuskopioihin, jotka säilötään oman tietokoneesi ulkopuolelle.","Weak":"Heikko","Weak passphrase":"Heikko salasana","Wed":"ke","Weeks":"Viikkoa","Where do you want to restore from?":"Mistä haluat palauttaa?","Where do you want to restore the files to?":"Mihin tiedostot palautetaan?","Years":"Vuotta","Yes":"Kyllä","Yes, I have stored the passphrase safely":"Kyllä, olen tallentanut salasanan turvallisesti","Yes, I understand the risk":"Kyllä, ymmärrän riskin","Yes, I'm brave!":"Kyllä, olen rohkea!","Yes, please break my backup!":"Kyllä, riko varmuuskopioni!","Yesterday":"Eilen","You are currently running {{appname}} {{version}}":"Käytössä oleva versio: {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vaihdoit salausmenetelmää, ja se saattaa rikkoa asioita. Harkitse kokonaan uuden varmuuskopion luomista sen sijaan.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vaihdoit salasanaa, mutta tätä toiminnallisuutta ei tueta. Luo sen sijaan kokonaan uusi varmuuskopio.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Valitsit salaamattoman varmuuskopioinnin. Salaaminen on suositeltua kaikella datalle, joka säilötään etäpalvelimelle.","You have chosen to restore to a new location, but not entered one":"Valitsit palautuksen uuteen sijaintiin, mutta et antanut sijaintia.","You must choose at least one source folder":"Vähintään yksi lähdekansio pitää valita","You must enter a name for the backup":"Varmuuskopiolle pitää antaa nimi","You must enter a passphrase or disable encryption":"Anna salasana tai poista salaus käytöstä","You must enter a positive number of backups to keep":"Syötä säilytettävien varmuuskopioiden määrä (positiivinen kokonaisluku)","You must enter a valid duration for the time to keep backups":"Syötä sallittu varmuuskopioiden säilytysaika","You must fill in the password":"Täytä salasana","You must fill in the server name or address":"Täytä palvelimen nimi tai osoite","You must fill in the username":"Täytä käyttäjätunnus","You must fill in {{field}}":"Täytä kenttä {{field}}","You must select or fill in the AuthURI":"Valitse tai syötä AuthURI","You must select or fill in the server":"Valitse tai syötä palvelin","You must specify a path":"Määritä polku","Your files and folders have been restored successfully.":"Tiedostot ja kansiot palautettiin onnistuneesti.","Your passphrase is easy to guess. Consider changing passphrase.":"Salasanasi on helppo arvata. Harkitse salasanan vaihtamista.","bucket/folder/subfolder":"bucket/kansio/alikansio","byte":"tavu","byte/s":"tavua/s","custom":"mukautettu","resume now":"jatka nyt","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}}n on pääasiallisesti kehittänyt {{dev1}} and {{dev2}}. {{appname}}n voi ladata osoitteesta {{websitename}}. {{appname}} on lisensoitu {{licensename}} -lisenssillä.","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versio","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versiota"],"{{number}} Hour":"{{number}} tuntia","{{number}} Hours":"{{number}} Tuntia","{{number}} Minutes":"{{number}} minuuttia","{{time}} (took {{duration}})":"{{time}} (kesto: {{duration}})"}); + gettextCatalog.setStrings('fr_CA', {"- pick an option -":"- choisissez une option -","...loading...":"... chargement...","AWS Access ID":"Clé d'accès AWS","AWS Access Key":"Clé d'accès secrète AWS","AWS IAM Policy":"AWS IAM Stratégies","About":"À propos","About {{appname}}":"À propos de {{appname}}","Access Key":"Clé d'accès","Access denied":"Accès refusé","Access to user interface":"Accès à l'interface utilisateur","Account name":"Nom du compte","Add a new backup":"Ajouter une nouvelle sauvegarde","Add a path directly":"Ajouter un répertoire directement","Add advanced option":"Ajouter une option avancée","Add backup":"Ajouter une sauvegarde","Add filter":"Ajouter un filtre","Add path":"Ajouter un chemin","Added":"Ajouté","Adjust bucket name?":"Modifier le nom du bucket","Advanced Options":"Options avancées","Advanced options":"options avancées","Advanced:":"Avancé :","All Hyper-V Machines":"Toutes les machines Hyper-V","All Microsoft SQL Databases":"Toutes les bases de données Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tous les rapports d'utilisation sont envoyés de manière anonyme et ne contiennent aucune information personnelle. Ils contiennent des informations sur le matériel et le système d'exploitation, sur le type d'infrastructure, la durée de la sauvegarde, la taille générale des fichiers sources et d'autres données similaires. Ils ne contiennent pas de chemin d'accès, noms de fichiers, noms d'utilisateurs, mots de passe ou des informations sensibles de ce type.","Allow remote access (requires restart)":"Autoriser l'accès à distance (nécessite un redémarrage)","Allowed days":"Jours autorisés","An existing file was found at the new location":"Un fichier existant a été trouvé au nouvel endroit","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fichier existant a été trouvé au nouvel endroit.\nÊtes-vous sûr de vouloir pointer la base de données vers un fichier existant ?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Une base de données locale pour le stockage a été trouvée.\nRéutiliser la base de données va permettre la ligne de commande et les instances serveurs de travailler sur le même stockage à distance.\n\nVoulez-vous utiliser la base de donnée existante ?","Anonymous usage reports":"Rapports d'utilisation anonyme","Applications":"Applications","As Command-line":"Comme ligne de commande","AuthID":"AuthID","Authentication password":"Mot de passe d'identification","Authentication username":"Nom d'utilisateur d'identification","Autogenerated passphrase":"Phrase secrète auto-générée","B2 Application Key":"Clé application B2","B2 Cloud Storage Account ID":"Identifiant du compte B2 Cloud Storage","B2 Cloud Storage Application Key":"Clé d'application B2 Cloud Storage","Back":"Retour","Backup complete!":"Sauvegarde Complète","Backup destination":"Destination de la sauvegarde","Backup location":"Emplacement de la sauvegarde","Backup retention":"Rétention de la sauvegarde","Backup:":"Sauvegarde :","Beta":"Béta","Broken access":"Accès rompu","Browse":"Parcourir","Browser default":"Paramètre par défaut du navigateur","Bucket create location":"Emplacement de la création du bucket","Bucket name":"Nom du bucket","Bucket storage class":"Classe de stockage du bucket","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"En autorisant l'accès à distance, le serveur écoute les requêtes de n'importe quel ordinateur de votre réseau. Si vous activez cette option, assurez-vous de toujours utiliser l'ordinateur sur un réseau protégé par un pare-feu.","Cache Files":"Fichiers de cache","Canary":"Canary","Cancel":"Annuler","Cannot move to existing file":"Impossible de déplacer vers un fichier existant","Changelog":"Journal des modifications","Changelog for {{appname}} {{version}}":"Journal des modifications pour {{appname}} {{version}}","Check failed:":"Vérification échouée :","Check for updates now":"Vérifier les mise à jour maintenant","Chose a storage type to get started":"Sélectionnez un type de stockage pour commencer","Click the AuthID link to create an AuthID":"Cliquez sur le lien AuthID pour créer un AuthID","Click to set throttle options":"Cliquez pour définir les options d'accélération","Compact Phase":"Étape de compactage","Compact now":"Compacter maintenant","Computer":"Ordinateur","Configuration file:":"Fichier de configuration :","Configuration:":"Configuration :","Configure a new backup":"Configurer une nouvelle sauvegarde","Confirm delete":"Confirmer suppression","Confirm encryption passphrase":"Confirmez la phrase secrète de chiffrement","Confirmation required":"Confirmation nécessaire","Connect":"Connecter","Connect now":"Connecter maintenant","Connection lost":"Connexion perdue","Connection worked!":"Connection fonctionnelle !","Container name":"Nom du conteneur","Container region":"Région du conteneur","Continue":"Continuer","Continue without encryption":"Continuer sans chiffrement","Copied!":"Copié!","Copy":"Copie","Copy Destination URL to Clipboard":"Copier l'URL de destination dans le presse-papier","Copy failed. Please manually copy the URL":"Copie échouée. Veuillez copier manuellement l'URL","Core options":"Options du noyau","Counting ({{files}} files found, {{size}})":"Comptage ({{files}} fichiers trouvés, {{size}})","Crashes only":"Uniquement les plantages","Create folder?":"Créer un dossier?","Created new limited user":"Nouvel utilisateur limité créé","Current action:":"Action en cours :","Current file:":"Fichier actuel :","Current version is {{versionname}} ({{versionnumber}})":"La version actuelle est {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"S3 endpoint personnalisé","Custom authentication url":"URL d'authentification personnalisée","Custom backup retention":"Rétention de sauvegarde personnalisée","Custom location ({{server}})":"Emplacement personnalisé ({{server)}}","Custom region for creating buckets":"Région personnalisée pour la créations de buckets","Custom region value ({{region}})":"Valeur personnalisée de région ({{region}})","Custom server url ({{server}})":"URL serveur personnalisée ({{server}})","Custom storage class ({{class}})":"Classe de stockage personnalisée ({{class}})","Days":"Jours","Default":"Défaut","Default ({{channelname}})":"({{channelname}}) par défaut","Default excludes":"Les exclusions par défaut","Default options":"Options par défaut","Delete":"Supprimer","Delete Phase (Old Backup Versions)":"Étape de suppression (ancienne version de sauvegarde)","Delete backup":"Supprimer la sauvegarde","Delete backups that are older than":"Supprimer les sauvegardes plus anciennes que","Delete local database":"Supprimer base de données locale","Delete remote files":"Supprimer les fichiers distants","Delete the local database":"Supprimer la base de données locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Supprimer {{filecount}} fichiers ({{filesize}}) du stockage distant ?","Deleted":"Supprimer","Deleted Versions":"Versions supprimés","Deleted files":"Fichiers supprimés","Description (optional)":"Description (facultatif)","Description:":"Description","Desktop":"Bureau","Destination":"Destination","Destination path":"Chemin de destination","Disabled":"Désactivé","Dismiss":"Rejeter","Dismiss all":"Rejeter la totalité","Display and color theme":"Affichage et couleur","Do you really want to delete the backup: \"{{name}}\" ?":"Voulez-vous vraiment supprimer la sauvegarde : \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Voulez-vous vraiment supprimer la base de données locale pour : {{name}} ?","Done":"Terminé","Download":"Téléchargement","Downloaded files":"Fichiers téléchargés","Duplicate option {{opt}}":"Option de duplication {{opt}}","Duplicati Website":"Site internet de Duplicati","Duplicati forum":"Forum de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati s'exécutera une fois démarré, mais restera en état de pause pendant la durée. Duplicati occupera un minimum de ressources système et aucune sauvegarde ne sera exécutée.","Duration":"Durée","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Chaque sauvegarde a une base de données locale associée à elle, elle stocke des informations localement à propos de la sauvegarde distante.\nQuand vous supprimez une sauvegarde, vous pouvez aussi supprimer la base de données locale sans affecter votre capacité à restaurer vos fichiers distants.\nSi vous utilisez la base de données locale pour vos sauvegardes à partir de la ligne de commande, vous devez conserver la base de données.","Edit as list":"Éditer en tant que liste","Edit as text":"Éditer en tant que texte","Encrypt file":"Chiffrement du fichier","Encryption":"Chiffrement","Encryption changed":"Chiffrement changé","End":"Terminé","Enter URL":"Entrer l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Entrez une stratégie de rétention manuellement. Les espaces réservés sont D / W / Y pour les jours / semaines / années et U pour illimité. La syntaxe est : 7D:1D, 4W:1W, 36M:1M. Cet exemple conserve une sauvegarde pour chacun des 7 prochains jours, une pour chacune des 4 prochaines semaines et une pour chacun des 36 prochains mois. Cela peut également être écrit comme 1W:1D, 1M:1W, 3Y:1M.","Enter backup passphrase, if any":"Entrez la phrase secrète de sauvegarde, si présente","Enter configuration details":"Entrer les détails de configuration","Enter encryption passphrase":"Entrez la phrase secrète de chiffrement","Enter expression here":"Entrez l'expression ici","Enter the destination path":"Entrez le chemin de destination","Error":"Erreur","Error!":"Erreur!","Errors and crashes":"Erreurs et plantages","Examined":"Examiné","Exclude":"Exclure","Exclude directories whose names contain":"Exclure répertoires dont le nom contient","Exclude expression":"Exclure expression","Exclude file":"Exclure fichier","Exclude file extension":"Exclure extension de fichier","Exclude files whose names contain":"Exclure fichiers dont le nom contient","Exclude filter group":"Exclure le groupe de filtres","Exclude folder":"Exclure dossier","Exclude regular expression":"Exclure expression régulière","Existing file found":"Fichier existant trouvé","Experimental":"Expérimental","Export":"Exporter","Export backup configuration":"Exporter la configuration de sauvegarde","Export configuration":"Exporter la configuration","Export passwords":"Exporter les mots de passe","External link":"Lien externe","FTP (Alternative)":"FTP (Alternatif)","Failed to build temporary database: {{message}}":"Échec de la construction de la base de données temporaire : {{message}}","Failed to connect:":"Échec de la connexion :","Failed to connect: {{message}}":"Échec de la connexion : {{message}}","Failed to delete:":"Échec de la suppression :","Failed to fetch path information: {{message}}":"Échec de la récupération des information du chemin : {{message}}","Failed to find backup:":"Impossible de trouver la sauvegarde","Failed to read backup defaults:":"Échec de la lecture des paramètres par défaut de la sauvegarde :","Failed to restore files: {{message}}":"Échec de la restauration des fichiers : {{message}}","Failed to save:":"Échec d'enregistrement :","File":"Fichier","Files larger than:":"Fichiers plus gros que :","Filters":"Filtres","Finished!":"Terminé!","First run setup":"Première mise en route","Folder":"Dossier","Folder path":"Chemin du dossier","Fri":"Ven.","GByte":"Goctet","GByte/s":"GOtects/s","GCS Project ID":"ID du projet GCS","General":"Général","General backup settings":"Paramètres généraux de sauvegarde","General options":"Options générales","Generate":"Générer","Generate IAM access policy":"Générer la statégie d'accès IAM","Group email":"Courriel de groupe","Hidden files":"Fichiers cachés","Hide":"Cacher","Hide hidden folders":"Masquer les dossiers cachés","Home":"Poste de travail","Hostnames":"Les noms d'hôtes","Hours":"Heures","How do you want to handle existing files?":"Comment voulez-vous traiter les fichiers existants?","Hyper-V Machine":"Machine Hyper-V","Hyper-V Machine:":"Machine Hyper-V :","Hyper-V Machines":"Machines Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Si une date a été manquée, le travail démarrera dès que possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si au moins une sauvegarde plus récente est trouvée, toutes les sauvegardes antérieures à cette date sont supprimées.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si vous n'entrez pas de chemin, tous les fichiers seront stockés dans le dossier de connexion.\nÊtes-vous sûr que c'est ce que vous voulez ?","If you do not enter an API Key, the tenant name is required":"Si vous n'entrez pas de clé API, le nom de l'entité est requis","Import":"Importer","Import Destination URL":"Importer l'URL de destination","Import backup configuration":"Importer la configuration de sauvegarde","Import from a file":"Importer depuis un fichier","Import metadata":"Importer des métadonnées","Include a file?":"Inclure un fichier ?","Include expression":"Inclure expression","Include regular expression":"Inclure expression régulière","Incorrect answer, try again":"Réponse incorrecte, essayez encore","Individual builds for developers only. Not for use with important data.":"Builds individuelles pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Information":"Information","Invalid characters in path":"Caractères invalides dans le chemin","Invalid retention time":"Temps de rétention invalide","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Il est possible de se connecter à certains FTP sans mot de passe.\nÊtes-vous sûr que votre serveur FTP prend en charge l'identification sans mot de passe ?","KByte":"KOctet","KByte/s":"KOctet/s","Keep a specific number of backups":"Conserver un nombre spécifique de sauvegardes","Keep all backups":"Conserver toutes les sauvegardes","Keystone API version":"Version de l'API Keystone","Language in user interface":"Langue dans l'interface utilisateur","Last month":"Mois dernier","Last successful backup:":"Dernière sauvegarde réussie :","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Dernière restauration réussie : {{time}} (a pris {{duration || '0 secondes'}})","Latest":"Dernière","Libraries":"Librairies","Live":"Direct","Load a configuration from an exported job or a storage provider":"Charger une configuration depuis un export ou un opérateur de stockage","Load destination from an exported job or a storage provider":"Charger la destination depuis un export ou un opérateur de stockage","Load older data":"Charger des données plus anciennes","Local database path:":"Chemin de la base de données locale :","Local repository":"Stockage local","Local storage":"Stockage local","Location":"Emplacement","Location where buckets are created":"Emplacement ou les buckets sont créés","Log data for {{Backup.Backup.Name}}":"Historique pour {{Backup.Backup.Name}}","Log data from the server":"Données d'historique du serveur","Log out":"Déconnexion","MByte":"MOctet","MByte/s":"MOctet/s","Maintenance":"Maintenance","Manually type path":"Entrée manuelle du chemin","Max download speed":"Vitesse maximum de téléchargement","Max upload speed":"Vitesse maximum de téléversement","Menu":"Menu","Microsoft SQL Database:":"Base de données Microsoft SQL :","Microsoft SQL Databases":"Bases de données Microsoft SQL","Minimum redundancy":"Redondance minimale","Minimum redundancy is 1.0":"La redondance minimale est de 1,0","Minutes":"Minutes","Missing name":"Nom manquant","Missing passphrase":"Phrase secrète manquante","Missing sources":"Sources manquantes","Modified":"Modifié","Mon":"Lun.","Months":"Mois","Move existing database":"Déplacer la base de données existante","Move failed:":"Échec de déplacement :","My Documents":"Mes documents","My Music":"Ma musique","My Photos":"Mes photos","My Pictures":"Mes photos","Name":"Nom","Never":"Jamais","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Le nouveau nom d'utilisateur est {{user}}.\nMise à jour des accès pour le nouvel utilisateur limité","Next":"Suivant","Next scheduled run:":"Prochaine exécution programmée :","Next scheduled task:":"Prochaine tâche planifiée :","Next task:":"Prochaine tâche :","Next time":"Prochaine fois","No":"Non","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Aucun certificat n'a été spécifié auparavant, veuillez vérifier que la clé est correcte auprès de votre administrateur système : {{key}}\n\nVoulez-vous approuver la clé de l'hôte mentionné ?","No editor found for the "{{backend}}" storage type":"Aucun éditeur trouvé pour le "{{backend}}" type de stockage","No encryption":"Pas de chiffrement","No items selected":"Aucun élément sélectionné","No items to restore, please select one or more items":"Aucun élément à restaurer, merci de sélectionner un ou plusieurs éléments","No passphrase entered":"Aucune phrase secrète entrée","No scheduled tasks":"Pas de tâche planifié","Non-matching passphrase":"La phrase secrète ne correspond pas","None / disabled":"Aucun / Désactivé","Not using encryption":"N'utilise pas le chiffrement","Nothing will be deleted. The backup size will grow with each change.":"Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque modification.","OK":"Ok","Once there are more backups than the specified number, the oldest backups are deleted.":"Une fois qu'il y a plus de sauvegardes que le nombre spécifié, les sauvegardes les plus anciennes sont supprimées.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Ouvert","Operating System":"Système d'exploitation","Operation":"Opération","Operations:":"Opérations :","Optional authentication password":"Mot de passe d'identification optionel","Optional authentication username":"Nom d'utilisateur d'identification optionel","Options":"Options","Original location":"Emplacement d'origine","Others":"Autres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Au fil du temps, les sauvegardes seront automatiquement supprimées. Il restera une sauvegarde pour chacun des 7 derniers jours, chacune des 4 dernières semaines, chacun des 12 derniers mois. Il y aura toujours au moins une sauvegarde restante.","Overwrite":"Écraser","Passphrase":"Phrase secrète","Passphrase (if encrypted)":"Phrase secrète (si chiffré)","Passphrase changed":"Phrase secrète changée","Passphrases are not matching":"Les phrases secrètes ne correspondent pas","Passphrases do not match":"Le mot de passe ne correspond pas","Password":"Mot de passe","Path":"Chemin","Path not found":"Chemin non trouvé","Path on server":"Chemin sur le serveur","Path or subfolder in the bucket":"Chemin ou sous-dossier dans le bucket","Pause":"Pause","Pause after startup or hibernation":"Pause après le démarrage ou l'hibernation","Pause options":"Options de pause","Permissions":"Permissions","Pick location":"Choisir l'emplacement","Point to your backup files and restore from there":"Donner votre fichier de sauvegarde et restaurer depuis celui-ci ","Port":"Port","Prevent tray icon automatic log-in":"Empêcher la connexion automatique de l'icône de la barre de tâches","Previous":"Précédent","Progress:":"Statut:","ProjectID is optional if the bucket exist":"Le ProjectID est optionel si le bucket existe","Proprietary":"Propriétaire","Purge Phase":"Étape de purge","Purging files complete!":"Purge des fichiers complétée!","Recreate (delete and repair)":"Récrée (suppression et réparation)","Recreate Database Phase":"Étape de recréation de la base de données","Relative paths not allowed":"Les chemins relatifs ne sont pas autorisés","Reload":"Recharger","Remote":"Distant","Remote Path":"Chemin d'accès distant","Remote Repository":"Stockage distant","Remote path":"Chemin d'accès distant","Remote repository":"Stockage distant","Remote volume size":"Taille du volume distant","Remove":"Retirer","Remove option":"Option de retrait","Removed files":"Fichiers supprimés","Repair":"Réparer","Repair Phase":"Étape de réparation","Repeat Passphrase":"Répeter la phrase secrète","Reporting:":"Communication de données :","Reset":"Réinitialiser","Restore":"Restaurer","Restore complete!":"La restauration a été complétée!","Restore files":"Restaurer les fichiers","Restore from":"Restaurer depuis","Restore from backup configuration":"Restaurer depuis une sauvegarde de configuration","Restore options":"Options de restauration","Restore read/write permissions":"Autorisations de lecture/écriture de restauration","Resume":"Reprendre","Rewritten File Lists":"Réécriture des listes de fichiers","Run again every":"Relancer tous les","Run now":"Démarrer maintenant","Running commandline entry":"Execution d'une ligne de commnde","Running task:":"Tâche en cours :","S3 Compatible":"Compatible S3","Same as the base install version: {{channelname}}":"Identique à la version de base installée : {{channelname}}","Sat":"Sam.","Save":"Enregistrer","Save and repair":"Enregistrer et réparer","Save different versions with timestamp in file name":"Enregistrer des versions différentes avec l'horodatage dans le nom du fichier","Save immediately":"Sauver immédiatement ","Schedule":"Planifier","Search":"Recherche","Search for files":"Recherche de fichiers","Seconds":"Secondes","Select a log level and see messages as they happen:":"Sélectionner un niveau d'historique et voyez les messages quand ils apparaissent :","Select files":"Sélectionner les fichiers","Server":"Serveur","Server and port":"Serveur et port","Server hostname or IP":"Nom d'hôte du serveur ou IP","Server is currently paused,":"Le serveur est actuellement en pause,","Server is currently paused, do you want to resume now?":"Le serveur est actuellement en pause, voulez-vous reprendre maintenant ?","Server password":"Mot de passe du serveur","Server paused":"Serveur en pause","Server state properties":"Propriétés du statut serveur","Settings":"Paramètres","Show":"Montrer","Show advanced editor":"Montrer l'éditeur avancé","Show hidden folders":"Montrer les dossiers cachés","Show log":"Montrer l'historique","Show treeview":"Afficher l'arborescence","Sia server password":"Mot de passe du serveur Sia","Smart backup retention":"Rétention de sauvegarde intelligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Certains fournisseurs OpenStack autorisent une clé API à la place d'un mot de passe et d'un nom d'entité","Source Data":"Données source","Source data":"Données source","Source folders":"Dossiers source","Source:":"Source :","Specific builds for developers only. Not for use with important data.":"Builds spécifiques pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Standard protocols":"Protocoles standards","Start":"Démarrer","Stop after the current file":"Arrêter après le fichier en cour","Stop running backup":"Arrêter la sauvegarde en cour","Stop running task":"Stopper la tâche en cour","Stopping task:":"Arrêt de la tâche","Storage Type":"Type de stockage","Storage class":"Classe de stockage","Storage class for creating a bucket":"Classe de stockage pour la création d'un bucket","Stored":"Stocké","Strong":"Fort","Success":"Succès","Sun":"Dim.","Symbolic link":"Lien symbolique","System Files":"Fichiers système","System default ({{levelname}})":"Paramètre par défaut du système ({{levelname}})","System files":"Fichiers système","System info":"Info système","System properties":"Propriétés système","TByte":"TOctet","TByte/s":"TOctet/s","Task is running":"La tâche est en cours","Temporary Files":"Fichiers temporaires","Temporary files":"Fichiers temporaires","Test Phase":"Étape de test","Test connection":"Tester la connexion","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Le champ '{{fieldname}}' contient un caractère non valide : {{character}} (valeur : {{value}}, index : {{pos}})","The backup is missing, has it been deleted?":"La sauvegarde est n'existe pas, a-t-elle été supprimée?","The backup was temporary and does not exist anymore, so the log data is lost":"La sauvegarde était temporaire et n'existe plus, les données du journal sont perdues.","The bucket name should be all lower-case, convert automatically?":"Le nom du bucket devrait être entièrement en minuscule, convertir automatiquement ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuration doit être gardée en sécurité. Êtes-vous sûr de vouloir enregistrer un fichier non crypté contenant vos mots de passe?","The dark theme (by Michal)":"Le thème sombre (de Michal)","The default blue on white theme (by Alex)":"Thème par défaut bleu sur fond blanc (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Le dossier {{dossier}} n'existe pas.\nCréez-le maintenant ?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clé de l'hôte a changé, veuillez vérifier avec l'administrateur du serveur si cela est correcte, car il pourrait s'agir d'une attaque de type \"intermédiaire\".\n\nVoulez-vous REMPLACER votre clé d'hôte COURANTE \"{{prev}}\" par la clé MENTIONNÉE : {{key}} ?","The passwords do not match":"Le mot de passe ne correspond pas","The path does not appear to exist, do you want to add it anyway?":"Le chemin ne semble pas exister, voulez-vous l'ajouter quand même ?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Le répertoire ne se termine pas par un caractère '{{dirsep}}', ce qui signifie que vous sélectionnez un fichier et non un dossier.\n\nVoulez-vous inclure le fichier spécifié ?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Le chemin doit être un chemin absolu, c.-à-d. Il doit commencer par un slash avant '/'","The region parameter is only applied when creating a new bucket":"Le paramètre régional n'est appliqué qu'à la création d'un nouveau bucket","The region parameter is only used when creating a bucket":"Le paramètre régional n'est utilisé qu'à la création d'un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Le certificat du serveur n'a pas pu être validé.\nVoulez-vous approuver le certificat SSL avec la somme de contrôle : {{hash}} ?","The storage class affects the availability and price for a stored file":"La classe de stockage affecte la disponibilité et le prix d'un fichier stocké","The target folder contains encrypted files, please supply the passphrase":"Le fichier cible contient des fichiers chiffrés, merci de fournir la phrase secrète","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utilisateur à trop d'autorisations. Voulez-vous créer un nouvel utilisateur limité avec uniquement les autorisations pour les chemins sélectionnés ?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Cette sauvegarde a été créée sur un autre système d’exploitation. Restaurer les fichiers sans préciser un dossier de destination peut créer des fichiers à des endroits inattendus. Êtes-vous surs de vouloir poursuivre sans choisir de dossier de destination ?","This month":"Ce mois","This week":"Cette semaine","Throttle settings":"Options d'accélération","Thu":"Jeu.","Time":"temps","To File":"Vers fichier","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pour confirmer que vous souhaitez supprimer tous les fichiers distants pour \"{{name}}\", veuillez entrer le mot situé ci-dessous","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Pour éviter diverses attaques basées sur le DNS, Duplicati limite les noms d'hôtes autorisés à ceux répertoriés ici. L'accès IP direct et localhost est toujours autorisé. Plusieurs noms d'hôte peuvent être fournis avec un séparateur de points-virgules. Si l'un des noms d'hôtes autorisés est un astérisque (*), tous les noms d'hôte sont autorisés et cette fonctionnalité est désactivée. Si le champ est vide, seule l'adresse IP et l'accès localhost sont autorisés.","Today":"Aujourd'hui","Trust host certificate?":"Faire confiance au certificat de l'hôte ?","Trust server certificate?":"Faire confiance au certificat du serveur ?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Essayez les nouvelles fonctionnalités sur lesquelles nous travaillons. Actuellement la version la plus stable disponible. Testez la restauration des données avant de l'utiliser dans des environnements de production.","Tue":"Mar.","Type passphrase here.":"Tapez mot de passe ici.","Type to highlight files":"Tapez pour mettre en surbrillance les fichiers","Unknown backup size and versions":"Taille et version de sauvegarde inconnue","Until resumed":"Jusqu'à la reprise","Update channel":"Canal de mise à jour","Update failed:":"Échec de mise à jour","Updating with existing database":"Mettre à jour avec une base de données existante","Uploaded files":"Fichiers téléchargés","Usage statistics":"Statistiques d'utilisation","Usage statistics, warnings, errors, and crashes":"Statistiques d'utilisation, avertissements, erreurs et accidents","Use SSL":"Utiliser SSL","Use existing database?":"Utiliser une base de données existante ?","Use weak passphrase":"Utiliser une phrase secrète faible","Useless":"Inutile","User data":"Données utilisateur","User domain name":"Nom de domaine de l'utilisateur","User has too many permissions":"L'utilisateur à trop d'autorisations","User interface settings":"Réglages interface utilisateur","Username":"Nom d'utilisateur","Verifications":"Vérifications","Verify files":"Vérifier les fichiers","Verifying answer":"Vérification de la réponse","Version ID":"ID de version","Very strong":"Très fort","Very weak":"Très faible","Visit us on":"Rendez nous visite sur","WARNING: This will prevent you from restoring the data in the future.":"ATTENTION : Cela va vous empêcher de restaurer vos données dans le futur.","Waiting for task to begin":"En attente du début de la tâche","Warnings, errors and crashes":"Avertissements, erreurs et accidents","We recommend that you encrypt all backups stored outside your system":"Nous vous recommandons de chiffrer toutes les sauvegardes stockées en dehors de votre système","Weak":"Faible","Weak passphrase":"Phrase secrète faible","Wed":"Mer.","Weeks":"Semaines","Where do you want to restore from?":"Ou voulez-vous restaurer vos fichiers ?","Where do you want to restore the files to?":"Ou voulez-vous restaurer vos fichiers ?","Years":"Années","Yes":"Oui","Yes, I have stored the passphrase safely":"Oui, j'ai conservé ma phrase secrète en sécurité","Yes, I understand the risk":"Oui, je comprends le risque","Yes, I'm brave!":"Oui, je suis courageux !","Yes, please break my backup!":"Oui, s'il vous plait cassez ma sauvegarde","Yesterday":"Hier","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Vous êtes en train de changer le chemin de la base de données depuis une base de donnée existante.\nÊtes-vous sûr que c'est ce que vous voulez ?","You are currently running {{appname}} {{version}}":"Vous êtes actuellement en train d'utiliser {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vous avez changé la méthode de chiffrement. Ceci peut endommager certaines choses. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vous avez changé la phrase secrète, ce qui n'est pas pris en charge. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Vous avez choisi de ne pas chiffrer votre sauvegarde. Le chiffrement est recommandé pour toutes les données stockées sur un serveur distant.","You have chosen to restore to a new location, but not entered one":"Vous avez demandé à restaurer vers un nouveau dossier, mais sans indiquer son chemin","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vous avez généré une mot de passe fort. Assurez-vous que vous avez effectué une copie sécurisée de ce mot de passe, car les données ne pourront pas être récupérées si vous le perdez.","You must choose at least one source folder":"Vous devez choisir au moins un dossier source","You must enter a domain name to use v3 API":"Vous devez entrer un nom de domaine pour utiliser l'API v3","You must enter a name for the backup":"Vous devez entrer un nom pour votre sauvegarde","You must enter a passphrase or disable encryption":"Vous devez entrer une phrase secrète ou désactiver le chiffrement","You must enter a password to use v3 API":"Vous devez entrer un mot de passe pour utiliser l'API v3","You must enter a positive number of backups to keep":"Vous devez entrer un nombre positif de sauvegarde à conserver","You must enter a tenant (aka project) name to use v3 API":"Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3","You must enter a valid duration for the time to keep backups":"Vous devez entrer une valeur correcte pour la durée de conservation de vos sauvegardes","You must fill in the password":"Vous devez renseigner le mot de passe","You must fill in the server name or address":"Vous devez renseigner le nom du serveur ou l'adresse","You must fill in the username":"Vous devez renseigner le nom d'utilisateur","You must fill in {{field}}":"Vous devez renseigner le champ : {{field}}","You must select or fill in the AuthURI":"Vous devez sélectionner ou renseigner l'AuthURI","You must select or fill in the server":"Vous devez sélectionner ou renseigner le serveur","You must specify a path":"Vous devez spécifier un chemin.","Your files and folders have been restored successfully.":"Vos fichiers et dossiers ont été restaurés avec succès.","Your passphrase is easy to guess. Consider changing passphrase.":"Votre phrase secrète est facile à deviner. Songez à la changer.","bucket/folder/subfolder":"bucket/dossier/sous-dossier","byte":"octet","byte/s":"octet/s","custom":"personnalisé ","resume now":"reprendre maintenant","unless you are explicitly specifying --group-id":"sauf si vous spécifiez explicitement --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a été principalement développée par {{dev1}} et {{dev2}}. {{appname}} peut être téléchargée depuis {{websitename}}. {{appname}} est sous licence {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fichiers {{size}}) à transferer {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Heure","{{number}} Hours":"{{number}} Heures","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (durée {{duration}})"}); + gettextCatalog.setStrings('fr', {"- pick an option -":"- choisir une option -","...loading...":"...chargement...","API key":"Clé API","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"À propos","About {{appname}}":"À propos de {{appname}}","Access Key":"Clé d'accès","Access denied":"Accès refusé","Access grant":"Octroi d'accès","Access to user interface":"Accès à l'interface utilisateur","Account name":"Nom du compte","Add a new backup":"Ajouter une nouvelle sauvegarde","Add a path directly":"Ajouter un répertoire directement","Add advanced option":"Ajouter une option avancée","Add backup":"Ajouter une sauvegarde","Add filter":"Ajouter un filtre","Add path":"Ajouter un chemin","Added":"Ajouté","Adjust bucket name?":"Modifier le nom du bucket ?","Advanced Options":"Options avancées","Advanced options":"Options avancées","Advanced:":"Avancé :","All Hyper-V Machines":"Toutes les machines Hyper-V","All Microsoft SQL Databases":"Toutes les bases de données Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tous les rapports d'utilisation sont envoyés de manière anonyme et ne contiennent aucune information personnelle. Ils contiennent des informations sur le matériel et le système d'exploitation, le type d'infrastructure, la durée de la sauvegarde, la taille générale des fichiers sources et d'autres données similaires. Ils ne contiennent pas les chemin d'accès, noms de fichiers, noms d'utilisateurs, mots de passe ou informations sensibles de ce type.","Allow remote access (requires restart)":"Autoriser l'accès à distance (nécessite un redémarrage)","Allowed days":"Jours autorisés","An existing file was found at the new location":"Un fichier existant a été trouvé au nouvel emplacement","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fichier existant a été trouvé au nouvel emplacement.\nÊtes-vous sûr de vouloir faire pointer la base de données vers un fichier existant ?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Une base de données locale pour le stockage a été trouvée.\nRéutiliser la base de données va permettre la ligne de commande et les instances serveur de travailler sur le même stockage à distance.\n\nVoulez-vous utiliser la base de donnée existante ?","Anonymous usage reports":"Rapports d'utilisation anonyme","Applications":"Applications","As Command-line":"Comme ligne de commande","AuthID":"AuthID","Authentication method":"Méthode d'authentification","Authentication method ({{auth_method}})":"Méthode d'authentification ({{auth_method}})","Authentication password":"Mot de passe d'identification","Authentication username":"Nom d'utilisateur d'identification","Autogenerated passphrase":"Phrase secrète auto-générée","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Précédent","Backup complete!":"Sauvegarde terminée !","Backup destination":"Destination de sauvegarde","Backup location":"Emplacement de la sauvegarde","Backup retention":"Rétention de la sauvegarde","Backup:":"Sauvegarde :","Beta":"Bêta","Broken access":"Accès rompu","Browse":"Parcourir","Browser default":"Paramètre par défaut du navigateur","Bucket create location":"Emplacement de la création du bucket","Bucket name":"Nom du bucket","Bucket storage class":"Classe de stockage du bucket","Building list of files to restore …":"Création d'une liste de fichiers à restaurer...","Building partial temporary database …":"Création d'une base de données temporaire partielle...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"En autorisant l'accès à distance, le serveur écoute les requêtes de n'importe quel ordinateur de votre réseau. Si vous activez cette option, assurez-vous de toujours utiliser l'ordinateur sur un réseau protégé par un pare-feu paramétré de manière ad-hoc.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Par défaut, l'icône de la barre d'état système ouvre l'interface utilisateur avec un jeton de sécurité. Ceci vous permet d'accéder à l'interface utilisateur à partir de l'icône de la barre d'état système, tout en demandant aux autres utilisateurs d'entrer un mot de passe. Si vous préférez saisir le mot de passe même lorsque vous accédez à l'interface utilisateur à partir de l'icône de la barre des tâches, activez cette option.","Cache Files":"Mettre les fichiers en cache","Canary":"Canary","Cancel":"Annuler","Cannot move to existing file":"Impossible de déplacer vers un fichier existant","Changelog":"Journal des modifications","Changelog for {{appname}} {{version}}":"Journal des modifications pour {{appname}} {{version}}","Check failed:":"Échec de la vérification :","Check for updates now":"Vérifier les mise à jour maintenant","Checking for updates …":"Recherche de mises à jour...","Chose a storage type to get started":"Sélectionner un type de stockage pour commencer","Click the AuthID link to create an AuthID":"Cliquer sur le lien pour créer un AuthID","Click to set throttle options":"Cliquez pour définir les options d'accélération","Client library to use":"Bibliothèque cliente à utiliser","Commandline …":"Ligne de commande...","Compact Phase":"Étape de compression","Compact now":"Compacter maintenant","Compacting remote data …":"Compression des données distantes...","Complete log":"Journal complet","Completing backup …":"Achèvement de la sauvegarde...","Completing previous backup …":"Achèvement de la sauvegarde précédente...","Computer":"Ordinateur","Configuration file:":"Fichier de configuration :","Configuration:":"Configuration :","Configure a new backup":"Configurer une nouvelle sauvegarde","Confirm delete":"Confirmer suppression","Confirm encryption passphrase":"Confirmez la phrase secrète de chiffrement","Confirm passphrase":"Confirmer la phrase secrète","Confirmation required":"Confirmation nécessaire","Connect":"Connecter","Connect now":"Connecter maintenant","Connecting to server …":"Connexion au serveur...","Connection lost":"Connexion perdue","Connection worked!":"Connection fonctionnelle !","Container name":"Nom du conteneur","Container region":"Région du conteneur","Continue":"Continuer","Continue without encryption":"Continuer sans chiffrement","Copied!":"Copié !","Copy":"Copie","Copy Destination URL to Clipboard":"Copier l'URL de destination dans le presse-papier","Copy failed. Please manually copy the URL":"Échec de la copie. Copier l'URL manuellement","Core options":"Options du noyau","Counting ({{files}} files found, {{size}})":"Énumération ({{files}} fichiers trouvés, {{size}})","Crashes only":"Plantages uniquement","Create bug report …":"Créer un rapport d'erreur...","Create folder?":"Créer un dossier ?","Created new limited user":"Nouvel utilisateur limité créé","Creating bug report …":"Création du rapport d'erreur...","Creating new user with limited access …":"Création d'un nouvel utilisateur avec un accès limité...","Creating target folders …":"Création des dossiers de destination...","Creating temporary backup …":"Création d'une sauvegarde temporaire...","Current action:":"Action en cours :","Current file:":"Fichier actuel :","Current version is {{versionname}} ({{versionnumber}})":"Version actuelle : {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"S3 endpoint personnalisé","Custom Satellite":"Satellite personnalisé","Custom Satellite ({{satellite}})":"Satellite personnalisé ({{satellite}})","Custom authentication url":"URL d'authentification personnalisée","Custom backup retention":"Rétention de sauvegarde personnalisée","Custom location ({{server}})":"Emplacement personnalisé ({{server)}}","Custom region for creating buckets":"Région personnalisée pour la créations de buckets","Custom region value ({{region}})":"Valeur personnalisée de région ({{region}})","Custom server url ({{server}})":"URL serveur personnalisée ({{server}})","Custom storage class ({{class}})":"Classe de stockage personnalisée ({{class}})","Database …":"Base de données...","Days":"Jours","Default":"Défaut","Default ({{channelname}})":"({{channelname}}) par défaut","Default excludes":"Exclusions par défaut","Default options":"Options par défaut","Delete":"Supprimer","Delete Phase (Old Backup Versions)":"Étape de suppression (anciennes versions de sauvegarde)","Delete backup":"Supprimer la sauvegarde","Delete backups that are older than":"Supprimer les sauvegardes plus anciennes que","Delete local database":"Supprimer la base de données locale","Delete remote files":"Supprimer les fichiers distants","Delete the local database":"Supprimer la base de données locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Supprimer {{filecount}} fichiers ({{filesize}}) du stockage distant ?","Delete …":"Supprimer...","Deleted":"Supprimé","Deleted Versions":"Versions supprimées","Deleted files":"Fichiers supprimés","Deleting remote files …":"Suppression des fichiers distants...","Deleting unwanted files …":"Suppression des fichiers non désirés...","Description (optional)":"Description (facultative)","Description:":"Description : ","Desktop":"Bureau","Destination":"Destination","Destination path":"Chemin de destination","Disabled":"Désactivé","Dismiss":"Rejeter","Dismiss all":"Rejeter la totalité","Display and color theme":"Thème d'affichage et de couleur","Do you really want to delete the backup: \"{{name}}\" ?":"Voulez-vous vraiment supprimer la sauvegarde : \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Voulez-vous vraiment supprimer la base de données locale pour : {{name}} ?","Done":"Fait","Download":"Téléchargement","Downloaded files":"Fichiers téléchargés","Downloading files …":"Téléchargement des fichiers...","Downloading update…":"Téléchargement de la mise à jour...","Duplicate option {{opt}}":"Option de duplication {{opt}}","Duplicati Website":"Site internet de Duplicati","Duplicati forum":"Forum de Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati s'exécutera une fois démarré, mais restera en pause pendant toute la durée. Duplicati occupera un minimum de ressources système et aucune sauvegarde ne sera exécutée.","Duration":"Durée","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Chaque sauvegarde a une base de données locale associée. Elle stocke des informations à propos de la sauvegarde distante.\nQuand vous supprimez une sauvegarde, vous pouvez aussi supprimer la base de données locale sans affecter votre capacité à restaurer vos fichiers distants.\nSi vous utilisez la base de données locale pour vos sauvegardes à partir de la ligne de commande, vous devez conserver la base de données.","Edit as list":"Éditer en tant que liste","Edit as text":"Éditer en tant que texte","Edit …":"Édition...","Encrypt file":"Chiffrement de fichier","Encryption":"Chiffrement","Encryption changed":"Chiffrement modifié","Encryption passphrase":"Phrase de chiffrement","End":"Fin","Enter URL":"Saisir l'URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Saisir une stratégie de rétention manuellement. Les espaces réservés sont D / W / Y pour les jours / semaines / années et U pour illimité. La syntaxe est : 7D:1D, 4W:1W, 36M:1M. Cet exemple conserve une sauvegarde pour chacun des sept prochains jours, une pour chacune des quatre prochaines semaines et une pour chacun des 36 prochains mois. Cela peut également être écrit comme 1W:1D, 1M:1W, 3Y:1M.","Enter backup passphrase, if any":"Saisir la phrase secrète de sauvegarde, si existante","Enter configuration details":"Saisir les détails de configuration","Enter encryption passphrase":"Saisir la phrase secrète de chiffrement","Enter expression here":"Saisir l'expression ici","Enter the destination path":"Saisir le chemin de destination","Error":"Erreur","Error!":"Erreur !","Errors and crashes":"Erreurs et plantages","Examined":"Examiné","Exclude":"Exclure","Exclude directories whose names contain":"Exclure les répertoires dont le nom contient","Exclude expression":"Exclure l'expression","Exclude file":"Exclure le fichier","Exclude file extension":"Exclure l'extension de fichier","Exclude files whose names contain":"Exclure les fichiers dont les noms contiennent","Exclude filter group":"Exclure le groupe de filtres","Exclude folder":"Exclure le dossier","Exclude regular expression":"Exclure l'expression régulière","Existing file found":"Fichier existant trouvé","Experimental":"Expérimental","Export":"Exporter","Export backup configuration":"Exporter la configuration de sauvegarde","Export configuration":"Exporter la configuration","Export passwords":"Exporter les mots de passe","Export …":"Exporter...","Exporting …":"Export...","External link":"Lien externe","FTP (Alternative)":"FTP (Alternatif)","Failed to build temporary database: {{message}}":"Échec de la construction de la base de données temporaire : {{message}}","Failed to connect:":"Échec de la connexion :","Failed to connect: {{message}}":"Échec de la connexion : {{message}}","Failed to delete:":"Échec de la suppression :","Failed to fetch path information: {{message}}":"Échec de la récupération des information du chemin : {{message}}","Failed to find backup:":"Impossible de trouver la sauvegarde : ","Failed to read backup defaults:":"Échec de la lecture des paramètres par défaut de la sauvegarde :","Failed to restore files: {{message}}":"Échec de la restauration des fichiers : {{message}}","Failed to save:":"Échec d'enregistrement :","Fetching path information …":"Récupération d'informations sur le chemin...","File":"Fichier","Files larger than:":"Fichiers plus gros que :","Filters":"Filtres","Finished!":"Terminé !","First run setup":"Première mise en route","Folder":"Dossier","Folder path":"Chemin du dossier","Fri":"Ven.","GByte":"Go","GByte/s":"Go/s","GCS Project ID":"GCS Project ID","General":"Général","General backup settings":"Paramètres généraux de sauvegarde","General options":"Options générales","Generate":"Générer","Generate IAM access policy":"Générer une politique d'accès IAM","Getting file versions …":"Récupération des versions de fichier...","Group email":"Courriel de groupe","Hidden files":"Fichiers cachés","Hide":"Masquer","Hide hidden folders":"Masquer les dossiers cachés","Home":"Poste de travail","Hostnames":"Noms d'hôtes","Hours":"Heures","How do you want to handle existing files?":"Comment voulez-vous traiter les fichiers existants ?","Hyper-V Machine":"Machine Hyper-V","Hyper-V Machine:":"Machine Hyper-V :","Hyper-V Machines":"Machines Hyper-V","ID:":"ID :","If a date was missed, the job will run as soon as possible.":"Si une date a été manquée, la tâche démarrera dès que possible.","If at least one newer backup is found, all backups older than this date are deleted.":"Si au moins une sauvegarde plus récente est trouvée, toutes les sauvegardes antérieures à cette date sont supprimées.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Si vous n'entrez pas de chemin, tous les fichiers seront stockés dans le dossier de connexion.\nÊtes-vous sûr que c'est ce que vous voulez ?","If you do not enter an API Key, the tenant name is required":"Si vous n'entrez pas de clé API, le nom de l'entité est requis","Import":"Importer","Import Destination URL":"Importer l'URL de destination","Import backup configuration":"Importer la configuration de sauvegarde","Import from a file":"Importer depuis un fichier","Import metadata":"Importer des métadonnées","Importing …":"Importation...","Include a file?":"Inclure un fichier ?","Include expression":"Inclure expression","Include regular expression":"Inclure expression régulière","Incorrect answer, try again":"Réponse incorrecte, essayez encore","Individual builds for developers only. Not for use with important data.":"Versions individuelles pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Information":"Information","Invalid characters in path":"Caractères invalides dans le chemin","Invalid retention time":"Temps de rétention invalide","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Il est possible de se connecter à certains FTP sans mot de passe.\nÊtes-vous sûr que votre serveur FTP prend en charge l'identification sans mot de passe ?","KByte":"Ko","KByte/s":"Ko/s","Keep a specific number of backups":"Conserver un nombre spécifique de sauvegardes","Keep all backups":"Conserver toutes les sauvegardes","Keystone API version":"Version de l'API Keystone","Language in user interface":"Langue de l'interface utilisateur","Last month":"Mois dernier","Last successful backup:":"Dernière sauvegarde réussie :","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Dernière restauration réussie : {{time}} (a pris {{duration || '0 secondes'}})","Latest":"Dernière","Libraries":"Bibliothèques","Listing backup dates …":"Énumération des dates de sauvegarde...","Listing remote files for purge …":"Énumération des fichiers distants à purger...","Listing remote files …":"Énumération des fichiers distants...","Live":"Direct","Load a configuration from an exported job or a storage provider":"Charger une configuration depuis un export ou un opérateur de stockage","Load destination from an exported job or a storage provider":"Charger la destination depuis un export ou un opérateur de stockage","Load older data":"Charger des données plus anciennes","Loading …":"Chargement...","Local database path:":"Chemin de la base de données locale :","Local repository":"Stockage local","Local storage":"Stockage local","Location":"Emplacement","Location where buckets are created":"Emplacement ou les buckets sont créés","Log data for {{Backup.Backup.Name}}":"Historique pour {{Backup.Backup.Name}}","Log data from the server":"Données d'historique du serveur","Log out":"Déconnexion","MByte":"Mo","MByte/s":"Mo/s","Maintenance":"Maintenance","Manually type path":"Entrée manuelle du chemin","Max download speed":"Vitesse maximum de téléchargement","Max upload speed":"Vitesse maximum de téléversement","Menu":"Menu","Microsoft SQL Database:":"Base de données Microsoft SQL :","Microsoft SQL Databases":"Bases de données Microsoft SQL","Minimum redundancy":"Redondance minimale","Minimum redundancy is 1.0":"La redondance minimale est de 1,0","Minutes":"Minutes","Missing name":"Nom manquant","Missing passphrase":"Phrase secrète manquante","Missing sources":"Sources manquantes","Modified":"Modifié","Mon":"Lun.","Months":"Mois","Move existing database":"Déplacer la base de données existante","Move failed:":"Échec de déplacement :","My Documents":"Mes documents","My Music":"Ma musique","My Photos":"Mes photos","My Pictures":"Mes photos","Name":"Nom","Never":"Jamais","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Le nouveau nom d'utilisateur est {{user}}.\nMise à jour des accès pour le nouvel utilisateur limité","Next":"Suivant","Next scheduled run:":"Prochaine exécution programmée :","Next scheduled task:":"Prochaine tâche planifiée :","Next task:":"Prochaine tâche :","Next time":"Prochaine fois","No":"Non","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Aucun certificat n'a été spécifié auparavant, veuillez vérifier que la clé est correcte auprès de votre administrateur système : {{key}}\n\nVoulez-vous approuver la clé de l'hôte mentionné ?","No editor found for the "{{backend}}" storage type":"Aucun éditeur trouvé pour le "{{backend}}" type de stockage","No encryption":"Pas de chiffrement","No items selected":"Aucun élément sélectionné","No items to restore, please select one or more items":"Aucun élément à restaurer, merci de sélectionner un ou plusieurs éléments","No passphrase entered":"Aucune phrase secrète entrée","No scheduled tasks":"Aucune tâche planifiée","Non-matching passphrase":"La phrase secrète ne correspond pas","None / disabled":"Aucun / Désactivé","Not using encryption":"Ne pas utiliser le chiffrement","Nothing will be deleted. The backup size will grow with each change.":"Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque modification.","OK":"Ok","Once there are more backups than the specified number, the oldest backups are deleted.":"Une fois qu'il y a plus de sauvegardes que le nombre spécifié, les sauvegardes les plus anciennes sont supprimées.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Ouvert","Operating System":"Système d'exploitation","Operation":"Opération","Operations:":"Opérations :","Optional authentication password":"Mot de passe d'identification optionel","Optional authentication username":"Nom d'utilisateur d'identification optionel","Options":"Options","Original location":"Emplacement d'origine","Others":"Autres","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Les sauvegardes seront automatiquement supprimées. Il restera une sauvegarde pour chacun des sept derniers jours, chacune des quatre dernières semaines et chacun des douze derniers mois. Il y aura toujours au moins une sauvegarde.","Overwrite":"Écraser","Passphrase":"Phrase secrète","Passphrase (if encrypted)":"Phrase secrète (si chiffré)","Passphrase changed":"Phrase secrète changée","Passphrases are not matching":"Les phrases secrètes ne correspondent pas","Passphrases do not match":"La phrase secrète ne correspond pas","Password":"Mot de passe","Patching files with local blocks …":"Correction des fichiers avec les blocs locaux...","Path":"Chemin","Path not found":"Chemin non trouvé","Path on server":"Chemin sur le serveur","Path or subfolder in the bucket":"Chemin ou sous-dossier dans le bucket","Pause":"Pause","Pause after startup or hibernation":"Pause après le démarrage ou l'hibernation","Pause options":"Options de pause","Permissions":"Permissions","Pick location":"Choisir emplacement","Point to your backup files and restore from there":"Indiquer l'emplacement des fichiers de sauvegarde ","Port":"Port","Prevent tray icon automatic log-in":"Empêcher la connexion automatique de l'icône de la barre de tâches","Previous":"Précédent","Progress:":"Statut :","ProjectID is optional if the bucket exist":"Le ProjectID est optionel si le bucket existe","Proprietary":"Propriétaire","Purge Phase":"Étape de purge","Purging files complete!":"Nettoyage des fichiers terminé !","Purging files …":"Nettoyage des fichiers…","Rebuilding local database …":"Reconstruction de la base de données locale...","Recreate (delete and repair)":"Régénération (supprimer et réparer)","Recreate Database Phase":"Etape de la régénération de la bases de données","Recreating database …":"Régénération de la base de données...","Registering temporary backup …":"Enregistrement d'une sauvegarde temporaire...","Relative paths not allowed":"Les chemins relatifs ne sont pas autorisés","Reload":"Recharger","Remote":"Distant","Remote Path":"Chemin d'accès distant","Remote Repository":"Stockage distant","Remote path":"Chemin d'accès distant","Remote repository":"Stockage distant","Remote volume size":"Taille du volume distant","Remove":"Supprimer","Remove option":"Option de suppression","Removed files":"Fichiers supprimés","Repair":"Réparer","Repair Phase":"Étape de réparation","Repairing database …":"Réparation de la base de données...","Repeat Passphrase":"Répéter la phrase secrète","Reporting:":"Communication de données :","Reset":"Réinitialiser","Restore":"Restaurer","Restore complete!":"Restauration terminée !","Restore files":"Restaurer les fichiers","Restore files …":"Restaurer les fichiers...","Restore from":"Restaurer depuis","Restore from backup configuration":"Restaurer depuis la sauvegarde de la configuration","Restore options":"Options de restauration","Restore read/write permissions":"Restauration des droits de lecture/écriture","Restored Files":"Fichiers restaurés","Restored Folders":"Dossiers restaurés","Restored Symlinks":"Liens symboliques restaurés","Restoring files …":"Restauration des fichiers...","Resume":"Reprendre","Rewritten File Lists":"Listes de fichiers réécrits","Run again every":"Relancer tous les","Run now":"Démarrer maintenant","Running commandline entry":"Exécution d'une ligne de commande","Running task:":"Tâche en cours :","Running …":"En cours...","S3 Compatible":"Compatible S3","Same as the base install version: {{channelname}}":"Identique à la version de base installée : {{channelname}}","Sat":"Sam.","Satellite":"Satellite","Save":"Enregistrer","Save and repair":"Enregistrer et réparer","Save different versions with timestamp in file name":"Enregistrer des versions différentes avec l'horodatage dans le nom du fichier","Save immediately":"Enregistrer immédiatement ","Scanning existing files …":"Analyse des fichiers existants...","Scanning for local blocks …":"Analyse des blocs locaux...","Schedule":"Planifier","Search":"Rechercher","Search for files":"Rechercher les fichiers","Seconds":"Secondes","Select a log level and see messages as they happen:":"Sélectionner un niveau d'historique et voyez les messages quand ils apparaissent :","Select files":"Sélectionner les fichiers","Server":"Serveur","Server and port":"Serveur et port","Server hostname or IP":"Nom d'hôte du serveur ou IP","Server is currently paused,":"Le serveur est actuellement en pause,","Server is currently paused, do you want to resume now?":"Le serveur est actuellement en pause, voulez-vous reprendre maintenant ?","Server password":"Mot de passe du serveur","Server paused":"Serveur en pause","Server state properties":"Propriétés du statut serveur","Settings":"Paramètres","Show":"Afficher","Show advanced editor":"Afficher l'éditeur avancé","Show hidden folders":"Afficher les dossiers cachés","Show log":"Afficher l'historique","Show log …":"Afficher le journal...","Show treeview":"Afficher l'arborescence","Sia server password":"Mot de passe du serveur Sia","Smart backup retention":"Rétention de sauvegarde intelligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Certains fournisseurs OpenStack autorisent une clé API à la place d'un mot de passe et d'un nom d'entité","Some S3 providers might only be compatible with a certain client library":"Certains fournisseurs S3 pourraient n'être compatibles qu'avec une bibliothèque cliente particulière.","Source Data":"Données source","Source Files":"Fichiers sources","Source data":"Données source","Source folders":"Dossiers source","Source:":"Source :","Specific builds for developers only. Not for use with important data.":"Versions spécifiques pour les développeurs uniquement. Ne pas utiliser avec des données importantes.","Standard protocols":"Protocoles standards","Start":"Démarrer","Starting backup …":"Démarrage de la sauvegarde...","Starting restore …":"Démarrage de la restauration...","Starting the restore process …":"Démarrage du processus de restauration...","Stop after the current file":"Arrêter après le fichier en cours","Stop running backup":"Arrêter la sauvegarde en cours","Stop running task":"Arrêter la tâche en cours","Stopping after the current file:":"Arrêt après le fichier en cours:","Stopping task:":"Arrêt de la tâche:","Storage Type":"Type de stockage","Storage class":"Classe de stockage","Storage class for creating a bucket":"Classe de stockage pour la création d'un bucket","Stored":"Stocké","Strong":"Fort","Success":"Succès","Sun":"Dim.","Symbolic link":"Lien symbolique","System Files":"Fichiers système","System default ({{levelname}})":"Paramètre par défaut du système ({{levelname}})","System files":"Fichiers système","System info":"Info système","System properties":"Propriétés système","TByte":"TByte","TByte/s":"TByte/s","Task is running":"La tâche est en cours","Temporary Files":"Fichiers temporaires","Temporary files":"Fichiers temporaires","Test Phase":"Étape de test","Test connection":"Tester la connexion","Testing permissions …":"Test des permissions...","Testing …":"Test...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Le champ '{{fieldname}}' contient un caractère non valide : {{character}} (valeur : {{value}}, index : {{pos}})","The backup is missing, has it been deleted?":"La sauvegarde est introuvable, a-t-elle été supprimée?","The backup was temporary and does not exist anymore, so the log data is lost":"La sauvegarde était temporaire et n'existe plus, alors les données du journal sont perdues.","The bucket name should be all lower-case, convert automatically?":"Le nom du bucket devrait être entièrement en minuscule, convertir automatiquement ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configuration doit être conservée en sécurité. Êtes-vous sûr de vouloir enregistrer un fichier non chiffré contenant vos mots de passe ?","The dark theme (by Michal)":"Le thème sombre (de Michal)","The default blue on white theme (by Alex)":"Thème par défaut bleu sur fond blanc (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Le dossier {{dossier}} n'existe pas.\nVoulez-vous le créer maintenant ?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La clé de l'hôte a changé. Veuillez vérifier avec l'administrateur du serveur si cela est correcte, car il pourrait s'agir d'une attaque de type \"intermédiaire\".\n\nVoulez-vous remplacer votre clé d'hôte actuelle \"{{prev}}\" par la clé indiquée : {{key}} ?","The passwords do not match":"Les mots de passe ne correspondent pas","The path does not appear to exist, do you want to add it anyway?":"Le chemin ne semble pas exister, voulez-vous l'ajouter quand même ?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Le chemin ne se termine pas par un caractère '{{dirsep}}', ce qui signifie que vous sélectionnez un fichier et non un dossier.\n\nVoulez-vous inclure le fichier spécifié ?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Le chemin doit être absolu, c.-à-d. qu'il doit commencer par une barre oblique '/'","The region parameter is only applied when creating a new bucket":"Le paramètre régional n'est appliqué qu'à la création d'un nouveau bucket","The region parameter is only used when creating a bucket":"Le paramètre régional n'est utilisé qu'à la création d'un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Le certificat du serveur n'a pas pu être validé.\nVoulez-vous approuver le certificat SSL avec la somme de contrôle : {{hash}} ?","The storage class affects the availability and price for a stored file":"La classe de stockage affecte la disponibilité et le prix d'un fichier stocké","The target folder contains encrypted files, please supply the passphrase":"Le fichier cible contient des fichiers chiffrés. Indiquer la phrase secrète","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utilisateur a des droits d'accès trop élevés. Voulez-vous créer un nouvel utilisateur limité avec des droits d'accès uniquement pour les chemins sélectionnés ?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Cette sauvegarde a été créée sur un autre système d’exploitation. Restaurer les fichiers sans préciser un dossier de destination peut créer des fichiers à des endroits inattendus. Êtes-vous surs de vouloir poursuivre sans choisir un dossier de destination ?","This month":"Ce mois","This week":"Cette semaine","Throttle settings":"Options de contrôle du débit","Thu":"Jeu.","Time":"Heure","To File":"Vers un fichier","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pour confirmer la suppression de tous les fichiers distants pour \"{{name}}\", entrer le mot affiché ci-dessous","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Pour éviter diverses attaques basées sur le DNS, Duplicati limite les noms d'hôtes autorisés à ceux répertoriés ici. L'accès IP direct et localhost est toujours autorisé. Plusieurs noms d'hôte peuvent être fournis séparés par un points-virgule. Si l'un des noms d'hôte autorisés est un astérisque (*), tous les noms d'hôte sont autorisés et cette fonctionnalité est désactivée. Si le champ est vide, seule l'adresse IP et l'accès localhost sont autorisés.","Today":"Aujourd'hui","Trust host certificate?":"Faire confiance au certificat de l'hôte ?","Trust server certificate?":"Faire confiance au certificat du serveur ?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Essayer les nouvelles fonctionnalités en développement. Actuellement la version la plus stable disponible. Tester la restauration des données avant de l'utiliser dans des environnements de production.","Tue":"Mar.","Type passphrase here.":"Tapez la phrase secrète ici.","Type to highlight files":"Tapez pour mettre en surbrillance les fichiers","Unknown backup size and versions":"Taille et versions des sauvegardes inconnues","Until resumed":"Jusqu'à la reprise","Update channel":"Canal de mise à jour","Update failed:":"Échec de mise à jour","Updating with existing database":"Mettre à jour avec une base de données existante","Uploaded files":"Fichiers téléversés","Uploading verification file …":"Envoi du fichier de vérification...","Usage statistics":"Statistiques d'utilisation","Usage statistics, warnings, errors, and crashes":"Statistiques d'utilisation, avertissements, erreurs et accidents","Use SSL":"Utiliser SSL","Use existing database?":"Utiliser une base de données existante ?","Use weak passphrase":"Utiliser une phrase secrète faible","Useless":"Inutile","User data":"Données utilisateur","User domain name":"Nom de domaine de l'utilisateur","User has too many permissions":"L'utilisateur à trop d'autorisations","User interface settings":"Réglages interface utilisateur","Username":"Nom d'utilisateur","Vacuuming database …":"Nettoyage de la base de données...","Validating …":"Validation...","Verifications":"Vérifications","Verify files":"Vérifier fichier","Verifying answer":"Vérification de la réponse","Verifying backend data …":"Vérification des données du backend...","Verifying files …":"Vérification des fichiers...","Verifying remote data …":"Vérification des données distantes...","Verifying restored files …":"Vérification des fichiers restaurés...","Verifying …":"Vérification...","Version ID":"ID de version","Very strong":"Très fort","Very weak":"Très faible","Visit us on":"Rendez nous visite sur","WARNING: This will prevent you from restoring the data in the future.":"ATTENTION : Cela va vous empêcher de restaurer vos données dans le futur.","Waiting for task to begin":"En attente du début de la tâche","Waiting for upload to finish …":"Attente de la fin du téléversement...","Warnings, errors and crashes":"Avertissements, erreurs et accidents","We recommend that you encrypt all backups stored outside your system":"Nous vous recommandons de chiffrer toutes les sauvegardes stockées en dehors de votre système","Weak":"Faible","Weak passphrase":"Phrase secrète faible","Wed":"Mer.","Weeks":"Semaines","Where do you want to restore from?":"Ou voulez-vous restaurer vos fichiers ?","Where do you want to restore the files to?":"Ou voulez-vous restaurer vos fichiers ?","Years":"Années","Yes":"Oui","Yes, I have stored the passphrase safely":"Oui, j'ai conservé ma phrase secrète en sécurité","Yes, I understand the risk":"Oui, je comprends le risque","Yes, I'm brave!":"Oui, je suis courageux !","Yes, please break my backup!":"Oui, s'il vous plait cassez ma sauvegarde","Yesterday":"Hier","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Vous êtes en train de changer le chemin de la base de données depuis une base de donnée existante.\nÊtes-vous sûr que c'est ce que vous voulez ?","You are currently running {{appname}} {{version}}":"Version installée : {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Vous avez changé la méthode de chiffrement. Ceci peut endommager certaines choses. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Vous avez changé la phrase secrète, ce qui n'est pas pris en charge. Nous vous encourageons à créer une nouvelle sauvegarde à la place.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Vous avez choisi de ne pas chiffrer votre sauvegarde. Le chiffrement est recommandé pour toutes les données stockées sur un serveur distant.","You have chosen to restore to a new location, but not entered one":"Vous avez demandé à restaurer vers un nouveau dossier, mais sans indiquer son chemin","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Vous avez généré une phrase secrète forte. Assurez-vous que vous avez effectué une copie sécurisée de cette phrase secrète, car les données ne pourront pas être récupérées si vous la perdez.","You must choose at least one source folder":"Vous devez choisir au moins un dossier source","You must enter a domain name to use v3 API":"Vous devez entrer un nom de domaine pour utiliser l'API v3","You must enter a name for the backup":"Vous devez entrer un nom pour votre sauvegarde","You must enter a passphrase or disable encryption":"Vous devez saisir une phrase secrète ou désactiver le chiffrement","You must enter a password to use v3 API":"Vous devez saisir un mot de passe pour utiliser l'API v3","You must enter a positive number of backups to keep":"Vous devez entrer un nombre positif de sauvegarde à conserver","You must enter a tenant (aka project) name to use v3 API":"Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3","You must enter a valid duration for the time to keep backups":"Vous devez entrer une valeur correcte pour la durée de conservation de vos sauvegardes","You must enter a valid retention policy string":"Vous devez saisir une chaîne de politique de conservation valide","You must fill in the password":"Vous devez renseigner le mot de passe","You must fill in the server name or address":"Vous devez renseigner le nom du serveur ou l'adresse","You must fill in the username":"Vous devez renseigner le nom d'utilisateur","You must fill in {{field}}":"Vous devez renseigner le champ : {{field}}","You must select or fill in the AuthURI":"Vous devez sélectionner ou renseigner l'AuthURI","You must select or fill in the server":"Vous devez sélectionner ou renseigner le serveur","You must specify a path":"Vous devez spécifier un chemin.","Your files and folders have been restored successfully.":"Vos fichiers et dossiers ont été restaurés avec succès.","Your passphrase is easy to guess. Consider changing passphrase.":"Votre phrase secrète est facile à deviner. Songez à la changer.","bucket/folder/subfolder":"bucket/dossier/sous-dossier","byte":"byte","byte/s":"byte/s","custom":"personnalisé ","resume now":"reprendre maintenant","unless you are explicitly specifying --group-id":"sauf si vous spécifiez explicitement --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a été principalement développé par {{dev1}} et {{dev2}}. {{appname}} peut être téléchargé depuis {{websitename}}. {{appname}} est sous licence {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fichiers {{size}}) à transférer {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Heure","{{number}} Hours":"{{number}} Heures","{{number}} Minutes":"{{number}} Minutes","{{time}} (took {{duration}})":"{{time}} (durée {{duration}})"}); + gettextCatalog.setStrings('hu', {"- pick an option -":"- válasszon -","...loading...":"...töltés...","API key":"API kulcs","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Névjegy","About {{appname}}":"{{appname}} néjegye","Access Key":"Hozzáférési kulcs","Access denied":"Hozzáférés megtagadva","Access to user interface":"Hozzáférés a felhasználói felülethez","Account name":"Fiók név","Add a new backup":"Új mentés hozzáadás","Add a path directly":"Útvonal hozzáadás közvetlenül","Add advanced option":"Haladó beállítás hozzáadása","Add backup":"Mentés hozzáadás","Add filter":"Szűrő hozzáadás","Add path":"Útvonal hozzáadás","Added":"Hozzáadva","Advanced Options":"Haladó beállítások","Advanced options":"Haladó beállítások","Advanced:":"Haladó:","All Hyper-V Machines":"Minden Hyper-V gép","All Microsoft SQL Databases":"Minde Microsoft SQL adatbázik","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Az összes felhasználási jelentést névtelenül küldjük el, és nem tartalmaznak személyes információt. Információkat tartalmaz a hardverről és az operációs rendszerről, a háttér típusáról, a biztonsági mentés időtartamáról, a forrásadatok teljes méretéről és hasonló adatokról. Nem tartalmaz útvonalakat, fájlneveket, felhasználóneveket, jelszavakat vagy hasonló érzékeny információkat.","Allow remote access (requires restart)":"Távoli hozzáférés engedélyezése (újraindítást igényel)","Allowed days":"Engedélyezett napok","An existing file was found at the new location":"Egy létező fájt találtam az új helyen","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Egy létező fájt találtam az új helyen\nBiztos vagy benne hogy az adatbázis a létező fájlra mutasson?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"A tároláshoz létező helyi adatbázis található. Az adatbázis újbóli használata lehetővé teszi, hogy a parancssori és a kiszolgálópéldányok ugyanabban a távoli tárolóban működjenek. \n\nSzeretné használni a meglévő adatbázist?","Anonymous usage reports":"Névtelen használati jelentések","Applications":"Alkalmazások","As Command-line":"Parancssorként","Authentication password":"Hitelesítési jelszó","Authentication username":"Hitelesítési felhasználónév","Autogenerated passphrase":"Automatikusan generált jelszó","Back":"Vissza","Backup complete!":"Mentés kész!","Backup destination":"Mentés cél","Backup location":"Mentés helye","Backup retention":"Mentés késleltetés","Backup:":"Mentés:","Beta":"Béta","Broken access":"Törött hozzáférés","Browse":"Tallóz","Browser default":"Böngésző alapértelmezett","Bucket create location":"Bucket létrehozásának helye","Bucket name":"Bucket neve","Building list of files to restore …":"Fájl lista összeállítás a visszaállításhoz...","Building partial temporary database …":"Részleges ideiglenes adatbázist készítése","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"A távoli elérés engedélyezésével a szerver minden kérésre hallgat a hálózaton. Csak akkor engedélyezd ezt az opciót, ha biztos vagy benne, hogy biztonságos, tűzfallal védett hálózaton van a számítógép.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Alapértelmezés szerint a tálca ikon megnyitja a felhasználói felületet egy tokennel, amely feloldja a felhasználói felületet. Ez biztosítja, hogy a tálcán található ikonnal hozzáférjen a felhasználói felülethez, miközben másoknak is meg kell adniuk a jelszót. Ha inkább be kell írnia a jelszót, akkor is engedélyezze ezt a beállítást, ha a felhasználói felületre a tálcaikonból fér hozzá.","Cache Files":"Gyorsítótás Fájlok","Cancel":"Mégsem","Cannot move to existing file":"Nem lehet létező fájlra átnevezni","Changelog":"Váztozások","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} változásnapló","Check failed:":"Ellenőrzés sikertelen:","Check for updates now":"Frissítés ellenőrzése most","Checking for updates …":"Frissítések ellenőrzése ...","Chose a storage type to get started":"A kezdéshez válassz tárhely típust","Click to set throttle options":"Kattints a sebességkorlátozás beállításához","Commandline …":"Parancssor...","Compact Phase":"Tömörített állapot","Compact now":"Tömörítés most","Compacting remote data …":"Távoli adatok tömörítése...","Complete log":"Teljes napló","Completing backup …":"Mentés befejezése...","Completing previous backup …":"Előző mentés befejezése...","Computer":"Számítógép","Configuration file:":"Konfigurációs fájl:","Configuration:":"Konfiguráció:","Configure a new backup":"Új mentés beállítás","Confirm delete":"Törlés megerősítése","Confirm encryption passphrase":"Titkosítási jelszó megerősítése","Confirm passphrase":"Jelmondat megerősítés","Confirmation required":"Megerősítés szükséges","Connect":"Csatlakozás","Connect now":"Csatlakozás most","Connecting to server …":"Csatlakozás a kiszolgálóhoz...","Connection lost":"Csatlakozás megszakadt","Connection worked!":"Csatlakozás működik!","Container name":"Tároló neve","Container region":"Tároló régió","Continue":"Folytatás","Continue without encryption":"Folytatás titkosítás nélkül","Copied!":"Másolva!","Copy":"Másolás","Copy Destination URL to Clipboard":"Cél URL másolása a Vágólapra","Copy failed. Please manually copy the URL":"Másolás sikertelen. Próbáld meg kézzel másolni az URL-t","Core options":"Mag beállítások","Counting ({{files}} files found, {{size}})":"Számolás ({{files}} megtalált fájl, {{size}})","Crashes only":"Csak összeomlások","Create bug report …":"Hibajelentés készítés...","Create folder?":"Mappa készítés?","Created new limited user":"Új korlátozott felhasználó létrehozva","Creating bug report …":"Hibajelentés készítés...","Creating new user with limited access …":"Új felhasználó létrehozása korlátozott hozzáféréssel...","Creating target folders …":"Cél mappák létrehozása...","Creating temporary backup …":"Ideiglenes mentés létrehozása...","Current action:":"Aktuális művelet:","Current file:":"Aktuális fájl:","Current version is {{versionname}} ({{versionnumber}})":"Aktuális verzió: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Egyéni S3 végpont","Custom authentication url":"Egyéni hitelesítési URL","Custom backup retention":"Egyéni mentés késleltetés","Custom location ({{server}})":"Egyéni hely ({{server}})","Custom region value ({{region}})":"Egyéni régió érték ({{region}})","Custom server url ({{server}})":"Egyéni kiszolgáló URL ({{server}})","Custom storage class ({{class}})":"Egyéni tároló osztály ({{class}})","Database …":"Adatbázis...","Days":"Nap","Default":"Alapértelmezett","Default ({{channelname}})":"Alapértelmezett ({{channelname}})","Default excludes":"Alapértelmezett kihagyások","Default options":"Alapértelmezett beállítások","Delete":"Törlés","Delete Phase (Old Backup Versions)":"Törlési fázis (régi mentés verziók)","Delete backup":"Mentés törlése","Delete backups that are older than":"Ennél régebbi mentések törlése","Delete local database":"Helyi adatbázis törlése","Delete remote files":"Távoli fájlok törlése","Delete the local database":"A helyi adatbázis törlése","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} fájl ({{filesize}}) törlése a távoli tárhelyről?","Delete …":"Törlés...","Deleted":"Törölve","Deleted Versions":"Törölt verziók","Deleted files":"Törölt fájlok","Deleting remote files …":"Távoli fájlok törlése","Deleting unwanted files …":"Felesleges fájlok törlése...","Description (optional)":"Leírás (nem kötelező)","Description:":"Leírás:","Desktop":"Asztal","Destination":"Cél","Destination path":"Cél útvonal","Disabled":"Letiltva","Dismiss":"Elvet","Dismiss all":"Elvet mindent","Display and color theme":"Megjelenés és szín téma","Do you really want to delete the backup: \"{{name}}\" ?":"Biztos, hogy törölni akarod ezt a mentést: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Biztos, hogy törölni akarod ezt a helyi adatbázist: {{name}}","Done":"Kész","Download":"Letöltés","Downloaded files":"Letöltött fájlok","Downloading files …":"Fájlok letöltése...","Downloading update…":"Frissítés letöltése...","Duplicate option {{opt}}":"Dupla beállítás: {{opt}}","Duplicati Website":"Duplicati webodal","Duplicati forum":"Duplicati fórum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"A másolat elindul, amikor elindul, de szüneteltetett állapotban marad mindaddig. A Duplicatiák minimális rendszer erőforrásokat foglalnak el, és biztonsági másolatot nem indítanak.","Duration":"Időtartam","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Minden biztonsági mentéshez egy helyi adatbázis tartozik, amely a távoli biztonsági mentésről információkat tárol a helyi számítógépen. Biztonsági másolat törlésekor törölheti a helyi adatbázist anélkül, hogy befolyásolná a távoli fájlok visszaállításának képességét. Ha a helyi adatbázist a parancssorból készített biztonsági másolatokra használja, meg kell őriznie az adatbázist.","Edit as list":"Szerkesztés listaként","Edit as text":"Szerkesztés szövegként","Edit …":"Szerkesztés...","Encrypt file":"Fájl titkosítás","Encryption":"Titkosítás","Encryption changed":"Titkosítás megváltozott","End":"Vége","Enter URL":"URL megadás","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Adjon meg egy megtartási stratégiát kézzel. A helyőrzők napok / hetek / évek feletti órás / év / év, korlátlan U A szintaxis: 7D: 1D, 4W: 1W, 36M: 1M. Ez a példa egy biztonsági másolatot készít a következő 7 nap mindegyikére, egyet a következő 4 hétre és egy a következő 36 hónapra. Ez is 1W: 1D, 1M: 1W, 3Y: 1M formátumban írható.","Enter backup passphrase, if any":"Mentés jelszó megadása, ha van","Enter configuration details":"Beállítások részletes megadása","Enter encryption passphrase":"Titkosítási jelszó megadása","Enter expression here":"Kifejezés megadása itt","Enter the destination path":"Cél útvonal megadása","Error":"Hiba","Error!":"Hiba!","Errors and crashes":"Hibák és összeomlások","Examined":"Vizsgálva","Exclude":"Kizár","Exclude directories whose names contain":"Könyvtárak kizárása, amelyek neve tartalmazza","Exclude expression":"Kifejezés kizárása","Exclude file":"A fájl kizárása","Exclude file extension":"Fájlkiterjesztés kizárása","Exclude files whose names contain":"Fájlok kizárása, amelyek nevei tartalmazzák","Exclude filter group":"Szűrőcsoport kizárása","Exclude folder":"Mappa kizárása","Exclude regular expression":"Reguláris kifejezés kizárása","Existing file found":"Meglévő fájl található","Experimental":"Kísérleti","Export":"Export","Export backup configuration":"Biztonsági mentés konfiguráció exportálása","Export configuration":"Konfiguráció exportálása","Export passwords":"Jelszó exportálása","Export …":"Exportálás…","Exporting …":"Exportálás ...","External link":"Külső hivatkozás","FTP (Alternative)":"FTP (alternatív)","Failed to build temporary database: {{message}}":"Nem sikerült létrehozni az ideiglenes adatbázist: {{message}}","Failed to connect:":"Nem sikerült csatlakozni:","Failed to connect: {{message}}":"Nem sikerült csatlakozni: {{message}}","Failed to delete:":"A törlés nem sikerült:","Failed to fetch path information: {{message}}":"Nem sikerült letölteni az elérési út adatait: {{message}}","Failed to find backup:":"Nem sikerült megtalálni a biztonsági másolatot:","Failed to read backup defaults:":"A biztonsági másolat alapértelmezett értékeinek olvasása nem sikerült:","Failed to restore files: {{message}}":"A fájlok helyreállítása nem sikerült: {{message}}","Failed to save:":"Nem sikerült elmenteni:","Fetching path information …":"Útvonal-információ lekérése ...","File":"Fájl","Files larger than:":"Fájlok nagyobb mint:","Filters":"Szürők","Finished!":"Kész!","First run setup":"Első futtatáskori beállítás","Folder":"Mappa","Folder path":"Mappa útvonal","Fri":"Pén","GByte":"GByte","GByte/s":"GByte/s","General":"Általános","General backup settings":"Általános mentési beállítások","General options":"Általános beállítások","Generate":"Generál","Getting file versions …":"Fájl verziók lekérdezése...","Group email":"Csoport e-mail","Hidden files":"Rejtett fájlok","Hide":"Elrejt","Hide hidden folders":"Rejtett mappák elrejtése","Home":"Kezdőlap","Hostnames":"Gazdagép nevek","Hours":"Óra","How do you want to handle existing files?":"Hogyan szeretnéd kezelni a létező fájlokat?","Hyper-V Machine":"Hyper-V gép","Hyper-V Machine:":"Hyper-V gép:","Hyper-V Machines":"Hyper-V gépek","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ha egy dátum kimaradt, a lehető leghamarabb elindul.","If at least one newer backup is found, all backups older than this date are deleted.":"Ha legalább egy újabb biztonsági másolatot talál, az összes ezen időpontnál régebbi biztonsági másolatot törli.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ha nem ad meg útvonalat, az összes fájlt a bejelentkezési mappában tárolja. Biztos benne, hogy ezt akarod?","If you do not enter an API Key, the tenant name is required":"Ha nem ad meg API-kulcsot, akkor kötelező a bérlő neve","Import":"Import","Import from a file":"Importálás egy fájlból","Import metadata":"Metaadatok importálása","Importing …":"Importálás...","Incorrect answer, try again":"Érvénytelen válasz, próbáld újra","Information":"Információ","Invalid characters in path":"Érvénytelen karakterek az útvonalban","Invalid retention time":"Érvénytelen késleltetési idő","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Meghatározott számú mentés megtartása","Keep all backups":"Minden mentés megtartása","Language in user interface":"Felhasználói felület nyelve","Last month":"Előző hónap","Last successful backup:":"Utolsó sikeres mentés:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Utolsó sikeres visszaállítás: {{time}} (took {{duration || '0 seconds'}})","Latest":"Legújabb","Libraries":"Könyvtárak","Listing backup dates …":"Mentési dátumok felsorolása…","Listing remote files for purge …":"Távoli fájlok felsorolása a tisztításhoz…","Listing remote files …":"Távoli fájlok felsorolása...","Live":"Élő","Load older data":"Régebbi adatok betöltése","Loading …":"Betöltés...","Local database path:":"Helyi adatbázis útvonal:","Local repository":"Helyi tároló","Local storage":"Helyi tárhely","Location":"Hely","Log out":"Kijelentkezés","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Karbantartás","Manually type path":"Útvonal kézi megadása","Max download speed":"Maximális letöltési sebesség","Max upload speed":"Maximális feltöltési sebesség","Menu":"Menü","Microsoft SQL Database:":"Microsoft SQL adatbázis:","Microsoft SQL Databases":"Microsoft SQL adatbázisok","Minimum redundancy":"Minimális redundancia","Minimum redundancy is 1.0":"A minimális redundancia 1.0","Minutes":"Perc","Missing name":"Hiányzó név","Missing passphrase":"Hiányzó jelszó","Missing sources":"Hiányzó források","Modified":"Módosított","Mon":"Hé","Months":"Hónap","Move existing database":"Létező adatbázis áthelyezése","Move failed:":"Áthelyezés sikertelen:","My Documents":"Dokumentumok","My Music":"Zenék","My Photos":"Fényképek","My Pictures":"Képek","Name":"Név","Never":"Soha","Next":"Következő","Next scheduled run:":"Következő időzített futtatás:","Next scheduled task:":"Következő időzített feladat:","Next task:":"Következő feladat:","Next time":"Következő dátum","No":"Nem","No encryption":"Nincs titkosítás","No items selected":"Nincsenek kijelölt elemek","No passphrase entered":"Nincs megadva jelszó","No scheduled tasks":"Nincs ütemezett feladat","Non-matching passphrase":"Nem egyező jelszavak","None / disabled":"Semmi / letiltva","Not using encryption":"Nem használ titkosítást","Nothing will be deleted. The backup size will grow with each change.":"Semmi sem lesz törölve. A mentés minden változáskor növekedni fog.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"A mentések megadott számának elérését követően, a régebbi mentések törlésre kerülnek.","Opened":"Megnyitva","Operating System":"Operációs rendszer","Operation":"Művelet","Operations:":"Tevékenységek:","Optional authentication password":"Opcionális hitelesítési jelszó","Options":"Beállítások","Original location":"Eredeti hely","Others":"Egyebek","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"A biztonsági másolatok idővel automatikusan törlődnek. Egy biztonsági másolat megmarad az elmúlt 7 napból, az utolsó 4 hétből és az utolsó 12 hónapból. Legalább egy biztonsági másolat mindig marad.","Overwrite":"Felülírás","Passphrase":"Jelmondat","Passphrase (if encrypted)":"Jelszó (ha titkosított)","Passphrase changed":"A jelmondat megváltozott","Passphrases are not matching":"A jelszavak nem egyeznek meg","Passphrases do not match":"A jelszavak nem egyeznek","Password":"Jelszó","Path":"Útvonal","Path not found":"Az útvonal nem található","Path on server":"Útvonal a kiszolgálón","Pause":"Szünet","Pause after startup or hibernation":"Szünet indítás vagy hibernálás után","Pause options":"Szünet beállítások","Permissions":"Engedélyek","Pick location":"Hely választása","Port":"Port","Prevent tray icon automatic log-in":"Tálca ikon automatikus bejelentkezés megakadályozása","Previous":"Előző","Progress:":"Folyamat:","Proprietary":"Tulajdonosi","Purge Phase":"Tisztítási fázis","Purging files complete!":"Fájlok tisztítása befejezve!","Purging files …":"Fájlok tisztítása...","Rebuilding local database …":"Helyi adatbázis újraépítése...","Recreate (delete and repair)":"Újraépítés (törlés és javítás)","Recreate Database Phase":"Adatbázis újraépítési fázis","Recreating database …":"Adatbázis újraépítése...","Registering temporary backup …":"Ideiglenes mentés regisztrálása...","Relative paths not allowed":"Relatív útvonalak nem engedélyezettek","Reload":"Újratöltés","Remote":"Távoli","Remote Path":"Távoli útvonal","Remote Repository":"Távoli tároló","Remote path":"Távoli útvonal","Remote repository":"Távoli tároló","Remote volume size":"Távoli kötet méret","Remove":"Eltávolít","Remove option":"Opció eltávolítás","Removed files":"Eltávolított fájlok","Repair":"Javítás","Repair Phase":"Javítási fázis","Repairing database …":"Adatbázis javítás...","Repeat Passphrase":"Jelmondat ismét","Reporting:":"Jelentés:","Reset":"Visszaállítás","Restore":"Visszaállítás","Restore complete!":"Visszaállítás sikeres!","Restore files":"Fájlok visszaállítása","Restore files …":"Fájlok visszaállítása...","Restore from":"Visszaállítás innen","Restore from backup configuration":"Visszaállítás mentési konfigurációból","Restore options":"Visszaállítási beállítások","Restore read/write permissions":"Irási/olvasási engedélyek visszaállítása","Restored Files":"Visszaállított fájlok","Restored Folders":"Visszaállított mappák","Restored Symlinks":"Visszaállított szimbolikus linkek","Restoring files …":"Fájlok visszaállítása...","Resume":"Folytatás","Rewritten File Lists":"Újraírt fájl listák","Run again every":"Futtassa újra minden","Run now":"Futtatás most","Running commandline entry":"Parancssori bejegyzés futtatása","Running task:":"Futó feladat:","Running …":"Fut...","S3 Compatible":"S3 kompatibilis","Same as the base install version: {{channelname}}":"Ugyanaz, mint az alap telepítési verzió: {{channelname}}","Sat":"Szo","Save":"Mentés","Save and repair":"Mentés és javítás","Save different versions with timestamp in file name":"Eltérő verziók mentése időbélyeggel a fájlnévben","Save immediately":"Mentés azonnal","Scanning existing files …":"Létező fájlok szkennelése...","Scanning for local blocks …":"Helyi blokkok szkennelése...","Schedule":"Időzítés","Search":"Keresés","Search for files":"Fájlok keresése","Seconds":"Másodperc","Select files":"Fájlok kiválasztása","Server":"Kiszolgáló","Server and port":"Kiszolgáló és port","Server hostname or IP":"Kiszolgáló gazdanév vagy IP","Server is currently paused,":"A kiszolgáló jelenleg szünetel.","Server is currently paused, do you want to resume now?":"A kiszolgáló jelenleg szünetel, szeretnéd folytatni?","Server password":"Szerver jelszó","Server paused":"Kiszolgáló szünetel","Server state properties":"Kiszolgáló állapot tulajdonságok","Settings":"Beállítások","Show":"Mutat","Show advanced editor":"Speciális szerkesztő megjelenítése","Show hidden folders":"Rejtett mappák megjelenítése","Show log":"Mutasd a naplót","Show log …":"Mutasd a naplót ...","Show treeview":"Fa nézet megjelenítése","Sia server password":"Sia szerver jelszó","Smart backup retention":"Intelligens mentés késleltetés","Source Data":"Forrás adat","Source Files":"Forrás fájlok","Source data":"Forrás adat","Source folders":"Forrás mappák","Source:":"Forrás:","Specific builds for developers only. Not for use with important data.":"Fejlesztőknek szánt kiadások. Fontos mentésére nem használható.","Standard protocols":"Szabványos protokollok","Start":"Start","Starting backup …":"Mentés indítása...","Starting restore …":"Visszaállítás indítása...","Starting the restore process …":"Visszaállítási folyamat indítása...","Stop after the current file":"Leállítás az aktuális fájl után","Stop running backup":"Mentés futtatásának leállítása","Stop running task":"Feladat futtatásának leállítása","Stopping after the current file:":"Leállítás az aktuális fájl után:","Stopping task:":"Feladat leállítása:","Storage Type":"Tárhely típus","Storage class":"Tároló osztály","Stored":"Tárolva","Strong":"Erős","Success":"Siker","Sun":"V","Symbolic link":"Szimbolikus link","System Files":"Rendszer fájlok","System default ({{levelname}})":"Rendszer alapértelmezés ({{levelname}})","System files":"Rendszer fájlok","System info":"Rendszer információ","System properties":"Rendszer tulajdonságok","TByte":"TByte","TByte/s":"TByete/s","Task is running":"A feladat fut","Temporary Files":"Ideiglenes fájlok","Temporary files":"Ideiglenes fájlok","Test Phase":"Teszt fázis","Test connection":"Kapcsolat tesztelése","Testing permissions …":"Engedélyek tesztelése...","Testing …":"Tesztelés...","The dark theme (by Michal)":"Sötét téma (by Michal)","The default blue on white theme (by Alex)":"Alapértelmezett kék-fehér téma (Alextől)","The folder {{folder}} does not exist.\nCreate it now?":"A mappa nem létezik: {{folder}} .\nLétrehozzam?","The passwords do not match":"A jelszavak nem egyeznek meg","The path does not appear to exist, do you want to add it anyway?":"Úgy tűnik, hogy a megadott útvonal nem létezik, mégis hozzá akarod adni?","This month":"Ez a hónap","This week":"Ez a hét","Throttle settings":"Sebességkorlátozás beállítások","Thu":"Cs","Time":"Idő","To File":"Fájlba","Today":"Ma","Trust host certificate?":"Megbízható a gazdagép tanúsítványa?","Trust server certificate?":"Megbízható kiszolgáló tanúsítványa?","Tue":"K","Type passphrase here.":"Írd ide a jelmondatot","Type to highlight files":"A fájlok kiemeléséhez gépeljen","Unknown backup size and versions":"Ismeretlen biztonsági mentés méret és verziók","Until resumed":"Folytatásig","Update channel":"Frissítési csatorna","Update failed:":"Frissítés sikertelen:","Updating with existing database":"Frissítés létező adatbázissal","Uploaded files":"Fájlok feltöltése","Uploading verification file …":"Ellenőrző fájl feltöltése...","Usage statistics":"Használati statisztikák","Usage statistics, warnings, errors, and crashes":"Használati statisztikák, figyelmeztetések, hibák és összeomlások","Use SSL":"SSL használata","Use existing database?":"Létező adatbázis használata?","Use weak passphrase":"Használja a gyenge jelmondatot","Useless":"Hasztalan","User data":"Felhasználói adat","User domain name":"Felhasználói domain név","User has too many permissions":"A felhasználónak túl sok engedélye van","User interface settings":"Felhasználói felület beállítások","Username":"Felhasználónév","Validating …":"Érvényesítés...","Verifications":"Ellenőrzések","Verify files":"Fájlok ellenőrzése","Verifying answer":"Válasz ellenőrzése","Verifying backend data …":"Háttér adat ellenőrzése...","Verifying files …":"Fájlok ellenőrzése...","Verifying remote data …":"Távoli adatok ellenőrzése...","Verifying restored files …":"Visszaállított fájlok ellenőrzése...","Verifying …":"Ellenőrzés...","Version ID":"Verzió ID","Very strong":"Nagyon erős","Very weak":"Nagyon gyenge","Visit us on":"Látogass meg minket itt","WARNING: This will prevent you from restoring the data in the future.":"FIGYELEM: Ez megakadályozza, hogy a jövőben helyreállítsd az adatokat.","Waiting for task to begin":"Várakozás a feladat elkezdésére","Waiting for upload to finish …":"Várakozás a feltöltés befejezésére...","Warnings, errors and crashes":"Figyelmeztetések, hibák és összeomlások","We recommend that you encrypt all backups stored outside your system":"Javasoljuk, hogy titkosítson minden, a rendszeren kívül tárolt biztonsági másolatot","Weak":"Hét","Weak passphrase":"Gyenge jelmondat","Wed":"Sze","Weeks":"Hét","Where do you want to restore from?":"Honnan szeretnél visszaállítani?","Where do you want to restore the files to?":"Hova szeretnéd visszaállítani a fájlokat?","Years":"Év","Yes":"Igen","Yes, I have stored the passphrase safely":"Igen, biztonságosan tárolom a jelmondatot","Yes, I understand the risk":"Igen, megértettem a kockázatot","Yes, I'm brave!":"Igen, bátor vagyok","Yes, please break my backup!":"Igen, kérlek tedd tönkre a mentésemet!","Yesterday":"Tegnap","You must fill in the password":"Ki kell töltened a jelszót","You must fill in the server name or address":"Ki kell töltened a szerver nevét vagy a címét","You must fill in the username":"Ki kell töltened a felhasználónevet","You must fill in {{field}}":"Ez ki kell töltened: {{field}}","You must specify a path":"Meg kell adnod egy útvonalat","Your files and folders have been restored successfully.":"A fájljaid és mappáid sikeresen vissza lettek állítva.","Your passphrase is easy to guess. Consider changing passphrase.":"A jelszavadat könnyű kitalálni. Érdemes lenne megváltoztatni.","byte":"byte","byte/s":"byte/s","custom":"egyéni","resume now":"folytatás most","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fájl ({{size}}) van még hátra {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzió","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzió"],"{{number}} Hour":"{{number}} óra","{{number}} Hours":"{{number}} óra","{{number}} Minutes":"{{number}} perc"}); + gettextCatalog.setStrings('it', {"(1 error{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["(1 errore{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} errori{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} errori{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(1 warning{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["(1 avviso{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} avvisi{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} avvisi{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(interrupted)":"(interrupted)","- pick an option -":"- seleziona un'opzione -","...loading...":"... caricamento in corso ...","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"Nota: Sia continuerà a potenziare la ridondanza in seguito finché sarai connesso ai tuoi host."," Edit as text":"Modifica come testo"," Edit as text":" Modifica come testo","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

La connessione al server è stata rifiutata a causa di un'autenticazione non valida.

\n

Fai accesso nuovamente, o riapri la pagina dall'icona della barra(se possibile)

","API key":"Chiave API","AWS Access ID":"ID di accesso AWS","AWS Access Key":"Chiave di accesso AWS","AWS IAM Policy":"Norme AWS IAM","About":"Informazioni","About {{appname}}":"Informazioni {{appname}}","Access Key":"Chiave di accesso","Access Key ID":"ID chiave di accesso","Access Key Secret":"Chiave di accesso segreta","Access denied":"Accesso negato","Access grant":"Concessione accesso","Access key":"Chiave di accesso","Access to user interface":"Accesso all'interfaccia utente","Account name":"Nome account","Add a new backup":"Aggiungi un nuovo backup","Add a path directly":"Aggiungi direttamente un percorso","Add advanced option":"Aggiungi opzione","Add backup":"Aggiungi backup","Add filter":"Aggiungi filtro","Add path":"Aggiungi percorso","Added":"Aggiunto","Adjust bucket name?":"Sistemare il nome bucket?","Advanced Options":"Opzioni Avanzate","Advanced options":"Opzioni avanzate","Advanced:":"Avanzate:","Aliyun OSS Endpoint":"Endpoint Aliyun OSS","Aliyun OSS documents and resources":"Documenti e risorse di Aliyun OSS","All Hyper-V Machines":"Tutte le Macchine Hyper-V","All Microsoft SQL Databases":"Tutti i database Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Tutti i rapporti sono inviati in modo anonimo e non contengono informazioni personali. Contengono informazioni sull'hardware, sul sistema operativo, il tipo di backend, la durata del backup, la dimensione complessiva dei dati sorgente ed dati simili. Non contengono i percorsi, nomi dei file, nomi utente, password o altre informazioni sensibili.","Allow remote access (requires restart)":"Consenti accesso remoto (richiede il riavvio)","Allowed days":"Giorni consentiti","Also pause transfers":"Metti in pausa anche i trasferimenti","An existing file was found at the new location":"Un file esistente è stato trovato nella nuova posizione","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un file esistente è stato trovato nella nuova posizione.\nSei sicuro di volere che il database punti ad un file esistente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Un database locale esistente per l'archiviazione è stato trovato.\nIl riutilizzo del database consentirà alle istanze da riga di comando e dal server di lavorare sullo stesso archivio remoto.\n\nVuoi usare il database esistente?","Anonymous usage reports":"Rapporti di utilizzo anonimi","Applications":"Applicazioni","Are you sure you want to delete the remote control registration?":"Sei sicuro di voler eliminare la registrazione del controllo remoto?","As Command-line":"Come riga di comando","AuthID":"AuthID","Authentication method":"Metodo di autenticazione","Authentication method ({{auth_method}})":"Metodo di autenticazione ({{auth_method}})","Authentication password":"Password di autenticazione","Authentication username":"Nome utente di autenticazione","Autogenerated passphrase":"Genera automaticamente passphrase","Automatically run backups":"Esegui automaticamente i backup.","B2 Application ID":"ID applicazione B2","B2 Application Key":"Chiave Applicazione B2","B2 Cloud Storage Account ID":"ID Account Cloud B2 Storage","B2 Cloud Storage Application ID":"ID applicazione di archiviazione cloud B2","B2 Cloud Storage Application Key":"Chiave applicazione Archiviazione Cloud B2","Back":"Indietro","Backend modules:

{{item.Key}}

":"Moduli Backend:

{{item.Key}}

","Backup complete!":"Backup completo!","Backup destination":"Destinazione backup","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"Il backup è crittografato ma non è disponibile una passphrase. Digitare di seguito una passphrase da utilizzare per il ripristino dei file o, in caso di crittografia GPG, lasciare vuoto per consentire a gpg di recuperare la passphrase richiamando il portachiavi del sistema.","Backup location":"Posizione Backup","Backup retention":"Conservazione backup","Backup:":"Dimensione backup:","Beta":"Beta","Broken access":"Accesso non riuscito","Browse":"Browse","Browser default":"Browser predefinito","Bucket create location":"Crea posizione bucket","Bucket name":"Nome bucket","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Il nome del bucket può avere una lunghezza compresa tra 3 e 63 caratteri e contenere solo caratteri minuscoli, numeri, punti e trattini","Bucket region":"Regione bucket","Bucket region ap-guangzhou":"Regione bucket ap-guangzhou","Bucket storage class":"Classe bucket","Bucket, format: BucketName-APPID":"Bucket, formato: BucketName-APPID","Building list of files to restore …":"Creazione di un elenco di file da ripristinare ...","Building partial temporary database …":"Creazione di un database temporaneo parziale ...","Busy …":"Occupato...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Consentendo l'accesso remoto, il server ascolta le richieste da qualsiasi computer sulla rete. Se abiliti questa opzione, assicurati di utilizzare sempre il computer su una rete sicura protetta da un firewall.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Per impostazione predefinita, l'icona nella barra delle applicazioni aprirà l'interfaccia utente con un token che sblocca l'interfaccia utente. Ciò garantisce che sia possibile accedere all'interfaccia utente dall'icona nella barra delle applicazioni, mentre si richiede agli altri di inserire una password. Se si preferisce digitare la password, anche quando si accede all'interfaccia utente dall'icona nella barra delle applicazioni, abilitare questa opzione.","COS App ID":"ID app COS","COS Path or subfolder in the bucket":"Percorso COS o sottocartella nel bucket","COS Secret ID":"ID segreto COS","COS Secret Key":"Chiave segreta COS","Cache Files":"File Cache","Canary":"Canary","Cancel":"Annulla","Cancel registration":"Cancella registrazione","Cannot include \"{{text}}\"":"Non può includere \"{{text}}\"","Cannot move to existing file":"Non puoi spostare in un file esistente","Cannot specify filter include or excludes in extra options":"Non è possibile specificare i filtri include o esclude nelle opzioni extra","Change server passphrase":"Cambia la passphrase del server","Change server password":"Cambia la password del server","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog di {{appname}} {{version}}","Check failed:":"Controllo fallito:","Check for updates now":"Controlla aggiornamenti ora","Checking for updates …":"Verifica aggiornamenti …","Checking …":"Controllo...","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"Scegli 1.0 per un backup veloce, 1.5 per un'affidabilità decente, 2.0 per un caricamento più sicuro ma un backup lento.","Chose a storage type to get started":"Scegliere un tipo di archiviazione per iniziare","Click the AuthID link to create an AuthID":"Clicca sul link AuthID per creare un nuovo AuthID","Click to set throttle options":"Clicca per impostare le opzioni di limitazione","Client library to use":"Libreria client da utilizzare","Cloud API Secret ID":"ID segreto API Cloud","Cloud API Secret Key":"Chiave segreta API Cloud","Command":"Comando","Commandline arguments":"Argomenti della riga di comando","Commandline …":"Riga di comando …","Compact Phase":"Fase Compattazione","Compact now":"Comprimi","Compacting remote data …":"Compattazione dei dati remoti ...","Complete log":"Registro completo","Completing backup …":"Completamento del backup ...","Completing previous backup …":"Completamento del backup precedente ...","Compression modules:

{{item.Key}}

":"Moduli di compressione:

{{item.Key}}

","Computer":"Computer","Configuration file:":"File di configurazione:","Configuration:":"Configurazione: ","Configure a new backup":"Configura un nuovo backup","Confirm delete":"Conferma cancellazione","Confirm encryption passphrase":"Conferma passphrase crittografia","Confirm new password":"Conferma nuova password","Confirm passphrase":"Conferma passphrase","Confirmation required":"Conferma richiesta","Connect":"Connetti","Connect now":"Connetti ora","Connecting to server …":"Connessione al server …","Connecting to task …":"Connessione all'attività...","Connecting …":"Connessione...","Connection lost":"Connessione persa","Connection worked!":"Connessione funzionante!","Container name":"Nome contenitore","Container region":"Area contenitore","Continue":"Continua","Continue without encryption":"Continua senza crittografia","Copied!":"Copiato!","Copy":"Copia","Copy Destination URL to Clipboard":"Copia URL Destinazione negli Appunti","Copy URL":"Copia l'URL","Copy failed. Please manually copy the URL":"Copia non riuscita. Per favore copia manualmente l'URL","Copy log":"Copia registro","Core options":"Opzioni base","Counting ({{files}} files found, {{size}})":"Conteggio ({{files}} file trovati, {{size}})","Crashes only":"Solo arresti anomali","Create bug report …":"Crea segnalazione bug ...","Create folder?":"Creare cartella?","Created new limited user":"Creato nuovo utente limitato","Creating bug report …":"Creazione segnalazione bug ...","Creating new user with limited access …":"Creazione di un nuovo utente con accesso limitato ...","Creating target folders …":"Creazione di cartelle di destinazione ...","Creating temporary backup …":"Creazione backup temporaneo ...","Creating user …":"Creazione utente...","Current action:":"Azione corrente:","Current file:":"File corrente:","Current version is {{versionname}} ({{versionnumber}})":"La versione attuale è {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Endpoint S3 personalizzato","Custom Satellite":"Satellite personalizzato","Custom Satellite ({{satellite}})":"Satellite personalizzato ({{satellite}})","Custom authentication url":"URL di autenticazione personalizzato","Custom backup retention":"Conservazione backup personalizzato","Custom bucket storage class":"Classe archiviazione bucket personalizzata","Custom location ({{server}})":"Posizione personalizzata ({{server}})","Custom region for creating buckets":"Area personalizzata per la creazione bucket","Custom region value ({{region}})":"Valore area personalizzata ({{region}})","Custom server url ({{server}})":"URL del server personalizzato ({{server}})","Custom storage class ({{class}})":"Classe di archiviazione personalizzata ({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"DEPRECATO: {{getDeprecationMessage(item)}}","Database …":"Banca dati …","Days":"Giorni","Default":"Predefinito","Default ({{channelname}})":"Predefinito ({{channelname}})","Default excludes":"Esclusioni predefinite","Default options":"Opzioni predefinite","Default value: \"{{getDefaultValue(item)}}\"":"Valore predefinito: \"{{getDefaultValue(item)}}\"","Delete":"Cancella","Delete Phase (Old Backup Versions)":"Fase Cancellazione (Vecchie versioni di backup)","Delete backup":"Cancella backup","Delete backups that are older than":"Elimina i backup più vecchi di","Delete local database":"Cancella database locale","Delete remote control setup":"Elimina impostazione di controllo remoto","Delete remote files":"Cancella file remoti","Delete the local database":"Cancella il database locale","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Cancella {{filecount}} file ({{filesize}}) dall'archivio remoto?","Delete …":"Elimina …","Deleted":"Cancellato","Deleted Versions":"Versioni Cancellate","Deleted files":"File cancellati","Deleting remote files …":"Eliminazione di file remoti ...","Deleting unwanted files …":"Eliminazione di file indesiderati ...","Description (optional)":"Descrizione (facoltativa)","Description:":"Descrizione:","Desktop":"Desktop","Destination":"Destinazione","Destination path":"Percorso destinazione","Direct restore from backup files …":"Ripristino diretto da file di backup...","Directory path":"Percorso cartella","Disable remote control":"Disabilita controllo remoto","Disabled":"Disattivato","Dismiss":"Annulla","Dismiss all":"Ignora tutto","Display and color theme":"Tema interfaccia","Do you really want to delete the backup: \"{{name}}\" ?":"Vuoi veramente cancellare il backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Vuoi veramente cancellare il database locale per: {{name}} ?","Domain name":"Nome dominio","Done":"Fatto","Download":"Scarica","Downloaded files":"File scaricati","Downloading files …":"Download di file...","Downloading update…":"Download dell'aggiornamento...","Duplicate option {{opt}}":"Opzione duplicata {{opt}}","Duplicati Website":"Sito web di Duplicati","Duplicati forum":"Forum Duplicati","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicati necessità di essere protetto con una passphrase e una passphrase casuale è stata generata per te.\nSe apri duplicati dall'icona della barra, non hai bisogno di una passphrase, ma se hai in programma di aprirlo da un altra posizione è necessario impostare una passphrase.\nVuoi impostare una passphrase ora?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati sarà eseguito all'avvio, ma rimarrà in pausa per la durata. Duplicati occuperà risorse di sistema minime e non saranno eseguiti backup.","Duration":"Durata","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Ogni backup dispone di un database locale associato, che archivia le informazioni del backup remoto sul computer locale.\nQuando si cancella un backup, è anche possibile cancellare il database locale senza influire sulla possibilità di ripristinare i file remoti.\nSe si utilizza il database locale per i backup dalla riga di comando, è necessario mantenere il database.","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"Ogni backup ha un database locale associato, che salva le informazioni riguardo il backup remoto sulla macchina locale. Questo rende possibile eseguire molte operazioni velocemente, e riduce la quantità di dati da scaricare per ogni operazione.","Edit as list":"Modifica come elenco","Edit as text":"Modifica come testo","Edit …":"Modifica …","Email address of the Office 365 group":"Indirizzo email del gruppo Office 365","Enable remote control":"Abilita controllo remoto","Encrypt file":"Cripta file","Encryption":"Crittografia","Encryption changed":"Crittografia cambiata","Encryption modules:

{{item.Key}}

":"Moduli di crittografia:

{{item.Key}}

","Encryption passphrase":"Passphrase di crittografia","Encryption passphrase (for verification)":"Passphrase di crittografia (per la verifica)","End":"Fine","Enter URL":"Inserisci URL","Enter a backup destination URL:":"Inserisci un URL di destinazione del backup:","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Inserisci una strategia di conservazione manualmente. I segnaposto sono D/W/Y per giorni/settimane/anni e U per illimitato. La sintassi è: 7D:1D,4W:1W,36M:1M. Questo esempio mantiene un backup per ciascuno dei prossimi 7 giorni, uno per ciascuna delle prossime 4 settimane e uno per ciascuno dei 36 mesi successivi. Questo può anche essere scritto come 1W:1D,1M:1W,3Y:1M.","Enter a url, or click the "Target URL >" link":"Inserisci un URL o fai clic sul "Target URL >" link","Enter backup passphrase, if any":"Inserisci la passphrase del backup, se presente","Enter configuration details":"Inserisci dettagli configurazione","Enter encryption passphrase":"Inserisci passphrase crittografia","Enter expression here":"Inserisci qui espressione","Enter one argument per line without quotes, e.g. *.txt":"Inserisci un argomento per riga senza virgolette, ad es. *.TXT","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"Inserisci un'opzione per riga nel formato della riga di comando, ad es. --dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"Inserisci un'opzione per riga nel formato della riga di comando, ad es. {0}","Enter the destination path":"Inserisci percorso destinazione","Error":"Errore","Error!":"Errore!","Errors and crashes":"Errori e arresti anomali","Examined":"Esaminato","Exclude":"Escludi","Exclude directories whose names contain":"Escludi cartelle il cui nome contiene","Exclude expression":"Escludi espressione","Exclude file":"Escludi file","Exclude file extension":"Escludi estensione del file","Exclude files whose names contain":"Escludi file il cui nome contiene","Exclude filter group":"Escludi gruppo filtri","Exclude folder":"Escludi cartella","Exclude regular expression":"Escludi espressione regolare","Existing file found":"Trovato file esistente","Experimental":"Sperimentale","Export":"Esporta","Export backup configuration":"Esporta configurazione backup","Export configuration":"Esporta configurazione","Export passwords":"Esporta le password","Export …":"Esporta …","Exporting …":"Esportazione in corso ...","External link":"Link esterno","FTP (Alternative)":"FTP (Alternativo)","Failed to build temporary database: {{message}}":"Fallita creazione del database temporaneo: {{message}}","Failed to connect:":"Connessione fallita:","Failed to connect: {{message}}":"Connessione fallita: {{message}}","Failed to delete:":"Cancellazione fallita: ","Failed to fetch path information: {{message}}":"Recupero informazioni sul percorso fallito: {{message}}","Failed to find backup:":"Impossibile trovare il backup:","Failed to get bug report URL: {{message}}":"Impossibile ottenere l'URL di segnalazione del bug: {{message}}","Failed to import: {{message}}":"Impossibile importare: {{message}}","Failed to read backup defaults:":"Lettura impostazioni predefinite backup fallita:","Failed to read file: {{message}}":"Impossibile leggere il file: {{message}}","Failed to restore files: {{message}}":"Ripristino dei file fallito: {{message}}","Failed to save:":"Salvataggio fallito:","Fatal error, no statistics collected":"Errore fatale, nessuna statistica raccolta","Fetching path information …":"Recupero delle informazioni sul percorso ...","File":"File","Files larger than:":"File più grandi di:","Filters":"Filtri","Finished!":"Finito!","First run setup":"Impostazione prima esecuzione","Folder":"Cartella","Folder in the bucket":"Cartella nel bucket","Folder path":"Percorso cartella","Folder path name":"Nome percorso cartella","Fri":"Ven","Full destination path, including the server name, but without https":"Percorso di destinazione completo, compreso il nome del server, ma senza https","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID Progetto GCS","General":"Generale","General backup settings":"Impostazioni generali backup","General options":"Opzioni generali","Generate":"Genera","Generate IAM access policy":"Genera criteri di accesso IAM","Getting file versions …":"Ottenere versioni di file ...","Group email":"Email gruppo","Hidden files":"File nascosti","Hide":"Nascondi","Hide hidden folders":"Nascondi cartelle nascoste","Home":"Home","Hostnames":"Nomi host","Hours":"Ore","How do you want to handle existing files?":"Come vuoi gestire i file esistenti?","Hyper-V Machine":"Sitema Hyper-V","Hyper-V Machine:":"Sistema Hyper-V:","Hyper-V Machines":"Sistemi Hyper-V","ID:":"ID:","IDrive Sync directory path":"Percorso cartella di sincronizzazione di IDrive","IDrive e2 Access Key ID":"ID chiave di accesso IDrive e2","IDrive e2 Access Key Secret":"Chiave di accesso IDrive e2 segreta","If a date was missed, the job will run as soon as possible.":"Se una pianificazione non è eseguita, il backup sarà effettuato il prima possibile.","If at least one newer backup is found, all backups older than this date are deleted.":"Se si trova almeno un backup più recente, tutti i backup precedenti a questa data sono eliminati.","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"Se il backup e l'archiviazione remote non sono sicronizzati, Duplicati richiederà di eseguire un operazione di riparazione per sicronizzare il database. Se la riparazione non ha successo, puoi eliminare il database locale e rigenerarlo.","If the backup file was not downloaded automatically, right click and choose "Save as …".":"se il file di backup non è stato scaricato automaticamente tasto destro e scegli "Salva come …" .","If the backup file was not downloaded automatically, right click and choose "Save as …".":"Se il file di backup non è stato scaricato automaticamente tasto destro e scegli "Salva come …" .","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se non inserisci un percorso, tutti i file saranno salvati nella cartella di accesso.\nSei sicuro che questo è quello che vuoi?","If you do not enter an API Key, the tenant name is required":"Se non inserisci una Chiave API, è richiesto il nome del detentore","If you pause transfers they could time out and cause retries or failures.":"Se si mettono in pausa i trasferimenti, questi potrebbero andare in timeout e causare tentativi o fallimenti.","If you want to use the backup later, you can export the configuration before deleting it.":"Se vuoi usare il backup successivamente, puoi esportare la configurazione prima di cancellarla.","Import":"Importa","Import Destination URL":"Importa URL Destinazione","Import URL":"Importa l'URL","Import backup configuration":"Importa configurazione backup","Import from a file":"Importa da un file","Import metadata":"Importa metadati","Importing …":"Importazione ...","Include a file?":"Includi un file?","Include expression":"Includi espressione","Include regular expression":"Includi espressione regolare","Incorrect answer, try again":"Risposta errata, riprova","Individual builds for developers only. Not for use with important data.":"Build individuali per soli sviluppatori. Non utilizzare con dati importanti.","Information":"Informazioni","Interrupted, no statistics collected":"Interrotto, nessuna statistica raccolta","Invalid characters in path":"Caratteri non validi nel percorso","Invalid retention time":"Tempo ritenzione non valido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"È possibile connettersi ad alcuni FTP senza una password.\nSei sicuro che il tuo server FTP supporta gli accessi senza password?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Mantieni un numero specifico di backup","Keep all backups":"Mantieni tutti i backup","Keystone API version":"Versione API Keystone","Language in user interface":"Lingua interfaccia utente","Last month":"Lo scorso mese","Last successful backup:":"Ultimo backup riuscito:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Ultimo ripristino riuscito: {{time}} (took {{duration || '0 seconds'}})","Latest":"Più recente","Libraries":"Librerie","Listing backup dates …":"Elenco date di backup ...","Listing remote files for purge …":"Elenco dei file remoti per l'eliminazione ...","Listing remote files …":"Elenco dei file remoti ...","Live":"In tempo reale","Load a configuration from an exported job or a storage provider":"Carica una configurazione da un lavoro esportato o da un provider di archiviazione","Load destination from an exported job or a storage provider":"Carica una destinazione da un lavoro esportato o da un provider di archiviazione","Load older data":"Carica dati precedenti","Loading remote storage usage …":"Caricamento utilizzo archiviazione remota…","Loading …":"Caricamento in corso …","Local database for {{Backup.Backup.Name}}…loading…":"Database locale per {{Backup.Backup.Name}}...caricamento...","Local database path:":"Percorso database locale:","Local repository":"Repository locale","Local storage":"Archivio locale","Location":"Posizione","Location where buckets are created":"Posizione in cui sono creati i bucket","Log data for {{Backup.Backup.Name}}":"Dati di log per {{Backup.Backup.Name}}","Log data from the server":"Dati di log dal server","Log in":"Log in","Log out":"Log out","MByte":"MByte","MByte/s":"MByte/s","Machine is now registered, open this link to add it to your account:":"La macchina è ora registrata, apri questo link per aggiungerlo al tuo account","Maintenance":"Manutenzione","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"Assicurati che rclone è nel tuo percorso, oppure aggiungi la posizione di rclone attraverso le opzioni avanzate","Manual":"Manuale","Manual update found:":"Aggiornamento manuale trovato:","Manually type path":"Digita manualmente il percorso","Max download speed":"Velocità massima per scaricare","Max upload speed":"Velocità massima per caricare","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Microsoft SQL Database","Minimum redundancy":"Ridondanza minima","Minimum redundancy is 1.0":"Ridondanza minima è 1.0","Minutes":"Minuti","Missing name":"Nome mancante","Missing passphrase":"Passphrase mancante","Missing sources":"Sorgente mancante","Modified":"Modificato","Mon":"Lun","Months":"Mesi","Move existing database":"Sposta database esistente","Move failed:":"Spostamento fallito:","My Documents":"Documenti","My Music":"Musica","My Photos":"Foto","My Pictures":"Immagini","Name":"Nome","Never":"Mai","New Password":"Nuova password","New update found: {{message}}":"Nuovo aggiornamento trovato: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Il nuovo nome utente è {{user}}.\nCredenziali aggiornate per utilizzare il nuovo utente limitato","Next":"Avanti","Next scheduled run:":"Prossima esecuzione: ","Next scheduled task:":"Prossima attività pianificata:","Next task:":"Prossima attività:","Next time":"Prossima volta","No":"No","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nessun certificato è stato specificato in precedenza, per favore verifica con l'amministratore del server che la chiave è corretta: {{key}}\n\nVuoi approvare la chiave host riportata?","No editor found for the "{{backend}}" storage type":"Nessun editor trovato per il "{{backend}}" tipo archivio","No encryption":"Nessuna crittografia","No items selected":"Nessun elemento selezionato","No items to restore, please select one or more items":"Nessun elemento da ripristinare, seleziona uno o più elementi","No passphrase entered":"Nessuna passphrase inserita","No scheduled tasks":"Nessuna attività pianificata","Non-matching passphrase":"Passphrase non corrispondente","None / disabled":"Nessuno / disattivato","Not using encryption":"Non usare la crittografia","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"Nota che le velocità sono inserite in byte, mentre le velocità delle linee sono tipicamente riportate in bit. Per la conversione utilizzare un fattore 8, in modo che una linea da 8 mbit/s equivalga a 1 MByte/s.","Nothing will be deleted. The backup size will grow with each change.":"Niente sarà eliminato. La dimensione del backup crescerà con ogni cambiamento.","OK":"OK","OSS Access Key ID":"ID chiave d'accesso OSS","OSS Access Key Secret":"Chiave di accesso segreta OSS","OSS Bucket Region":"Regione del bucket OSS","OSS Bucket name":"OSS nome del Bucket","OSS Endpoint":"Endpoint OSS","OSS Path or subfolder in the bucket":"Percorso OSS o sottocartella nel bucket","OSS Region":"Regione dell'OSS","Official releases":"Rilasci ufficiali","Once there are more backups than the specified number, the oldest backups are deleted.":"Una volta che ci sono più backup del numero specificato, i backup più vecchi sono cancellati.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aperto","Openstack API key are not supported in v3 keystone API":"La chiave API Openstack non è supportata nell'API keystone v3","Operating System":"Sistema Operativo","Operation":"Operazione","Operations:":"Operazioni:","Optional API key":"Chiave API opzionale","Optional authentication password":"Password opzionale per l'autenticazione","Optional authentication username":"Nome utente opzionale per l'autenticazione","Optional region":"Regione opzionale","Optional tenant name":"Nome detentore facoltativo","Options":"Opzioni","Options added here are applied to all backups, but can be overridden in each individual backup.":"Le opzioni aggiunte qui sono applicate a tutti i backup, ma possono essere sovrascritte in ogni singolo backup.","Original location":"Percorso originale","Others":"Altri","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Nel corso del tempo i backup saranno eliminati automaticamente. Rimarrà un backup per ciascuno degli ultimi 7 giorni, ognuna delle ultime 4 settimane, ciascuno degli ultimi 12 mesi. Ci sarà sempre almeno un backup rimanente.","Overwrite":"Sovrascrivi","Passphrase":"Passphrase","Passphrase (if encrypted)":"Passphrase (se criptato)","Passphrase changed":"Passphrase modificata","Passphrases are not matching":"Passphrase non corrispondenti","Passphrases do not match":"Le passphrase non corrispondono","Password":"Password","Patching files with local blocks …":"Patch di file con blocchi locali ...","Path":"Percorso","Path not found":"Percorso non trovato","Path on server":"Percorso sul server","Path or subfolder in the bucket":"Percorso o sottocartella bucket","Pause":"Pausa","Pause after startup or hibernation":"Pausa dopo avvio o ibernazione","Pause options":"Opzioni pausa","Permissions":"Autorizzazioni","Pick location":"Scegli posizione","Please select a file to import":"Seleziona un file da importare","Point to your backup files and restore from there":"Puntare ai file di backup e ripristinare da lì","Port":"Porta","Prevent tray icon automatic log-in":"Previeni il log-in automatico dell'icona nella barra delle applicazioni","Previous":"Precedente","Processing files to backup …":"Elaborazione dei file per il backup ...","Progress:":"Avanzamento:","ProjectID is optional if the bucket exist":"ID Progetto è opzionale se esiste un bucket","Proprietary":"Proprietario","Purge Phase":"Fase eliminazione","Purging files complete!":"Eliminazione dei file completata!","Purging files …":"Eliminazione dei file ...","Rebuilding local database …":"Ricostruzione del database locale ...","Recreate (delete and repair)":"Ricrea (cancella e ripara)","Recreate Database Phase":"Fase ricreazione database","Recreating database …":"Ricreazione del database ...","Region":"Regione","Register for remote control":"Registrazione per il controllo remoto","Registered, waiting for accept":"Registrato, in attesa di accettazione","Registering machine...":"Registro macchina...","Registering temporary backup …":"Registrazione backup temporaneo ...","Registration URL":"URL di registrazione","Registration failed":"Registrazione fallita","Relative paths not allowed":"Percorsi relativi non consentiti","Reload":"Ricarica","Remote":"Remoto","Remote Path":"Percorso remoto","Remote Repository":"Repository remoto","Remote access control":"Controllo dell'accesso remoto","Remote control is configured but not enabled":"Controllo remoto è configurato ma non è abilitato","Remote control is connected":"Controllo remoto è connesso","Remote control is enabled but not connected":"Controllo remoto è abilitato ma non connesso","Remote control is not set up":"Controllo remoto non è configurato","Remote path":"Percorso remoto","Remote repository":"Repository remoto","Remote volume size":"Dimensione volume remoto","Remove":"Rimuovi","Remove option":"Rimuovi opzione","Removed files":"File rimossi","Repair":"Ripara","Repair Phase":"Fase riparazione","Repairing database …":"Ripristino del database ...","Repeat Passphrase":"Ripeti Passphrase","Reporting:":"Segnalazione:","Reset":"Reset","Restore":"Ripristina","Restore complete!":"Ripristino completato!","Restore files":"Ripristina file","Restore files from:":"Ripristina file da:","Restore files …":"Ripristina file ...","Restore from":"Ripristina da","Restore from backup configuration":"Ripristino dalla configurazione backup","Restore from configuration …":"Ripristina dalla configurazione…","Restore options":"Opzioni ripristino","Restore read/write permissions":"Ripristina autorizzazioni lettura/scrittura","Restored Files":"File ripristinati","Restored Folders":"Cartelle ripristinate","Restored Symlinks":"Symlink ripristinati","Restoring files …":"Ripristino di file ...","Resume":"Riprendi","Rewritten File Lists":"Elenchi file riscritti","Run again every":"Esegui ogni","Run now":"Esegui ora","Running commandline entry":"Riga di comando in esecuzione","Running task:":"Attività in esecuzione:","Running …":"In esecuzione …","Running … stop now":"In esecuzione...ferma ora","S3 Compatible":"Compatibile S3","Same as the base install version: {{channelname}}":"Come la versione di base installata: {{channelname}}","Sat":"Sab","Satellite":"Satellitare","Save":"Salva","Save and repair":"Salva e ripara","Save different versions with timestamp in file name":"Salva versioni diverse con timestamp nel nome del file","Save immediately":"Salva immediatamente","Scanning existing files …":"Scansione di file esistenti ...","Scanning for local blocks …":"Scansione per blocchi locali ...","Schedule":"Pianificazione","Search":"Cerca","Search for files":"Cerca per file","Seconds":"Secondi","Select a log level and see messages as they happen:":"Selezionare un livello di log e visiona i messaggi che avvengono:","Select files":"Seleziona file","Server":"Server","Server and port":"Server e porta","Server hostname or IP":"Nome host o IP del server","Server is currently paused,":"Server è attualmente in pausa,","Server is currently paused, resume now":"Il server è attualmente in pausa, riprendi adesso","Server is currently paused, do you want to resume now?":"Server attualmente in pausa, vuoi riprendere ora?","Server password":"Password del server","Server paused":"Server in pausa","Server state properties":"Proprietà stato del server","Set timezone to default":"Imposta il fuso orario predefinito","Settings":"Impostazioni","Show":"Mostra","Show advanced editor":"Mostra editor avanzato","Show hidden folders":"Mostra cartelle nascoste","Show log":"Mostra log","Show log …":"Mostra registro …","Show treeview":"Visualizza ad albero","Sia server password":"Password del server Sia","Smart backup retention":"Conservazione intelligente backup","Some OpenStack providers allow an API key instead of a password and tenant name":"Alcuni provider OpenStack consentono una chiave API anziché una password e un nome detentore","Some S3 providers might only be compatible with a certain client library":"Alcuni provider S3 potrebbero essere compatibili solo con una determinata libreria client","Source Data":"Dati Sorgente","Source Files":"Sorgente File","Source data":"Dati sorgente","Source folders":"Cartella sorgente","Source:":"Sorgente:","Specific builds for developers only. Not for use with important data.":"Build specifiche per soli sviluppatori. Non utilizzare con dati importanti.","Stable":"Stabile","Standard protocols":"Protocolli standard","Start":"Avvio","Starting backup …":"Avvio backup ...","Starting restore …":"Avvio ripristino ...","Starting the restore process …":"Avvio del processo di ripristino ...","Status: {{getRemoteControlStatusText()}}":"Stato: {{getRemoteControlStatusText()}}","Stop after the current file":"Ferma dopo il file corrente","Stop running backup":"Ferma esecuzione backup","Stop running task":"Ferma esecuzione attività","Stopping after the current file:":"Arresto dopo il file corrente:","Stopping task:":"Ferma attività:","Storage Type":"Tipo archivio","Storage class":"Classe archivio","Storage class for creating a bucket":"Classe di archiviazione per la creazione di un bucket","Stored":"Archiviati","Strong":"Forte","Success":"Successo","Sun":"Dom","Symbolic link":"Link simbolico","System Files":"File di Sistema","System default ({{levelname}})":"Sistema predefinito ({{levelname}})","System files":"File di sistema","System info":"Informazioni di sistema","System properties":"Proprietà di sistema","TByte":"TByte","TByte/s":"TByte/s","Target URL >":"URL di destinazione >","Target path. Example: /backup":"Percorso di destinazione. Esempio: /backup","Task is running":"Attività in esecuzione","Temporary Files":"File Temporanei","Temporary files":"File temporanei","Tenant name":"Nome detentore","Tencent Cloud Account APPID":"APPID dell'account Tencent Cloud","Tencent Cloud COS documents and resources":"Documentazione e risorse di Tencent Cloud COS","Terminate":"Termina","Test Phase":"Fase test","Test connection":"Prova connessione","Testing connection …":"Verifica della connessione...","Testing permissions …":"Test delle autorizzazioni ...","Testing …":"Test in corso...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Il campo '{{fieldname}}' contiene un carattere non valido: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Il backup è mancante, è stato cancellato?","The backup was temporary and does not exist anymore, so the log data is lost":"Il backup era temporaneo e non esiste più, quindi i dati del registro sono persi","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"I backup saranno suddivisi in più file chiamati volumi. Qui è possibile impostare la dimensione massima dei singoli file di volume. Consulta questa pagina per ulteriori informazioni.","The bucket name should be all lower-case, convert automatically?":"Il nome del bucket dovrebbe essere tutto minuscolo, convertirlo automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"La configurazione dovrebbe essere mantenuta al sicuro. Sei sicuro di voler salvare un file non criptato contenente le tue password?","The connection to the server is lost, attempting again in {{time}} …":"La connessione al server è stata persa, nuovo tentativo tra {{time}} …","The dark theme (by Michal)":"Tema scuro (da Michal)","The default blue on white theme (by Alex)":"Predefinito - Tema blu su bianco (da Alex)","The encryption passphrases do not match":"Le passphrase di crittografia non corrispondono","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"La dimensione del file è {{size}}, superiore alla dimensione massima specificata. Se la dimensione del file diminuisce, sarà inclusa nei backup futuri.","The folder {{folder}} does not exist.\nCreate it now?":"La cartella {{folder}} non esiste. \nCreala adesso?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"La chiave host è cambiata, per favore consulta l'amministratore del server se questa è corretta, altrimenti potresti essere la vittima di un attacco UOMO-NEL-MEZZO.\n\nVuoi SOSTITUIRE la chiave host CORRENTE \"{{prev}}\" con la chiave host SEGNALATA: {{key}}?","The passwords do not match":"Le password non corrispondono","The path does not appear to exist, do you want to add it anyway?":"Il percorso sembra non esistere, vuoi aggiungerlo comunque?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Il percorso non termina con un carattere '{{dirsep}}', il che significa che si include un file, non una cartella.\n\nVuoi includere il file specificato?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Il percorso deve essere un percorso assoluto, cioè deve iniziare con una barra '/'","The region parameter is only applied when creating a new bucket":"Il parametro regione è applicato solo quando si crea un nuovo bucket","The region parameter is only used when creating a bucket":"Il parametro regione è utilizzato solo quando si crea un bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Il certificato del server non può essere convalidato.\n\nVuoi approvare il certificato SSL con l'hash: {{hash}}?","The storage class affects the availability and price for a stored file":"La classe di archiviazione influisce sulla disponibilità e sul prezzo per un file archiviato","The target folder contains encrypted files, please supply the passphrase":"La cartella di destinazione contiene file criptati, per favore fornisci la passphrase","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"L'utente dispone di troppe autorizzazioni. Vuoi creare un nuovo utente limitato, con solo autorizzazioni per il percorso selezionato?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Questo backup è stato creato su un altro sistema operativo. Il ripristino dei file senza specificare una cartella di destinazione può causare il ripristino di file in luoghi imprevisti. Sei sicuro di voler continuare senza scegliere una cartella di destinazione?","This month":"Questo mese","This week":"Questa settimana","Throttle settings":"Impostazioni limitazione","Thu":"Mar","Time":"Tempo","Time zone":"Fuso orario","To File":"Al File","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Per confermare che vuoi cancellare tutti i file remoti che contengono \"{{name}}\", digita la parla che vedi di seguito","To export without a passphrase, uncheck the \"Encrypt file\" box":"Per esportare senza una passphrase, deselezionare la casella \"Cripta file\"","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"Per evitare conflitti di denominazione dei bucket, è consigliabile anteporre l'ID dell'account al nome del bucket. Anteporre automaticamente?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Per prevenire vari attacchi basati su DNS, Duplicati limita gli hostname consentiti a quelli qui elencati. L'accesso IP e localhost diretti sono sempre consentiti. Più nomi host possono essere forniti con un separatore di punto e virgola. Se uno qualsiasi dei nomi host consentiti è un asterisco (*), tutti i nomi host sono consentiti e questa funzione è disabilitata. Se il campo è vuoto, sono consentiti solo gli accessi dall'indirizzo IP e localhost.","Today":"Oggi","Trust host certificate?":"Certificato host affidabile?","Trust server certificate?":"Certificato server affidabile?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Prova le nuove funzionalità su cui stiamo lavorando. Attualmente la versione più stabile disponibile. Prova il Ripristino dati prima di utilizzarla negli ambienti di produzione.","Tue":"Gio","Type passphrase here.":"Scrivi la passphrase qui.","Type to highlight files":"Digitare per evidenziare i file","Unknown backup size and versions":"Dimensione e versione backup sconosciute","Until resumed":"Finché non riprende","Update {{state.updatedVersion}} is available. Download now":"L'aggiornamento {{state.updatedVersion}} è disponibile. Scaricalo ora","Update channel":"Canale di aggiornamento","Update failed:":"Aggiornamento fallito:","Updating with existing database":"Aggiornamento con database esistente","Uploaded files":"File caricati","Uploading verification file …":"Caricamento file di verifica ...","Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics.":"I rapporti di utilizzo ci aiutano a migliorare l'esperienza dell'utente e a valutare l'impatto di nuove funzionalità. Li utilizziamo per generare statistiche di utilizzo pubblico.","Usage statistics":"Statistiche di utilizzo","Usage statistics, warnings, errors, and crashes":"Statistiche di utilizzo, avvisi, errori e arresti anomali","Use SSL":"Usa SSL","Use existing database?":"Usare database esistente?","Use weak passphrase":"Usa passphrase debole","Useless":"Inutile","User data":"Dati utente","User domain name":"Nome dominio utente","User has too many permissions":"L'utente ha troppe autorizzazioni","User interface settings":"Impostazioni interfaccia utente","Username":"Nome utente","Vacuuming database …":"Prelevamento database ...","Validating …":"Convalida in corso ...","Verifications":"Verifiche","Verify encryption passphrase":"Verifica la passphrase di crittografia","Verify files":"Verifica file","Verifying answer":"Verifica risposta","Verifying backend data …":"Verifica dei dati di backend ...","Verifying files …":"Verifica dei file ...","Verifying remote data …":"Verifica dei dati remoti ...","Verifying restored files …":"Verifica dei file ripristinati ...","Verifying …":"Verifica in corso ...","Version ID":"Versione ID","Very strong":"Molto forte","Very weak":"Molto debole","Visit us on":"Seguici su","WARNING: The remote database is found to be in use by the commandline library.":"WARNING: The remote database is found to be in use by the commandline library.","WARNING: This will prevent you from restoring the data in the future.":"ATTENZIONE: Questo ti impedirà di ripristinare i dati in futuro.","Waiting for task to begin":"In attesa dell'attività per iniziare","Waiting for task to start …":"In attesa dell'avvio dell'attività...","Waiting for upload to finish …":"In attesa del completamento del caricamento ...","Warnings, errors and crashes":"Avvisi, errori e arresti anomali","We recommend that you encrypt all backups stored outside your system":"Ti consigliamo di criptare tutti i backup archiviati al di fuori del tuo sistema","Weak":"Debole","Weak passphrase":"Passphrase debole","Wed":"Mer","Weeks":"Settimane","Where do you want to restore from?":"Da dove vuoi ripristinare?","Where do you want to restore the files to?":"Dove vuoi ripristinare i files?","Years":"Anni","Yes":"Si","Yes, I have stored the passphrase safely":"Si, ho archiviato la passphrase in modo sicuro","Yes, I understand the risk":"Sì, capisco il rischio","Yes, I'm brave!":"Sì, sono coraggioso!","Yes, please break my backup!":"Sì, per favore rompi il mio backup!","Yesterday":"Ieri","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Stai cambiando il percorso di un database esistente.\nSei sicuro che questo è ciò che vuoi?","You are currently running {{appname}} {{version}}":"Attualmente stai eseguendo {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished. If you terminate the backup, the next run will need to recover from a failed backup.":"È possibile interrompere il backup al termine del caricamento dei file in corso. Se si interrompe il backup, l'esecuzione successiva dovrà ripristinare un backup non riuscito.","You can stop the task immediately, or allow the process to continue its current file and then stop. If you terminate the task, the backup could be left in an inconsistent state.":"È possibile interrompere immediatamente l'attività o consentire al processo di continuare il suo file corrente e quindi interromperlo. Se si termina l'attività, il backup potrebbe rimanere in uno stato inconsistente.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Hai modificato l'algoritmo di crittografia. Questa azione potrebbe corrompere i dati. Ti consigliamo di creare un nuovo backup.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Hai modificato la passphrase ma questo non è supportato. Ti consigliamo di creare un nuovo backup.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Hai scelto di non criptare il backup. È consigliabile criptare tutti i dati custoditi su server remoti.","You have chosen to restore to a new location, but not entered one":"Si è scelto di ripristinare in una nuova posizione, ma non ne è stata inserita una","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Hai generato una passphrase forte. Assicurati di aver fatto una copia sicura della passphrase, poiché i dati non possono essere recuperati se perdi la passphrase.","You must choose at least one source folder":"Devi scegliere almeno una cartella sorgente","You must enter a domain name to use v3 API":"Devi inserire un nome di dominio per utilizzare l'API v3","You must enter a name for the backup":"Devi inserire un nome per il backup","You must enter a passphrase or disable encryption":"Devi inserire una passphrase o disattivare la crittografia","You must enter a password to use v3 API":"Devi inserire una password per utilizzare l'API v3","You must enter a positive number of backups to keep":"Devi inserire un numero positivo di backup da mantenere","You must enter a tenant (aka project) name to use v3 API":"Devi inserire un detentore (aka progetto) per utilizzare l'API v3","You must enter a tenant name if you do not provide an API key":"È necessario inserire un nome detentore se non si fornisce una chiave API","You must enter a valid duration for the time to keep backups":"Devi inserire un periodo di tempo valido in cui mantenere i backup","You must enter a valid retention policy string":"Devi inserire una stringa di criteri di conservazione valida","You must enter either a password or an API key":"È necessario inserire una password o una chiave API","You must enter either a password or an API key, not both":"È necessario inserire una password o una chiave API, non entrambe","You must fill in the password":"Devi compilare in password","You must fill in the server name or address":"Devi compilare in nome del server o indirizzo","You must fill in the username":"Devi compilare in nome utente","You must fill in {{field}}":"Devi compilare in {{field}}","You must select or fill in the AuthURI":"Devi selezionare o compilare in AuthURI","You must select or fill in the server":"Devi selezionare o compilare in server","You must specify a path":"Devi specificare un percorso","You should fill in {{field}} {{reason}}":"Dovresti compilare {{field}} {{reason}}","Your files and folders have been restored successfully.":"I tuoi file e cartelle sono stati ripristinati correttamente.","Your passphrase is easy to guess. Consider changing passphrase.":"La tua passphrase è facile da indovinare. Considera l'idea di cambiarla.","bucket/folder/subfolder":"bucket/cartella/sottocartella","byte":"byte","byte/s":"byte/s","cos_app_id":"cos_app_id","cos_bucket":"cos_bucket","cos_region":"cos_region","cos_secret_id":"cos_secret_id","cos_secret_key":"cos_secret_key","custom":"Personalizzato","failed":"fallito","local repository, leave empty for local":"repository locale, lascia vuoto per il locale","oss_access_key_id":"oss_access_key_id","oss_access_key_secret":"oss_access_key_secret","oss_bucket_name":"oss_bucket_name","oss_endpoint":"oss_endpoint","oss_region":"oss_region","pCloud EU (eapi.pcloud.com)":"pCloud EU (eapi.pcloud.com)","pCloud Global (api.pcloud.com)":"pCloud Global (api.pcloud.com)","remote path, e.g. backup":"percorso remoto, ad es. backup","remote repository, e.g. remote":"repository remoto, ad es. remoto","resume now":"riprendi ora","storj_shared_access":"storj_shared_access","unless you are explicitly specifying --group-id":"a meno che tu non stia specificando esplicitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} è stato sviluppato principalmente da {{dev1}} e {{dev2}}. {{appname}} può essere scaricato da {{websitename}}. {{appname}} è sotto la licenza {{licensename}}.","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}} utilizza le seguenti librerie di terze parti:","{{files}} files ({{size}}) to go {{speed_txt}}":"Caricamento di {{files}} file ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versione","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versioni","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versioni"],"{{number}} Hour":"{{number}} Ore","{{number}} Hours":"{{number}} Ore","{{number}} Minutes":"{{number}} Minuti","{{time}} (took {{duration}})":"{{time}} (durata {{duration}})"}); + gettextCatalog.setStrings('ja_JP', {"(1 error{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":"({{$count}}件のエラー{{item.Result.Interrupted? ('、中断されました'|translate) : ''}})","(1 warning{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":"({{$count}}件の警告{{item.Result.Interrupted? ('、中断されました'|translate) : ''}})","(interrupted)":"(中断されました)","- pick an option -":"- オプションを選択してください -","...loading...":"…読み込んでいます…","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"注意:ホストに接続している間、Siaは後から冗長性を増加させます。"," Edit as text":" テキストで編集"," Edit as text":" テキストで編集","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

不正認証のためサーバーへの接続は拒否されました。

\n

再度ログインするか、トレイのアイコンからページを再度開いてください(該当する場合)。

","API key":"APIキー","AWS Access ID":"AWSのアクセスID","AWS Access Key":"AWSのアクセスキー","AWS IAM Policy":"AWSのIAMポリシー","About":"概要","About {{appname}}":"{{appname}}について","Access Key":"アクセスキー","Access Key ID":"アクセスキーのID","Access Key Secret":"アクセスキーのシークレット","Access denied":"アクセスが拒否されました","Access grant":"アクセス権","Access key":"アクセスキー","Access to user interface":"ユーザーインターフェースへのアクセス","Account name":"アカウント名","Add a new backup":"新しいバックアップを作成","Add a path directly":"パスディレクトリを追加","Add advanced option":"高度な設定を追加","Add backup":"バックアップを追加","Add filter":"フィルターを追加","Add path":"パスを追加","Added":"追加済","Adjust bucket name?":"バケットの名称を変更しますか?","Advanced Options":"高度な設定","Advanced options":"高度な設定","Advanced:":"高度:","Aliyun OSS Endpoint":"Aliyun OSSのエンドポイント","Aliyun OSS documents and resources":"Aliyun OSSのドキュメントと参考資料","All Hyper-V Machines":"全てのHyper-Vマシン","All Microsoft SQL Databases":"全てのMicrosoft SQLデータベース","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"使用状況に関する報告は全て匿名で送信され、個人情報を含みません。報告には、ハードウェア、OS、バックエンドの種類、バックアップの保持期間、バックアップ元のデータなどの全体のサイズに関するデータが含まれます。パス、ファイル名、ユーザー名、パスワードなどの機密情報は含まれません。","Allow remote access (requires restart)":"リモートアクセスを許可(要再起動)","Allowed days":"実行を許可する日","An existing file was found at the new location":"既存のファイルが新しい場所で見つかりました","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"既存のファイルが新しい場所で見つかりました。\nデータベースを既存のファイルに指定してよろしいですか?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"保存領域のデータベースがローカルに存在しています。データベースを再利用すると、コマンドラインと、サーバーのインスタンスが、リモートの同じ保存領域で作業できるようになります。\n\nローカルに存在するデータベースを使用しますか?","Anonymous usage reports":"使用状況に関する匿名の報告","Applications":"アプリケーション","As Command-line":"コマンドライン","AuthID":"認証ID","Authentication method":"認証方法","Authentication method ({{auth_method}})":"認証方法({{auth_method}})","Authentication password":"認証パスワード","Authentication username":"認証ユーザー名","Autogenerated passphrase":"自動生成したパスフレーズ","Automatically run backups":"バックアップを自動的に実行","B2 Application ID":"B2 アプリケーションのID","B2 Application Key":"B2 アプリケーションのキー","B2 Cloud Storage Account ID":"B2 クラウドストレージのアカウントのID","B2 Cloud Storage Application ID":"B2 クラウドストレージのアプリケーションのID","B2 Cloud Storage Application Key":"B2 クラウドストレージのアプリケーションのキー","Back":"戻る","Backend modules:

{{item.Key}}

":"バックエンドモジュール:

{{item.Key}}

","Backup complete!":"バックアップが完了しました!","Backup destination":"バックアップ先","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"バックアップは暗号化されていますが、パスフレーズが指定されていません。ファイルを復元するには、以下にパスフレーズを入力するか、GPGによる暗号化を行っている場合は、以下を空欄のままにして、gpgでシステムのキーチェーンからパスフレーズを取得してください。","Backup location":"バックアップの場所","Backup retention":"バックアップの保持期間","Backup:":"バックアップ:","Beta":"ベータ版","Broken access":"アクセスが壊れています","Browse":"参照","Browser default":"ブラウザ設定","Bucket create location":"バケットを作成する場所","Bucket name":"バケット名","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"バケット名は3文字から63文字までの間で指定してください。バケット名には、アルファベットの小文字、数字、点、ダッシュのみを含めることができます。","Bucket region":"バケットのリージョン","Bucket region ap-guangzhou":"バケットのリージョン ap-guangzhou","Bucket storage class":"バケットのストレージクラス","Bucket, format: BucketName-APPID":"バケット名。形式:BucketName-APPID","Building list of files to restore …":"復元するファイルの一覧を作成しています…","Building partial temporary database …":"一時的なデータベースを構築しています…","Busy …":"取り込み中…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"遠隔アクセスを許可すると、サーバーはあなたのネットワークの任意のコンピューターからのリクエストを受け付けます。このオプションを有効にする場合は、ファイヤーウォールで安全に保護されているネットワークのコンピューターを使用してください。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"トレイアイコンは既定で、トークンでロックを解除してユーザーインターフェースを開きます。この場合、他のユーザーはパスワードを入力する必要がありますが、ユーザーはトレイアイコンからユーザーインターフェースにアクセスすることができます。トレイアイコンからアクセスする場合にパスワードを入力するよう設定したい場合は、このオプションを有効にしてください。","COS App ID":"COS AppのID","COS Path or subfolder in the bucket":"COSのパスあるいはバケットのサブフォルダー","COS Secret ID":"COSのシークレットのID","COS Secret Key":"COSの秘密鍵","Cache Files":"キャッシュファイル","Canary":"実験的(カナリア)","Cancel":"キャンセル","Cannot include \"{{text}}\"":"「{{text}}」を含めることはできません","Cannot move to existing file":"既にファイルがあるため移動できません","Cannot specify filter include or excludes in extra options":"追加のオプションに、含めたり除外したりするフィルターを指定することはできません","Change server passphrase":"サーバーのパスフレーズを変更","Change server password":"サーバーのパスワードを変更","Changelog":"更新履歴","Changelog for {{appname}} {{version}}":"更新履歴 {{appname}} {{version}}","Check failed:":"確認できませんでした:","Check for updates now":"アップデートを確認","Checking for updates …":"アップデートを確認しています…","Checking …":"確認しています…","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"高速なバックアップには1.0、安定性を求める場合は1.5、速度に代えて安全性を求める場合は2.0を指定してください。","Chose a storage type to get started":"初めにストレージの種類を選択してください","Click the AuthID link to create an AuthID":"認証IDのリンクをクリックして作成してください","Click to set throttle options":"クリックで速度制限のオプションを設定","Client library to use":"使用するクライアントライブラリー","Cloud API Secret ID":"Cloud APIのシークレットID","Cloud API Secret Key":"Cloud APIの秘密鍵","Command":"コマンド","Commandline arguments":"コマンドラインの引数","Commandline …":"コマンドライン…","Compact Phase":"圧縮化の段階","Compact now":"圧縮","Compacting remote data …":"リモートデータを圧縮しています…","Complete log":"完全なログ","Completing backup …":"バックアップを完了しています…","Completing previous backup …":"以前のバックアップを完了しています…","Compression modules:

{{item.Key}}

":"圧縮モジュール:

{{item.Key}}

","Computer":"コンピューター","Configuration file:":"設定ファイル:","Configuration:":"設定:","Configure a new backup":"新しいバックアップを設定","Confirm delete":"削除を確認","Confirm encryption passphrase":"暗号化用パスフレーズを確認","Confirm new password":"新しいパスワードを再度入力してください","Confirm passphrase":"パスフレーズを確認","Confirmation required":"確認が必要です","Connect":"接続","Connect now":"今すぐ接続","Connecting to server …":"サーバーに接続しています…","Connecting to task …":"タスクに接続しています…","Connecting …":"接続しています…","Connection lost":"切断しました","Connection worked!":"接続できました!","Container name":"コンテナ名","Container region":"コンテナのリージョン","Continue":"続行","Continue without encryption":"暗号化なしで続行","Copied!":"コピーしました!","Copy":"コピー","Copy Destination URL to Clipboard":"バックアップ先のURLをクリップボードにコピー","Copy URL":"URLをコピー","Copy failed. Please manually copy the URL":"コピーできませんでした。URLを手動でコピーしてください","Copy log":"ログをコピー","Core options":"中心のオプション","Counting ({{files}} files found, {{size}})":"計測中({{files}}個のファイルが見つかりました。サイズは{{size}})","Crashes only":"クラッシュのみ","Create bug report …":"バグレポートを作成…","Create folder?":"フォルダーを作成しますか?","Created new limited user":"新規の制限ユーザーを作成しました","Creating bug report …":"バグレポートを作成しています…","Creating new user with limited access …":"アクセスが制限されている新規ユーザーを作成しています…","Creating target folders …":"バックアップ先のフォルダーを作成しています…","Creating temporary backup …":"一時的なバックアップを作成しています…","Creating user …":"ユーザーを作成しています…","Current action:":"現在のアクション:","Current file:":"現在のファイル:","Current version is {{versionname}} ({{versionnumber}})":"現在のバージョンは {{versionname}}({{versionnumber}})","Custom S3 endpoint":"ユーザー定義のS3エンドポイント","Custom Satellite":"ユーザー定義のサテライト","Custom Satellite ({{satellite}})":"ユーザー定義のサテライト({{satellite}})","Custom authentication url":"ユーザー定義の認証用URL","Custom backup retention":"ユーザー定義のバックアップの保持期間","Custom bucket storage class":"ユーザー定義のバケットストレージのクラス","Custom location ({{server}})":"ユーザー定義の場所({{server}})","Custom region for creating buckets":"バケットを作成するユーザー定義のリージョン","Custom region value ({{region}})":"ユーザー定義のリージョンの値({{region}})","Custom server url ({{server}})":"ユーザー定義のサーバーURL ({{server}})","Custom storage class ({{class}})":"ユーザー定義の保存領域のクラス({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"非推奨:{{getDeprecationMessage(item)}}","Database …":"データベース…","Days":"日","Default":"初期設定","Default ({{channelname}})":"既定({{channelname}})","Default excludes":"既定で除外するアイテム","Default options":"既定のオプション","Default value: \"{{getDefaultValue(item)}}\"":"既定値:「{{getDefaultValue(item)}}」","Delete":"削除","Delete Phase (Old Backup Versions)":"削除の段階","Delete backup":"バックアップを削除","Delete backups that are older than":"古いバックアップから削除","Delete local database":"ローカルデータベースを削除","Delete remote files":"リモートファイルを削除","Delete the local database":"ローカルデータベースを削除","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}}個のファイル({{filesize}})をリモートの保存領域から削除しますか?","Delete …":"削除...","Deleted":"削除済","Deleted Versions":"削除されたバージョン","Deleted files":"削除されたファイル","Deleting remote files …":"リモートファイルを削除しています…","Deleting unwanted files …":"不要なファイルを削除しています…","Description (optional)":"概要(任意)","Description:":"概要:","Desktop":"デスクトップ","Destination":"バックアップ先","Destination path":"バックアップ先のパス","Direct restore from backup files …":"バックアップファイルから直接復元…","Directory path":"ディレクトリーのパス","Disabled":"無効","Dismiss":"表示しない","Dismiss all":"すべて表示しない","Display and color theme":"テーマカラー","Do you really want to delete the backup: \"{{name}}\" ?":"バックアップ \"{{name}}\" を削除してよろしいですか?","Do you really want to delete the local database for: {{name}}":"{{name}} のデータベースを削除してよろしいですか?","Domain name":"ドメイン名","Done":"完了","Download":"ダウンロード","Downloaded files":"ダウンロードされたファイル","Downloading files …":"ファイルをダウンロードしています…","Downloading update…":"アップデートをダウンロードしています…","Duplicate option {{opt}}":"複製に関するオプション {{opt}}","Duplicati Website":"Duplicatiのウェブサイト","Duplicati forum":"Duplicatiのフォーラム","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicatiはパスフレーズで保護する必要があります。ランダムなパスフレーズを作成しました。\nDuplicatiをトレイアイコンから開く場合はパスフレーズは必要ありませんが、別の場所から開くにはパスフレーズを入力する必要があります。\nパスフレーズを設定しますか?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicatiは起動と同時に実行しますが、ここで指定した時間が経過するまで一時停止の状態を維持します。一時停止の間、Duplicatiは最低限のシステムの処理能力しか使用せず、その間バックアップは実行されません。","Duration":"経過","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"それぞれのバックアップには、ローカルのコンピューターに保存されるデータベースがあります。\nバックアップを削除する際、リモートファイルの復元に影響を与えずにローカルのデータベースを削除することもできます。\nコマンドラインからバックアップ用のローカルのデータベースを使用している場合は、データベースを削除しないでください。","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"それぞれのバックアップには、ローカルのコンピューターに保存されるデータベースがあります。このデータベースには、リモートバックアップに関する情報が保存されており、操作の速度を改善したり、その都度の操作でダウンロードするデータ量を減らしたりする効果があります。","Edit as list":"一覧で編集","Edit as text":"テキストで編集","Edit …":"編集...","Email address of the Office 365 group":"Office 365グループのメールアドレス","Encrypt file":"ファイルを暗号化","Encryption":"暗号化の方式","Encryption changed":"暗号化の方式が変更されました","Encryption modules:

{{item.Key}}

":"暗号化モジュール:

{{item.Key}}

","Encryption passphrase":"暗号化用のパスフレーズ","Encryption passphrase (for verification)":"暗号化用のパスフレーズ(確認用)","End":"終了","Enter URL":"URLを入力してください","Enter a backup destination URL:":"バックアップ先のURLを入力してください。","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"バックアップの保持期間の方針を手動で設定できます。使用できる文字にはD、W、Y、Uがあり、それぞれ日、週、年、無制限(Unlimited)を指します。構文の形式は「7D:1D,4W:1W,36M:1M」となります。この例では、今後7日間にわたり毎日1個ずつ、今後4週間にわたり毎週1個ずつ、今後36か月にわたり毎月1個ずつバックアップが作成、保存されます。これはまた「1W:1D,1M:1W,3Y:1M」と表記することもできます。","Enter a url, or click the "Target URL >" link":"URLを入力するか、「バックアップ用のURL >」のリンクをクリック","Enter backup passphrase, if any":"バックアップのパスフレーズがある場合は入力してください","Enter configuration details":"設定の詳細を入力","Enter encryption passphrase":"暗号化用のパスフレーズを入力してください","Enter expression here":"式をここに入力してください","Enter one argument per line without quotes, e.g. *.txt":"各行に1個の引数を、引用符を付けずに入力してください(例:*.txt)。","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"コマンドラインの形式で1行に1つのオプションを入力してください。例:--dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"コマンドラインの形式で1行に1つのオプションを入力してください。例:{0}","Enter the destination path":"バックアップ先のパスを入力してください","Error":"エラー","Error!":"エラー!","Errors and crashes":"エラーとクラッシュ","Examined":"検査済","Exclude":"除外","Exclude directories whose names contain":"次の文字を含むディレクトリを除外","Exclude expression":"次の文字を含むファイル・ディレクトリを除外","Exclude file":"除外するファイル名","Exclude file extension":"除外する拡張子","Exclude files whose names contain":"次の文字を含むファイルを除外","Exclude filter group":"グループで除外","Exclude folder":"除外するディレクトリ名","Exclude regular expression":"正規表現で除外","Existing file found":"既存のファイルが見つかりました","Experimental":"実験的","Export":"エクスポート","Export backup configuration":"バックアップの設定をエクスポート","Export configuration":"設定をエクスポート","Export passwords":"パスワードをエクスポート","Export …":"エクスポート…","Exporting …":"エクスポートしています…","External link":"外部リンク","FTP (Alternative)":"FTP(代替)","Failed to build temporary database: {{message}}":"一時的なデータベースを構築できませんでした:{{message}}","Failed to connect:":"接続できませんでした:","Failed to connect: {{message}}":"接続できませんでした。{{message}}","Failed to delete:":"削除できませんでした:","Failed to fetch path information: {{message}}":"パスの情報を取得できませんでした:{{message}}","Failed to find backup:":"バックアップが見つかりませんでした:","Failed to get bug report URL: {{message}}":"バグレポートのURLを取得できませんでした:{{message}}","Failed to import: {{message}}":"インポートできませんでした:{{message}}","Failed to read backup defaults:":"バックアップの既定の設定を読み込めませんでした:","Failed to read file: {{message}}":"ファイルを読み込めませんでした:{{message}}","Failed to restore files: {{message}}":"ファイルを復元できませんでした:{{message}}","Failed to save:":"保存できませんでした:","Fatal error, no statistics collected":"深刻なエラーが発生しました。統計は収集されていません","Fetching path information …":"パスの情報を取得しています…","File":"ファイル","Files larger than:":"閾値より大きなファイル:","Filters":"フィルター","Finished!":"完了しました!","First run setup":"初回実行セットアップ","Folder":"フォルダー","Folder in the bucket":"バケット内のフォルダー","Folder path":"フォルダーのパス","Folder path name":"フォルダーのパスの名称","Fri":"金曜日","Full destination path, including the server name, but without https":"サーバーの名称を含む、バックアップ先の完全なパス(httpsは除く)","GByte":"ギガバイト","GByte/s":"ギガバイト秒","GCS Project ID":"GCS プロジェクトID","General":"全般","General backup settings":"バックアップの設定","General options":"設定","Generate":"生成","Generate IAM access policy":"IAMアクセスポリシーを生成","Getting file versions …":"ファイルのバージョンを取得しています…","Group email":"グループの電子メール","Hidden files":"隠しファイル","Hide":"隠す","Hide hidden folders":"隠しフォルダーを表示しない","Home":"ホーム","Hostnames":"ホスト名","Hours":"時間","How do you want to handle existing files?":"既存のファイルはどのように扱いますか?","Hyper-V Machine":"Hyper-V マシン","Hyper-V Machine:":"Hyper-V マシン:","Hyper-V Machines":"Hyper-V マシン","ID:":"ID:","IDrive Sync directory path":"IDrive Syncのディレクトリーのパス","IDrive e2 Access Key ID":"IDrive e2のアクセスキーのID","IDrive e2 Access Key Secret":"IDrive e2のアクセスキーのシークレット","If a date was missed, the job will run as soon as possible.":"予定の日時を逃してしまった場合、ジョブは即座に実行します。","If at least one newer backup is found, all backups older than this date are deleted.":"最低1つ以上のより新しいバックアップが存在する場合、この日付よりも古い全てのバックアップを削除します。","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"バックアップとリモートの保存領域が同期していない場合、データベースを修復して同期させる必要があります。修復が上手く行かない場合は、ローカルのデータベースを削除して、改めてこれを作成してください。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"バックアップのファイルが自動的にダウンロードされなかった場合は、 "名前を付けて保存"を右クリックして選択してください。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"バックアップのファイルが自動的にダウンロードされなかった場合は、 "名前を付けて保存"を右クリックして選択してください。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"パスを入力しない場合、全てのファイルはログインフォルダーに保存されます。\n続行してよろしいですか?","If you do not enter an API Key, the tenant name is required":"APIを入力しない場合、テナント名が必要です","If you want to use the backup later, you can export the configuration before deleting it.":"後にバックアップを使用したい場合は、削除する前に設定をエクスポートできます。","Import":"インポート","Import Destination URL":"バックアップ先のURLをインポート","Import URL":"URLをインポート","Import backup configuration":"バックアップの設定をインポート","Import from a file":"ファイルからインポート","Import metadata":"メタデータをインポート","Importing …":"インポートしています…","Include a file?":"ファイルを含めますか?","Include expression":"次の文字列を含む","Include regular expression":"次の正規表現を含む","Incorrect answer, try again":"答えが正しくありません。もう一度試してください","Individual builds for developers only. Not for use with important data.":"開発者用の個別のビルドです。重要なデータのバックアップには使用しないでください。","Information":"情報","Interrupted, no statistics collected":"中断されました。統計は収集されていません","Invalid characters in path":"無効な文字がパスに含まれています","Invalid retention time":"無効な保持期間が設定されています","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"FTPサーバーの中にはパスワードを入力せずに接続できるものがあります。\nこのFTPサーバーは、パスワード無しのログインをサポートしていますか?","KByte":"キロバイト","KByte/s":"キロバイト秒","Keep a specific number of backups":"指定した数のバックアップを保存","Keep all backups":"全てのバックアップを保存","Keystone API version":"Keystone APIのバージョン","Language in user interface":"言語設定","Last month":"先月","Last successful backup:":"最後に成功したバックアップ:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"最後に成功した復元:{{time}}(完了までの時間 {{duration || '0秒'}})","Latest":"最新","Libraries":"ライブラリー","Listing backup dates …":"バックアップの日付を一覧表示しています…","Listing remote files for purge …":"削除するリモートファイルの一覧を作成しています…","Listing remote files …":"リモートファイルの一覧を作成しています…","Live":"ライブ","Load a configuration from an exported job or a storage provider":"エクスポートしたジョブまたはストレージ提供者から、設定を読み込む","Load destination from an exported job or a storage provider":"エクスポートしたジョブまたはストレージ提供者から、バックアップ先を読み込む","Load older data":"さらに古いデータを読み込む","Loading remote storage usage …":"リモートストレージの使用量を読み込んでいます…","Loading …":"読み込んでいます…","Local database for {{Backup.Backup.Name}}…loading…":"{{Backup.Backup.Name}}…読み込んでいます…のローカルのデータベース","Local database path:":"ローカルのデータベースのパス:","Local repository":"ローカルのリポジトリー","Local storage":"ローカルストレージ","Location":"場所","Location where buckets are created":"バケットを作成する場所","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}}のログデータ","Log data from the server":"サーバー上のログデータ","Log in":"ログイン","Log out":"ログアウト","MByte":"メガバイト","MByte/s":"メガバイト秒","Maintenance":"メンテナンス","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"Rcloneの実行ファイルをパスで指定するか、実行ファイルの場所を「高度な設定」で指定してください。","Manual":"マニュアル","Manual update found:":"手動アップデートが見つかりました:","Manually type path":"手動でパスを入力","Max download speed":"最大ダウンロード速度","Max upload speed":"最大アップロード速度","Menu":"メニュー","Microsoft SQL Database:":"Microsoft SQLデータベース:","Microsoft SQL Databases":"Microsoft SQLデータベース","Minimum redundancy":"最小の冗長性","Minimum redundancy is 1.0":"最小の冗長性は1.0です","Minutes":"分","Missing name":"名前がありません","Missing passphrase":"パスフレーズがありません","Missing sources":"バックアップ元のファイルがありません","Modified":"変更済","Mon":"月曜日","Months":"月","Move existing database":"既存のデータベースを移動","Move failed:":"移動できませんでした:","My Documents":"マイドキュメント","My Music":"マイミュージック","My Photos":"マイフォト","My Pictures":"マイピクチャ","Name":"名前","Never":"未実行","New Password":"新しいパスワードを入力してください","New update found: {{message}}":"新しいアップデートが見つかりました:{{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新しいユーザー名は{{user}}です。\n新規の制限ユーザーを使用するためのログイン情報を更新しました","Next":"次へ","Next scheduled run:":"次の実行予定日時:","Next scheduled task:":"次に予定されているタスク:","Next task:":"次のタスク:","Next time":"次回","No":"いいえ","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"以前に指定された証明書はありません。鍵が正しいかどうか、サーバーの管理者に確認してください:{{key}} \n\n報告されたホストの鍵を承認してよろしいですか?","No editor found for the "{{backend}}" storage type":""{{backend}}" の保存領域の種類に関するエディターが見つかりませんでした","No encryption":"暗号化なし","No items selected":"アイテムが選択されていません","No items to restore, please select one or more items":"復元するアイテムがありません。1つ以上のアイテムを選択してください","No passphrase entered":"パスフレーズが入力されていません","No scheduled tasks":"予定されているタスクはありません","Non-matching passphrase":"パスフレーズが一致しません","None / disabled":"なし / 無効","Not using encryption":"暗号化を行っていません","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"ここで入力する速度はバイト表記ですが、回線速度は通常、ビットで報告されます。ビットからバイトへと数値を換算するには、これを8で割ってください。8メガビット秒の回線は1メガバイト秒に相当します。","Nothing will be deleted. The backup size will grow with each change.":"バックアップは削除されません。バックアップのサイズはその都度の変更に従って大きくなります。","OK":"OK","OSS Access Key ID":"OSSのアクセスキーのID","OSS Access Key Secret":"OSSのアクセスキーのシークレット","OSS Bucket Region":"OSSのバケットのリージョン","OSS Bucket name":"OSSのバケット名","OSS Endpoint":"OSSのエンドポイント","OSS Path or subfolder in the bucket":"OSSのパスあるいはバケットのサブフォルダー","OSS Region":"OSSのリージョン","Official releases":"公式リリース版","Once there are more backups than the specified number, the oldest backups are deleted.":"指定した数以上のバックアップが作成された場合、古いバックアップから削除されます。","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack オブジェクトストレージ / Swift","Opened":"展開済","Openstack API key are not supported in v3 keystone API":"OpenstackのAPIキーは、バージョン3のkeystone APIではサポートされていません。","Operating System":"オペレーティングシステム","Operation":"操作","Operations:":"操作:","Optional API key":"APIのキー(オプション)","Optional authentication password":"認証に必要なパスワード(オプション)","Optional authentication username":"認証に必要なユーザー名(オプション)","Optional region":"リージョン(オプション)","Optional tenant name":"テナント名(オプション)","Options":"オプション","Options added here are applied to all backups, but can be overridden in each individual backup.":"ここで追加したオプションは全てのバックアップに適用されますが、それぞれのバックアップの設定で上書きすることができます。","Original location":"元の場所","Others":"その他","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"バックアップは時間の経過につれて自動的に削除されます。7日ごと、4週ごと、12ヶ月ごとのバックアップはそれぞれ保持されます。最低でも1つはバックアップが残ります。","Overwrite":"上書き","Passphrase":"パスフレーズ","Passphrase (if encrypted)":"パスフレーズ(暗号化されている場合)","Passphrase changed":"パスフレーズを変更しました","Passphrases are not matching":"パスフレーズが一致しません","Passphrases do not match":"パスフレーズが一致しません","Password":"パスワード","Patching files with local blocks …":"ファイルをローカルのブロックで修復しています…","Path":"パス","Path not found":"パスが見つかりません","Path on server":"サーバー上のパス","Path or subfolder in the bucket":"パスまたはバケットのサブフォルダー","Pause":"一時停止","Pause after startup or hibernation":"起動時またはハイバネート時に一時停止","Pause options":"一時停止の設定","Permissions":"権限","Pick location":"場所を入力","Please select a file to import":"インポートするファイルを選択してください","Point to your backup files and restore from there":"バックアップファイルを指定し、そこから復元","Port":"ポート","Prevent tray icon automatic log-in":"トレイアイコンの自動ログインを行わない","Previous":"前へ","Progress:":"進行度:","ProjectID is optional if the bucket exist":"バケットが存在する場合、ProjectIDはオプションです","Proprietary":"独自プロトコル","Purge Phase":"削除の段階","Purging files complete!":"ファイルを削除しました!","Purging files …":"ファイルを削除しています…","Rebuilding local database …":"ローカルデータベースを再構築しています…","Recreate (delete and repair)":"改めて作成(削除して修復)","Recreate Database Phase":"データベースの再構築の段階","Recreating database …":"データベースを改めて作成しています…","Region":"リージョン","Registering temporary backup …":"一時的なバックアップを登録しています…","Relative paths not allowed":"相対パスは許可されていません","Reload":"更新","Remote":"リモート","Remote Path":"リモートのパス","Remote Repository":"リモートのリポジトリー","Remote path":"リモートのパス","Remote repository":"リモートのリポジトリー","Remote volume size":"リモートのボリュームのサイズ","Remove":"削除","Remove option":"設定を削除","Removed files":"削除したファイル","Repair":"修復","Repair Phase":"修復の段階","Repairing database …":"データベースを修復しています…","Repeat Passphrase":"パスフレーズ(再度)","Reporting:":"報告:","Reset":"リセット","Restore":"復元","Restore complete!":"復元しました!","Restore files":"ファイルの復元","Restore files from:":"ファイルの復元:","Restore files …":"ファイルを復元…","Restore from":"データを復元するバックアップ","Restore from backup configuration":"バックアップの設定から復元","Restore from configuration …":"設定から復元…","Restore options":"復元オプション","Restore read/write permissions":"読み込み/書き込み権限を復元","Restored Files":"復元されたファイル","Restored Folders":"復元されたフォルダー","Restored Symlinks":"復元されたシンボリックリンク","Restoring files …":"ファイルを復元しています…","Resume":"再開","Rewritten File Lists":"ファイルの一覧を書き換えました","Run again every":"実行タイミング","Run now":"すぐに実行","Running commandline entry":"コマンドラインのエントリーを実行しています","Running task:":"タスクを実行しています:","Running …":"実行しています…","Running … stop now":"実行しています … 停止","S3 Compatible":"S3互換","Same as the base install version: {{channelname}}":"基本インストールのバージョンと同じです:{{channelname}}","Sat":"土曜日","Satellite":"サテライト","Save":"保存","Save and repair":"保存して修復","Save different versions with timestamp in file name":"ファイル名にタイムスタンプを入れて、異なるバージョンとして保存","Save immediately":"即座に保存","Scanning existing files …":"ファイルをスキャンしています…","Scanning for local blocks …":"ローカルのブロックをスキャンしています…","Schedule":"スケジュール","Search":"検索","Search for files":"ファイルの検索","Seconds":"秒","Select a log level and see messages as they happen:":"ログの水準を選択すると、メッセージを出力順に表示します。","Select files":"ファイルの選択","Server":"サーバー","Server and port":"サーバーとポート","Server hostname or IP":"サーバーのホスト名またはIPアドレス","Server is currently paused,":"サーバーは現在停止中です。","Server is currently paused, resume now":"サーバーは現在停止中です。再開","Server is currently paused, do you want to resume now?":"サーバーは現在停止中です。再開しますか?","Server password":"サーバーのパスワード","Server paused":"サーバーを一時停止しました","Server state properties":"サーバーの状態に関するプロパティー","Settings":"設定","Show":"表示","Show advanced editor":"拡張エディターを表示","Show hidden folders":"隠しフォルダーを表示","Show log":"ログを表示","Show log …":"ログを表示...","Show treeview":"フォルダーツリーを表示","Sia server password":"Siaサーバーのパスワード","Smart backup retention":"スマートなバックアップ保持期間","Some OpenStack providers allow an API key instead of a password and tenant name":"OpenStackのサービス提供者の中には、パスワードとテナント名の代わりにAPIキーを許可するものもあります","Some S3 providers might only be compatible with a certain client library":"いくつかのS3プロバイダーは特定のクライアントライブラリーにしか対応していないおそれがあります","Source Data":"バックアップ元","Source Files":"バックアップ元のファイル","Source data":"バックアップ元","Source folders":"バックアップ元のフォルダー","Source:":"バックアップ元:","Specific builds for developers only. Not for use with important data.":"開発者用の特定のビルドです。重要なデータのパックアップには使用しないでください。","Stable":"安定版","Standard protocols":"標準プロトコル","Start":"開始","Starting backup …":"バックアップを開始しています…","Starting restore …":"復元を開始しています…","Starting the restore process …":"復元プロセスを開始しています…","Stop after the current file":"現在のファイルの後で停止","Stop running backup":"実行中のバックアップを停止","Stop running task":"実行中のタスクを停止","Stopping after the current file:":"現在のファイルの後で停止:","Stopping task:":"タスクを停止しています:","Storage Type":"ストレージの種類","Storage class":"ストレージのクラス","Storage class for creating a bucket":"バケットを作成する際のストレージのクラス","Stored":"保存済","Strong":"強","Success":"成功","Sun":"日曜日","Symbolic link":"シンボリックリンク","System Files":"システムファイル","System default ({{levelname}})":"システムの既定値({{levelname}})","System files":"システムファイル","System info":"システムの情報","System properties":"システムのプロパティー","TByte":"テラバイト","TByte/s":"テラバイト秒","Target URL >":"バックアップ用のURL >","Target path. Example: /backup":"バックアップ先のパス。例:/backup","Task is running":"タスクは実行中です","Temporary Files":"一時ファイル","Temporary files":"一時ファイル","Tenant name":"テナント名","Tencent Cloud Account APPID":"Tencent CloudアカウントのAPPID","Tencent Cloud COS documents and resources":"Tencent Cloud COSのドキュメントと参考資料","Test Phase":"テストの段階","Test connection":"接続をテスト","Testing connection …":"接続をテストしています…","Testing permissions …":"権限をテストしています…","Testing …":"テストしています…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"「{{fieldname}}」のフィールドには不正な文字「{{character}}」が含まれています(値:{{value}}、インデックス:{{pos}})","The backup is missing, has it been deleted?":"バックアップがありません。削除された模様です","The backup was temporary and does not exist anymore, so the log data is lost":"バックアップは一時的で既に存在しないため、ログデータは削除されています","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"バックアップは「ボリューム」と呼ばれる複数のファイルに分割されます。ここで、各ボリュームの最大のサイズを設定できます。詳細についてはこのページを確認してください。","The bucket name should be all lower-case, convert automatically?":"バケット名には小文字のみが使用できます。自動的に変換しますか?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"設定ファイルは安全に保存すべきです。ファイルにはパスワードが含まれていますが、暗号化せずに保存してよろしいですか?","The connection to the server is lost, attempting again in {{time}} …":"サーバーとの接続が失われました。{{time}}後に再試行します…","The dark theme (by Michal)":"ダークテーマ(by Michal)","The default blue on white theme (by Alex)":"既定の白地に青テーマ(by Alex)","The encryption passphrases do not match":"暗号化用のパスフレーズが一致しません","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"ファイルのサイズが{{size}}であり、指定されている最大のサイズを超えています。サイズが指定されている最大のサイズよりも小さくなると、このファイルは以後のバックアップに含まれます。","The folder {{folder}} does not exist.\nCreate it now?":"フォルダー「{{folder}}」は存在しません。\n作成しますか?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"ホストの鍵が変更されました。変更が正しいかどうか、サーバーの管理者に問い合わせてください。変更が正しくない場合、中間車攻撃を受けているおそれがあります。\n\n現在のホストの鍵「{{prev}}」を、報告されたホストの鍵「{{key}}」で置き換えますか?","The passwords do not match":"パスワードが一致しません","The path does not appear to exist, do you want to add it anyway?":"パスは存在しないようですが、追加してよろしいですか?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"パスは「{{dirsep}}」で終わっていません。フォルダーではなく、ファイルが含まれています。\n\n指定したファイルを含めますか?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"パスにはスラッシュから始まる絶対パスを指定してください","The region parameter is only applied when creating a new bucket":"リージョンパラメーターは、バケットを新たに作成する際にのみ適用されます","The region parameter is only used when creating a bucket":"リージョンパラメーターは、バケットを作成する際にのみ使用されます","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"サーバーの証明書を検証できませんでした。\n次のハッシュ値をもつSSLの証明書を承認してよろしいですか:{{hash}}","The storage class affects the availability and price for a stored file":"保存領域のクラスは、保存されているファイルの利用可能性と価格に影響します","The target folder contains encrypted files, please supply the passphrase":"バックアップ先のフォルダーには暗号化されているファイルがあります。パスフレーズを指定してください。","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"ユーザーに付与されている権限が多すぎます。選択したパスに関する権限のみを有する制限ユーザーを新たに作成しますか?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"このバックアップは別のオペレーティングシステムで作成されました。バックアップの復元先となるフォルダーを指定せずにファイルを復元すると、予期しない場所にファイルが復元される可能性があります。復元先のフォルダーを選択せず続行してよろしいですか?","This month":"当月","This week":"この週","Throttle settings":"速度制限の設定","Thu":"木曜日","Time":"時間","To File":"ファイルへ","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"「{{name}}」の全てのリモートファイルを本当に削除したい場合は、表示されている語を以下に入力してください","To export without a passphrase, uncheck the \"Encrypt file\" box":"パスフレーズなしでエクスポートするには、「ファイルを暗号化」のチェックを外してください","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"バケット名の競合を防ぐため、バケット名の先頭にはアカウントIDを付けることが推奨されます。アカウントIDを自動的に付けますか?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"DNSに基づく攻撃を防ぐため、Duplicatiは、ここに入力されたホスト名しか許可しません。IPアドレスまたはlocalhostによるアクセスは常に許可されます。複数のホスト名を指定する場合は、セミコロンで区切ってください。ただし、アスタリスク(*)がホスト名として入力されている場合は、どのホスト名も許可され、この機能は無効となります。また、ホスト名が入力されていない場合は、IPアドレスまたはlocalhostによるアクセスのみが許可されます。","Today":"今日","Trust host certificate?":"ホストの証明書を信用しますか?","Trust server certificate?":"サーバーの証明書を信用しますか?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"開発中の新機能を試してみてください。現在、最も安定したバージョンが利用できます。本番環境で使用する前に、データの復元のテストを行ってください。","Tue":"火曜日","Type passphrase here.":"ここにパスフレーズを入力してください。","Type to highlight files":"見つけたいファイル名を入力してください","Unknown backup size and versions":"バックアップのサイズとバージョンが不明です","Until resumed":"再開するまで","Update {{state.updatedVersion}} is available. Download now":"アップデート {{state.updatedVersion}} が利用できます。ダウンロード","Update channel":"アップデートチャンネル","Update failed:":"アップデートできませんでした:","Updating with existing database":"既存のデータベースでアップデートしています","Uploaded files":"アップロードされたファイル","Uploading verification file …":"検証用ファイルをアップロードしています…","Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics.":"使用状況に関する報告は、ソフトウェアの使い勝手を改善したり、新しい機能の効果を評価したりする際に参照されます。また、私達はこの報告を用いて、使用状況に関する公開の統計を作成しています。","Usage statistics":"使用状況に関する統計","Usage statistics, warnings, errors, and crashes":"使用状況に関する統計、警告、エラー、クラッシュ","Use SSL":"SSLを使用","Use existing database?":"既存のデータベースを使用しますか?","Use weak passphrase":"弱いパスフレーズを使用","Useless":"弱すぎます","User data":"ユーザーデータ","User domain name":"ユーザーのドメイン名","User has too many permissions":"ユーザーに付与されている権限が多すぎます","User interface settings":"インターフェースの設定","Username":"ユーザー名","Vacuuming database …":"データベースのバキュームを行っています…","Validating …":"検証しています…","Verifications":"検証","Verify encryption passphrase":"暗号化用のパスフレーズを再入力","Verify files":"ファイルを検証","Verifying answer":"回答を検証しています","Verifying backend data …":"バックエンドのデータを検証しています…","Verifying files …":"ファイルを検証しています…","Verifying remote data …":"リモートデータを検証しています…","Verifying restored files …":"復元したファイルを検証しています…","Verifying …":"検証しています…","Version ID":"バージョンID","Very strong":"最強","Very weak":"最弱","Visit us on":"関連リンク","WARNING: The remote database is found to be in use by the commandline library.":"警告:リモートのデータベースはコマンドラインのライブラリーによって使用されています。","WARNING: This will prevent you from restoring the data in the future.":"警告:これを行うと将来データを復元できなくなります。","Waiting for task to begin":"タスクが開始するのを待機しています","Waiting for task to start …":"タスクの開始を待機しています…","Waiting for upload to finish …":"アップロードの完了を待機しています…","Warnings, errors and crashes":"警告、エラー、クラッシュ","We recommend that you encrypt all backups stored outside your system":"システム外に保存する全てのバックアップに関しては、暗号化を行うことを推奨します","Weak":"弱","Weak passphrase":"弱いパスフレーズ","Wed":"水曜日","Weeks":"週","Where do you want to restore from?":"どこから復元しますか?","Where do you want to restore the files to?":"復元したファイルはどこに保存しますか?","Years":"年","Yes":"はい","Yes, I have stored the passphrase safely":"はい、パスフレーズを安全な場所に保存しました","Yes, I understand the risk":"はい、リスクを理解しました","Yes, I'm brave!":"はい、問題ありません!","Yes, please break my backup!":"バックアップが壊れることを了承して続行","Yesterday":"昨日","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"既存のデータベースからデータベースのパスを変更しようとしています。\n続行してよろしいですか?","You are currently running {{appname}} {{version}}":"あなたは現在 {{appname}} {{version}}を使用しています。","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"暗号化モードが変更されています。データが壊れる可能性があるため、新しいバックアップを代わりに作成することを推奨します","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"パスフレーズが変更されましたが、これはサポートされていません。新しいバックアップを代わりに作成することを推奨します。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"バックアップを暗号化しない設定となっていますが、リモートサーバーに保存する全てのデータに関して、暗号化を行うことを推奨します。","You have chosen to restore to a new location, but not entered one":"新しい場所に復元するよう選択しましたが、場所が入力されていません","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"強力なパスフレーズを生成しました。パスフレーズの紛失時にもデータを復元できるよう、パスフレーズを安全な場所にコピーして保存してください。","You must choose at least one source folder":"最低1つのバックアップ元のフォルダーを選択してください","You must enter a domain name to use v3 API":"バージョン3のAPIを使用するにはドメイン名を入力してください","You must enter a name for the backup":"バックアップの名称を入力してください","You must enter a passphrase or disable encryption":"パスフレーズを入力するか、暗号化を無効にしてください","You must enter a password to use v3 API":"バージョン3のAPIを使用するにはパスワードを入力してください","You must enter a positive number of backups to keep":"保存するバックアップの数を入力してください","You must enter a tenant (aka project) name to use v3 API":"バージョン3のAPIを使用するにはテナント(プロジェクト)名を入力してください","You must enter a tenant name if you do not provide an API key":"APIキーを指定しない場合はテナント名の入力が必要です","You must enter a valid duration for the time to keep backups":"バックアップを保持する期間を正しく指定してください","You must enter a valid retention policy string":"保持期間のポリシーを正しく入力してください","You must enter either a password or an API key":"パスワードかAPIキーを入力してください","You must enter either a password or an API key, not both":"パスワードまたはAPIキーのどちらかを入力してください","You must fill in the password":"パスワードを入力してください","You must fill in the server name or address":"サーバー名またはアドレスを入力してください","You must fill in the username":"ユーザー名を入力してください","You must fill in {{field}}":"{{field}}を入力してください","You must select or fill in the AuthURI":"AuthURIを選択または入力してください","You must select or fill in the server":"サーバーを選択または入力してください","You must specify a path":"パスを指定してください","You should fill in {{field}} {{reason}}":"{{reason}}{{field}}を入力してください。","Your files and folders have been restored successfully.":"ファイルとフォルダーを復元しました。","Your passphrase is easy to guess. Consider changing passphrase.":"設定したパスフレーズは容易に推測できます。パスフレーズの変更を考慮してください。","bucket/folder/subfolder":"バケット/フォルダー/サブフォルダー","byte":"バイト","byte/s":"バイト秒","cos_app_id":"COS AppのID","cos_bucket":"バケット名","cos_region":"リージョン","cos_secret_id":"COSのシークレットのID","cos_secret_key":"COSの秘密鍵","custom":"ユーザー定義","failed":"失敗しました","oss_access_key_id":"OSSのアクセスキーのID","oss_access_key_secret":"OSSのアクセスキーのシークレット","oss_bucket_name":"OSSのバケット名","oss_endpoint":"OSSのエンドポイント","oss_region":"OSSのリージョン","remote path, e.g. backup":"リモートのパス(例:backup)","remote repository, e.g. remote":"リモートのリポジトリー名(例:remote)","resume now":"再開","storj_shared_access":"アクセス権","unless you are explicitly specifying --group-id":"--group-idを明示的に指定しているのでない限り、","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}}は最初に{{dev1}}と{{dev2}}によって開発されました。{{appname}}は{{websitename}}からダウンロードできます。{{appname}}は{{licensename}}によってライセンスされています。","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}}は以下のサードパーティー製のライブラリーを使用しています。","{{files}} files ({{size}}) to go {{speed_txt}}":"残り{{files}}個のファイル ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}}個のバージョン","{{number}} Hour":"{{number}}時間","{{number}} Hours":"{{number}}時間","{{number}} Minutes":"{{number}}分","{{time}} (took {{duration}})":"{{time}}(完了までの時間 {{duration}})"}); + gettextCatalog.setStrings('ko', {"- pick an option -":"- 옵션을 선택하십시오 -","...loading...":"...로딩...","About":"정보","About {{appname}}":"{{appname}} 정보","Access Key":"접근 키","Access denied":"접근 불가","Access to user interface":"액세스 설정","Account name":"계정 이름","Add a new backup":"새 백업 추가","Add a path directly":"경로 직접 추가","Add advanced option":"고급 옵션 추가","Add backup":"백업 추가","Add filter":"필터 추가","Add path":"경로 추가","Added":"추가됨","Adjust bucket name?":"버켓 이름을 적용 하시겠습니까?","Advanced Options":"고급 옵션","Advanced options":"고급 옵션","Advanced:":"고급:","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"모든 사용 보고서는 익명으로 전송되며 개인 정보를 포함하지 않습니다. 여기에는 하드웨어 및 운영 체제, 백엔드 유형, 백업 기간, 원본 데이터의 전체 크기 및 이와 유사한 데이터에 대한 정보가 포함되어 있습니다. 경로, 파일 이름, 사용자 이름, 암호 또는 이와 유사한 중요한 정보는 포함되어 있지 않습니다.","Allow remote access (requires restart)":"원격 액세스 허용 (다시 시작 필요)","Allowed days":"허용된 요일","Anonymous usage reports":"익명 사용 보고서","AuthID":"AuthID","Back":"이전","Backup destination":"백업 대상","Backup location":"백업 위치","Backup retention":"백업 보존","Backup:":"백업:","Beta":"Beta","Browse":"찾아보기","Bucket name":"Bucket 이름","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"원격 액세스를 허용하면 서버는 네트워크의 모든 컴퓨터에서 접속할 수 있습니다. 이 옵션을 사용하도록 설정하려면 방화벽으로 보호된 네트워크에서 컴퓨터를 사용하고 있는지 확인하십시오.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"기본적으로 트레이 아이콘은 토큰으로 잠금을 해제합니다. 이렇게 하면 다른 사용자가 암호를 입력하도록 요구하면서 트레이 아이콘에서는 사용자 인터페이스에 액세스할 수 있습니다. 트레이 아이콘에서 사용자 인터페이스에 액세스하는 때도 암호를 입력해야 하는 경우 이 옵션을 사용하도록 설정하십시오.","Canary":"Canary","Cancel":"취소","Cannot move to existing file":"기존 파일로 이동할 수 없습니다","Changelog":"변경로그","Changelog for {{appname}} {{version}}":"{{appname}} {{version}}에 대한 변경로그","Check failed:":"확인 실패:","Check for updates now":"업데이트 확인","Checking for updates …":"업데이트 확인 중 …","Chose a storage type to get started":"시작할 저장소 유형을 선택하세요","Click to set throttle options":"속도 제한 옵션을 설정하려면 클릭","Commandline …":"명령줄 …","Compact now":"최적화 실행","Computer":"내 PC","Configuration file:":"구성 파일:","Configuration:":"구성:","Configure a new backup":"새 백업 구성","Confirm encryption passphrase":"암호화 암호 확인","Connect":"연결","Connect now":"지금 연결하기","Connecting to server …":"서버에 연결하는 중 …","Connection lost":"연결이 끊어짐","Connection worked!":"연결되었습니다!","Continue":"계속","Copied!":"복사됨!","Copy":"복사","Copy Destination URL to Clipboard":"대상 URL을 클립보드에 복사","Core options":"핵심 옵션","Crashes only":"충돌만","Create bug report …":"버그 리포트 생성 …","Create folder?":"폴더를 생성하시겠습니까?","Creating bug report …":"버그 리포트 생성 중 …","Current action:":"현재 작업:","Current file:":"현재 파일:","Custom backup retention":"사용자 지정 백업 보존","Database …":"데이터베이스 …","Days":"일","Default":"기본값","Default ({{channelname}})":"기본값 ({{channelname}})","Default options":"기본 옵션","Delete":"삭제","Delete backup":"백업 삭제","Delete backups that are older than":"이전 백업 삭제","Delete local database":"로컬 데이터베이스 삭제","Delete remote files":"원격 파일 삭제","Delete the local database":"로컬 데이터베이스 삭제","Delete …":"삭제 …","Deleted":"삭제됨","Deleted Versions":"삭제된 버전들","Deleted files":"삭제된 파일들","Deleting unwanted files …":"원치 않는 파일 삭제 중 …","Description (optional)":"설명 (선택 사항)","Desktop":"바탕 화면","Destination":"대상","Destination path":"대상 경로","Disabled":"비활성화","Dismiss":"닫기","Dismiss all":"모두 닫기","Display and color theme":"인터페이스 테마","Done":"완료","Download":"다운로드","Downloading files …":"파일 다운로드 중 …","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati 포럼","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati는 시작할 때 실행되지만 지정된 시간 동안 일시 중지된 상태로 유지됩니다. Duplicati는 최소한의 시스템 리소스를 차지하며 백업이 실행되지 않습니다.","Edit as list":"목록으로 편집","Edit as text":"텍스트로 편집","Edit …":"편집 …","Encrypt file":"파일 암호화","Encryption":"암호화","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"보존 전략을 직접 입력합니다. 자리 표시자는 일/주/년이 각각 D/W/Y이고 U는 무제한입니다. 예) 7D:1D,4W:1W,36M:1M. 이 예제는 다음 7일 각각에 대해 하나의 백업을 유지하며, 다음 4주마다 하나씩, 다음 36개월마다 하나씩 백업합니다. 이것은 또한 1W:1D, 1M:1W,3Y:1M으로 표현할 수 있습니다.","Enter backup passphrase, if any":"백업 암호가 있는 경우 입력합니다.","Enter configuration details":"구성 세부 정보 입력","Enter the destination path":"대상 경로 입력","Error":"오류","Error!":"오류!","Errors and crashes":"오류 및 충돌","Exclude":"제외","Experimental":"Experimental","Export":"내보내기","Export backup configuration":"백업 구성 내보내기","Export configuration":"구성 내보내기","Export passwords":"암호 내보내기","Export …":"내보내기 …","Exporting …":"내보내는 중 …","Fetching path information …":"경로 정보를 가져오는 중 …","Files larger than:":"큰 파일","Filters":"필터","Folder path":"폴더 경로","Fri":"금요일","GByte":"GByte","GByte/s":"GByte/s","General":"일반","General backup settings":"일반 백업 설정","General options":"일반 옵션","Generate":"생성","Getting file versions …":"파일 버전을 구하는 중 ...","Hidden files":"숨김 파일","Hide":"숨기기","Hide hidden folders":"숨김 폴더 숨기기","Home":"홈","Hours":"시","How do you want to handle existing files?":"기존 파일을 어떻게 처리하시겠습니까?","If a date was missed, the job will run as soon as possible.":"날짜를 놓친 경우 작업이 가능한 한 빨리 실행됩니다.","If at least one newer backup is found, all backups older than this date are deleted.":"새 백업이 발견되면 이 날짜보다 오래된 모든 백업이 삭제됩니다.","Import Destination URL":"대상 URL 가져오기","Import backup configuration":"백업 구성 가져오기","Import from a file":"파일에서 가져오기","Import metadata":"메타데이터 가져오기","Individual builds for developers only. Not for use with important data.":"개발자 전용 개별 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오.","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"특정 수의 백업 유지","Keep all backups":"모든 백업 유지","Language in user interface":"인터페이스 언어","Last month":"지난 달","Last successful backup:":"마지막으로 성공한 백업:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"마지막으로 성공한 복원: {{time}} ({{duration || '0초'}} 소요)","Latest":"최근","Libraries":"라이브러리","Load a configuration from an exported job or a storage provider":"내보낸 작업 또는 저장소 공급자에서 구성 로드","Load destination from an exported job or a storage provider":"내보낸 작업 또는 저장소 공급자에서 대상 로드","Load older data":"이전 데이터 로드","Loading …":"로딩 …","Local database path:":"로컬 데이터베이스 경로:","Local repository":"로컬 리포지토리","Local storage":"로컬 저장소","Location":"위치","Log data from the server":"서버에서 가져온 로그 데이터","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"유지 관리","Manually type path":"수동 경로 입력","Max download speed":"최대 다운로드 속도","Max upload speed":"최대 업로드 속도","Microsoft SQL Database:":"Microsoft SQL Database:","Minutes":"분","Mon":"월요일","Months":"분","Move existing database":"기존 데이터베이스 이동","My Documents":"문서","My Music":"음악","My Pictures":"사진","Name":"이름","Never":"없음","Next":"다음","Next scheduled run:":"다음 백업 일정:","Next scheduled task:":"다음 예약 작업:","Next time":"시작","No":"아니오","No encryption":"암호화 없음","No items selected":"선택된 항목 없음","No items to restore, please select one or more items":"복원할 항목이 없습니다. 하나 이상의 항목을 선택하십시오.","No scheduled tasks":"스케줄링된 작업 없음","None / disabled":"비활성화","Nothing will be deleted. The backup size will grow with each change.":"아무 것도 삭제되지 않습니다. 백업 크기는 변경될 때마다 커집니다.","OK":"확인","Once there are more backups than the specified number, the oldest backups are deleted.":"지정된 수보다 많은 백업이 있으면 가장 오래된 백업이 삭제됩니다.","Operations:":"작업:","Options":"옵션","Original location":"원래 위치","Others":"기타","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"시간이 지남에 따라 백업이 자동으로 삭제됩니다. 지난 7일, 지난 4주, 지난 12개월 각각에 대해 하나의 백업이 유지됩니다. 항상 하나 이상의 남은 백업이 있습니다.","Overwrite":"덮어쓰기","Passphrase":"암호","Passphrase (if encrypted)":"암호 (암호화된 경우)","Password":"암호","Path on server":"서버의 경로","Pause":"일시 중지","Pause after startup or hibernation":"부팅 또는 최대 절전 모드 후 일시 중지","Pause options":"일시 중지 옵션","Permissions":"권한","Pick location":"위치 선택","Point to your backup files and restore from there":"백업 파일을 선택하고 복원","Prevent tray icon automatic log-in":"트레이 아이콘 자동 로그인 방지","Previous":"이전","Progress:":"진행률:","Proprietary":"독점","Recreate (delete and repair)":"재생성 (삭제 및 수리)","Recreating database …":"데이터베이스를 다시 만드는 중 …","Remote":"원격","Remote path":"원격 경로","Remote repository":"원격 저장소","Remote volume size":"원격 볼륨 크기","Remove":"제거","Remove option":"설정 제거","Removed files":"파일들 제거","Repair":"수리","Repeat Passphrase":"암호 재입력","Reporting:":"리포트:","Reset":"초기화","Restore":"복원","Restore complete!":"저장이 완료되었습니다!","Restore files":"파일 복원","Restore files …":"파일 복원 …","Restore from":"버전 선택","Restore from backup configuration":"백업 구성에서 복원","Restore options":"복원 옵션","Restore read/write permissions":"읽기/쓰기 권한 복원","Restoring files …":"파일 복원 중 …","Run again every":"실행 주기","Run now":"백업 실행","Same as the base install version: {{channelname}}":"기본 설치 버전과 동일: {{channelname}}","Sat":"토요일","Save":"저장","Save and repair":"저장 및 수리","Save different versions with timestamp in file name":"파일명에 타임스탬프 추가","Save immediately":"즉시 저장","Schedule":"일정","Search":"검색","Search for files":"파일 검색","Seconds":"초","Select a log level and see messages as they happen:":"로그 레벨을 선택하고 발생하는 메시지를 확인하십시오:","Select files":"파일 선택","Server state properties":"서버 상태 속성","Settings":"설정","Show":"표시","Show advanced editor":"고급 편집기 표시","Show hidden folders":"숨김 폴더 표시","Show log":"로그 표시","Show log …":"로그 표시 …","Smart backup retention":"스마트 백업 보존","Source Data":"원본 데이터","Source data":"원본 데이터","Source folders":"원본 폴더","Source:":"대상:","Specific builds for developers only. Not for use with important data.":"개발자 전용 특정 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오.","Standard protocols":"표준 프로토콜","Starting backup …":"백업 시작 중 …","Stop after the current file":"현재 파일까지 진행 후 중지","Stop running backup":"백업 실행 중지","Stopping after the current file:":"현재 파일까지 진행 후 중지 중:","Storage Type":"저장소 유형","Strong":"강한","Success":"성공","Sun":"일요일","System files":"시스템 파일","System info":"시스템 정보","System properties":"시스템 속성","TByte":"TByte","TByte/s":"TByte/s","Temporary Files":"임시 파일","Temporary files":"임시 파일","Test connection":"연결 테스트","The dark theme (by Michal)":"어두운 테마 (by Michal)","The default blue on white theme (by Alex)":"파란색의 밝은 테마 (by Alex)","The passwords do not match":"암호가 일치하지 않음","This month":"이번 달","This week":"이번 주","Throttle settings":"속도 제한 설정","Thu":"목요일","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"현재 작업 중인 새로운 기능을 사용해 보십시오. 현재 가장 안정적인 버전을 사용할 수 있습니다. 프로덕션 환경에서 이 데이터를 사용하기 전에 데이터를 복원합니다.","Tue":"화요일","Type to highlight files":"파일을 강조 표시하려면 입력","Until resumed":"다시 시작할 때까지","Update channel":"업데이트 채널","Usage statistics":"사용 통계","Usage statistics, warnings, errors, and crashes":"사용 통계, 경고, 오류 및 충돌","Useless":"쓸모없는","User data":"사용자 데이터","User interface settings":"인터페이스 설정","Username":"사용자 이름","Verify files":"무결성 확인","Verifying backend data …":"백엔드 데이터 확인 중 …","Verifying files …":"파일 검증 중 …","Verifying remote data …":"원격 데이터 확인 중 …","Very strong":"매우 강한","Very weak":"매우 약한","Visit us on":"Visit us on","Waiting for upload to finish …":"업로드가 완료되기를 기다리는 중 …","Warnings, errors and crashes":"경고, 오류 및 충돌","Weak":"약한","Wed":"수요일","Weeks":"주","Where do you want to restore from?":"어디에서 복원하시겠습니까?","Where do you want to restore the files to?":"파일을 어디에 복원하시겠습니까?","Years":"년","Yes":"예","Yes, I have stored the passphrase safely":"예, 암호를 안전하게 저장했습니다","Yes, I understand the risk":"네, 위험을 이해했습니다.","Yes, I'm brave!":"네,저는 용감합니다!","Yesterday":"어제","You are currently running {{appname}} {{version}}":"현재 사용 중: {{appname}} {{version}}","You must enter a name for the backup":"백업 이름을 입력해야 합니다","You must fill in the password":"암호를 입력해야 합니다","You must fill in the server name or address":"서버 이름 또는 주소를 채워야합니다.","You must fill in the username":"사용자 이름을 채워야합니다.","You must specify a path":"경로를 지정해야 합니다.","Your files and folders have been restored successfully.":"파일 및 폴더가 성공적으로 복원되었습니다.","byte":"byte","byte/s":"byte/s","custom":"사용자 지정","resume now":"지금 다시 시작","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} 파일 ({{size}}), 속도: {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 버전","{{number}} Hour":"{{number}}시간 동안","{{number}} Hours":"{{number}}시간 동안","{{number}} Minutes":"{{number}}분 동안","{{time}} (took {{duration}})":"{{time}} ({{duration}} 소요)"}); + gettextCatalog.setStrings('lt', {"- pick an option -":"- pasirinkite parametrą -","...loading...":"...įkeliama...","API key":"API raktas","AWS Access ID":"AWS prieigos ID","AWS Access Key":"AWS prieigos raktas","AWS IAM Policy":"AWS IAM politika","About":"Apie","About {{appname}}":"Apie {{appname}}","Access Key":"Prieigos raktas","Access denied":"Prieiga uždrausta","Access grant":"Prieiga leista","Access to user interface":"Pasiekti vartotojo sąsają","Account name":"Paskyros vardas","Add a new backup":"Pridėti naują kopiją","Add a path directly":"Pridėti kelią tiesiiogiai","Add advanced option":"Pridėti papildomą parametrą","Add backup":"Pridėti kopiją","Add filter":"Pridėti filtrą","Add path":"Pridėti kelią","Added":"Pridėta","Adjust bucket name?":"Keisti saugyklos pavadinimą?","Advanced Options":"Išplėstiniai parametrai","Advanced options":"Išplėstiniai parametrai","Advanced:":"Papildomai:","All Hyper-V Machines":"Visos Hyper-V mašinos","All Microsoft SQL Databases":"Visos Microsoft SQL duombazės","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Visos naudojimo ataskaitos siunčiamos anonimiškai ir jose nėra jokios asmeninės informacijos. Juose pateikiama informacija apie techninę įrangą ir operacinę sistemą, saugyklos tipą, kopijos kūrimo laiką, visų kopijuojamų failų dydį ir pan. Juose nėra kelių, failų pavadinimų, naudotojų, slaptažodžių ir panašios privačios informacijos.","Allow remote access (requires restart)":"Leisti nuotolinę prieigą (reikia paleisti iš naujo)","Allowed days":"Leidžiamos dienos","An existing file was found at the new location":"Naujoje vietoje rasti jau esantys failai","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Naujoje vietoje rasti jau esantys failai.\nAr tikrai norite duomenų bazę rašyti vietoj esamų failų?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Buvo rasta esama vietinė duomenų saugykla.\nNaudojant tą pačią duombazę, komandinės eilutės ir serverio procesai galės veikti toje pačioje nuotolinėje saugykloje.\n\n Ar norite naudoti esamą duomenų bazę?","Anonymous usage reports":"Anoniminės naudojimo ataskaitos","Applications":"Programos","As Command-line":"Kaip komandinę eilutę","AuthID":"AuthID","Authentication method":"Autorizacijos metodas","Authentication method ({{auth_method}})":"Autorizacijos metodas ({{auth_method}})","Authentication password":"Autorizacijos slaptažodis","Authentication username":"Autorizacijos naudotojas","Autogenerated passphrase":"Automatiškai sugeneruota slapta frazė","B2 Application ID":"B2 programos ID","B2 Application Key":"B2 programos raktas","B2 Cloud Storage Account ID":"B2 debesų saugyklos paskyros ID","B2 Cloud Storage Application ID":"B2 debesų saugyklos programos ID","B2 Cloud Storage Application Key":"B2 debesų saugyklos programos raktas","Back":"Atgal","Backup complete!":"Kopija padaryta!","Backup destination":"Kopijų saugojimo vieta","Backup location":"Kopijų saugojimo vieta","Backup retention":"Atsarginės kopijos saugojimo laikas","Backup:":"Kopija:","Beta":"Beta","Broken access":"Sugadinta prieiga","Browse":"Naršyti","Browser default":"Naršyklės numatyta reišmė","Bucket create location":"Sukurti saugyklos vietą","Bucket name":"Saugyklos pavadinimas","Bucket storage class":"Saugyklos klasė","Building list of files to restore …":"Kuriamas atkūriamų failų sąrašas...","Building partial temporary database …":"Kuriama dalinė laikina duomenų bazė...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Leidus nuotolinę prieigą, serveris atsakys į visas užklausas tinke. Jei įjungsite - įsitikinkite, kad kompiuteris yra už geros ugniasienės.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Pradžioje dėklo piktograma naudojama vartotojo aplinkos atidarymui. Tai užtikrina, kad aplinka būtu pasiekiama, kai tuo tarpu kiti turi įvesti slaptažodį. Jei norite, kad būtu reikalaujama slaptažodžio visais atvejais - įjunkite šį nustatymą.","Cache Files":"Talpyklos failai","Canary":"Canary","Cancel":"Atšaukti","Cannot move to existing file":"Negalima perkelti į esamo failo vietą","Changelog":"Pakeitimų žurnalas","Changelog for {{appname}} {{version}}":"Programos {{appname}} {{version}} pakeitimų žurnalas","Check failed:":"Patikrinimas nepavyko:","Check for updates now":"Ieškoti atnaujinimų dabar","Chose a storage type to get started":"Norėdami pradėti pasirinkite saugyklos tipą","Click the AuthID link to create an AuthID":"Norėdami sukurti AuthID paspauskite AuthID nuorodą","Click to set throttle options":"Spustelėkite, kad nustatyti akceleratoriaus parametrus","Compact now":"Suspausti dabar","Computer":"Kompiteris","Configuration file:":"Konfigūracijos failas:","Configuration:":"Konfigūracija:","Configure a new backup":"Derinti naują kopiją","Confirm delete":"Patvirtinkite tryminą","Confirmation required":"Reikalingas patvirtinimas","Connect":"Prisijungti","Connect now":"Prisijungti dabar","Connection lost":"Prisijungimas nutrūko","Connection worked!":"Prisijungti pavyko!","Container name":"Konteinerio pavadinimas","Container region":"Konteinerio regionas","Continue":"Tęsti","Continue without encryption":"Tęsti be šifravimo","Copied!":"Nukopijuota!","Copy":"Kopija","Copy Destination URL to Clipboard":"Kopijuoti paskirties URL į iškarpinę","Copy failed. Please manually copy the URL":"Kopijavimas nepavyko. Nukopijuokite URL rankiniu būdu","Core options":"Pagrindiniai parametrai","Counting ({{files}} files found, {{size}})":"Skaičiuojama, rasta failų: ({{files}}, {{size}})","Crashes only":"Tik lūžimai","Create folder?":"Sukurti aplanką?","Created new limited user":"Sukurtas naujas ribotas vartotojas","Current action:":"Dabartinis veiksmas:","Current file:":"Dabartinis failas:","Current version is {{versionname}} ({{versionnumber}})":"Dabartinė versija: {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Nestandartinė S3 saugykla","Custom authentication url":"Nestandartinis autorizacijos URL","Custom backup retention":"Derintas kopijų saugojimo laikas","Custom location ({{server}})":"Nestandartinė vieta ({{server}})","Custom region for creating buckets":"Nestandartinis regionas kuriamoms saugykloms","Custom region value ({{region}})":"Nestandartinio regiono reikšmė ({{region}})","Custom server url ({{server}})":"Nestandartinis serverio url ({{server}})","Custom storage class ({{class}})":"Nestandartinė saugyklos klasė ({{class}})","Days":"Dienos","Default":"Numatyta","Default ({{channelname}})":"Numatytas ({{channelname}})","Default excludes":"Numatytos išimtys","Default options":"Numatyti parametrai","Delete":"Ištrinti","Delete backup":"Ištrinti kopiją","Delete backups that are older than":"Ištrinti kopijas, kurios senesnės nei","Delete local database":"Ištrinti lokalią duombazę","Delete remote files":"Ištrinti nutolusius failus","Delete the local database":"Ištrinti lokalią duombazę","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Trinti failus {{filecount}}, ({{filesize}}) iš nutolusios saugyklos?","Desktop":"Darbastalis","Destination":"Paskirtis","Destination path":"Kelias iki paskirties","Disabled":"Išjungta","Dismiss":"Neberodyti","Dismiss all":"Neberodyti visko","Display and color theme":"Vaizdo ir spalvų tema","Do you really want to delete the backup: \"{{name}}\" ?":"Ar tikrai norite ištrinti kopiją: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Ar tikrai norite ištrinti lokalią duomenų bazę: {{name}}","Done":"Baigta","Download":"Atsisiųsti","Duplicate option {{opt}}":"Pasikartojantis parametras {{opt}}","Duplicati Website":"Duplicati svetainė","Duplicati forum":"Duplicati forumas","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Kiekviena atsarginė kopija turi su ja susietą duomenų bazę, kurioje saugoma informacija apie nuotolinę saugykla vietiniame kompiuteryje.\nTrindami kopiją galite ištrinti ir lokalią duombazę, atkurti duomenis iš nutolusių failų vis tiek galėsite.\nJei lokalią duombazę naudojate kopijoms per komandinę eilutę, tada duombazę turėtumėt palikti.","Edit as list":"Taisyti kaip sąrašą","Edit as text":"Taisyti kaip tekstą","Encrypt file":"Šifruoti failą","Encryption":"Šifravimas","Encryption changed":"Šifravimas pakeistas","Enter URL":"Įveskite URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Aprašykite saugojimo strategiją. Sutrumpinimai D/W/Y reiškai dienos/savaitės/metai, U reiškia saugoti visada. Pavyzdys: 7D:1D,4W:1W,36M:1M. Šis pavyzdys reiškia, kad bus saugoma po vieną kopiją 7 dienas, po vieną kopiją kas 4 savaites ir viena ne senesnė nei 36 mėn. Galima aprašyti ir taip: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Jei naudojama šifravimo slapta frazė, įveskite ją","Enter configuration details":"Įveskite konfigūracijos detales","Enter encryption passphrase":"Įveskite šifravimo slaptą frazę","Enter expression here":"Įveskite čia išraišką","Enter the destination path":"Įveskite paskirties kelią","Error":"Klaida","Error!":"Klaida!","Errors and crashes":"Klaidos ir lūžimai","Exclude":"Išimtys","Exclude directories whose names contain":"Neįtraukti aplankų, kurių pavadinime yra","Exclude expression":"Neįtraukti išraiškos","Exclude file":"Neįtraukti failo","Exclude file extension":"Neįtraukti failų plėtinio","Exclude files whose names contain":"Neįtraukti failų, kurių pavadinime yra","Exclude folder":"Neįtraukti aplanko","Exclude regular expression":"Neįtraukti standartinės išraiškos","Existing file found":"Rastas esamas failas","Experimental":"Eksperimentinis","Export":"Eksportas","Export backup configuration":"Eksportuoti atsarginės kopijos konfigūraciją","Export configuration":"Eksportuoti konfigūraciją","External link":"Išorinė nuoroda","FTP (Alternative)":"FTP (Alternatyva)","Failed to build temporary database: {{message}}":"Nepavyko sukurti laikinos duomenų bazės: {{message}}","Failed to connect:":"Nepavyko prisijungti:","Failed to connect: {{message}}":"Nepavyko prisijungti: {{message}}","Failed to delete:":"Nepavyko ištrinti:","Failed to fetch path information: {{message}}":"Nepavyko gauti aplanko informacijos: {{message}}","Failed to read backup defaults:":"Nepavyko nuskaityti kopijos numatytus parametrus:","Failed to restore files: {{message}}":"Failų atkūrimas nepavyko: {{message}}","Failed to save:":"Išsaugoti nepavyko:","File":"Failas","Files larger than:":"Failai didesni nei:","Filters":"Filtrai","Finished!":"Baigta!","First run setup":"Pirmojo paleidimo sąranka","Folder":"Aplankas","Folder path":"Aplanko kelias","Fri":"Pn","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS projekto ID","General":"Pagrindiniai","General backup settings":"Pagrindiniai kopijos nustatymai","General options":"Pagrindiniai parametrai","Generate":"Generuoti","Generate IAM access policy":"Generuoti IAM prieigos politiką","Group email":"Grupės el. paštas","Hidden files":"Paslėpti failai","Hide":"Paslepti","Hide hidden folders":"Nerodyti paslėptų aplankų","Home":"Pradžia","Hostnames":"Serverio vardas","Hours":"Valandos","How do you want to handle existing files?":"Kaip elgtis su esamais failais?","Hyper-V Machine":"Hyper-V mašina","Hyper-V Machine:":"Hyper-V mašina:","Hyper-V Machines":"Hyper-V mašinos","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jai kopijos laikas praleistas, užduotis bus vykdoma pirmai progai pasitaikius.","If at least one newer backup is found, all backups older than this date are deleted.":"Rasta bent viena naujesnė kopija, visos kopijos senesnės nei ši data bus ištrintos.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jei nenurodysite kelio, visi failai bus išsaugoti pagrindiniame aplanke.\nAr tikrai to norite?","If you do not enter an API Key, the tenant name is required":"Jei nurodysite API raktą, būtina nurodyti savininką","Import":"Importas","Import Destination URL":"Importo paskirties URL","Import backup configuration":"Importuoti kopijos konfigūraciją","Import from a file":"Importas iš failo","Import metadata":"Importuoti meta duomenis","Include a file?":"Įtraukti failą?","Include expression":"Įtraukti išraišką","Include regular expression":"Įtraukti standartinę išraišką","Incorrect answer, try again":"Atsakymas neteisingas, bandykite dar kartą","Individual builds for developers only. Not for use with important data.":"Individualios versijos skirtos programuotojams. Netinkamos naudoti su svarbiais duomenimis.","Information":"Informacija","Invalid characters in path":"Kelio pavadinime yra netinkamų simbolių","Invalid retention time":"Netinkamas saugojimo laikas","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Prie kai kurių FTP serverių galima prisijungti be slaptažodžio.\nAr jūs įsitikinę, kad FTP serveris leidžia prisijungimus be slaptažodžio?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"Saugoti nurodyta kiekį kopijų","Keep all backups":"Saugoti visas kopijas","Keystone API version":"Keystone API versija","Language in user interface":"Kalba vartotojo interfeise","Last month":"Praeitas mėnuo","Last successful backup:":"Paskutinė sėkminga kopija:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Paskutinis sėkmingas atkūrimas: {{time}} (užtruko {{duration || '0 sek.'}})","Latest":"Naujausias","Libraries":"Bibliotekos","Live":"Gyvai","Load a configuration from an exported job or a storage provider":"Įkelti konfigūraciją iš eksportuotos užduoties arba saugojimo paslaugų tiekėjo","Load destination from an exported job or a storage provider":"Įkelti paskirtį iš eksportuotos užduoties arba saugojimo paslaugų tiekėjo","Load older data":"Įkelti senesnius duomenis","Local database path:":"Lokalios duomenų bazės kelias:","Local repository":"Vietinė saugykla","Local storage":"Lokali saugykla","Location":"Vieta","Location where buckets are created":"Vieta, kur sukuriamos saugyklos","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}}žurnalo duomenys","Log data from the server":"Žurnalo duomenys iš serverio","Log out":"Atsijungti","MByte":"MB","MByte/s":"MB/s","Maintenance":"Priežiūra","Manually type path":"Rankiniu būdu įveskite kelią","Max download speed":"Maksimalus atsisiuntimo greitis","Max upload speed":"Maksimalus įkėlimo greitis","Menu":"Meniu","Microsoft SQL Database:":"Microsoft SQL duomenų bazė:","Microsoft SQL Databases":"Microsoft SQL duomenų bazės","Minimum redundancy":"Minimalus perteklinių kopijų kiekis","Minimum redundancy is 1.0":"Minimalus perteklinių kopijų skaičius yra 1.0","Minutes":"Minutės","Missing name":"Trūksta pavadinimo","Missing passphrase":"Trūksta slaptos frazės","Missing sources":"Trūksta šaltinių","Mon":"Pr","Months":"Mėnesiai","Move existing database":"Perkelti esamą duomenų bazę","Move failed:":"Perkelti nepavyko:","My Documents":"Mano dokumentai","My Music":"Mano muzika","My Photos":"Mano nuotraukos","My Pictures":"Mano paveikslėliai","Name":"Vardas","Never":"Niekada","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Naujas vartotojo vardas {{user}}.\nNaujo riboto vartotojo prisijungimo duomenys atnaujinti","Next":"Kitas","Next scheduled run:":"Kitas planuojamas paleidimas:","Next scheduled task:":"Kita planuojama užduotis:","Next task:":"Kita užduotis","Next time":"Kitą kartą","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Anksčiau nebuvo nurodytas sertifikatas, su serverio administratoriumi patikrinkite kad raktas teisingas: {{key}} \n\nAr patvirtinate pateiktą mazgo raktą?","No editor found for the "{{backend}}" storage type":"Saugyklos tipui "{{backend}}" nerastas redaktorius","No encryption":"Be šifravimo","No items selected":"Nieko nepasirinkta","No items to restore, please select one or more items":"Nėra ko atkurti, pasirinkite vieną ar kelis elementus","No passphrase entered":"Neįvesta slapta frazė","No scheduled tasks":"Nėra planinių užduočių","Non-matching passphrase":"Netinkama slapta frazė","None / disabled":"Nieko / išjungta","Nothing will be deleted. The backup size will grow with each change.":"Niekas nebus trinama. Kopijos dydis didės su kiekvienu pasikeitimu.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Kai bus sukurta daugiau kopijų nei nurodyta - seniausia kopija bus ištrinta.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack objekto saugykla / Swift","Operating System":"Operacinė sistema","Operations:":"Operacijos","Optional authentication password":"Neprivalomas autorizavimo slaptažodis","Optional authentication username":"Neprivalomas autorizavimo vartotojas","Options":"Parametrai","Original location":"Originali vieta","Others":"Kiti","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Senos kopijos bus šalinamos automatiškai. Bus saugoma po vieną kopiją 7 dienas, po vieną kas 4 savaites ir po vieną kas 12 mėnesių. Visada bus bent viena likusi kopija.","Overwrite":"Perrašyti","Passphrase":"Slapta frazė","Passphrase (if encrypted)":"Slapta frazė (jei šifruota)","Passphrase changed":"Slapta frazė pakeista","Passphrases are not matching":"Slaptos frazės nesutampa","Password":"Slaptažodis","Path":"Kelias","Path not found":"Kelias nerastas","Path on server":"Kelias iki serverio","Path or subfolder in the bucket":"Kelias arba pakatalogis saugykloje","Pause":"Pauzė","Pause after startup or hibernation":"Pauzė po paleidimo ar ramybės būsenos","Pause options":"Pauzės parametrai","Permissions":"Leidimai","Pick location":"Pasirinkite vietą","Point to your backup files and restore from there":"Pasirinkite atsarginės kopijos failus ir atkurkite iš jos","Port":"Portas","Prevent tray icon automatic log-in":"Neleisti automatinio prisijungimo per dėklo piktogramą","Previous":"Ankstesnis","Progress:":"Progresas:","ProjectID is optional if the bucket exist":"ProjectID yra neprivalomas, jei egzistuoja saugykla","Proprietary":"Patentuota","Recreate (delete and repair)":"Perkurti (ištrinti ir taisyti)","Relative paths not allowed":"Santykiniai keliai neleidžiami","Reload":"Užkrauti iš naujo","Remote":"Nuotolinis","Remote Path":"Kelias iki nutolusio serverio","Remote Repository":"Nutolusi saugykla","Remote path":"Kelias iki nutolusio serverio","Remote repository":"Nutolusi saugykla","Remote volume size":"Nutolusio tomo dydis","Remove":"Pašalinti","Remove option":"Pašalinti parinktį","Repair":"Remontuoti","Repeat Passphrase":"Pakartokite slaptą frazę","Reporting:":"Ataskaitų teikimas:","Reset":"Atstatyti","Restore":"Atkurti","Restore files":"Atkurti failus","Restore from":"Atkurti iš","Restore from backup configuration":"Atkurti iš atsarginės kopijos konfigūracijos","Restore options":"Atkurimo parinktis","Restore read/write permissions":"Atkurti skaitymo/rašymo leidimus","Resume":"Tęsti","Run again every":"Vykdyti dar kartą kas","Run now":"Vykdyti dabar","Running commandline entry":"Vykdoma komandų eilutės komanda","Running task:":"Vykdoma užduotis:","S3 Compatible":"Suderinamas su S3","Same as the base install version: {{channelname}}":"Ta pati, kaip pagrindinė diegimo versija: {{channelname}}","Sat":"Šešt","Save":"Įrašyti","Save and repair":"Įrašyti ir taisyti","Save different versions with timestamp in file name":"Išsaugokite kitą versiją su laiko žymoma failo pavadinime","Save immediately":"Įrašyti nedelsiant","Schedule":"Tvarkaraštis","Search":"Paieška","Search for files":"Failų paieška","Seconds":"Sekundės","Select a log level and see messages as they happen:":"Pasirinkite žurnalo lygį ir peržiūrėkite pranešimus, kaip jie įvyksta:","Select files":"Pasirinkite failus","Server":"Serveris","Server and port":"Serveris ir portas","Server hostname or IP":"Serverio pavadinimas ir IP","Server is currently paused,":"Serveris šiuo metu pristabdytas","Server is currently paused, do you want to resume now?":"Serveris šiuo metu pristabdytas, ar norite pratęsti jo darbą?","Server password":"Serverio slaptažodis","Server paused":"Serveris pristabdytas","Server state properties":"Serverio būsenos parametrai","Settings":"Nustatymai","Show":"Rodyti","Show advanced editor":"Rodyti patobulintą redaktorių","Show hidden folders":"Rodyti paslėptus aplankus","Show log":"Rodyti žurnalą","Show treeview":"Rodyti medžio vaizdą","Sia server password":"Sia serverio slaptažodis","Smart backup retention":"Išmanus kopijų saugojimas","Some OpenStack providers allow an API key instead of a password and tenant name":"Kai kurie OpenStack tiekėjai vietoj slaptažodžio pateikia API raktą ir nuomininko vardą","Source Data":"Šaltinio duomenys","Source data":"Šaltinio duomenys","Source folders":"Šaltinio aplankai","Source:":"Šaltinis:","Specific builds for developers only. Not for use with important data.":"Specifinės versijos skirtos tik programuotojams. Netinkamos naudoti su svarbiais duomenimis.","Standard protocols":"Standartiniai protokolai","Stop after the current file":"Stabdyti po dabartinio failo","Stop running backup":"Stabdyti vykdomą atsarginę kopiją","Stop running task":"Stabdyti vykdomą užduotį","Stopping task:":"Stabdoma užduotis:","Storage Type":"Saugyklos tipas","Storage class":"Saugyklos klasė","Storage class for creating a bucket":"Saugyklos klasė saugyklos kūrimui","Stored":"Išsaugota","Strong":"Stiprus","Success":"Sėkmė","Sun":"Sekm","Symbolic link":"Simbolinė nuoroda","System Files":"Sisteminiai failai","System default ({{levelname}})":"Sistemos numatytasis ({{levelname}})","System files":"Sisteminiai failai","System info":"Sistemos informacija","System properties":"Sistemos ypatybės","TByte":"TByte","TByte/s":"TByte/sek","Task is running":"Užduotis vykdoma","Temporary Files":"Laikini failai","Temporary files":"Laikini failai","Test connection":"Patikrinti prisijungimą","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"'{{fieldname}}' yra netinkamas simbolis: {{character}} (reikšmė: {{value}}, pozicija: {{pos}})","The bucket name should be all lower-case, convert automatically?":"Saugyklos pavadinimas turi būti iš mažųjų raidžių, konvertuoti automatiškai?","The dark theme (by Michal)":"Tamsi tema (nuo Michal)","The default blue on white theme (by Alex)":"Numatyta mėlyna ant balto tema (nuo Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Aplankas {{folder}} neegzistuoja.\nSukurti jį dabar?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Serverio raktas pasikeitė, su administratoriumi patikrinkite ar jis geras, priešingu atveju jūsų duomenys gali būti perimti.\n\nAr norite PAKEISTI jūsų DABARTINĮ serverio raktą \"{{prev}}\" PATEIKTU serverio raktu: {{key}}?","The path does not appear to exist, do you want to add it anyway?":"Panašu, kad toks kelias neegzistuoja, vis tiek jį pridėti?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Kelias pasibaigia ne '{{dirsep}}' simboliu, tai reiškia, kad pridėjote failą, ne aplanką.\n\nAr norite pridėti nurodytą failą?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Kelias turi būti absoliutus, tai yra turi prasidėti simboliu '/'","The region parameter is only applied when creating a new bucket":"Regiono parametras taikomas tik naujai saugyklai","The region parameter is only used when creating a bucket":"Regiono parametras panaudojamas tik kuriant saugyklą","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Serverio sertifikatas negali būti patikrintas.\nAr patvirtinate SSL sertifikatą su maiša: {{hash}}?","The storage class affects the availability and price for a stored file":"Saugyklos klasė turi įtakos failo pasiekiamumui ir kainai","The target folder contains encrypted files, please supply the passphrase":"Paskirties duomenys užšifruoti, pateikite slaptą frazę","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Naudotojas turi per daug teisių. Ar norite sukurti naują naudotoją, su prieiga tik prie pasirinkto kelio?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ši kopija buvo sukurta kitoje operacinėje sistemoje. Atkuriant failus nenurodžius paskirties vietos - jie gali atsirasti netikėtose vietose. Ar tęsti be paskirties kelio?","This month":"Šį mėnesį","This week":"Šią savaitę","Throttle settings":"Greičio nustatymai","Thu":"Ket","To File":"Į failą","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Kad patvirtintumėte visų \"{{name}}\" nutolusių failų trynimą, įveskite žodį, kurį matote žemiau","To export without a passphrase, uncheck the \"Encrypt file\" box":"Kad eksportuoti be slaptos frazės, palikite nepažymėtą varnelę \"Šifruoti failą\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Kad apsisaugoti nuo įvairių DNS atakų, Duplicati riboje galimų serverių vardus pagal nurodytą sąrašą. IP adresai ir localhost visada leidžiami. Keli serverių vardai leidžiami atskiriant kabliataškiu. Jei leidžiamas serverio vardas yra su žvaigždute (*), leidžiami visi serverių vardai ir ši savybė išjungta. Jei laukas tuščias - leidžiami tik IP adresai ir localhost.","Today":"Šiandien","Trust host certificate?":"Pasitikite saito sertifikatu?","Trust server certificate?":"Pasitikite serverio sertifikatu?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Išbandykite naujas galimybes, prie kurių šiuo metu dirbame. Šiuo metu stabiliausia versija pasiekiama. Išbadykite duomenų atkūrimą prie naudodami su svarbiais duomenimis.","Tue":"An","Type to highlight files":"Rašykite, kad paryškinti failus","Unknown backup size and versions":"Nežinomas kopijos dydis ir versijos","Until resumed":"Kol bus pratęsta","Update channel":"Atnaujinimų kanalas","Update failed:":"Atnaujinimas nepavyko:","Updating with existing database":"Atnaujinama su egzistuojančia duomenų baze","Usage statistics":"Naudojimo statistika","Usage statistics, warnings, errors, and crashes":"Naudojimo statistika, įspėjimai, klaidos ir lūžimai","Use SSL":"Naudoti SSL","Use existing database?":"Naudoti turimą duomenų bazę?","Use weak passphrase":"Naudoti silpną slaptą frazę","Useless":"Nenaudinga","User data":"Naudotojo duomenys","User domain name":"Naudotojo domeno vardas","User has too many permissions":"Naudotojas turi per daug teisių","User interface settings":"Naudotojo aplinkos nustatymai","Username":"Naudotojo vardas","Verify files":"Tikrinti failus","Verifying answer":"Tikrinamas atsakymas","Very strong":"Labai stiprus","Very weak":"Labai silpnas","Visit us on":"Aplankykite mus","WARNING: This will prevent you from restoring the data in the future.":"DĖMESIO: Tai neleis ateityje atkurti duomenis.","Waiting for task to begin":"Laukiama kol prasidės užduotis","Warnings, errors and crashes":"Įspėjimai, klaidos ir lūžimai","We recommend that you encrypt all backups stored outside your system":"Rekomenduojame šifruoti visas kopijas, kurios saugomos už jūsų sistemos ribų","Weak":"Silpna","Weak passphrase":"Silpna slapta frazė","Wed":"Tre","Weeks":"Savaitės","Where do you want to restore from?":"Iš kur norite atkurti?","Where do you want to restore the files to?":"Kur norite atkurti failus?","Years":"Metai","Yes":"Taip","Yes, I have stored the passphrase safely":"Taip, aš saugiai išsaugojau slaptą frazę","Yes, I'm brave!":"Taip, aš drąsus!","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{Item.Backup.Metadata.TargetSizeString}} / {{$ count}} versija","{{Item.Backup.Metadata.TargetSizeString}} / {{$ count}} versijos","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versijų","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versijų"]}); + gettextCatalog.setStrings('lv', {"- pick an option -":"- izvēlieties iestatījumu -","...loading...":"...notiek ielāde...","AWS Access ID":"AWS Piekļuves ID","AWS Access Key":"AWS Piekļuves atslēga","AWS IAM Policy":"AWS IAM Politika","About":"Par","About {{appname}}":"Par {{appname}}","Access Key":"Piekļuves atslēga","Access denied":"Piekļuve liegta","Access to user interface":"Piekļuve lietotāja saskarnei","Account name":"Konta nosaukums","Add a new backup":"Pievienot jaunu dublējumkopiju","Add a path directly":"Pievienot tiešo ceļu","Add advanced option":"Pievienot pielāgotu iestatījumu","Add backup":"Pievienot dublējumkopiju","Add filter":"Pievienot filtru","Add path":"Pievienot ceļu","Added":"Pievienots","Adjust bucket name?":"Precizēt spaiņa iestatījumu?","Advanced Options":"Pielāgotas Opcijas","Advanced options":"Pielāgotas opcijas","Advanced:":"Pielāgots:","All Hyper-V Machines":"Visas Hyper-V Mašīnas","All Microsoft SQL Databases":"Visas Microsoft SQL Datubāzes","Allow remote access (requires restart)":"Atļaut attālinātu piekļuvi (nepieciešams restartēt programmu)","Allowed days":"Atļautās dienas","An existing file was found at the new location":"Tika atrasts jau esošs fails jaunajā atrašanās vietā","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Tika atrasts jau esošs fails jaunajā atrašanās vietā\nVai esat pārliecināts, ka vēlaties datubāzi novirzīt uz jau esošo failu?","Anonymous usage reports":"Anonīmas lietošanas atskaites","Applications":"Lietotnes","As Command-line":"Kā Komand-rinda","Authentication password":"Autentifikācijas parole","Authentication username":"Autentifikācijas lietotājvārds","Autogenerated passphrase":"Automātiski izveidota piekļuves frāze","Back":"Atpakaļ","Backup complete!":"Dublējumkopijas veidošana pabeigta!","Backup destination":"Dublējumkopijas mērķa atrašanās vieta","Backup location":"Dublējumkopijas atrašanās vieta","Backup retention":"Dublējumkopiju saglabāšanas ilgums","Backup:":"Dublējumkopija:","Beta":"Beta versija","Browse":"Pārlūkot","Browser default":"Pārlūka noklusējums","Bucket name":"Spaiņa nosaukums","Bucket storage class":"Spaiņa uzglabāšanas klase","Canary":"Canary","Cancel":"Atcelt","Changelog":"Izmaiņu žurnāls","Check failed:":"Pārbaude neizdevās:","Check for updates now":"Pārbaudīt atjauninājumus tagad","Click to set throttle options":"Uzklikšķiniet, lai uzstādītu ierobežojumus","Compact now":"Saspiest tagad","Computer":"Dators","Configuration file:":"Konfigurācijas fails:","Configuration:":"Konfigurācija:","Configure a new backup":"Konfigurēt jaunu dublējumkopiju","Confirm delete":"Apstiprināt dzēšanu","Confirmation required":"Nepieciešams apstiprinājums","Connect":"Pieslēgties","Connect now":"Pieslēgties tagad","Connecting to server …":"Pieslēdzas serverim...","Connection lost":"Savienojums ir zudis","Connection worked!":"Savienojums strādā!","Continue":"Turpināt","Continue without encryption":"Turpināt bez šifrēšanas","Copied!":"Nokopēts!","Core options":"Pamata opcijas","Crashes only":"Tikai avārijas","Create folder?":"Izveidot mapi?","Custom region for creating buckets":"Specifiskais reģions spaiņu izveidei","Days":"Dienas","Default":"Noklusējums","Default options":"Noklusējuma iestatījumi","Delete":"Izdzēst","Delete backup":"Izdzēst dublējumkopiju","Delete local database":"Izdzēst lokālo datubāzi","Delete remote files":"Dzēst attālinātos failus","Delete the local database":"Izdzēst lokālo datubāzi","Desktop":"Darbavirsma","Destination":"Mērķis","Disabled":"Atspējots","Dismiss":"Atmest","Display and color theme":"Displeja un krāsu motīvs","Done":"Pabeigts","Download":"Lejupielādēt","Duplicati Website":"Duplicati tīmekļa vietne","Duplicati forum":"Duplicati forums","Edit as list":"Rediģēt kā sarakstu","Edit as text":"Rediģēt kā tekstu","Encrypt file":"Šifrēt failu","Encryption":"Šifrēšana","Encryption changed":"Šifrēšana mainīta","Enter URL":"Ievadiet URL","Enter backup passphrase, if any":"Ievadiet dublējumkopijas pieejas frāzi, ja tāda eksistē","Enter configuration details":"Ievadiet konfigurācijas detaļas","Enter encryption passphrase":"Ievadiet pieejas frāzi šifrēšanai","Enter the destination path":"Ievadiet mērķa atrašanās vietu","Error":"Kļūda","Error!":"Kļūda!","Errors and crashes":"Kļūdas un avārijas","Experimental":"Eksperimentāls","Export":"Eksportēt","Export configuration":"Eksportēt konfigurāciju","FTP (Alternative)":"FTP (Alternatīvs)","Failed to connect:":"Neizdevās izveidot savienojumu:","File":"Fails","Files larger than:":"Faili lielāki par:","Filters":"Filtrs","Finished!":"Pabeigts!","Folder":"Mape","General":"Vispārīgi","General backup settings":"Vispārīgie dublējumkopiju iestatījumi","General options":"Vispārīgie iestatījumi","Generate":"Izveidot","Hidden files":"Paslēptie faili","Hide":"Paslēpt","Hide hidden folders":"Paslēpt paslēptās mapes","Home":"Mājas","Hours":"Stundas","How do you want to handle existing files?":"Kā jūs vēlaties rīkoties ar jau esošajiem failiem?","Hyper-V Machine":"Hyper-V Mašīna","Hyper-V Machine:":"Hyper-V Mašīna:","Hyper-V Machines":"Hyper-V Mašīnas","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ja tika nokavēts datums, uzdevums tiks palaists cik ātri vien iespējams.","Import":"Importēt","Import from a file":"Pievienot no faila","Incorrect answer, try again":"Nepareiza atbilde, mēģiniet vēlreiz","Information":"Informācija","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Ir iespējams pievienoties pie kāda FTP servera bez paroles.\nVai esat pārliecināts, ka jūsu FTP serveris atbalsta bez-paroles pieslēgšanos?","Language in user interface":"Lietotāja saskarnes valoda:","Last month":"Pagājušais mēnesis","Latest":"Pēdējais","Libraries":"Bibliotēkas","Load older data":"Ielādēt vecākus datus","Local database path:":"Ceļš uz lokālo datubāzi:","Local storage":"Lokālā krātuve","Location":"Atrašanās vieta","Log out":"Izrakstīties","Maintenance":"Apkope","Max download speed":"Maksimālais lejupielādes ātrums","Max upload speed":"Maksimālais augšupielādes ātrums","Menu":"Izvēlne","Minutes":"Minūtes","Missing passphrase":"Trūkst pieejas frāze","Modified":"Modificēts","Mon":"Pirm","Months":"Mēneši","Move existing database":"Pārvietot esošo datubāzi","Move failed:":"Pārvietošana neizdevās:","My Documents":"Mani dokumenti","My Music":"Mana mūzika","My Photos":"Mani fotoattēli","My Pictures":"Mani attēli","Never":"Nekad","Next":"Nākamais","Next scheduled run:":"Nākamā plānotā norise","Next scheduled task:":"Nākamais plānotais uzdevums:","Next task:":"Nākamais uzdevums:","Next time":"Nākamreiz","No":"Nē","No encryption":"Nav šifrešanas","No items selected":"Nav izvēlētu vienību","No items to restore, please select one or more items":"Nav vienību ko atjaunot, lūdzu izvēlieties vienu vai vairākas vienības","No passphrase entered":"Pieejas frāze nav ievadīta","No scheduled tasks":"Nav ieplānotu uzdevumu","Non-matching passphrase":"Nesakrītoša pieejas frāze","None / disabled":"Nav / Atspējots","OK":"Labi","Operations:":"Darbības:","Optional authentication password":"Neobligāta autentifikācijas parole","Options":"Iestatījumi","Original location":"Sākotnējā atrašanās vieta","Others":"Citi","Overwrite":"Pārrakstīt","Passphrase":"Pieejas frāze","Passphrase (if encrypted)":"Pieejas frāze (ja šifrēts)","Passphrase changed":"Pieejas frāze nomainīta","Passphrases are not matching":"Pieejas frāzes nesakrīt","Password":"Parole","Path not found":"Ceļš nav atrasts","Path on server":"Ceļs uz servera","Pause":"Pauzēt","Pause options":"Pauzēt opcijas","Permissions":"Atļaujas","Port":"Ports","Reload":"Pārlādēt","Remote":"Attālināts","Remove":"Noņemt","Remove option":"Noņemt iestatījumu","Repair":"Salabot","Repeat Passphrase":"Atkārtot pieejas frāzi","Reset":"Attiestatīt","Restore":"Atgūt","Restore files":"Atgūt failus","Restore options":"Atjaunot opcijas","Restore read/write permissions":"Atjaunot lasīšanas/rakstīšanas atļaujas","Resume":"Turpināt","Run again every":"Palaist atkal katru","Run now":"Palaist tagad","Sat":"Sest","Save":"Saglabāt","Save and repair":"Saglabāt un salabot","Save immediately":"Saglabāt uzreiz","Search":"Meklēt","Search for files":"Meklēt failus","Seconds":"sekundes","Select files":"Izvēlēties failus","Server":"Serveris","Server and port":"Serveris un ports","Server hostname or IP":"Resursdatora nosaukums vai IP adrese","Server password":"Servera parole","Settings":"Iestatījumi","Show":"Parādīt","Show hidden folders":"Parādīt paslēptās mapes","Show log":"Parādīt žurnālu","Sia server password":"Sia servera parole","Source Data":"Avota Dati","Source data":"Avota dati","Source folders":"Avota mapes","Source:":"Avots:","Stop running task":"Pārtraukt uzdevuma izpildi","Stopping task:":"Aptur uzdevumu:","Storage Type":"Krātuves Tips","Strong":"Spēcīgs","Sun":"Svēt","Symbolic link":"Simboliskā saite","System files":"Sistēmas faili","System info":"Sistēmas informācija","System properties":"Sistēmas īpašības","Task is running":"Uzdevums ir palaists","Temporary files":"Pagaidu faili","Test connection":"Pārbaudīt savienojumu","The dark theme (by Michal)":"Tumšais motīvs (veidoja Michal)","The default blue on white theme (by Alex)":"Noklusējuma zils uz balta motīvs (veidoja Alex)","This month":"Šis mēnesis","This week":"Šī diena","Thu":"Cetr","Today":"Šodien","Tue":"Otr","Update channel":"Atjauninājumu kanāls","Update failed:":"Atjaunināšana neizdevās:","Usage statistics":"Izmantošanas statistika","Use SSL":"Izmantot SSL","Use weak passphrase":"Lietot vāju pieejas frāzi","Useless":"Bezjēdzīgs","User data":"Lietotāja dati","User interface settings":"Lietotāja saskarnes iestatījumi","Username":"Lietotājvārds","Verify files":"Pārbaudīt failus","Very strong":"Ļoti stiprs","Very weak":"Ļoti vājš","Warnings, errors and crashes":"Brīdinājumi, kļūdas un avārijas","We recommend that you encrypt all backups stored outside your system":"Mēs iesakām jums šifrēt visas dublējumkopijas, kuras tiek uzglabātas ārpus jūsu sistēmas","Weak":"Vājš","Weak passphrase":"Vāja pieejas frāze","Wed":"Treš","Weeks":"Nedēļas","Years":"Gadi","Yes":"Jā","Yes, I have stored the passphrase safely":"Jā, esmu noglabājais pieejas frāzi droši","Yes, I'm brave!":"Jā, esmu drosmīgs!","Yes, please break my backup!":"Jā, lūdzu salauziet manu dublējumkopiju!","Yesterday":"Vakardiena","You must enter a name for the backup":"Nepieciešams ievadīt dublējumkopijas nosaukumu","You must enter a passphrase or disable encryption":"Jums nepieciešams ievadīt pieejas frāzi vai atspējot šifrēšanu","You must fill in the password":"Nepieciešams ievadīt paroli!","You must specify a path":"Jums jānorāda ceļš","Your passphrase is easy to guess. Consider changing passphrase.":"Jūsu pieejas frāzi ir vienkārsi uzminēt. Apdomājiet pieejas frāzes nomaiņu.","bucket/folder/subfolder":"spainis/mape/apakšmape","byte":"baits","byte/s":"baiti/sekundē","resume now":"turpināt tagad","{{number}} Hour":"{{number}} Stunda","{{number}} Minutes":"{{number}} Minūtes"}); + gettextCatalog.setStrings('nl_NL', {"(1 error{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["({{$count}} errors{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} errors{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(1 warning{{item.Result.Interrupted? (', interrupted'|translate) : ''}})":["({{$count}} warnings{{item.Result.Interrupted? (', interrupted'|translate) : ''}})","({{$count}} warnings{{item.Result.Interrupted? (', interrupted'|translate) : ''}})"],"(interrupted)":"(onderbroken)","- pick an option -":" - kies een optie -","...loading...":"...laden...","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"Let op: Sia zal later nog steeds de redundantie verhogen zolang u verbonden bent met uw hosts."," Edit as text":" Bewerk als tekst"," Edit as text":" Bewerk als tekst","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

Verbinding met server is afgewezen vanwege ongeldige authenticatie.

\n

Meld u opnieuw aan of open de pagina opnieuw vanuit het Systeemvak (indien van toepassing)

","API key":"API sleutel","AWS Access ID":"AWS Toegangs ID","AWS Access Key":"AWS Toegangssleutel","AWS IAM Policy":"AWS IAM Beleid","About":"Over","About {{appname}}":"Over {{appname}}","Access Key":"Toegangssleutel","Access Key ID":"Toegangssleutel-ID","Access Key Secret":"Toegangssleutel Geheim","Access denied":"Toegang geweigerd","Access grant":"Toegang verleend","Access key":"Toegangssleutel","Access to user interface":"Toegang tot gebruikersomgeving","Account name":"Accountnaam","Add a new backup":"Nieuwe back-up toevoegen","Add a path directly":"Voeg een pad rechtstreeks toe","Add advanced option":"Voeg geavanceerde optie toe","Add backup":"Back-up toevoegen","Add filter":"Voeg filter toe","Add path":"Voeg pad toe","Added":"Toegevoegd","Adjust bucket name?":"Bucket naam aanpassen?","Advanced Options":"Geavanceerde Opties","Advanced options":"Geavanceerde opties","Advanced:":"Geavanceerd:","Aliyun OSS Endpoint":"Aliyun OSS Eindpunt","Aliyun OSS documents and resources":"Aliyun OSS documenten en bronnen","All Hyper-V Machines":"Alle Hyper-V Machines","All Microsoft SQL Databases":"Alle Microsoft SQL Databases","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alle gebruiksrapporten worden anoniem verstuurd en bevatten geen enkele persoonlijke informatie. Ze bevatten informatie over hardware en besturingssysteem, het type backend, back-up tijdsduur, totale grootte van brongegevens en soortgelijke gegevens. Ze bevatten geen paden, bestandsnamen, gebruikersnamen, wachtwoorden of soortgelijke gevoelige informatie.","Allow remote access (requires restart)":"Remote toegang toestaan (herstart vereist)","Allowed days":"Alleen op deze dagen","An existing file was found at the new location":"Een bestaand bestand was gevonden op de nieuwe locatie","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Een bestaand bestand was gevonden op de nieuwe locatie. Weet u zeker dat de database moet verwijzen naar een bestaand bestand?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Een bestaande lokale database voor de opslag is gevonden.\nHergebruik van de database zal toestaan dat de opdrachtregel- en server instances werken op dezelfde remote opslag.\n\nWilt u de bestaande database gebruiken?","Anonymous usage reports":"Anonieme gebruiksrapporten","Applications":"Toepassingen","As Command-line":"Als Opdrachtregel","AuthID":"AuthID","Authentication method":"Authenticatiemethode","Authentication method ({{auth_method}})":"Authenticatiemethode ({{auth_method}})","Authentication password":"Authenticatie wachtwoord","Authentication username":"Authenticatie gebruikersnaam","Autogenerated passphrase":"Automatisch gegenereerde wachtwoordzin","Automatically run backups":"Automatisch back-ups uitvoeren","B2 Application ID":"B2 Applicatie ID","B2 Application Key":"B2 Applicatiesleutel","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Applicatie ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Applicatiesleutel","Back":"Vorige","Backend modules:

{{item.Key}}

":"Backend modules:

{{item.Key}}

","Backup complete!":"Back-up compleet!","Backup destination":"Back-updoel","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"Back-up is gecodeerd maar er is geen wachtwoordzin beschikbaar. Typ hieronder een wachtwoordzin om te gebruiken voor het herstellen van uw bestanden, of, in het geval van GPG-codering, laat dit leeg om de gpg-code de wachtwoordzin op te laten halen door een beroep te doen op de keychain van uw systeem.","Backup location":"Back-up locatie","Backup retention":"Back-up retentie","Backup:":"Back-up:","Beta":"Beta","Broken access":"Verbroken toegang","Browse":"Bladeren","Browser default":"Browser standaard","Bucket create location":"Bucket aanmaaklocatie","Bucket name":"Bucketnaam","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Bucket-naam kan alleen tussen 3 en 63 tekens lang zijn en mag alleen kleine letters, cijfers, punten en mintekens bevatten","Bucket region":"Bucket-regio","Bucket region ap-guangzhou":"Bucket-regio ap-guangzhou","Bucket storage class":"Bucket opslagklasse","Bucket, format: BucketName-APPID":"Bucket, formaat: BucketNaam-APPID","Building list of files to restore …":"Opbouwen lijst te herstellen bestanden ...","Building partial temporary database …":"Opbouwen gedeeltelijke tijdelijke database ...","Busy …":"Bezig …","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Door remote toegang toe te staan, luistert de server naar aanvragen van een willekeurige machine op het netwerk. Verzeker u ervan dat de computer wordt gebruikt op een netwerk dat wordt beschermd door een veilig ingestelde firewall als u deze optie wilt inschakelen.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Standaard opent het systeemvak-pictogram de gebruikersomgeving met een token dat de gebruikersomgeving ontgrendelt. Dit zorgt ervoor dat u toegang heeft tot de gebruikersomgeving vanaf het systeemvak-pictogram, zonder dat u anderen hoeft te vragen het wachtwoord in te voeren. Schakel deze optie in als u er de voorkeur aan geeft zelf het wachtwoord in te voeren, zelfs wanneer de gebruikersomgeving wordt geopend vanuit het systeemvak-pictogram.","COS App ID":"COS App ID","COS Path or subfolder in the bucket":"COS Pad of submap in de bucket","COS Secret ID":"COS Geheim ID","COS Secret Key":"COS Geheime Sleutel","Cache Files":"Cache bestanden","Canary":"Canary","Cancel":"Annuleren","Cannot include \"{{text}}\"":"Mag \"{{text}}\" niet bevatten","Cannot move to existing file":"Kan niet verplaatsen naar bestaand bestand","Cannot specify filter include or excludes in extra options":"Kan geen in- of uitsluitingsfilters opnemen in extra opties","Change server passphrase":"Wijzig server wachtwoordzin","Change server password":"Wijzig serverwachtwoord","Changelog":"Aanpassingen-log","Changelog for {{appname}} {{version}}":"Aanpassingen-log voor {{appname}} {{version}}","Check failed:":"Controle mislukt:","Check for updates now":"Controleer nu op updates","Checking for updates …":"Controleren op updates ...","Checking …":"Controleren …","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"Kies 1.0 voor snelle back-up, 1.5 voor redelijke betrouwbaarheid, 2.0 voor veiliger uploaden maar trage back-up.","Chose a storage type to get started":"Kies een opslagtype om aan de slag te gaan","Click the AuthID link to create an AuthID":"Klik op de AuthID link om een AuthID aan te maken","Click to set throttle options":"Klik om bandbreedte-opties in te stellen","Client library to use":"Te gebruiken client-blibliotheek","Cloud API Secret ID":"Cloud API Geheim ID","Cloud API Secret Key":"Cloud API Geheime Sleutel","Command":"Commando","Commandline arguments":"Opdrachtregel-argumenten","Commandline …":"Opdrachtregel ...","Compact Phase":"Opruimen Subtaak","Compact now":"Nu opruimen","Compacting remote data …":"Opschonen remote gegevens ...","Complete log":"Compleet log","Completing backup …":"Afronden back-up ...","Completing previous backup …":"Afronden vorige back-up ...","Compression modules:

{{item.Key}}

":"Compressiemodules:

{{item.Key}}

","Computer":"Computer","Configuration file:":"Configuratiebestand","Configuration:":"Configuratie:","Configure a new backup":"Een nieuwe back-up instellen","Confirm delete":"Bevestig verwijderen","Confirm encryption passphrase":"Bevestig wachtwoordzin voor versleuteling","Confirm new password":"Bevestig nieuw wachtwoord","Confirm passphrase":"Bevestig wachtwoordzin","Confirmation required":"Bevestiging vereist","Connect":"Verbind","Connect now":"Verbind nu","Connecting to server …":"Verbinden met server ...","Connecting to task …":"Verbinden met taak …","Connecting …":"Verbinden …","Connection lost":"Verbinding verbroken","Connection worked!":"Verbinding werkt!","Container name":"Containernaam","Container region":"Container-regio","Continue":"Volgende","Continue without encryption":"Ga verder zonder versleuteling","Copied!":"Gekopieerd!","Copy":"Kopie","Copy Destination URL to Clipboard":"Kopieer doel URL naar Klembord","Copy URL":"Kopie URL","Copy failed. Please manually copy the URL":"Kopiëren mislukt. Kopieer de URL handmatig","Copy log":"Kopie log","Core options":"Kern-opties","Counting ({{files}} files found, {{size}})":"Tellen ({{files}} bestanden gevonden, {{size}})","Crashes only":"Alleen crashes","Create bug report …":"Bug rapport maken ...","Create folder?":"Map aanmaken?","Created new limited user":"Nieuwe beperkte gebruiker aangemaakt","Creating bug report …":"Bug rapport maken ...","Creating new user with limited access …":"Nieuwe gebruiker met beperkte toegang aanmaken ...","Creating target folders …":"Doelmappen aanmaken ...","Creating temporary backup …":"Tijdelijke back-up aanmaken ...","Creating user …":"Gebruiker aanmaken …","Current action:":"Huidige actie:","Current file:":"Huidig bestand:","Current version is {{versionname}} ({{versionnumber}})":"Huidige versie is {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Aangepaste S3 endpoint","Custom Satellite":"Aangepaste Satellite","Custom Satellite ({{satellite}})":"Aangepaste Satellite ({{satellite}})","Custom authentication url":"Aangepaste authenticatie url","Custom backup retention":"Aangepaste back-up retentie","Custom bucket storage class":"Aangepaste bucket-opslagklasse","Custom location ({{server}})":"Aangepaste locatie ({{server}})","Custom region for creating buckets":"Aangepaste regio voor het aanmaken van buckets","Custom region value ({{region}})":"Aangepaste regio waarde ({{region}})","Custom server url ({{server}})":"Aangepaste server url ({{server}})","Custom storage class ({{class}})":"Aangepaste opslagklasse ({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"VEROUDERD: {{getDeprecationMessage(item)}}","Database …":"Database ...","Days":"Dagen","Default":"Standaard","Default ({{channelname}})":"Standaard ({{channelname}})","Default excludes":"Standaard uitsluitingen","Default options":"Standaard opties","Default value: \"{{getDefaultValue(item)}}\"":"Standaardwaarde: \"{{getDefaultValue(item)}}\"","Delete":"Verwijderen","Delete Phase (Old Backup Versions)":"Verwijderen Subtaak (Oude Back-upversies)","Delete backup":"Verwijder back-up","Delete backups that are older than":"Verwijder back-ups die ouder zijn dan","Delete local database":"Verwijder lokale database","Delete remote files":"Verwijder remote bestanden","Delete the local database":"Verwijder de lokale database","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"{{filecount}} bestanden ({{filesize}}) van de remote opslag verwijderen?","Delete …":"Verwijderen ...","Deleted":"Verwijderd","Deleted Versions":"Verwijderde versies","Deleted files":"Verwijderde bestanden","Deleting remote files …":"Remote bestanden verwijderen ...","Deleting unwanted files …":"Ongewenste bestanden verwijderen ...","Description (optional)":"Omschrijving (optioneel)","Description:":"Omschrijving:","Desktop":"Desktop","Destination":"Doel","Destination path":"Doelpad","Direct restore from backup files …":"Direct herstellen vanuit back-upbestanden …","Directory path":"Directory-pad","Disabled":"Uitgeschakeld","Dismiss":"Afwijzen","Dismiss all":"Alles afwijzen","Display and color theme":"Weergave en kleurenschema","Do you really want to delete the backup: \"{{name}}\" ?":"Wilt u de back-up \"{{name}}\" echt verwijderen?","Do you really want to delete the local database for: {{name}}":"Wilt u de lokale database voor: {{name}} echt verwijderen?","Domain name":"Domeinnaam","Done":"Klaar","Download":"Download","Downloaded files":"Gedownloade bestanden","Downloading files …":"Bestanden downloaden ...","Downloading update…":"Update downloaden ...","Duplicate option {{opt}}":"Dubbele optie {{opt}}","Duplicati Website":"Duplicati Website","Duplicati forum":"Duplicati forum","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicati moet worden beveiligd met een wachtwoordzin en er is een willekeurige wachtwoordzin voor u gegenereerd.\nAls u Duplicati opent via het systeemvakpictogram, heeft u geen wachtwoordzin nodig, maar als u van plan bent het te openen vanaf een andere locatie moet u een wachtwoordzin instellen die u kent.\nWilt u nu een wachtwoordzin instellen?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati zal bij het starten worden uitgevoerd, maar zolang als opgegeven gepauzeerd blijven. Duplicati zal een minimale hoeveelheid systeembronnen gebruiken en er zullen geen back-ups gestart worden.","Duration":"Tijdsduur","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Iedere back-up heeft een lokale database waarmee het geassocieerd is, die informatie opslaat over de remote back-up op de lokale machine.\nBij het verwijderen van een back-up kan eveneens de lokale database verwijderd worden, zonder dat dit invloed heeft op de mogelijkheid van het terugzetten van de remote bestanden.\nAls de lokale database gebruikt wordt voor back-ups vanaf de opdrachtregel, moet de database behouden blijven.","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"Aan elke back-up is een lokale database gekoppeld, waarin informatie over de externe back-up wordt opgeslagen op de lokale machine. Dit maakt het sneller om veel bewerkingen uit te voeren en vermindert de hoeveelheid gegevens die voor elke bewerking moet worden gedownload.","Edit as list":"Bewerk als lijst","Edit as text":"Bewerk als tekst","Edit …":"Bewerken ...","Email address of the Office 365 group":"E-mailadres van de Office 365-groep","Encrypt file":"Versleutel bestand","Encryption":"Versleuteling","Encryption changed":"Versleuteling aangepast","Encryption modules:

{{item.Key}}

":"Coderingsmodules:

{{item.Key}}

","Encryption passphrase":"Encryptie wachtwoordzin","Encryption passphrase (for verification)":"Coderings-wachtwoordzin (voor verificatie)","End":"Einde","Enter URL":"Geef URL in","Enter a backup destination URL:":"Voer de URL van een back-updoel in:","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Geef handmatig een retentie-strategie op. Tijdelijke aanduidingen zijn D/W/Y voor dagen/weken/jaren en U voor onbeperkt. De syntaxis is: 7D:1D,4W:1W,36M:1M. Dit voorbeeld bewaart één back-up voor elk van de volgende 7 dagen, één voor elk van de volgende 4 weken, en één voor elk van de volgende 36 maanden. Dit kan eveneens worden geschreven als 1W:1D,1M:1W,3Y:1M.","Enter a url, or click the "Target URL >" link":"Geef een URL in, of klik de "Doel-URL >" link","Enter backup passphrase, if any":"Geef eventueel back-up wachtwoordzin in","Enter configuration details":"Voer configuratie-details in","Enter encryption passphrase":"Geef een wachtwoordzin in voor versleuteling","Enter expression here":"Geef uitdrukking hier in","Enter one argument per line without quotes, e.g. *.txt":"Geef één argument per regel op zonder aanhalingstekens, bijv. *.txt","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"Geef één optie op in opdrachtregelformaat, bijv. --dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"Geef één optie op in opdrachtregelformaat, bijv. {0}","Enter the destination path":"Geef het doelpad in","Error":"Fout","Error!":"Fout!","Errors and crashes":"Fouten en crashes","Examined":"Onderzocht","Exclude":"Uitsluiten","Exclude directories whose names contain":"Sluit mappen uit waarvan de naam bevat:","Exclude expression":"Sluit uitdrukking uit","Exclude file":"Sluit bestand uit","Exclude file extension":"Sluit bestandsextensie uit","Exclude files whose names contain":"Sluit bestanden uit waarvan de naam bevat:","Exclude filter group":"Sluit filtergroep uit","Exclude folder":"Sluit map uit","Exclude regular expression":"Sluit reguliere expressie uit","Existing file found":"Bestaand bestand gevonden","Experimental":"Experimenteel","Export":"Exporteer","Export backup configuration":"Exporteer back-upconfiguratie","Export configuration":"Exporteer configuratie","Export passwords":"Exporteer wachtwoorden","Export …":"Exporteren ...","Exporting …":"Exporteren ...","External link":"Externe link","FTP (Alternative)":"FTP (Alternatief)","Failed to build temporary database: {{message}}":"Opbouwen tijdelijke database mislukt: {{message}}","Failed to connect:":"Verbinden mislukt:","Failed to connect: {{message}}":"Verbinden mislukt: {{message}}","Failed to delete:":"Verwijderen mislukt:","Failed to fetch path information: {{message}}":"Ophalen pad-informatie mislukt: {{message}}","Failed to find backup:":"Back-up kon niet worden gevonden:","Failed to get bug report URL: {{message}}":"Kan de URL van het bugrapport niet ophalen: {{message}}","Failed to import: {{message}}":"Kan niet importeren: {{message}}","Failed to read backup defaults:":"Standaard instellingen voor back-up inlezen mislukt:","Failed to read file: {{message}}":"Kan bestand niet lezen: {{message}}","Failed to restore files: {{message}}":"Herstellen bestanden mislukt: {{message}}","Failed to save:":"Opslaan mislukt:","Fatal error, no statistics collected":"Fatale fout, geen statistieken verzameld","Fetching path information …":"Ophalen pad-informatie ...","File":"Bestand","Files larger than:":"Bestanden groter dan:","Filters":"Filters","Finished!":"Klaar!","First run setup":"Instellen voor eerste gebruik","Folder":"Map","Folder in the bucket":"Map in de bucket","Folder path":"Map-pad","Folder path name":"Map-padnaam","Fri":"Vrijdag","Full destination path, including the server name, but without https":"Volledig bestemmingspad, inclusief de servernaam, maar zonder https","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"Algemeen","General backup settings":"Algemene back-upinstellingen","General options":"Algemene opties","Generate":"Genereer","Generate IAM access policy":"Genereer IAM toegangsbeleid","Getting file versions …":"Bestandsversies ophalen ...","Group email":"Groep e-mail","Hidden files":"Verborgen bestanden","Hide":"Verberg","Hide hidden folders":"Verberg verborgen bestanden","Home":"Start","Hostnames":"hostnamen","Hours":"Uur","How do you want to handle existing files?":"Hoe wilt u omgaan met bestaande bestanden?","Hyper-V Machine":"Hyper-V Machine","Hyper-V Machine:":"Hyper-V Machine:","Hyper-V Machines":"Hyper-V Machines","ID:":"ID:","IDrive Sync directory path":"IDrive Sync directory-pad","IDrive e2 Access Key ID":"IDrive e2 Toegangssleutel-ID","IDrive e2 Access Key Secret":"IDrive e2 Toegangssleutel-geheim","If a date was missed, the job will run as soon as possible.":"Als een geplande taak werd overgeslagen, zal de taak zo snel mogelijk na het geplande tijdstip starten.","If at least one newer backup is found, all backups older than this date are deleted.":"Als tenminste één nieuwere back-up is gevonden, zullen alle back-ups die ouder zijn dan deze datum worden verwijderd.","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"Als de back-up en de externe opslag niet volledig gesynchroniseerd zijn, vereist Duplicati dat u een reparatiebewerking uitvoert om de database te synchroniseren. Als de reparatie mislukt, kunt u de lokale database verwijderen en opnieuw genereren.","If the backup file was not downloaded automatically, right click and choose "Save as …".":"Als het back-upbestand niet automatisch is gedownload, klik met rechts en kies "Opslaan als ...".","If the backup file was not downloaded automatically, right click and choose "Save as …".":"Als het back-upbestand niet automatisch is gedownload, klik met rechts en kies "Opslaan als ...".","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Als u geen pad ingeeft, zullen alle bestanden opgeslagen worden in de login map.\nWeet u zeker dat dit is wat u wilt?","If you do not enter an API Key, the tenant name is required":"Als u geen API sleutel ingeeft, is een tenant naam vereist","If you want to use the backup later, you can export the configuration before deleting it.":"Als u de back-up later wilt gebruiken, kunt u de configuratie exporteren alvorens hem te verwijderen.","Import":"Importeer","Import Destination URL":"Importeer Doel URL","Import URL":"Import URL","Import backup configuration":"Importeer back-upconfiguratie","Import from a file":"Importeer vanuit een bestand","Import metadata":"Importeer metadata","Importing …":"Importeren ...","Include a file?":"Een bestand opnemen?","Include expression":"Uitdrukking opnemen","Include regular expression":"Reguliere expressie opnemen","Incorrect answer, try again":"Incorrect antwoord, probeer opnieuw","Individual builds for developers only. Not for use with important data.":"Individuele builds alleen voor ontwikkelaars. Niet voor gebruik met belangrijke gegevens.","Information":"Informatie","Interrupted, no statistics collected":"Onderbroken, geen statistieken verzameld","Invalid characters in path":"Ongeldige tekens in pad","Invalid retention time":"Ongeldige retentietijd","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Het is mogelijk te verbinden met sommige FTP servers zonder een wachtwoord.\nWeet u zeker dat uw FTP server aanmelden zonder wachtwoord ondersteunt?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Behoud een specifiek aantal back-ups","Keep all backups":"Behoud alle back-ups","Keystone API version":"Keystone API versie","Language in user interface":"Taal in gebruikersomgeving","Last month":"Vorige maand","Last successful backup:":"Laatste succesvolle back-up:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Laatste succesvolle hersteloperatie: {{time}} (duurde {{duration || '0 seconden'}})","Latest":"Laatste","Libraries":"Bibliotheken","Listing backup dates …":"Back-updatums weergeven ...","Listing remote files for purge …":"Remote bestanden tonen voor wissen ...","Listing remote files …":"Remote bestanden weergeven ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Laad een configuratie vanuit een geëxporteerde taak of een opslagprovider","Load destination from an exported job or a storage provider":"Laad doel vanuit een geëxporteerde taak of een opslagprovider","Load older data":"Laad oudere gegevens","Loading remote storage usage …":"Gebruik van externe opslag laden …","Loading …":"Laden ...","Local database for {{Backup.Backup.Name}}…loading…":"Lokale database voor {{Backup.Backup.Name}}…laden…","Local database path:":"Lokaal database-pad:","Local repository":"Lokale opslagplaats","Local storage":"Lokale opslag","Location":"Locatie","Location where buckets are created":"Locatie waar buckets gemaakt worden","Log data for {{Backup.Backup.Name}}":"Log gegevens voor {{Backup.Backup.Name}}","Log data from the server":"Log gegevens van de server","Log in":"Inloggen","Log out":"Uitloggen","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Onderhoud","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"Zorg ervoor vat rclone zich in uw pad bevindt, of voeg de locatie van rclone toe via de geavanceerde opties.","Manual":"Handmatig","Manual update found:":"Handmatige update gevonden:","Manually type path":"Voer pad handmatig in","Max download speed":"Max downloadsnelheid","Max upload speed":"Max Uploadsnelheid","Menu":"Menu","Microsoft SQL Database:":"Microsoft SQL Database","Microsoft SQL Databases":"Microsoft SQL Databases","Minimum redundancy":"Minimale redundantie","Minimum redundancy is 1.0":"Minimale redundantie is 1.0","Minutes":"Minuten","Missing name":"Ontbrekende naam","Missing passphrase":"Ontbrekende wachtwoordzin","Missing sources":"Ontbrekende bronnen","Modified":"Gewijzigd","Mon":"Maandag","Months":"Maanden","Move existing database":"Verplaats bestaande database","Move failed:":"Verplaatsen mislukt:","My Documents":"Mijn Documenten","My Music":"Mijn Muziek","My Photos":"Mijn Foto's","My Pictures":"Mijn Afbeeldingen","Name":"Naam","Never":"Nooit","New Password":"Nieuw Wachtwoord","New update found: {{message}}":"Nieuwe update gevonden: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nieuwe gebruikersnaam is {{user}}.\nGebruikersreferenties bijgewerkt om de nieuwe beperkte gebruiker te gebruiken","Next":"Volgende","Next scheduled run:":"Volgende geplande uitvoering:","Next scheduled task:":"Volgende geplande taak:","Next task:":"Volgende taak:","Next time":"Volgende keer","No":"Nee","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Er is eerder geen certificaat opgegeven, controleer svp met de serverbeheerder of de sleutel correct is: {{key}}\n\nWilt u de gerapporteerde host-sleutel goedkeuren?","No editor found for the "{{backend}}" storage type":"Geen bewerkingsprogramma gevonden voor het "{{backend}}" opslagtype","No encryption":"Geen versleuteling","No items selected":"Geen items geselecteerd","No items to restore, please select one or more items":"Geen items om te herstellen, selecteer één of meer items","No passphrase entered":"Geen wachtwoordzin ingegeven","No scheduled tasks":"Geen geplande taken","Non-matching passphrase":"Niet-bijbehorende wachtwoordzin","None / disabled":"Geen / uitgeschakeld","Not using encryption":"Zonder versleuteling","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"Houd er rekening mee dat snelheden in bytes worden opgegeven, en dat lijnsnelheden doorgaans in bits worden gerapporteerd. Gebruik bij de conversie een factor 8, zodat een lijn van 8 mbit/s gelijkstaat aan 1 MByte/s.","Nothing will be deleted. The backup size will grow with each change.":"Er zal niets verwijderd worden. De back-upgrootte zal toenemen met iedere verandering.","OK":"OK","OSS Access Key ID":"OSS Toegangssleutel-ID","OSS Access Key Secret":"OSS Toegangssleutel Geheim","OSS Bucket Region":"OSS Bucket-regio","OSS Bucket name":"OSS Bucket-naam","OSS Endpoint":"OSS Eindpunt","OSS Path or subfolder in the bucket":"OSS Pad of submap in de bucket","OSS Region":"OSS-Regio","Official releases":"Officiële releases","Once there are more backups than the specified number, the oldest backups are deleted.":"Zodra er meer back-ups zijn dan het opgegeven aantal, zullen de oudste back-ups worden verwijderd.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Geopend","Openstack API key are not supported in v3 keystone API":"Openstack API Sleutels worden niet ondersteund in v3 keystone API","Operating System":"Besturingssysteem","Operation":"Bewerking","Operations:":"Bewerkingen:","Optional API key":"Optionele API-sleutel","Optional authentication password":"Optioneel authenticatie wachtwoord","Optional authentication username":"Optionele authenticatie gebruikersnaam","Optional region":"Optionele regio","Optional tenant name":"Optionele tenant-naam","Options":"Opties","Options added here are applied to all backups, but can be overridden in each individual backup.":"Opties die hier worden toegevoegd, worden toegepast op alle back-ups, maar kunnen worden overschreven in iedere afzonderlijke back-up.","Original location":"Originele locatie","Others":"Anderen","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Na verloop van tijd zullen back-ups automatisch verwijderd worden. Er zal één back-up overblijven voor elk van de laatste 7 dagen, voor elk van de laatste 4 weken, en voor elk van de laatste 12 maanden. Er zal altijd tenminste één back-up overblijven.","Overwrite":"Overschrijven","Passphrase":"Wachtwoordzin","Passphrase (if encrypted)":"Wachtwoordzin (indien versleuteld)","Passphrase changed":"Wachtwoordzin veranderd","Passphrases are not matching":"Wachtwoordzinnen komen niet overeen","Passphrases do not match":"Wachtwoordzinnen komen niet overeen","Password":"Wachtwoord","Patching files with local blocks …":"Bestanden bijwerken met lokale blokken ...","Path":"Pad","Path not found":"Pad niet gevonden","Path on server":"Pad op server","Path or subfolder in the bucket":"Pad of submap in de bucket","Pause":"Pauze","Pause after startup or hibernation":"Pauzeer na opstarten of slaapmodus","Pause options":"Pauzeer-opties","Permissions":"Permissies","Pick location":"Kies locatie","Please select a file to import":"Selecteer een bestand om te importeren","Point to your backup files and restore from there":"Verwijs naar de back-up bestanden en herstel daar vandaan","Port":"Poort","Prevent tray icon automatic log-in":"Voorkom automatisch inloggen door systeemvak-pictogram","Previous":"Vorige","Progress:":"Voortgang:","ProjectID is optional if the bucket exist":"ProjectID is optioneel als de bucket bestaat","Proprietary":"Fabrikantgebonden","Purge Phase":"Uitwissen Subtaak","Purging files complete!":"Wissen van bestanden compleet!","Purging files …":"Bestanden wissen ...","Rebuilding local database …":"Opnieuw opbouwen van lokale database ...","Recreate (delete and repair)":"Opnieuw aanmaken (verwijderen en repareren)","Recreate Database Phase":"Opnieuw aanmaken Database Subtaak","Recreating database …":"Opnieuw aanmaken van de database ...","Region":"Regio","Registering temporary backup …":"Registreren tijdelijke back-up ...","Relative paths not allowed":"Relatieve paden zijn niet toegestaan","Reload":"Andere code","Remote":"Remote","Remote Path":"Remote Pad","Remote Repository":"Remote Opslagplaats","Remote path":"Remote pad","Remote repository":"Remote opslagplaats","Remote volume size":"Remote volume grootte","Remove":"Verwijderen","Remove option":"Verwijder optie","Removed files":"Verwijderde bestanden","Repair":"Repareren","Repair Phase":"Repareren Subtaak","Repairing database …":"Database repareren ...","Repeat Passphrase":"Herhaal wachtwoordzin","Reporting:":"Rapportage:","Reset":"Reset","Restore":"Herstellen","Restore complete!":"Herstellen compleet!","Restore files":"Herstel bestanden","Restore files from:":"Herstel bestanden van:","Restore files …":"Bestanden herstellen ...","Restore from":"Herstellen vanaf","Restore from backup configuration":"Herstel vanuit back-up configuratie","Restore from configuration …":"Herstellen vanuit configuratie …","Restore options":"Herstelopties","Restore read/write permissions":"Herstel lees/schrijfpermissies","Restored Files":"Herstelde Bestanden","Restored Folders":"Herstelde Mappen","Restored Symlinks":"Herstelde Symbolische Links","Restoring files …":"Bestanden worden hersteld ...","Resume":"Hervat","Rewritten File Lists":"Herschreven bestandslijsten","Run again every":"Voer opnieuw uit iedere","Run now":"Nu uitvoeren","Running commandline entry":"Opdrachtregelinvoer in uitvoering","Running task:":"Taak in uitvoering:","Running …":"In uitvoering ...","Running … stop now":"In uitvoering … nu stoppen","S3 Compatible":"S3 Compatible","Same as the base install version: {{channelname}}":"Zelfde als de basis installatie versie: {{channelname}}","Sat":"Zaterdag","Satellite":"Satellite","Save":"Opslaan","Save and repair":"Opslaan en repareren","Save different versions with timestamp in file name":"Sla verschillende versies op met tijdstempel in de bestandsnaam","Save immediately":"Onmiddellijk opslaan","Scanning existing files …":"Scannen bestaande bestanden ...","Scanning for local blocks …":"Scannen op lokale blokken ...","Schedule":"Planning","Search":"Zoek","Search for files":"Zoek bestanden","Seconds":"Seconden","Select a log level and see messages as they happen:":"Selecteer een logniveau en bekijk meldingen zodra ze zich voordoen:","Select files":"Selecteer bestanden","Server":"Server","Server and port":"Server en poort","Server hostname or IP":"Server hostnaam of IP","Server is currently paused,":"Server is momenteel gepauzeerd,","Server is currently paused, resume now":"Server is momenteel gepauzeerd, nu hervatten","Server is currently paused, do you want to resume now?":"Server is momenteel gepauzeerd, wilt u nu hervatten?","Server password":"Server wachtwoord","Server paused":"Server gepauzeerd","Server state properties":"Server status eigenschappen","Settings":"Instellingen","Show":"Tonen","Show advanced editor":"Toon geavanceerde editor","Show hidden folders":"Toon verborgen mappen","Show log":"Log weergeven","Show log …":"Log weergeven ...","Show treeview":"Toon boomstructuur","Sia server password":"Sia server wachtwoord","Smart backup retention":"Slimme back-up retentie","Some OpenStack providers allow an API key instead of a password and tenant name":"Sommige OpenStack providers staan een API key toe in plaats van een wachtwoord en tenant naam","Some S3 providers might only be compatible with a certain client library":"Sommige S3 providers zouden alleen compatible kunnen zijn met een specifieke client-bibliotheek","Source Data":"Bron","Source Files":"Bronbestanden","Source data":"Brongegevens","Source folders":"Bronmappen","Source:":"Bron:","Specific builds for developers only. Not for use with important data.":"Specifieke builds alleen voor ontwikkelaars. Niet voor gebruik met belangrijke gegevens.","Stable":"Stabiel","Standard protocols":"Standaard protocollen","Start":"Start","Starting backup …":"Back-up wordt gestart ...","Starting restore …":"Herstellen wordt gestart ...","Starting the restore process …":"Starten van het herstelproces ...","Stop after the current file":"Stop na het huidige bestand","Stop running backup":"Stop de back-up in uitvoering","Stop running task":"Stop de taak in uitvoering","Stopping after the current file:":"Stoppen na het huidige bestand:","Stopping task:":"Taak wordt gestopt:","Storage Type":"Opslagtype","Storage class":"Opslagklasse","Storage class for creating a bucket":"Opslagklasse voor het aanmaken van een bucket","Stored":"Opgeslagen","Strong":"Sterk","Success":"Succes","Sun":"Zondag","Symbolic link":"Symbolische link","System Files":"Systeembestanden","System default ({{levelname}})":"Systeem standaard ({{levelname}})","System files":"Systeembestanden","System info":"Systeeminformatie","System properties":"Systeemeigenschappen","TByte":"TByte","TByte/s":"TByte/s","Target URL >":"Doel-URL >","Target path. Example: /backup":"Doelpad. Voorbeeld: /backup","Task is running":"Taak is in uitvoering","Temporary Files":"Tijdelijke bestanden","Temporary files":"Tijdelijke bestanden","Tenant name":"Tenant-naam","Tencent Cloud Account APPID":"Tencent Cloud Account APPID","Tencent Cloud COS documents and resources":"Tencent Cloud COS documenten en bronnen","Test Phase":"Testen Subtaak","Test connection":"Test verbinding","Testing connection …":"Testen van de verbinding …","Testing permissions …":"Testen van de permissies ...","Testing …":"Testen ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Het '{{fieldname}}' veld bevat een ongeldig teken: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"De back-up ontbreekt, is deze verwijderd?","The backup was temporary and does not exist anymore, so the log data is lost":"De back-up was tijdelijk en bestaat niet meer, waardoor de log-gegevens verloren zijn gegaan","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"De back-ups worden opgesplitst in meerdere bestanden die volumes worden genoemd. Hier kunt u de maximale grootte van de individuele volumebestanden instellen. Zie deze pagina voor meer informatie.","The bucket name should be all lower-case, convert automatically?":"De bucket-naam hoort in kleine letters te zijn, automatisch converteren?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"De configuratie moet op een veilige plaats bewaard worden. Weet u zeker dat u een onversleuteld bestand wilt opslaan dat uw wachtwoorden bevat?","The connection to the server is lost, attempting again in {{time}} …":"De verbinding met de server is verbroken, opnieuw proberen over {{time}} …","The dark theme (by Michal)":"Het donkere thema (door Michal)","The default blue on white theme (by Alex)":"Het standaard blauw op wit thema (door Alex)","The encryption passphrases do not match":"De coderings-wachtwoordzinnen komen niet overeen","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"De bestandsgrootte is {{size}}, groter dan de maximaal opgegeven grootte. Als de bestandsgrootte afneemt, zal het worden opgenomen in toekomstige back-ups.","The folder {{folder}} does not exist.\nCreate it now?":"De map {{folder}} bestaat niet.\nNu aanmaken?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"De host sleutel is veranderd, controleer met uw server beheerder of dit correct is, in het andere geval zou u het slachtoffer kunnen zijn van een MAN-IN-THE-MIDDLE aanval.\n\nWilt u de HUIDIGE host sleutel \"{prev}\" VERVANGEN door de GERAPPORTEERDE host sleutel: {{key}}?","The passwords do not match":"De wachtwoorden komen niet overeen","The path does not appear to exist, do you want to add it anyway?":"Het pad lijkt niet te bestaan, wilt u het desondanks toevoegen?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Het pad eindigt niet met een '{{dirsep}}' teken, wat betekent dat u een bestand opneemt, niet een map.\n\nWilt u het aangegeven bestand opnemen?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Het pad moet een absoluut pad zijn, bijvoorbeeld het moet beginnen met een forward slash '/'","The region parameter is only applied when creating a new bucket":"De regio parameter wordt alleen toegepast bij het aanmaken van een bucket","The region parameter is only used when creating a bucket":"De regio parameter wordt alleen gebruikt bij het aanmaken van een bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Het servercertificaat kon niet gevalideerd worden.\nWilt u het certificaat goedkeuren met deze hash: {{hash}}?","The storage class affects the availability and price for a stored file":"De opslagklasse beïnvloedt de beschikbaarheid en prijs van een opgeslagen bestand","The target folder contains encrypted files, please supply the passphrase":"De doelmap bevat versleutelde bestanden, geef alstublieft de wachtwoordzin","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"De gebruiker heeft teveel permmissies. Wilt u een nieuwe beperkte gebruiker aanmaken, met enkel permissies tot het aangegeven pad?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"De back-up werd aangemaakt op een ander besturingssysteem. Bestanden terugzetten zonder een doelmap op te geven kan tot gevolg hebben dat bestanden worden teruggezet naar onverwachte plaatsen. Bent u er zeker van dat u wilt doorgaan zonder een doelmap te kiezen?","This month":"Afgelopen maand","This week":"Afgelopen week","Throttle settings":"Bandbreedte-instellingen","Thu":"Donderdag","Time":"Tijd","To File":"Naar Bestand","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Om te bevestigen dat u alle remote bestanden wilt verwijderen voor \"{{name}}\", geef svp het woord in dat u hieronder ziet","To export without a passphrase, uncheck the \"Encrypt file\" box":"Om te exporteren zonder een wachtwoordzin, deselecteer het \"Versleutel bestand\" vakje","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"Om problemen met de bucketnaamgeving te voorkomen, wordt aanbevolen om het account-ID vooraf te laten gaan door de bucketnaam. Automatisch vooraf laten gaan?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Om verschillende op DNS gebaseerde aanvallen te voorkomen, beperkt Duplicati de toegestane hostnamen tot de hier genoemde. Directe IP-toegang en localhost zijn altijd toegestaan. Meerdere hostnamen kunnen worden opgegeven met een puntkomma als scheidingsteken. Als één van de toegestane hostnamen een asterisk (*) is, zijn alle hostnamen toegestaan en is deze functie uitgeschakeld. Als het veld leeg is, is toegang alleen toegestaan via het IP adres en localhost.","Today":"Vandaag","Trust host certificate?":"Vertrouw host certificaat?","Trust server certificate?":"Vertrouw server certificaat?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Probeer de nieuwste functies waar we aan werken. Momenteel de meest stabiele beschikbare versie. Test Herstellen van bestanden alvorens te gebruiken in productie-omgevingen.","Tue":"Dinsdag","Type passphrase here.":"Type hier de wachtwoordzin.","Type to highlight files":"Typ om bestanden uit te lichten","Unknown backup size and versions":"Onbekende back-up grootte en versies","Until resumed":"Tot hervatting","Update {{state.updatedVersion}} is available. Download now":"Update {{state.updatedVersion}} is beschikbaar. Download nu","Update channel":"Updatekanaal","Update failed:":"Update mislukt:","Updating with existing database":"Updaten met bestaande database","Uploaded files":"Geüploade bestanden","Uploading verification file …":"Uploaden controlebestand ...","Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics.":"Gebruiksrapporten helpen ons de gebruikerservaring te verbeteren en de impact van nieuwe mogelijkheden te evalueren. We gebruiken ze om openbare gebruikstatistieken te genereren.","Usage statistics":"Gebruikstatistieken","Usage statistics, warnings, errors, and crashes":"Gebruikstatistieken, waarschuwingen, fouten en crashes","Use SSL":"Gebruik SSL","Use existing database?":"Gebruik bestaande database?","Use weak passphrase":"Gebruik zwakke wachtwoordzin","Useless":"Waardeloos","User data":"Gebruikersgegevens","User domain name":"Gebruikers domeinnaam","User has too many permissions":"Gebruiker heeft teveel permissies","User interface settings":"Gebruikersomgeving-instellingen","Username":"Gebruikersnaam","Vacuuming database …":"Database opschonen ...","Validating …":"Valideren ...","Verifications":"Controles","Verify encryption passphrase":"Verifieer coderings-wachtwoordzin","Verify files":"Bestanden controleren","Verifying answer":"Antwoord controleren","Verifying backend data …":"Controleren van backend gegevens ...","Verifying files …":"Controleren bestanden ...","Verifying remote data …":"Controleren remote gegevens ...","Verifying restored files …":"Controleren herstelde bestanden ...","Verifying …":"Controleren ...","Version ID":"Versie ID","Very strong":"Erg sterk","Very weak":"Erg zwak","Visit us on":"Bezoek ons op","WARNING: The remote database is found to be in use by the commandline library.":"WAARSCHUWING: De remote database blijkt in gebruik te zijn door de opdrachtregel bibliotheek.","WARNING: This will prevent you from restoring the data in the future.":"WAARSCHUWING: Dit zal het onmogelijk maken om in de toekomst bestanden te herstellen.","Waiting for task to begin":"Wachten op het starten van de taak","Waiting for task to start …":"Wachten tot een taak begint …","Waiting for upload to finish …":"Wachten op voltooien van upload ...","Warnings, errors and crashes":"Waarschuwingen, fouten en crashes","We recommend that you encrypt all backups stored outside your system":"We raden aan dat u alle back-ups die buiten uw systeem worden opgeslagen versleutelt","Weak":"Zwak","Weak passphrase":"Zwakke wachtwoordzin","Wed":"Woensdag","Weeks":"Weken","Where do you want to restore from?":"Waar vandaan wilt u herstellen?","Where do you want to restore the files to?":"Waarheen wilt u de bestanden herstellen?","Years":"Jaren","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, ik heb de wachtwoordzin op een veilige plaats opgeborgen","Yes, I understand the risk":"Ja, ik begrijp het risico","Yes, I'm brave!":"Ja, ik ben dapper!","Yes, please break my backup!":"Ja, help mijn back-up om zeep!","Yesterday":"Gisteren","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"U verandert het database pad weg van een bestaande database.\nWeet u zeker dat dit is wat u wilt?","You are currently running {{appname}} {{version}}":"U werkt momenteel met {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"U hebt de versleutelingsmodus veranderd. Dit kan dingen kapotmaken. U wordt daarom aangemoedigd een nieuwe back-up aan te maken","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"U hebt de wachtwoordzin aangepast, wat niet wordt ondersteund. U wordt daarom aangemoedigd een nieuwe back-up aan te maken.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"U hebt ervoor gekozen de back-up niet te versleutelen. Encryptie is aanbevolen voor alle gegevens die worden opgeslagen op een remote server.","You have chosen to restore to a new location, but not entered one":"U koos voor terugzetten naar een nieuwe locatie, maar hebt geen locatie opgegeven","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"U hebt een sterke wachtwoordzin gegenereerd. Verzeker u ervan dat u een veilige kopie heeft van de wachtwoordzin, omdat de gegevens niet hersteld kunnen worden als u de wachtwoordzin verliest.","You must choose at least one source folder":"U moet tenminste één bronmap kiezen","You must enter a domain name to use v3 API":"Een domeinnaam moet worden opgegeven om v3 API te gebruiken","You must enter a name for the backup":"U moet een naam ingeven voor de back-up","You must enter a passphrase or disable encryption":"U moet een wachtwoordzin ingeven of versleuteling uitschakelen","You must enter a password to use v3 API":"Een wachtwoord moet worden opgegeven om v3 API te gebruiken","You must enter a positive number of backups to keep":"U moet een positief getal opgeven voor de hoeveelheid te bewaren back-ups","You must enter a tenant (aka project) name to use v3 API":"Een tenant (ofwel project) naam moet worden opgegeven om v3 API te gebruiken ","You must enter a tenant name if you do not provide an API key":"U moet een tenant naam ingeven als u de API sleutel niet verstrekt","You must enter a valid duration for the time to keep backups":"U moet een geldige tijdsduur ingeven voor de tijd dat back-ups bewaard moeten worden","You must enter a valid retention policy string":"Er moet een geldige waarde voor retentiebeleid worden opgegeven","You must enter either a password or an API key":"U moet òf een wachtwoord, òf een API sleutel ingeven","You must enter either a password or an API key, not both":"U moet òf een wachtwoord, òf een API sleutel ingeven, niet beide","You must fill in the password":"U moet het wachtwoord invullen","You must fill in the server name or address":"U moet de servernaam of -adres invullen","You must fill in the username":"U moet de gebruikersnaam invullen","You must fill in {{field}}":"U moet {{field}} invullen","You must select or fill in the AuthURI":"U moet de AuthURI selecteren of invullen","You must select or fill in the server":"U moet de server selecteren of invullen","You must specify a path":"U moet een pad opgeven","You should fill in {{field}} {{reason}}":"U moet {{field}} {{reason}} invullen","Your files and folders have been restored successfully.":"Uw bestanden en mappen zijn succesvol hersteld","Your passphrase is easy to guess. Consider changing passphrase.":"Uw wachtwoordzin is eenvoudig te raden. Overweeg de wachtwoordzin te veranderen.","bucket/folder/subfolder":"bucket/map/submap","byte":"byte","byte/s":"byte/s","cos_app_id":"cos_app_id","cos_bucket":"cos_bucket","cos_region":"cos_region","cos_secret_id":"cos_secret_id","cos_secret_key":"cos_secret_key","custom":"aangepast","failed":"mislukt","oss_access_key_id":"oss_access_key_id","oss_access_key_secret":"oss_access_key_secret","oss_bucket_name":"oss_bucket_name","oss_endpoint":"oss_endpoint","oss_region":"oss_region","remote path, e.g. backup":"extern pad, bijv. backup","remote repository, e.g. remote":"externe opslagplaats, bijv. remote","resume now":"nu hervatten","storj_shared_access":"storj_shared_access","unless you are explicitly specifying --group-id":"tenzij u expliciet --group-id opgeeft","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} werd in eerste instantie ontwikkeld door {{dev1}} en {{dev2}}. {{appname}} kan gedownload worden van {{websitename}}. {{appname}} is gelicenseerd onder de {{licensename}}.","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}} gebruikt de volgende bibliotheken van derden:","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} bestanden ({{size}}) te gaan {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versie","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versies"],"{{number}} Hour":"{{number}} Uur","{{number}} Hours":"{{number}} Uur","{{number}} Minutes":"{{number}} Minuten","{{time}} (took {{duration}})":"{{time}} (duurde {{duration}})"}); + gettextCatalog.setStrings('pl', {"- pick an option -":"- wybierz opcję -","...loading...":"...ładowanie...","API key":"klucz API","AWS Access ID":"Identyfikator dostępu AWS","AWS Access Key":"Klucz dostepu AWS","AWS IAM Policy":"Polityka AWS IAM","About":"O programie","About {{appname}}":"O programie {{appname}}","Access Key":"Klucz dostępu","Access denied":"Dostęp zabroniony","Access grant":"Dostęp przyznany","Access to user interface":"Dostęp do interfejsu użytkownika","Account name":"Nazwa konta","Add a new backup":"Dodaj nową kopię","Add a path directly":"Dodaj ścieżkę bezpośrednio","Add advanced option":"Dodaj opcję zaawansowaną","Add backup":"Dodaj kopię","Add filter":"Dodaj filtr","Add path":"Dodaj ścieżkę","Added":"Dodano","Adjust bucket name?":"Poprawić nazwę zasobnika?","Advanced Options":"Opcje Zaawansowane","Advanced options":"Opcje zaawansowane","Advanced:":"Zaawansowane:","All Hyper-V Machines":"Wszystkie Maszyny Hyper-V","All Microsoft SQL Databases":"Wszystkie Bazy Danych Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Wszystkie raporty użycia są wysyłane anonimowo i nie zawierają żadnych danych osobistych. Raporty zawierają informacje o sprzęcie i systemie operacyjnym, rodzaju kopii zapasowej, czasie trwania, ogólnej ilości danych źródłowych i tym podobne. Raporty nie zawierają ścieżek, nazw plików, nazw użytkowników, haseł i tym podobnych danych wrażliwych.","Allow remote access (requires restart)":"Zezwalaj na dostęp zdalny (wymaga restartu)","Allowed days":"Dozwolone dni","An existing file was found at the new location":"Znaleziono istniejący plik w nowym położeniu","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Istniejący plik został znaleziony w nowej lokalizacji\nCzy na pewno chcesz skierować bazę danych do istniejącego pliku?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Znaleziono istniejącą, lokalną bazę danych dla magazynu.\nPonowne użycie tej bazy pozwoli pracować instancji wiersza poleceń oraz serwerowej z tym samym zdalnym magazynem.\n\nCzy chcesz użyć istniejącej bazy danych?","Anonymous usage reports":"Anonimowy raport użycia","Applications":"Aplikacje","As Command-line":"Jako Linia poleceń","AuthID":"AuthID","Authentication method":"Metoda uwierzytelnienia","Authentication method ({{auth_method}})":"Metoda uwierzytelnienia ({{auth_method}})","Authentication password":"Hasło uwierzytenienia","Authentication username":"Nazwa uwierzytelnienia","Autogenerated passphrase":"Automatycznie wygenerowane długie hasło","B2 Application ID":"ID aplikacji B2","B2 Application Key":"Klucz aplikacji B2","B2 Cloud Storage Account ID":"ID konta magazynu w chmurze B2","B2 Cloud Storage Application ID":"ID aplikacji magazynu w chmurze B2","B2 Cloud Storage Application Key":"Klucz aplikacji B2 magazynu w chmurze","Back":"Wstecz","Backup complete!":"Backup zakończony!","Backup destination":"Miejsce docelowe kopii","Backup location":"Lokalizacja kopii","Backup retention":"Retencja kopii zapasowej","Backup:":"Kopia:","Beta":"Beta","Broken access":"Przerwany dostęp","Browse":"Przeglądaj","Browser default":"Domyślna przeglądarka","Bucket create location":"Miejsce tworzenia zasobnika","Bucket name":"Nazwa zasobnika","Bucket storage class":"Klasa przechowywania zasobnika","Building list of files to restore …":"Tworzenie listy plików do przywrócenia ...","Building partial temporary database …":"Tworzenie tymczasowej częściowej bazy danych ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Po umożliwieniu zdalnego dostępu, serwer nasłuchuje żądań z każdego urządzenia w twojej sieci. Jeśli aktywujesz tę opcję, upewnij się, że używasz komputera w bezpiecznej, zabezpieczonej firewallem sieci.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Domyślnie, z ikony w zasobniku można otworzyć interfejs użytkownika dzięki tokenowi który odblokowuje interfejs. To zapewnia że masz dostęp do interfejsu użytkownika bezpośrednio z ikony w zasobniku, podczas gdy od innych będzie wymagane wprowadzenie hasła. Jeśli wolisz konieczność wprowadzenia hasła nawet przy otwieraniu interfejsu użytkownika z ikony w zasobniku, aktywuj tę funkcję.","Cache Files":"Pliki pamięci podręcznej","Canary":"Robocze","Cancel":"Anuluj","Cannot move to existing file":"Nie można przenieść do istniejącego plku","Changelog":"Lista zmian","Changelog for {{appname}} {{version}}":"Lista zmian dla {{appname}} {{version}}","Check failed:":"Sprawdzenie nieudane:","Check for updates now":"Sprawdź uaktualnienia ","Checking for updates …":"Sprawdzanie uaktualnień ...","Chose a storage type to get started":"Wybierz typ magazynu by rozpocząć","Click the AuthID link to create an AuthID":"Kliknij link AuthID by utworzyć AuthID","Click to set throttle options":"Kliknij, aby ustawić limity prędkości","Client library to use":"Biblioteka klienta do użycia","Commandline …":"Linia poleceń ...","Compact Phase":"Faza kompaktowania","Compact now":"Kompaktuj teraz","Compacting remote data …":"Kompaktowanie zdalnych danych ...","Complete log":"Log kompletny","Completing backup …":"Kończenie kopii ...","Completing previous backup …":"Kończenie poprzedniej kopii ...","Computer":"Komputer","Configuration file:":"Plik konfiguracyjny:","Configuration:":"Konfiguracja:","Configure a new backup":"Skonfiguruj nową kopię","Confirm delete":"Potwierdź usunięcie","Confirm encryption passphrase":"Potwierdź hasło szyfrowania","Confirm passphrase":"Potwierdź hasło","Confirmation required":"Potwierdzenie wymagane","Connect":"Połącz","Connect now":"Połącz teraz","Connecting to server …":"Łączenie z serwerem ...","Connection lost":"Utracono połączenie","Connection worked!":"Połączenie działa!","Container name":"Nazwa zasobnika","Container region":"Region zasobnika","Continue":"Kontynuuj","Continue without encryption":"Kontynuuj bez szyfrowania","Copied!":"Skopiowane!","Copy":"Kopiuj","Copy Destination URL to Clipboard":"Kopiuj Docelowy URL do Schowka","Copy failed. Please manually copy the URL":"Niepowodzenie kopiowania. Proszę skopiować URL ręcznie","Core options":"Opcje podstawowe","Counting ({{files}} files found, {{size}})":"Liczenie ({{files}} znaleziono plików, {{size}})","Crashes only":"Tylko awarie","Create bug report …":"Utwórz raport o błędach ...","Create folder?":"Utworzyć folder","Created new limited user":"Utwórz nowego użytkownika z ograniczeniami","Creating bug report …":"Tworzenie raportu o błędach ...","Creating new user with limited access …":"Tworzenie nowego użytkownika z ograniczonym dostępem ...","Creating target folders …":"Tworzenie folderów docelowych ...","Creating temporary backup …":"Tworzenie kopii tymczasowej ...","Current action:":"Bieżące działanie:","Current file:":"Aktualny plik:","Current version is {{versionname}} ({{versionnumber}})":"Bieżąca wersja to {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Niestandardowy węzeł końcowy S3","Custom Satellite":"Niestandardowy satelita","Custom Satellite ({{satellite}})":"Niestandardowy satelita ({{satellite}})","Custom authentication url":"Niestandardowy URL uwierzytelniania","Custom backup retention":"Niestandardowa retencja kopii","Custom location ({{server}})":"Niestandardowa lokalizacja ({{serwer}})","Custom region for creating buckets":"Niestandardowy region do tworzenia zasobników","Custom region value ({{region}})":"Niestandardowa wartość regionu ({{region}})","Custom server url ({{server}})":"Niestandardowy adres url serwera ({{serwer}})","Custom storage class ({{class}})":"Niestandardowa klasa magazynu ({{Klasa}})","Database …":"Baza danych ...","Days":"Dni","Default":"Domyślny","Default ({{channelname}})":"Domyślny ({{channelname}})","Default excludes":"Domyślne wykluczenia","Default options":"Opcje domyślne","Delete":"Usuń","Delete Phase (Old Backup Versions)":"Faza usuwania (stare wersje kopii)","Delete backup":"Usuń kopię","Delete backups that are older than":"Usuń kopie zapasowe starsze niż","Delete local database":"Usuń lokalną bazę danych","Delete remote files":"Usuń zdalne pliki","Delete the local database":"Usuń lokalną bazę danych","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Usunąć {{filecount}} plików ({{filesize}}) ze zdalnego magazynu?","Delete …":"Usuń ...","Deleted":"Usunięto","Deleted Versions":"Usunięte wersje","Deleted files":"Usunięte pliki","Deleting remote files …":"Usuwanie zdalnych plików ...","Deleting unwanted files …":"Usuwanie niepotrzebnych plików ...","Description (optional)":"Opis (opcjonalnie)","Description:":"Opis:","Desktop":"Pulpit","Destination":"Lokalizacja docelowa","Destination path":"Ścieżka docelowa","Disabled":"Wyłączone","Dismiss":"Odrzuć","Dismiss all":"Odrzucić wszystkie","Display and color theme":"Schemat ekranu i kolorystyki","Do you really want to delete the backup: \"{{name}}\" ?":"Naprawdę chcesz usunąć kopię: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Czy naprawdę chcesz usunąć lokalna bazę danych: {{name}}","Done":"Wykonane","Download":"Pobranie","Downloaded files":"Pobrane pliki","Downloading files …":"Pobieranie plików ...","Downloading update…":"Pobieranie uaktualnienia ...","Duplicate option {{opt}}":"Powielenie opcji {{opt}}","Duplicati Website":"Strona Duplicati","Duplicati forum":"Forum Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplikati będzie działać po uruchomieniu, ale pozostanie w stanie wstrzymania na wskazany czas. Duplikati będzie używać minimalne zasoby systemowe i nie będą wykonywane żadne kopie zapasowe.","Duration":"Czas trwania","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Każda skonfigurowana kopia posiada powiązaną z nią lokalną bazę danych, w której przechowuje na komputerze lokalnym informacje o zdalnej kopii.\rKiedy konfiguracja kopii jest usuwana, można również usunąć lokalną bazę danych bez wpływu na możliwość odtworzenia plików zdalnych.\rJeśli używasz lokalnej bazy danych do kopii zapasowych z wiersza poleceń, powinieneś zachować bazę danych.","Edit as list":"Edytuj jako listę","Edit as text":"Edytuj jako tekst","Edit …":"Edycja ...","Encrypt file":"Zaszyfruj plik","Encryption":"Szyfrowanie","Encryption changed":"Szyfrowanie zmienione","Encryption passphrase":"Hasło szyfrowania","End":"Zakończono","Enter URL":"Podaj URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Wprowadź strategię przechowywania ręcznie. Symbole D/W/Y oznaczają dni/tygodnie/lata oraz U - nieograniczony. Schemat składni: 7D:1D,4W:1W,36M:1M. Ten przykład zachowuje kopię dla każdego z 7 kolejnych dni, kopię dla kolejnych 4 tygodni i jedną dla kolejnych 36 miesięcy. Może to być zapisane także jako: 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Podaj długie hasło, jeśli jest","Enter configuration details":"Wprowadź szczegóły konfiguracji","Enter encryption passphrase":"Podaj długie hasło szyfrowania","Enter expression here":"Tutaj wprowadź wyrażenie","Enter the destination path":"Wprowadź ścieżkę docelową","Error":"Błąd","Error!":"Błąd!","Errors and crashes":"Błędy i awarie","Examined":"Sprawdzono","Exclude":"Wyklucz","Exclude directories whose names contain":"Wyklucz katalogi z nazwą zawierającą","Exclude expression":"Wyklucz wyrażenie","Exclude file":"Wyklucz plik","Exclude file extension":"Wyklucz rozszerzenie pliku","Exclude files whose names contain":"Wyklucz pliki z nazwą zawierającą","Exclude filter group":"Grupa filtrów wykluczajacych","Exclude folder":"Wyklucz folder","Exclude regular expression":"Wyklucz wyrażenie regularne","Existing file found":"Znaleziono istniejący plik","Experimental":"Eksperymentalne","Export":"Eksport","Export backup configuration":"Eksportuj konfigurację kopii","Export configuration":"Eksportuj konfigurację","Export passwords":"Eksportuj hasła","Export …":"Eksport ...","Exporting …":"Eksportowanie ...","External link":"Link zewnętrzny","FTP (Alternative)":"FTP (Alternatywny)","Failed to build temporary database: {{message}}":"Nie udało się utworzyć tymczasowej bazy danych: {{message}}","Failed to connect:":"Nie udało się połączyć:","Failed to connect: {{message}}":"Nie udało się połączyć: {{message}}","Failed to delete:":"Nie udało się usunąć:","Failed to fetch path information: {{message}}":"Nie udało się pobrać informacji o ścieżce: {{message}}","Failed to find backup:":"Nie udało się znaleźć kopii zapasowej:","Failed to read backup defaults:":"Nie udało się odczytać domyślnych danych kopii:","Failed to restore files: {{message}}":"Nie udało się odtworzyć plików: {{message}}","Failed to save:":"Nie udało się zapisać:","Fetching path information …":"Pobieranie informacji o ścieżce ...","File":"Plik","Files larger than:":"Pliki większe niż:","Filters":"Filtry","Finished!":"Zakończono!","First run setup":"Konfiguracja początkowa","Folder":"Katalog","Folder path":"Ścieżka katalogu","Fri":"Pt","GByte":"GBajt","GByte/s":"GBajt/s","GCS Project ID":"GCS Project ID","General":"Ogólne","General backup settings":"Ogólne ustawienia kopii","General options":"Opcje ogólne","Generate":"Generuj","Generate IAM access policy":"Wygeneruj politykę dostępu IAM","Getting file versions …":"Pobieranie wersji plików ...","Group email":"E-mail grupowy","Hidden files":"Ukryte pliki","Hide":"Ukryj","Hide hidden folders":"Ukryj ukryte foldery","Home":"Strona główna","Hostnames":"Nazwy hostów","Hours":"Godziny","How do you want to handle existing files?":"Jak chcesz potraktować istniejące pliki?","Hyper-V Machine":"Maszyna Hyper-V","Hyper-V Machine:":"Maszyna Hyper-V:","Hyper-V Machines":"Maszyny Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Jeśli brak daty, zadanie zostanie uruchomione najwcześniej gdy to możliwe.","If at least one newer backup is found, all backups older than this date are deleted.":"Jeśli znajdzie się przynajmniej jedna nowa kopia, wszystkie kopie starsze od niej zostaną skasowane.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Jeśli ścieżka nie zostanie wprowadzona, to wszystkie pliki będą przechowywane w katalogu logowania. Czy na pewno tak właśnie ma być?","If you do not enter an API Key, the tenant name is required":"Jeśli nie podasz Klucza API, nawa dzierżawcy jest wymagana","Import":"Import","Import Destination URL":"Import Docelowego URL","Import backup configuration":"Importuj konfigurację kopii","Import from a file":"Zaimportuj z pliku","Import metadata":"Importuj metadane","Importing …":"Importowanie ...","Include a file?":"Dołaczyć plik?","Include expression":"Dołącz wyrażenie","Include regular expression":"Dołącz wyrażenie regularne","Incorrect answer, try again":"Nieprawidłowa odpowiedź, spróbuj ponownie","Individual builds for developers only. Not for use with important data.":"Indywidualne kompilacje tylko dla programistów. Nie do użytku z ważnymi danymi.","Information":"Informacja","Invalid characters in path":"Nieprawidłowe znaki w ścieżce","Invalid retention time":"Nieprawidłowy czas przechowywania","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Do niektórych serwerów FTP można łączyć się bez hasła.\nCzy na pewno Twój serwer FTP obsługuje logowanie bez hasła?","KByte":"KBajty","KByte/s":"KBajty/s","Keep a specific number of backups":"Zachowaj określoną ilość kopii","Keep all backups":"Zachowaj wszystkie kopie","Keystone API version":"Wersja Keystone API","Language in user interface":"Język w interfejsie użytkownika","Last month":"Ostatni miesiąc","Last successful backup:":"Ostatnia prawidłowa kopia zapasowa:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Ostatnie udane odtworzenie: {{time}} (zajęło {{duration || '0 sekund'}})","Latest":"Ostatni","Libraries":"Biblioteki","Listing backup dates …":"Listowanie dat kopii ...","Listing remote files for purge …":"Listowanie zdalnych plików do wyczyszczenia ...","Listing remote files …":"Listowanie zdalnych plików ...","Live":"Aktywne","Load a configuration from an exported job or a storage provider":"Wczytaj konfigurację z wyeksportowanego zadania lub magazynu","Load destination from an exported job or a storage provider":"Wczytaj miejsce docelowe z wyeksportowanego zadania lub magazynu","Load older data":"Załaduj starsze dane","Loading …":"Ładowanie ...","Local database path:":"Ścieżka lokalnej bazy danych:","Local repository":"Magazyn lokalny","Local storage":"Magazyn lokalny","Location":"Położenie","Location where buckets are created":"Położenie, gdzie będą utworzone zasobniki","Log data for {{Backup.Backup.Name}}":"Logi dla {{Backup.Backup.Name}}","Log data from the server":"Logi z serwera","Log out":"Wyloguj","MByte":"MBajt","MByte/s":"MBajty/s","Maintenance":"Konserwacja","Manually type path":"Podaj ścieżkę ręcznie ","Max download speed":"Maksymalna szybkość pobierania","Max upload speed":"Maksymalna szybkość wysyłania","Menu":"Menu","Microsoft SQL Database:":"Baza danych Microsoft SQL:","Microsoft SQL Databases":"Bazy danych Microsoft SQL:","Minimum redundancy":"Minimalna redundancja","Minimum redundancy is 1.0":"Minimalna redundancja wynosi 1,0","Minutes":"Minuty","Missing name":"Brak nazwy","Missing passphrase":"Brak długiego hasła","Missing sources":"Brak źródła","Modified":"Zmodyfikowano","Mon":"Pn","Months":"Miesiące","Move existing database":"Przenieś istniejącą bazę danych","Move failed:":"Nie udało się przenieść:","My Documents":"Moje Dokumenty","My Music":"Moja Muzyka","My Photos":"Moje Zdjęcia","My Pictures":"Moje Obrazy","Name":"Nazwa","Never":"Nigdy","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nowa nazwa użytkownika to {{user}}.\nUaktualniono uwierzytelnienia dla użytkownika o ograniczonym dostępie","Next":"Następny","Next scheduled run:":"Następne zaplanowane uruchomienie:","Next scheduled task:":"Następne zaplanowane zadanie:","Next task:":"Następne zadanie","Next time":"Następny raz","No":"Nie","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Certyfikat nie został wcześniej określony, należy sprawdzić u administratora serwera czy klucz jest poprawny: {{key}} \n\nCzy akceptujesz podany klucz?","No editor found for the "{{backend}}" storage type":"Nie znaleziono edytora dla magazynu typu "{{backend}}"","No encryption":"Bez szyfrowania","No items selected":"Nie wybrano pozycji","No items to restore, please select one or more items":"Brak pozycji do odtworzenia, proszę wybrać jedną lub więcej pozycji.","No passphrase entered":"Nie wprowadzono długiego hasła","No scheduled tasks":"Brak zaplanowanych zadań","Non-matching passphrase":"Niepasujące długie hasła","None / disabled":"Żaden / wyłączone","Not using encryption":"Bez użycia szyfrowania","Nothing will be deleted. The backup size will grow with each change.":"Nic nie będzie kasowane. Kopia będzie zwiększała rozmiar z każdą zmianą.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Kiedy wystąpi więcej kopii niż określona ilość, najstarsze kopie zostaną skasowane.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Otwarto","Operating System":"System operacyjny","Operation":"Operacja","Operations:":"Operacje:","Optional authentication password":"Opcjonalne hasło uwierzytelnienia","Optional authentication username":"Opcjonalny użytkownik uwierzytelnienia","Options":"Opcje","Original location":"Położenie oryginalne","Others":"Inne","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Z biegiem czasu kopie będą usuwane automatycznie. Pozostanie jedna kopia dla każdego z ostatnich 7 dni, dla każdego z 4 ostatnich tygodni, dla każdego z 12 ostatnich miesięcy. Zawsze będzie zachowana przynajmniej jedna kopia.","Overwrite":"Nadpisz","Passphrase":"Długie hasło","Passphrase (if encrypted)":"Długie hasło (jeśli zaszyfrowane)","Passphrase changed":"Zmieniono hasło","Passphrases are not matching":"Hasła różnią się od siebie","Passphrases do not match":"Hasła różnią się od siebie","Password":"Hasło","Patching files with local blocks …":"Poprawianie plików za pomocą lokalnych bloków ...","Path":"Ścieżka","Path not found":"Ścieżka nie znaleziona","Path on server":"Ścieżka na serwerze","Path or subfolder in the bucket":"Ścieżka lub podkatalog w zasobniku","Pause":"Wstrzymaj","Pause after startup or hibernation":"Wstrzymaj po uruchomieniu lub hibernacji","Pause options":"Opcje wstrzymania","Permissions":"Uprawnienia","Pick location":"Wybierz położenie","Point to your backup files and restore from there":"Wskaż pliki kopii zapasowej i odtwórz z nich","Port":"Port","Prevent tray icon automatic log-in":"Zapobiegaj automatycznemu logowaniu z ikony w trayu","Previous":"Poprzedni","Progress:":"Postęp:","ProjectID is optional if the bucket exist":"ProjectID jest opcjonalne jeśli zasobnik istnieje","Proprietary":"Własny","Purge Phase":"Faza czyszczenia","Purging files complete!":"Czyszczenie plików zakończone!","Purging files …":"Czyszczenie plików ...","Rebuilding local database …":"Odbudowa lokalnej bazy danych ...","Recreate (delete and repair)":"Odtworzenie (usunięcie i naprawienie)","Recreate Database Phase":"Faza odtwarzania bazy danych","Recreating database …":"Odtwarzanie bazy danych ...","Registering temporary backup …":"Rejestrowanie kopii tymczasowej ...","Relative paths not allowed":"Ścieżki względne nie są dopuszczalne","Reload":"Przeładuj","Remote":"Zdalny","Remote Path":"Ścieżka zdalna","Remote Repository":"Magazyn zdalny","Remote path":"Ścieżka zdalna","Remote repository":"Magazyn zdalny","Remote volume size":"Rozmiar wolumenu zdalnego","Remove":"Usuń","Remove option":"Usuń opcję","Removed files":"Usunięte pliki","Repair":"Napraw","Repair Phase":"Faza naprawiania","Repairing database …":"Naprawianie bazy danych ...","Repeat Passphrase":"Powtórz długie hasło","Reporting:":"Raportowanie:","Reset":"Resetuj","Restore":"Odtwórz","Restore complete!":"Odtwarzanie zakończone!","Restore files":"Odtwórz pliki","Restore files …":"Odtwórz pliki ...","Restore from":"Odtwórz z","Restore from backup configuration":"Odtwórz z konfiguracji kopii","Restore options":"Opcje odtwarzania","Restore read/write permissions":"Odtwórz uprawnienia odczytu/zapisu","Restored Files":"Odtworzone pliki","Restored Folders":"Odtworzone foldery","Restored Symlinks":"Odtworzone linki symboliczne","Restoring files …":"Odtworzone pliki ...","Resume":"Wznów","Rewritten File Lists":"Przepisana lista plików","Run again every":"Uruchom ponownie co","Run now":"Uruchom teraz","Running commandline entry":"Uruchamianie komend z linii poleceń","Running task:":"Działające zadania:","Running …":"Działanie ...","S3 Compatible":"Kompatybilny z S3","Same as the base install version: {{channelname}}":"Zgodny z bazową wersją instalacji: {{channelname}}","Sat":"So","Satellite":"Satelita","Save":"Zapisz","Save and repair":"Zapisz i napraw","Save different versions with timestamp in file name":"Zapisz różne wersje z sygnaturą czasową w nazwie","Save immediately":"Zapisz niezwłocznie","Scanning existing files …":"Przeglądanie istniejących plików ...","Scanning for local blocks …":"Szukanie lokalnych bloków ...","Schedule":"Harmonogram","Search":"Szukaj","Search for files":"Szukaj plików","Seconds":"Sekundy","Select a log level and see messages as they happen:":"Wybierz zakres dziennika i zobacz co się wydarzyło:","Select files":"Wybierz pliki","Server":"Serwer","Server and port":"Serwer i port","Server hostname or IP":"Nazwa serwera lub IP","Server is currently paused,":"Serwer jest obecnie wstrzymany,","Server is currently paused, do you want to resume now?":"Serwer jest obecnie wstrzymany, czy chcesz teraz wznowić jego pracę?","Server password":"Hasło serwera","Server paused":"Serwer wstrzymany","Server state properties":"Właściwości stanu serwera","Settings":"Ustawienia","Show":"Pokaż","Show advanced editor":"Pokaż edytor zaawansowany","Show hidden folders":"Pokaż ukryte foldery","Show log":"Pokaż dziennik","Show log …":"Pokaż dziennik ...","Show treeview":"Pokaż drzewo widoku","Sia server password":"Hasło serwera Sia","Smart backup retention":"Inteligentna retencja kopii","Some OpenStack providers allow an API key instead of a password and tenant name":"Niektórzy dostawcy OpenStack dopuszczają klucz API zamiast hasła i nazwy najemcy","Some S3 providers might only be compatible with a certain client library":"Niektórzy dostawcy S3, mogą być zgodni tylko z określoną biblioteką klienta","Source Data":"Dane źródłowe","Source Files":"Pliki źródłowe","Source data":"Dane źródłowe","Source folders":"Foldery źródłowe","Source:":"Źródło:","Specific builds for developers only. Not for use with important data.":"Szczególne kompilacje tylko dla programistów. Nie do użytku z ważnymi danymi.","Standard protocols":"Protokoły standardowe","Start":"Rozpoczęto","Starting backup …":"Rozpoczynanie kopii ...","Starting restore …":"Uruchamianie odzyskiwania ...","Starting the restore process …":"Uruchamianie procesu odzyskiwania ...","Stop after the current file":"Zatrzymaj po bieżącym pliku","Stop running backup":"Zatrzymaj wykonywaną kopię","Stop running task":"Zatrzymaj wykonywane zadanie","Stopping after the current file:":"Zatrzymywanie po bieżącym pliku:","Stopping task:":"Zatrzymywanie zadania:","Storage Type":"Typ Magazynu","Storage class":"Klasa magazynu","Storage class for creating a bucket":"Klasa magazynu dla utworzenia zasobnika","Stored":"Zachowane","Strong":"Silne","Success":"Powodzenie","Sun":"Nie","Symbolic link":"Link symboliczny","System Files":"Pliki systemowe","System default ({{levelname}})":"System domyślny ({{levelname}})","System files":"Pliki systemowe","System info":"Informacja systemowa","System properties":"Właściwości systemowe","TByte":"TBajty","TByte/s":"TBajty/s","Task is running":"Zadanie jest wykonywane","Temporary Files":"Pliki tymczasowe","Temporary files":"Pliki tymczasowe","Test Phase":"Faza testu","Test connection":"Sprawdź połączenie","Testing permissions …":"Sprawdzanie uprawnień ...","Testing …":"Testowanie ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Pole '{{fieldname}}' zawiera niedozwolony znak: {{character}} (value: {{value}}, indeks: {{pos}})","The backup is missing, has it been deleted?":"Kopia nie istnieje, czy została usunięta?","The backup was temporary and does not exist anymore, so the log data is lost":"Kopia była tymczasowa i nie istnieje, stąd dane dziennika są utracone","The bucket name should be all lower-case, convert automatically?":"Nazwa zasobnika powinna być pisana wersalikami, zmienić automatycznie ?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfiguracja powinna być przetrzymywana bezpiecznie. Jesteś pewien, że chcesz zapisać niezaszyfrowany plik zawierający twoje hasła?","The dark theme (by Michal)":"Ciemny schemat (wyk. Michal)","The default blue on white theme (by Alex)":"Domyślny schemat niebieski na białym (wyk. Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Folder {{folder}} nie istnieje.\nUtworzyć go teraz?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Klucz komputera został zmieniony, proszę sprawdzić z administratorem serwera czy jest to poprawne, w przeciwnym razie możesz zostać ofiarą ataku typu MAN-IN--MIDDLE.\n\nCzy chcesz ZASTĄPIĆ twój BIEŻĄCY klucz komputera \"{{prev}}\" na PODANY klucz: {{klucz}}?","The passwords do not match":"Hasła różnią się od siebie","The path does not appear to exist, do you want to add it anyway?":"Wygląda, że ścieżka nie istnieje, czy mimo to chcesz ją dodać?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Ścieżka nie kończy się znakiem \"{{dirsep}}\", co oznacza, że dołączasz plik, a nie folder.\n\nCzy chcesz dołączyć określony plik?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Ścieżka musi być ścieżką bezwzględną, tzn. musi rozpoczynać się prawym ukośnikiem '/'","The region parameter is only applied when creating a new bucket":"Parametr regionu jest stosowany tylko podczas tworzenia nowego zasobnika","The region parameter is only used when creating a bucket":"Parametr regionu jest używany tylko podczas tworzenia zasobnika","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certyfikat serwera nie może być zweryfikowany.\nCzy aprobujesz certyfikat SSL z sygnaturą: {{hash}}?","The storage class affects the availability and price for a stored file":"Klasa magazynu danych ma wpływ na dostępność i cenę za przechowywany plik","The target folder contains encrypted files, please supply the passphrase":"Docelowy folder zawiera zaszyfrowane pliki, proszę podać długie hasło","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Użytkownik ma za duże uprawnienia. Czy chcesz stworzyć nowego użytkownika z uprawnieniami ograniczonymi do wybranej ścieżki?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ta kopia zapasowa została utworzona na innym systemie operacyjnym. Odzyskiwanie plików bez określania folderu docelowego może spowodować, że pliki zostaną przywrócone w nieoczekiwanych miejscach. Czy na pewno chcesz kontynuować bez wyboru folderu docelowego?","This month":"Bieżący miesiąc","This week":"Bieżący tydzień","Throttle settings":"Limity prędkości","Thu":"Czw","Time":"Czas","To File":"Do Pliku","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Aby potwierdzić, że chcesz skasować wszystkie zdalne pliki dla \"{{name}}\", proszę wprowadzić słowo zamieszczone poniżej","To export without a passphrase, uncheck the \"Encrypt file\" box":"Aby wyeksportować bez hasła, odznacz pole \"Szyfruj plik\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"By zapobiec różnym atakom bazujących na DNS, Duplicati limituje dozwolone nazwy hostów do tu wymienionych. Bezpośredni dostęp z IP i localhost zawsze są dozwolone. Wiele nazw hostów może być wpisane i rozdzielone średnikiem. Jeśli któraś z podanych nazw hosta jest gwiazdką (*), wszystkie nazwy hostów są dozwolone i ta funkcja jest wyłączona. Jeśli pole jest puste, tylko dostęp z IP i localhost jest dozwolony.","Today":"Dzisiaj","Trust host certificate?":"Certyfikat zaufanego hosta?","Trust server certificate?":"Certyfikat zaufanego serwera?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Wypróbuj nowe funkcje nad którymi pracujemy. Obecnie najbardziej stabilna dostępna wersja. Przetestuj przywracanie danych przed ich użyciem w środowiskach produkcyjnych.","Tue":"Wt","Type passphrase here.":"Wpisz tutaj hasło.","Type to highlight files":"Napisz by podświetlić pliki","Unknown backup size and versions":"Nieznany rozmiar kopii i wersje","Until resumed":"Do wznowienia","Update channel":"Kanał uaktualnień","Update failed:":"Nie udało się uaktualnić","Updating with existing database":"Uaktualnij z istniejącą bazą danych","Uploaded files":"Przesłane pliki","Uploading verification file …":"Przesyłanie pliku weryfikującego ...","Usage statistics":"Statystyki użycia","Usage statistics, warnings, errors, and crashes":"Statystyki użycia , ostrzeżenia, błędy i awarie","Use SSL":"Użyj SSL","Use existing database?":"Użyj istniejącej bazy danych","Use weak passphrase":"Użyj słabego długiego hasła","Useless":"Bezużyteczne","User data":"Dane użytkownika","User domain name":"Nazwa domeny użytkownika","User has too many permissions":"Użytkownik ma za duże uprawnienia","User interface settings":"Ustawienia interfejsu użytkownika","Username":"Nazwa użytkownika","Vacuuming database …":"Oczyszczanie bazy danych ...","Validating …":"Walidacja ...","Verifications":"Weryfikacje","Verify files":"Sprawdź pliki","Verifying answer":"Weryfikacja odpowiedzi","Verifying backend data …":"Weryfikowanie danych silnika ...","Verifying files …":"Weryfikacja plików ...","Verifying remote data …":"Weryfikacja zdalnych danych ...","Verifying restored files …":"Weryfikowanie odzyskanych plików ...","Verifying …":"Weryfikowanie ...","Version ID":"ID wersji","Very strong":"Bardzo silne","Very weak":"Bardzo słabe","Visit us on":"Odwiedź nas na","WARNING: This will prevent you from restoring the data in the future.":"UWAGA: To uniemożliwi odtworzenie danych w przyszłości.","Waiting for task to begin":"Oczekiwanie na rozpoczęcie zadania","Waiting for upload to finish …":"Oczekiwanie na zakończenie przesyłania ...","Warnings, errors and crashes":"Ostrzeżenia, błędy i awarie","We recommend that you encrypt all backups stored outside your system":"Zalecamy szyfrowanie wszystkich kopii przechowywanych poza twoim systemem","Weak":"Słabe","Weak passphrase":"Słabe długie hasło","Wed":"Śr","Weeks":"Tygodnie","Where do you want to restore from?":"Gdzie chcesz odtworzyć?","Where do you want to restore the files to?":"Gdzie chcesz odtworzyć pliki?","Years":"Lata","Yes":"Tak","Yes, I have stored the passphrase safely":"Tak, długie hasło zostało bezpiecznie zachowane.","Yes, I understand the risk":"Tak, rozumiem ryzyko","Yes, I'm brave!":"Tak. Jestem dzielny!","Yes, please break my backup!":"Tak, proszę zepsuj moją kopię!","Yesterday":"Wczoraj","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Zmieniłeś ścieżkę na nie prowadzącą do istniejącej bazy danych.\nCzy jesteś pewny, że takie było twoje rzeczywiste zamierzenie?","You are currently running {{appname}} {{version}}":"Aktualnie używasz {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Zmieniłeś tryb szyfrowania. Może to spowodować uszkodzenie zawartości. Zamiast tego zachęcamy do utworzenia nowej kopii zapasowej.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Zmieniono hasło - zmiana hasła nie jest obsługiwana. Zachęcamy Cię zamiast tego do utworzenia nowej kopii zapasowej.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Wybrałeś opcję nieszyfrowania kopii zapasowej. Szyfrowanie jest zalecane dla wszystkich danych przechowywanych na serwerze zdalnym.","You have chosen to restore to a new location, but not entered one":"Możesz wybrać odtworzenie do nowej lokalizacji, ale nie tej wprowadzonej","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Wygenerowałeś silne hasło. Upewnij się, że wykonałeś bezpieczną kopię hasła, ponieważ danych nie będzie można odzyskać, jeśli utracisz hasło.","You must choose at least one source folder":"Musisz wybrać co najmniej jeden folder źródłowy","You must enter a domain name to use v3 API":"Musisz podać domenę aby użyć v3 API","You must enter a name for the backup":"Musisz podać nazwę kopii zapasowej","You must enter a passphrase or disable encryption":"Musisz podać długie hasło lub wyłączyć szyfrowanie","You must enter a password to use v3 API":"Musisz podać hasło aby użyć v3 API","You must enter a positive number of backups to keep":"Musisz podać dodatnią liczbę kopii do zachowania","You must enter a tenant (aka project) name to use v3 API":"Musisz podać nazwę dzierżawcy (znanego jako projekt) aby użyć v3 API","You must enter a valid duration for the time to keep backups":"Musisz podać prawidłowy okres przechowywania kopii zapasowych","You must enter a valid retention policy string":"Musisz wprowadzić prawidłowy ciąg zasad przechowywania","You must fill in the password":"Musisz wypełnić pole hasło","You must fill in the server name or address":"Musisz wypełnić pole nazwa serwera lub adres","You must fill in the username":"Musisz wypełnić pole użytkownik","You must fill in {{field}}":"Musisz wypełnić pole {{field}}","You must select or fill in the AuthURI":"Musisz wybrać lub wypełnić pole AuthURI","You must select or fill in the server":"Musisz wybrać lub wypełnić pole serwer","You must specify a path":"Musisz podać ścieżkę","Your files and folders have been restored successfully.":"Twoje pliki i foldery zostały pomyślnie odtworzone.","Your passphrase is easy to guess. Consider changing passphrase.":"Twoje długie hasło jest łatwe do odgadnięcia. Rozważ zmianę długiego hasła.","bucket/folder/subfolder":"zasobnik/folder/podfolder","byte":"bajtów","byte/s":"bajtów/s","custom":"dostosowany","resume now":"wznów teraz","unless you are explicitly specifying --group-id":"chyba że wyraźnie określisz --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} zostało opracowane głównie przez {{dev1}} i {{dev2}}. {{appname}} można pobrać z {{websitename}}. {{appname}} podlega licencji {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} pliki ({{size}}), do zakończenia {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersja","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersji","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersji","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Wersje"],"{{number}} Hour":"{{number}} Godzin","{{number}} Hours":"{{number}} godzin","{{number}} Minutes":"{{number}} Minut","{{time}} (took {{duration}})":"{{time}} (trwało {{duration}})"}); + gettextCatalog.setStrings('pt_BR', {"- pick an option -":"- selecione uma opção -","...loading...":"...carregando...","API key":"Chave API","AWS Access ID":"ID de acesso do AWS","AWS Access Key":"Chave de acesso do AWS","AWS IAM Policy":"Política de IAM do AWS","About":"Sobre","About {{appname}}":"Sobre {{appname}}","Access Key":"Chave de acesso","Access denied":"Acesso negado","Access grant":"Concessão de acesso","Access to user interface":"Acesso à interface do usuário","Account name":"Nome do usuário","Add a new backup":"Adicionar um novo backup","Add a path directly":"Adicione um caminho diretamente","Add advanced option":"Adicionar opção avançada","Add backup":"Adicionar backup","Add filter":"Adicionar filtro","Add path":"Adicionar caminho","Added":"Adicionado","Adjust bucket name?":"Ajustar o nome do bucket?","Advanced Options":"Opções avançadas","Advanced options":"Opções avançadas","Advanced:":"Avançado:","All Hyper-V Machines":"Todas as máquinas Hyper-V","All Microsoft SQL Databases":"Todas as bases Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos os relatórios de uso são enviados de forma anônima e não contêm dados pessoais. As informações contidas são sobre o hardware e o Sistema Operacional, o backend utilizado, a duração do backup, o tamanho total dos dados de origem e dados similares. Os relatórios não contêm caminhos, nomes de arquivos, usuários, senhas ou informações similares.","Allow remote access (requires restart)":"Permitir acesso remoto (restart necessário)","Allowed days":"Dias permitidos","An existing file was found at the new location":"Um arquivo foi encontrado no local escolhido","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Um arquivo foi encontrado no local escolhido\nVocê tem certeza que quer apontar a database para um arquivo existente?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Uma base local foi encontrada.\nReutilizar a basa permitirá que as ferramentas de linha de comando e as instâncias trabalhem no mesmo armazenamento remoto.\nGostaria de utilizar a base existente?","Anonymous usage reports":"Relatório anônimo de uso","Applications":"Aplicações","As Command-line":"Como linha de comando","AuthID":"AuthID","Authentication method":"Método de autenticação","Authentication method ({{auth_method}})":"Método de autenticação ({{auth_method}})","Authentication password":"Senha de autenticação","Authentication username":"Usuário de autenticação","Autogenerated passphrase":"Senha gerada automaticamente","B2 Application ID":"ID da aplicação B2","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"ID da aplicação B2 armazenagem em nuvem","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Voltar","Backup complete!":"Backup concluído!","Backup destination":"Destino do backup","Backup location":"Localização do backup","Backup retention":"Retenção de backup","Backup:":"Backup:","Beta":"Beta","Broken access":"Acesso quebrado","Browse":"Navegar","Browser default":"Navegador padrão","Bucket create location":"Localização do Bucket","Bucket name":"Nome do Bucket","Bucket storage class":"Classe de storage do Bucket","Building list of files to restore …":"Criando lista de arquivos para restauração ...","Building partial temporary database …":"Construindo um banco de dados parcial temporário ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ao permitir o acesso remoto, o servidor escuta solicitações de qualquer máquina em sua rede. Se você habilitar essa opção, verifique se está sempre usando o computador em uma rede protegida por firewall seguro.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Por padrão, o ícone da bandeja abrirá a interface do usuário com um token que desbloqueia a interface do usuário. Isso garante que você possa acessar a interface do usuário a partir do ícone da bandeja, exigindo que outras pessoas insiram uma senha. Se você preferir digitar a senha, mesmo ao acessar a interface do usuário no ícone da bandeja, ative essa opção. ","Cache Files":"Arquivos de Cache","Canary":"Canary","Cancel":"Cancelar","Cannot move to existing file":"Não permitido mover para um arquivo existente","Changelog":"Changelog","Changelog for {{appname}} {{version}}":"Changelog para {{appname}} {{version}}","Check failed:":"Falha na verificação:","Check for updates now":"Buscar atualizações","Checking for updates …":"Procurando atualizações ... ","Chose a storage type to get started":"Para iniciar, escolha o tipo de armazenamento","Click the AuthID link to create an AuthID":"Clique no link AuthID para criar uma AuthID","Click to set throttle options":"Clique para definir opções de limite","Client library to use":"Biblioteca cliente para ser usada","Commandline …":"Linha de comando ...","Compact Phase":"Fase Compacta","Compact now":"Compactar agora","Compacting remote data …":"Compactando dados remotos","Complete log":"Log completo","Completing backup …":"Finalizando backup... ","Completing previous backup …":"Completando o backup anterior ...","Computer":"Computador","Configuration file:":"Arquivo de configuração:","Configuration:":"Configuração:","Configure a new backup":"Configurar novo backup","Confirm delete":"Confirmar remoção","Confirm encryption passphrase":"Confirma frase de segurança encriptada","Confirm passphrase":"Confirmar frase-senha","Confirmation required":"Confirmação necessária","Connect":"Conectar","Connect now":"Conectar agora","Connecting to server …":"Conectando ao servidor ...","Connection lost":"Conexão perdida","Connection worked!":"Conexão estabelecida!","Container name":"Nome do Container","Container region":"Região do Container","Continue":"Continuar","Continue without encryption":"Continuar sem utilizar criptografia","Copied!":"Copiado!","Copy":"Copiar","Copy Destination URL to Clipboard":"Copiar URL do destino","Copy failed. Please manually copy the URL":"Falha na cópia. Copie a URL manualmente","Core options":"Opções básicas","Counting ({{files}} files found, {{size}})":"Contabilizando ({{files}} arquivos encontrados, {{size}})","Crashes only":"Somente falhas","Create bug report …":"Criar relatório de errors ...","Create folder?":"Criar diretório?","Created new limited user":"Criar novo usuário com limitações no acesso","Creating bug report …":"Criando relatório de erros ...","Creating new user with limited access …":"Criando novo usuário com acesso limitado ...","Creating target folders …":"Criando diretórios de destino…","Creating temporary backup …":"Criando backup temporário ...","Current action:":"Ação atual:","Current file:":"Arquivo atual:","Current version is {{versionname}} ({{versionnumber}})":"A versão atual é {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Endpoint S3 modificado","Custom Satellite":"Satélite customizado","Custom Satellite ({{satellite}})":"Satélite customizado ({{satellite}})","Custom authentication url":"URL de autenticação modificada","Custom backup retention":"Retenção de backup personalizada","Custom location ({{server}})":"Localização personalizada ({{server}})","Custom region for creating buckets":"Região personalizada para a criação dos buckets","Custom region value ({{region}})":"Valor personalizado da region ({{region}})","Custom server url ({{server}})":"URL personalizada do servidor ({{server}})","Custom storage class ({{class}})":"Classe de armazenamento personalizada ({{class}})","Database …":"Banco de dados","Days":"Dias","Default":"Padrão","Default ({{channelname}})":"Padrão ({{channelname}})","Default excludes":"Exclusões padrão","Default options":"Opções padrão","Delete":"Remover","Delete Phase (Old Backup Versions)":"Fase de Exclusão (Versões de Backup Antigas)","Delete backup":"Remover backup","Delete backups that are older than":"Excluir backups mais antigos que","Delete local database":"Remover base local","Delete remote files":"Remover arquivos remotos","Delete the local database":"Remover a base local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Remover {{filecount}} arquivos ({{filesize}}) do armazenamento remoto?","Delete …":"Remover ","Deleted":"Deletado","Deleted Versions":"Versões Deletadas","Deleted files":"Arquivos deletados","Deleting remote files …":"Removendo arquivos remotos ...","Deleting unwanted files …":"Removendo arquivos indesejados ...","Description (optional)":"Descrição (opcional)","Description:":"Descrição:","Desktop":"Área de Trabalho","Destination":"Destino","Destination path":"Caminho de destino","Disabled":"Desabilitado","Dismiss":"Ok","Dismiss all":"Ignorar tudo","Display and color theme":"Tela e cores do tema","Do you really want to delete the backup: \"{{name}}\" ?":"Deseja realmente remover o backup: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Deseja realmente remover a base local para: {{name}}","Done":"Finalizado","Download":"Baixar","Downloaded files":"Arquivos baixados","Downloading files …":"Baixando arquivos ... ","Downloading update…":"Baixando atualização... ","Duplicate option {{opt}}":"Duplicar opção {{opt}}","Duplicati Website":"Site do Duplicati","Duplicati forum":"Fórum do Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati será executado quando iniciado, mas permanecerá em um estado pausado pela duração. O Duplicati ocupará recursos mínimos do sistema e nenhum backup será executado.","Duration":"Duração","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada backup tem um banco de dados local associado a ele, que armazena informações sobre o backup remoto na máquina local.\n Ao excluir um backup, você também pode excluir o banco de dados local sem afetar a capacidade de restaurar os arquivos remotos.\n Se você estiver usando o banco de dados local para backups a partir da linha de comando, deverá manter o banco de dados.","Edit as list":"Editar como lista","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Criptografar arquivo","Encryption":"Criptografia","Encryption changed":"A criptografia mudou","Encryption passphrase":"Frase-senha de criptografia ","End":"Fim","Enter URL":"Informe a URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Insira uma estratégia de retenção. Os espaços reservados são D / W / Y para dias / semanas / anos e U para ilimitado. A sintaxe é: 7D: 1D, 4W: 1W, 36M: 1M. Este exemplo mantém um backup para cada um dos próximos 7 dias, um para cada uma das próximas 4 semanas e um para cada um dos próximos 36 meses. Isso também pode ser escrito como 1W: 1D, 1M: 1W, 3Y: 1M.","Enter backup passphrase, if any":"Informe a senha do backup, caso exista","Enter configuration details":"Inserir detalhes da configuração","Enter encryption passphrase":"Informe a senha de criptografia","Enter expression here":"Informe a expressão aqui","Enter the destination path":"Informe o caminho no destino","Error":"Erro","Error!":"Erro!","Errors and crashes":"Erros e problemas","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir diretórios que contenham","Exclude expression":"Excluir utilizando expressão","Exclude file":"Excluir arquivo","Exclude file extension":"Excluir arquivos com extensão","Exclude files whose names contain":"Excluir arquivos que contenham","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Excluir diretório","Exclude regular expression":"Excluir utilizando expressão regular","Existing file found":"Excluir arquivo encontrado","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuração do backup","Export configuration":"Exportar configuração","Export passwords":"Exportar senhas","Export …":"Exportar ...","Exporting …":"Exportando ...","External link":"Link externo","FTP (Alternative)":"FTP (alternativo)","Failed to build temporary database: {{message}}":"Falha ao construir base temporária: {{message}}","Failed to connect:":"Falha ao conectar:","Failed to connect: {{message}}":"Falha ao conectar: {{message}}","Failed to delete:":"Falha ao remover:","Failed to fetch path information: {{message}}":"Falha ao obter informação do caminho: {{message}}","Failed to find backup:":"Falha ao encontrar backup:","Failed to read backup defaults:":"Falha ao ler os padrões do backup","Failed to restore files: {{message}}":"Falha ao restaurar arquivos: {{message}}","Failed to save:":"Falha ao salvar:","Fetching path information …":"Buscando informações do caminho …","File":"Arquivo","Files larger than:":"Arquivos maiores que:","Filters":"Filtros","Finished!":"Finalizado!","First run setup":"Configuração inicial","Folder":"Diretório","Folder path":"Caminho do diretório","Fri":"Sex","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID do Projeto GCS","General":"Geral","General backup settings":"Configurações gerais de backup","General options":"Opções gerais","Generate":"Gerar","Generate IAM access policy":"Gerar política de acesso IAM","Getting file versions …":"Obtendo versões do arquivo ... ","Group email":"E-mail do grupo","Hidden files":"Arquivos ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar diretórios ocultos","Home":"Home","Hostnames":"Hostnames","Hours":"Horas","How do you want to handle existing files?":"Como você quer lidar com arquivos existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Caso um backup não ocorra na data específica, ele executará assim que possível.","If at least one newer backup is found, all backups older than this date are deleted.":"Se um novo backup for encontrado, todos os backups anteriores a esta data são excluídos.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se você não inserir um caminho, todos os arquivos serão armazenados na pasta de login.\nTem certeza de que isso é o que quer?","If you do not enter an API Key, the tenant name is required":"Se você não inserir uma chave de API, o nome do projeto é necessário","Import":"Importar","Import Destination URL":"Importar URL de destino","Import backup configuration":"Importar configuração de backup","Import from a file":"Importar de um arquivo","Import metadata":"Importar metadados","Importing …":"Importando ...","Include a file?":"Incluir um arquivo?","Include expression":"Incluir expressão","Include regular expression":"Incluir expressão regular","Incorrect answer, try again":"Resposta incorreta, tente novamente","Individual builds for developers only. Not for use with important data.":"Versões apenas para desenvolvedores. Não para uso com dados importantes.","Information":"Informação","Invalid characters in path":"Caracteres inválidos no caminho","Invalid retention time":"Tempo de retenção inválido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"É possível conectar em alguns servidores FTP sem utilizar senha.\nTem certeza que o seu servidor FTP suporta autenticação sem senha?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Manter um número específico de backups","Keep all backups":"Manter todos os backups","Keystone API version":"Versão da API Keystone","Language in user interface":"Idioma da interface do usuário","Last month":"Último mês","Last successful backup:":"Último backup bem-sucedido:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Última restauração bem-sucedida: {{time}} (demorou {{duration || '0 segundos'}})","Latest":"Mais recentes","Libraries":"Bibliotecas","Listing backup dates …":"Listando datas de backup ... ","Listing remote files for purge …":"Listando arquivos remotos para limpeza…","Listing remote files …":"Listando arquivos remotos…","Live":"Ao vivo","Load a configuration from an exported job or a storage provider":"Carregar uma configuração de um trabalho exportado ou de um provedor de armazenamento","Load destination from an exported job or a storage provider":"Carregar destino a partir de um trabalho exportado ou de um provedor de armazenamento","Load older data":"Abrir dados antigos","Loading …":"Carregando …","Local database path:":"Caminho do banco de dados local:","Local repository":"Repositório local","Local storage":"Armazenamento local","Location":"Localização","Location where buckets are created":"Local onde os compartimentos são criados","Log data for {{Backup.Backup.Name}}":"Grave log para {{Backup.Backup.Name}} ","Log data from the server":"Registrar dados do servidor","Log out":"Sair","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Manutenção","Manually type path":"Digite manualmente o caminho","Max download speed":"Velocidade de download máxima","Max upload speed":"Velocidade de upload máxima","Menu":"Menu","Microsoft SQL Database:":"Banco de dados Microsoft SQL:","Microsoft SQL Databases":"Banco de Dados Microsoft SQL","Minimum redundancy":"Redundância mínima","Minimum redundancy is 1.0":"Redundância mínima é 1.0","Minutes":"Minutos","Missing name":"Faltando o nome","Missing passphrase":"Faltando a frase de senha","Missing sources":"Faltando as origens","Modified":"Modificado","Mon":"Seg","Months":"Meses","Move existing database":"Mover o banco de dados existente","Move failed:":"Falha ao mover:","My Documents":"Meus Documentos","My Music":"Minhas Músicas","My Photos":"Minhas Fotos","My Pictures":"Minhas Imagens","Name":"Nome","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nome nome de usuário é {{user}}\nAutorizações atualizadas para uso de um novo usuário limitado","Next":"Próximo","Next scheduled run:":"Próxima execução agendada:","Next scheduled task:":"Próxima tarefa agendada:","Next task:":"Próxima tarefa:","Next time":"Próxima vez","No":"Não","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nenhum certificado foi especificado anteriormente, verifique com o administrador do servidor se a chave está correta: {{key}}\n\nDeseja aprovar a chave de host relatada?","No editor found for the "{{backend}}" storage type":"Editor não encontrado para o tipo de armazenamento "{{backend}}"","No encryption":"Sem criptografia","No items selected":"Itens não selecionados","No items to restore, please select one or more items":"Sem itens para restaurar. por favor selecione um ou mais itens","No passphrase entered":"Nenhuma senha inserida","No scheduled tasks":"Sem tarefas agendadas","Non-matching passphrase":"Senha não correspondente","None / disabled":"Nenhum / desabilitado","Not using encryption":"Sem criptografia","Nothing will be deleted. The backup size will grow with each change.":"Nada será excluído. O tamanho do backup crescerá com cada mudança.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Se existir mais backups do que o número especificado, os backups mais antigos serão excluídos.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aberto","Operating System":"Sistema operacional","Operation":"Operações:","Operations:":"Operações:","Optional authentication password":"Senha opcional de autenticação","Optional authentication username":"Usuário opcional de autenticação","Options":"Opções","Original location":"Localização original","Others":"Outros","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Com o tempo, as versões de backup serão excluídas automaticamente. Permanecerá um backup dos últimos 7 dias, das últimas 4 semanas e cada um dos últimos 12 meses. Sempre haverá pelo menos um backup.","Overwrite":"Sobrescrever","Passphrase":"Frase de segurança","Passphrase (if encrypted)":"Senha (se criptografado)","Passphrase changed":"Senha alterada","Passphrases are not matching":"Senhas não correspondem","Passphrases do not match":"As senhas não correspondem","Password":"Senha","Patching files with local blocks …":"Aplicando patch nos arquivos com blocos locais ...","Path":"Caminho","Path not found":"Caminho não encontrado","Path on server":"Caminho do servidor","Path or subfolder in the bucket":"Caminho ou subpasta no bucket","Pause":"Parar","Pause after startup or hibernation":"Pausa após a inicialização ou a hibernação","Pause options":"Interromper opções","Permissions":"Permissões","Pick location":"Escolher localização","Point to your backup files and restore from there":"Aponte para os arquivos de backup e restaure de lá","Port":"Porta","Prevent tray icon automatic log-in":"Impedir login automático no ícone da bandeja","Previous":"Anterior","Progress:":"Progresso:","ProjectID is optional if the bucket exist":"ProjectID é opcional se o bucket já existe","Proprietary":"Proprietário","Purge Phase":"Estágio deleção","Purging files complete!":"Deleção de arquivos completo!","Purging files …":"Limpando arquivos ...","Rebuilding local database …":"Reconstruindo banco de dados local ...","Recreate (delete and repair)":"Recriar (excluir e reparar)","Recreate Database Phase":"Recriar banco de dados","Recreating database …":"Recriaando banco de dados ...","Registering temporary backup …":"Registrando backup temporário ...","Relative paths not allowed":"Caminhos relativos não são permitidos","Reload":"Recarregar","Remote":"Remoto","Remote Path":"Caminho remoto","Remote Repository":"Repositório remoto","Remote path":"Caminho remoto","Remote repository":"Repositório remoto","Remote volume size":"Tamanho do volume remoto","Remove":"Remover","Remove option":"Remover opção","Removed files":"Arquivos Removidos","Repair":"Reparar","Repair Phase":"Reparar","Repairing database …":"Reparando banco de dados ...","Repeat Passphrase":"Repetir frase de segurança","Reporting:":"Relatórios:","Reset":"Redefinir","Restore":"Restaurar","Restore complete!":"Restauração Completa!","Restore files":"Restaurar arquivos","Restore files …":"Restaurar arquivos ...","Restore from":"Restaurar de","Restore from backup configuration":"Restaurar a partir da configuração de backup","Restore options":"Restaurar opções","Restore read/write permissions":"Restaurar permissões leitura/escrita","Restored Files":"Arquivos Restaurados","Restored Folders":"Diretórios Restaurados","Restored Symlinks":"Links Simbólicos Restaurados","Restoring files …":"Restaurando arquivos ...","Resume":"Continuar","Rewritten File Lists":"Listas de arquivos reescritos","Run again every":"Executar novamente a cada","Run now":"Executar agora","Running commandline entry":"Executando entrada de linha de comando","Running task:":"Executando tarefa:","Running …":"Executando ...","S3 Compatible":"S3 Compatível","Same as the base install version: {{channelname}}":"Igual à versão de instalação base: {{channelname}}","Sat":"Sáb","Satellite":"Satélite","Save":"Salvar","Save and repair":"Salvar e reparar","Save different versions with timestamp in file name":"Salve diferentes versões com marcas de horário no nome do arquivo","Save immediately":"Salvar imediatamente","Scanning existing files …":"Procurando arquivos existentes ...","Scanning for local blocks …":"Procurando por blocos locais ...","Schedule":"Agendar","Search":"Buscar","Search for files":"Procurar por arquivos","Seconds":"Segundos","Select a log level and see messages as they happen:":"Selecione um nível de log e veja as mensagens conforme elas aparecem:","Select files":"Selecionar arquivos","Server":"Servidor","Server and port":"Servidor e porta","Server hostname or IP":"Nome do servidor ou IP","Server is currently paused,":"Servidor está atualmente parado,","Server is currently paused, do you want to resume now?":"Servidor está atualmente parado, você quer recomeçar agora?","Server password":"Senha do servidor","Server paused":"Servidor parado","Server state properties":"Propriedades do estado do servidor","Settings":"Configurações","Show":"Exibir","Show advanced editor":"Mostrar editor avançado","Show hidden folders":"Exibir pastas ocultas","Show log":"Exibir log","Show log …":"Exibir log ...","Show treeview":"Mostrar hierarquia","Sia server password":"Senha do servidor Sia","Smart backup retention":"Retenção de backup inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns provedores OpenStack permitem uma chave de API em vez de uma senha e nome de projeto","Some S3 providers might only be compatible with a certain client library":"Alguns provedores S3 podem ser compatíveis apenas com uma determinada biblioteca cliente","Source Data":"Dados de origem","Source Files":"Arquivos de Origem","Source data":"Dados de origem","Source folders":"Pasta de origem","Source:":"Origem:","Specific builds for developers only. Not for use with important data.":"Versão apenas para desenvolvedores. Não para uso com dados importantes.","Standard protocols":"Protocolos padrão","Start":"Inicio","Starting backup …":"Iniciando backup ...","Starting restore …":"Iniciando restauração ...","Starting the restore process …":"Iniciando o processo de restauração ...","Stop after the current file":"Parar após o arquivo atual","Stop running backup":"Parar de executar o backup","Stop running task":"Parar de executar a tarefa","Stopping after the current file:":"Parando após o arquivo atual:","Stopping task:":"Tarefa de parada:","Storage Type":"Tipo de armazenamento","Storage class":"Classe de armazenamento","Storage class for creating a bucket":"Classe de armazenamento para criar um bucket","Stored":"Armazenado","Strong":"Forte","Success":"Sucesso","Sun":"Dom","Symbolic link":"Link simbólico","System Files":"Arquivos do sistema","System default ({{levelname}})":"Sistema padrão ({{levelname}})","System files":"Arquivos do sistema","System info":"Informação do sistema","System properties":"Propriedades do sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Tarefa está executando","Temporary Files":"Arquivos temporários","Temporary files":"Arquivos temporários","Test Phase":"Fase de teste","Test connection":"Teste de conexão","Testing permissions …":"Testando permissões ...","Testing …":"Testando ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"O campo '{{fieldname}}' contém um caractere inválido: {{character}} (valor: {{value}}, índice: {{pos}})","The backup is missing, has it been deleted?":"O backup está faltando, foi excluído?","The backup was temporary and does not exist anymore, so the log data is lost":"O backup era temporário e não existe mais, portanto, os dados de log serão perdidos","The bucket name should be all lower-case, convert automatically?":"O nome do bucket deve ser todo em minúsculas. Converter automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"A configuração deve ser mantida segura. Tem certeza de que deseja salvar um arquivo não criptografado contendo suas senhas?","The dark theme (by Michal)":"O tema escuro (por Michal)","The default blue on white theme (by Alex)":"O tema padrão azul sobre branco (por Alex)","The folder {{folder}} does not exist.\nCreate it now?":"O diretório {{folder}} não existe.\nDeseja cria-lo agora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"A chave do host mudou, verifique com o administrador do servidor se está correta, caso contrário você poderia ser vítima de um ataque MAN-IN-THE MIDDLE.\n\nDeseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" com a chave do host REPORTADA: {{key}}?","The passwords do not match":"Senhas não conferem","The path does not appear to exist, do you want to add it anyway?":"O caminho não parece existir, você deseja adicioná-lo de qualquer maneira?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"O caminho não termina com um caractere '{{dirsep}}, o que significa que você inclui um arquivo, não uma pasta.\n\nDeseja incluir o arquivo especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"O caminho deve ser um caminho absoluto, ou seja, deve começar com uma barra progressiva '/'","The region parameter is only applied when creating a new bucket":"O parâmetro de região só é aplicado ao criar um novo bucket","The region parameter is only used when creating a bucket":"O parâmetro de região só é usado na criação de um bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"O certificado do servidor não pôde ser validado.\nDeseja aprovar o certificado SSL com o hash: {{hash}}?","The storage class affects the availability and price for a stored file":"A classe de armazenamento afeta a disponibilidade e o preço de um arquivo armazenado","The target folder contains encrypted files, please supply the passphrase":"A pasta de destino contém arquivos criptografados. Forneça a senha","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"O usuário tem muitas permissões. Deseja criar um novo usuário limitado, com apenas permissões para o caminho selecionado?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Este backup foi criado em outro sistema operacional. A restauração de arquivos sem especificar uma pasta de destino pode fazer com que os arquivos sejam restaurados em locais inesperados. Tem certeza de que deseja continuar sem escolher uma pasta de destino?","This month":"Este mês","This week":"Esta semana","Throttle settings":"Configurações de limitação","Thu":"Qui","Time":"Tempo","To File":"Para o arquivo","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que deseja excluir todos os arquivos remotos para \"{{nome}}\", insira a palavra abaixo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sem uma senha, desmarque a caixa \"Criptografar arquivo\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar vários ataques baseados em DNS, o Duplicati limita os nomes de host permitidos aos listados aqui. O acesso IP direto e o host local sempre são permitidos. Vários nomes de host podem ser fornecidos com um separador de ponto-e-vírgula. Se qualquer um dos nomes de host permitidos for um asterisco (*), todos os nomes de host serão permitidos e esse recurso será desativado. Se o campo estiver vazio, somente o endereço IP e o acesso ao host local serão permitidos.","Today":"Hoje","Trust host certificate?":"Confiar no certificado de host?","Trust server certificate?":"Confiar no certificado de servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Experimente os novos recursos em que estamos trabalhando. Atualmente, a versão mais estável disponível. Teste Restaurar dados antes de usar isso em ambientes de produção.","Tue":"Ter","Type passphrase here.":"Nenhuma senha inserida","Type to highlight files":"Tipo para destacar arquivos","Unknown backup size and versions":"Tamanho do backup e versões desconhecidos","Until resumed":"Até retomar","Update channel":"Canal de atualização","Update failed:":"Atualização falhou:","Updating with existing database":"Atualizando com o banco de dados existente","Uploaded files":"Arquivos enviados","Uploading verification file …":"Enviando arquivo de verificação ...","Usage statistics":"Estatísticas de uso","Usage statistics, warnings, errors, and crashes":"Estatísticas de uso, avisos, erros e falhas","Use SSL":"Utilizar SSL","Use existing database?":"Usar um banco de dados existente?","Use weak passphrase":"Usar uma senha fraca","Useless":"Sem utilidade","User data":"Dados do usuário","User domain name":"Nome de domínio do usuário","User has too many permissions":"O usuário tem muitas permissões","User interface settings":"Configurações da interface do usuário","Username":"Nome de usuário","Vacuuming database …":"Limpando banco de dados ...","Validating …":"Validando ...","Verifications":"Verificações","Verify files":"Verificar arquivos","Verifying answer":"Verificando pergunta","Verifying backend data …":"Verificando dados do backend ...","Verifying files …":"Verificando arquivos ...","Verifying remote data …":"Verificando dados remotos ...","Verifying restored files …":"Verificando arquivos restaurados ...","Verifying …":"Verificando ...","Version ID":"ID da versão","Very strong":"Muito forte","Very weak":"Muito fraca","Visit us on":"Visite-nos em","WARNING: This will prevent you from restoring the data in the future.":"AVISO: isso impedirá que você restaure os dados no futuro.","Waiting for task to begin":"Aguardando o início da tarefa","Waiting for upload to finish …":"Aguardando o upload terminar ...","Warnings, errors and crashes":"Avisos, erros e falhas","We recommend that you encrypt all backups stored outside your system":"Recomendamos que criptografe todos os backups armazenados fora do seu sistema","Weak":"Fraca","Weak passphrase":"Frase de segurança fraca","Wed":"Qua","Weeks":"Semanas","Where do you want to restore from?":"De onde você deseja restaurar?","Where do you want to restore the files to?":"Para onde você deseja restaurar os arquivos?","Years":"Anos","Yes":"Sim","Yes, I have stored the passphrase safely":"Sim, eu tenho armazenado uma frase de acesso segura","Yes, I understand the risk":"Sim, entendo o risco","Yes, I'm brave!":"Sim, sou corajoso!","Yes, please break my backup!":"Sim, corrompa meu backup!","Yesterday":"Ontem","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Você está mudando o caminho do banco de dados para longe de um banco de dados existente.\nTem certeza de que isso é o que deseja?","You are currently running {{appname}} {{version}}":"Você está atualmente executando {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Você mudou o modo de criptografia. Isso pode estragar algo. É aconselhado criar um novo backup em vez disso","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Você alterou a senha, o que não é suportado. É aconselhado criar um novo backup.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Você escolheu não criptografar o backup. Encriptação é recomendada para todos dados armazenados em um servidor remoto.","You have chosen to restore to a new location, but not entered one":"Você escolheu restaurar para um novo local, mas não inseriu um","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Você gerou uma senha segura. Certifique-se de fazer um cópia da mesma, pois os dados não podem ser recuperados se você perder a senha.","You must choose at least one source folder":"Você deve escolher pelo menos uma pasta de origem","You must enter a domain name to use v3 API":"Você deve inserir um nome de domínio para usar a API v3","You must enter a name for the backup":"Você deve inserir um nome para o backup","You must enter a passphrase or disable encryption":"Você deve inserir uma senha ou desativar a criptografia","You must enter a password to use v3 API":"Você deve digitar uma senha para usar a API v3","You must enter a positive number of backups to keep":"Você deve inserir um número positivo de backups para manter.","You must enter a tenant (aka project) name to use v3 API":"Você deve inserir um nome de inquilino (aka project) para usar a API v3","You must enter a valid duration for the time to keep backups":"Você deve inserir uma duração válida de tempo para manter os backups","You must enter a valid retention policy string":"Você tem que inserir uma string de política de retenção válida","You must fill in the password":"Você deve preencher a senha","You must fill in the server name or address":"Você deve preencher o nome do servidor ou endereço","You must fill in the username":"Você deve preencher o usuário","You must fill in {{field}}":"Você deve preencher {{field}}","You must select or fill in the AuthURI":"Você deve selecionar ou preencher a AuthURI","You must select or fill in the server":"Você deve selecionar ou preencher o servidor","You must specify a path":"Você deve especificar um caminho","Your files and folders have been restored successfully.":"Seus arquivos e pastas foram restaurados com êxito.","Your passphrase is easy to guess. Consider changing passphrase.":"Sua senha é fácil de adivinhar. Considere alterá-la.","bucket/folder/subfolder":"bucket/pasta/subpasta","byte":"byte","byte/s":"byte/s","custom":"personalizado","resume now":"continuar agora","unless you are explicitly specifying --group-id":"a menos que você esteja explicitamente especificando --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} foi desenvolvido inicialmente por {{dev1}} e{{dev2}}. {{appname}} pode ser baixado em {{websitename}}. {{appname}} é licenciado sob a {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} arquivos ({{size}}) restantes {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versão","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versões","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versões"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} horas","{{number}} Minutes":"{{number}} Minutos","{{time}} (took {{duration}})":"{{time}} (demorou {{duration}})"}); + gettextCatalog.setStrings('pt', {"- pick an option -":"- escolha uma opção -","...loading...":"...a carregar...","API key":"Chave API","AWS Access ID":"ID do acesso AWS","AWS Access Key":"Chave do acesso AWS","AWS IAM Policy":"Política de acesso e identidade AWS","About":"Sobre","About {{appname}}":"Sobre o {{appname}}","Access Key":"Chave de acesso","Access denied":"Acesso recusado","Access grant":"Acesso concedido","Access to user interface":"Acesso à interface","Account name":"Nome da conta","Add a new backup":"Adicionar nova cópia de segurança","Add a path directly":"Digitar caminho","Add advanced option":"Adicionar opção avançada","Add backup":"Adicionar cópia de segurança","Add filter":"Adicionar filtro","Add path":"Adicionar caminho","Added":"Adicionado","Adjust bucket name?":"Ajustar nome do 'bucket'?","Advanced Options":"Opções avançadas","Advanced options":"Opções avançadas","Advanced:":"Avançado:","All Hyper-V Machines":"Todas as máquinas Hyper-V","All Microsoft SQL Databases":"Todas as bases de dados Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Todos os relatórios de utilização são enviados de forma anónima. Contêm informação sobre o hardware, sobre o sistema operativo, o tipo de 'backend', a duração da cópia de segurança, o tamanho dos dados e informações similares. Não contêm caminhos, ficheiros, utilizadores, palavras-passe ou quaisquer outras informações pessoais.","Allow remote access (requires restart)":"Permitir acesso remoto (tem que reiniciar)","Allowed days":"Dias permitidos","An existing file was found at the new location":"Encontrado um ficheiro na nova localização","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Foi encontrado um ficheiro na nova localização.\nTem a certeza de que deseja que a base de dados aponte para este ficheiro?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Foi encontrada uma base de dados local para o armazenamento.\nA reutilização da base de dados permite o funcionamento das instâncias do servidor e da linha de comandos no mesmo armazenamento remoto.\n\nDeseja reutilizar a base de dados existente?","Anonymous usage reports":"Relatório anónimos de utilização","Applications":"Aplicações","As Command-line":"Como linha de comandos","AuthID":"AuthID","Authentication method":"Método de autenticação","Authentication method ({{auth_method}})":"Método de autenticação ({{auth_method}})","Authentication password":"Palavra-passe de autenticação","Authentication username":"Nome de utilizador de autenticação","Autogenerated passphrase":"Frase-passe gerada automaticamente","B2 Application ID":"ID Aplicação B2","B2 Application Key":"Chave da aplicação B2","B2 Cloud Storage Account ID":"ID da conta B2 Cloud Storage","B2 Cloud Storage Application ID":"ID Aplicação B2 Cloud Storage","B2 Cloud Storage Application Key":"Chave da aplicação B2 Cloud Storage","Back":"Recuar","Backup complete!":"Cópia de segurança terminada!","Backup destination":"Destino da cópia de segurança","Backup location":"Localização da cópia de segurança","Backup retention":"Retenção de cópias de segurança","Backup:":"Cópia de segurança:","Beta":"Beta","Broken access":"Acesso danificado","Browse":"Explorar","Browser default":"Navegador padrão","Bucket create location":"Localização de criação do 'bucket'","Bucket name":"Nome do 'bucket'","Bucket storage class":"Classe de armazenamento do 'bucket'","Building list of files to restore …":"A criar a lista de ficheiros a restaurar ...","Building partial temporary database …":"A criar a base de dados parcial temporária ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Ao permitir o acesso remoto, o servidor escuta solicitações de qualquer máquina na sua rede. Se ativar esta opção, certifique-se que está a usar sempre o computador numa rede protegida por uma firewall segura.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Por pré-definição, o ícone da barra de tarefas abrirá a interface do utilizador com um token que desbloqueia a mesma. Isto permite-lhe que consegue aceder à interface do utilizador a partir do ícone da barra de tarefas, garantindo que terceiros tenham de introduzir uma palavra-passe. Se preferir introduzir a palavra-passe ao aceder a partir do ícone da barra de tarefas, ative esta opção.","Cache Files":"Ficheiros em cache","Canary":"Canary","Cancel":"Cancelar","Cannot move to existing file":"Não foi possível mover o ficheiro existente","Changelog":"Registo de alterações","Changelog for {{appname}} {{version}}":"Registo de alterações para {{appname}} {{version}}","Check failed:":"Falha de verificação:","Check for updates now":"Procurar atualizações agora","Checking for updates …":"A procurar atualizações ...","Chose a storage type to get started":"Escolha o tipo de armazenamento para iniciar","Click the AuthID link to create an AuthID":"Clique na ligação para criar uma AuthID","Click to set throttle options":"Clique para definir as opções de velocidade","Client library to use":"Biblioteca do cliente a utilizar","Commandline …":"Linha de comandos ...","Compact Phase":"Fase de compactar","Compact now":"Compactar agora","Compacting remote data …":"A compactar dados remotos ...","Complete log":"Registo completo","Completing backup …":"A terminar a cópia de segurança ...","Completing previous backup …":"A completar a cópia de segurança anterior ...","Computer":"Computador","Configuration file:":"Ficheiro de configuração:","Configuration:":"Configuração:","Configure a new backup":"Configurar nova cópia de segurança","Confirm delete":"Confirmação de eliminação","Confirm encryption passphrase":"Confirme a chave de encriptação","Confirm passphrase":"Confirme a chave","Confirmation required":"Requer confirmação","Connect":"Estabelecer ligação","Connect now":"Estabelecer ligação agora","Connecting to server …":"A ligar ao servidor ...","Connection lost":"Ligação perdida","Connection worked!":"Ligação funcional!","Container name":"Nome do 'container'","Container region":"Região do 'container'","Continue":"Continuar","Continue without encryption":"Continuar sem encriptação","Copied!":"Copiada!","Copy":"Copiar","Copy Destination URL to Clipboard":"Copiar URL para a área de transferência","Copy failed. Please manually copy the URL":"Falha ao copiar. Copie o URL manualmente.","Core options":"Opções de core","Counting ({{files}} files found, {{size}})":"Encontrados ({{files}} ficheiros, {{size}})","Crashes only":"Apenas términos","Create bug report …":"Criar relatório de erros ...","Create folder?":"Criar pasta?","Created new limited user":"Criar utilizador com restrições","Creating bug report …":"A criar relatório de erros ...","Creating new user with limited access …":"A criar novo utilizador com acesso limitado ...","Creating target folders …":"A criar pastas de destino ...","Creating temporary backup …":"A criar cópia de segurança temporária ...","Current action:":"Ação atual:","Current file:":"Ficheiro atual:","Current version is {{versionname}} ({{versionnumber}})":"A versão atual é a {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"URL S3 personalizado","Custom Satellite":"Satélite personalizado","Custom Satellite ({{satellite}})":"Satélite personalizado ({{satellite}})","Custom authentication url":"URL personalizado de autenticação","Custom backup retention":"Retenção de cópias de segurança personalizada","Custom location ({{server}})":"Localização personalizada ({{server}})","Custom region for creating buckets":"Região personalizada para a criação de 'buckets'","Custom region value ({{region}})":"Valor personalizado da região ({{region}})","Custom server url ({{server}})":"URL personalizado do servidor ({{server}})","Custom storage class ({{class}})":"Classe personalizada do armazenamento ({{class}})","Database …":"Base de dados ...","Days":"Dias","Default":"Padrão","Default ({{channelname}})":"Padrão ({{channelname}})","Default excludes":"Exclusões padrão","Default options":"Opções padrão","Delete":"Eliminar","Delete Phase (Old Backup Versions)":"Fase de eliminar (versões de cópias de segurança antigas)","Delete backup":"Eliminar cópia de segurança","Delete backups that are older than":"Eliminar cópias de segurança mais antigas do que","Delete local database":"Eliminar base de dados local","Delete remote files":"Eliminar ficheiros remotos","Delete the local database":"Eliminar base de dados local","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Eliminar {{filecount}} ficheiros ({{filesize}}) do armazenamento remoto?","Delete …":"A apagar ...","Deleted":"Eliminado","Deleted Versions":"Versões eliminadas","Deleted files":"Ficheiros eliminados","Deleting remote files …":"A apagar ficheiros remotos ...","Deleting unwanted files …":"A apagar ficheiros desnecessários ...","Description (optional)":"Descrição (opcional)","Description:":"Descrição:","Desktop":"Ambiente de trabalho","Destination":"Destino","Destination path":"Caminho de destino","Disabled":"Desativada","Dismiss":"Descartar","Dismiss all":"Descartar tudo","Display and color theme":"Visualização e cor do tema","Do you really want to delete the backup: \"{{name}}\" ?":"Tem a certeza de que deseja eliminar a cópia de segurança: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Tem a certeza de que deseja eliminar a base de dados local para: {{name}}?","Done":"Terminado","Download":"Descarregar","Downloaded files":"Descarregar ficheiros","Downloading files …":"A transferir ficheiros ...","Downloading update…":"A transferir atualizações ...","Duplicate option {{opt}}":"Opção duplicada {{opt}}","Duplicati Website":"Site do Duplicati","Duplicati forum":"Fórum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"O Duplicati será executado quando iniciado, mas permanecerá no estado pausado pela duração. O Duplicati ocupará recursos mínimos do sistema e não será executada nenhuma cópia de segurança.","Duration":"Duração","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Cada cópia de segurança tem uma base de dados local associada e que armazena as informações sobre a cópia de segurança remota na sua máquina local.\nAo eliminar uma cópia de segurança, também elimina a base de dados local e afetará a possibilidade de restaurar os ficheiros remotos.\nSe estiver a utilizar uma base de dados local para cópias de segurança a partir da linha de comandos deve manter esta base de dados.","Edit as list":"Editar como lista...","Edit as text":"Editar como texto","Edit …":"Editar ...","Encrypt file":"Encriptar ficheiro","Encryption":"Encriptação","Encryption changed":"Encriptação alterada","Encryption passphrase":"Frase-passe de encriptação","End":"Fim","Enter URL":"Digite o URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Introduza uma estratégia de retenção. Os espaços reservados são D/W/Y para dias / semanas / anos e U para ilimitado. A sintaxe é: 7D:1D,4W:1W,36M:1M. Este exemplo mantém uma cópia de segurança para cada um dos próximos 7 dias, uma para cada uma das próximas 4 semanas e uma para cada um dos próximos 36 meses. Isso também pode ser escrito como 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Digite a frase-passe da cópia de segurança, se existente","Enter configuration details":"Digite os detalhes da configuração","Enter encryption passphrase":"Digite a frase-passe de encriptação","Enter expression here":"Digite aqui a expressão","Enter the destination path":"Digite o caminho do destino","Error":"Erro","Error!":"Erro!","Errors and crashes":"Erros e términos","Examined":"Examinado","Exclude":"Excluir","Exclude directories whose names contain":"Excluir diretórios cujo nome contém","Exclude expression":"Expressão de exclusão","Exclude file":"Ficheiro de exclusão","Exclude file extension":"Tipo de ficheiro de exclusão","Exclude files whose names contain":"Excluir ficheiros cujo nome contém","Exclude filter group":"Excluir grupo de filtros","Exclude folder":"Pasta de exclusão","Exclude regular expression":"Expressão regular de exclusão","Existing file found":"Encontrado ficheiro","Experimental":"Experimental","Export":"Exportar","Export backup configuration":"Exportar configuração de cópia de segurança","Export configuration":"Exportar configuração","Export passwords":"Exportar palavras-passe","Export …":"Exportar ...","Exporting …":"A Exportar ...","External link":"Ligação externa","FTP (Alternative)":"FTP (Alternativo)","Failed to build temporary database: {{message}}":"Falha ao criar a base de dados temporária: {{message}}","Failed to connect:":"Falha ao estabelecer ligação:","Failed to connect: {{message}}":"Falha ao estabelecer ligação: {{message}}","Failed to delete:":"Falha ao eliminar:","Failed to fetch path information: {{message}}":"Falha ao obter a informação do caminho: {{message}}","Failed to find backup:":"Falha ao encontrar a cópia de segurança:","Failed to read backup defaults:":"Falha ao ler as definições da cópia de segurança:","Failed to restore files: {{message}}":"Falha ao restaurar os ficheiros: {{message}}","Failed to save:":"Falha ao guardar:","Fetching path information …":"A obter informação do caminho ...","File":"Ficheiro","Files larger than:":"Ficheiros maiores do que:","Filters":"Filtros","Finished!":"Terminado!","First run setup":"Configuração de primeira utilização","Folder":"Pasta","Folder path":"Caminho da pasta","Fri":"Sex","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"ID do projeto GSC","General":"Geral","General backup settings":"Definições gerias de cópia de segurança","General options":"Opções gerais","Generate":"Gerar","Generate IAM access policy":"Gerar política de acesso IAM","Getting file versions …":"A obter versão dos ficheiros ...","Group email":"E-mail do grupo","Hidden files":"Ficheiros ocultos","Hide":"Ocultar","Hide hidden folders":"Ocultar ficheiros ocultos","Home":"Página inicial","Hostnames":"Nomes de hosts","Hours":"Horas","How do you want to handle existing files?":"Como deseja gerir os ficheiros existentes?","Hyper-V Machine":"Máquina Hyper-V","Hyper-V Machine:":"Máquina Hyper-V:","Hyper-V Machines":"Máquinas Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Se não existir data, a tarefa será executada assim que possível.","If at least one newer backup is found, all backups older than this date are deleted.":"Se for encontrada uma cópia de segurança mais recente, todas as cópias de segurança anteriores a esta data serão eliminadas.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Se não digitar o cominho, todos os ficheiros serão guardados na pasta raiz.\nTem a certeza de que é isto que deseja?","If you do not enter an API Key, the tenant name is required":"Se não digitar a chave API, será necessário o nome do 'tenant' (projeto).","Import":"Importar","Import Destination URL":"Importar URL do destino","Import backup configuration":"Importar configuração da cópia de segurança","Import from a file":"Importar de um ficheiro","Import metadata":"Importar meta-dados","Importing …":"A importar ...","Include a file?":"Incluir um ficheiro?","Include expression":"Expressão de inclusão","Include regular expression":"Expressão regular de exclusão","Incorrect answer, try again":"Resposta errada, tente novamente.","Individual builds for developers only. Not for use with important data.":"Versões apenas para programadores. Não destinadas a serem utilizadas com dados importantes.","Information":"Informação","Invalid characters in path":"Caracteres inválidos no caminho","Invalid retention time":"Tempo de retenção inválido","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"É possível estabelecer ligação a servidores FTP sem palavra-passe.\nTem a certeza de que o servidor FTP possui suporte a sessões no modo anónimo?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Manter um número específico","Keep all backups":"Manter todas as cópias de segurança","Keystone API version":"Versão da API Keystone","Language in user interface":"Idioma da interface de utilizador","Last month":"Último mês","Last successful backup:":"Última cópia de segurança com sucesso:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Último restauro bem-sucedido: {{time}} (demorou {{duration || '0 segundos'}})","Latest":"Último","Libraries":"Bibliotecas","Listing backup dates …":"A listar datas das cópias de segurança ...","Listing remote files for purge …":"A listar ficheiros remotos para apagar ...","Listing remote files …":"A listar ficheiros remotos ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Carregar uma configuração de uma tarefa exportada ou de um fornecedor de armazenamento","Load destination from an exported job or a storage provider":"Carregar um destino de uma tarefa exportada ou de um fornecedor de armazenamento","Load older data":"Carregar dados antigos","Loading …":"A carregar ...","Local database path:":"Caminho da base de dados local:","Local repository":"Repositório local","Local storage":"Armazenamento local","Location":"Localização","Location where buckets are created":"Localização para a criação dos 'buckets'","Log data for {{Backup.Backup.Name}}":"Registo para {{Backup.Backup.Name}}","Log data from the server":"Registo a partir do servidor","Log out":"Terminar sessão","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Manutenção","Manually type path":"Digitar caminho manualmente","Max download speed":"Velocidade máxima para descargas","Max upload speed":"Velocidade máxima para envios","Menu":"Menu","Microsoft SQL Database:":"Base de dados Microsoft SQL:","Microsoft SQL Databases":"Bases de dados Microsoft SQL","Minimum redundancy":"Redundância mínima","Minimum redundancy is 1.0":"A redundância mínima é 1.0","Minutes":"Minutos","Missing name":"Nome em falta","Missing passphrase":"Frase-passe inexistente","Missing sources":"Fontes em falta","Modified":"Modificado","Mon":"Seg","Months":"Meses","Move existing database":"Mover base de dados existente","Move failed:":"Falha ao mover:","My Documents":"Meus documentos","My Music":"Minhas músicas","My Photos":"Minhas fotos","My Pictures":"Minhas imagens","Name":"Nome","Never":"Nunca","New user name is {{user}}.\nUpdated credentials to use the new limited user":"O novo nome de utilizador é {{user}}.\nAs credenciais foram atualizadas para usar o utilizador limitado","Next":"Seguinte","Next scheduled run:":"Próximo agendamento:","Next scheduled task:":"Próxima tarefa agendada:","Next task:":"Próxima tarefa:","Next time":"Próxima hora","No":"Não","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Não foi especificado nenhum certificado anteriormente, verifique com o administrador do servidor se a chave está correta: {{key}}\n\nQuer aprovar a chave de host reportada?","No editor found for the "{{backend}}" storage type":"Não foi encontrado nenhum editor para o tipo de armazenamento "{{backend}}"","No encryption":"Sem encriptação","No items selected":"Nenhum item selecionado","No items to restore, please select one or more items":"Não existem itens a restaurar, selecione um ou mais itens","No passphrase entered":"Frase-passe não introduzida","No scheduled tasks":"Nenhuma tarefa agendada","Non-matching passphrase":"Disparidade de frases-passe","None / disabled":"Nenhum / desativado","Not using encryption":"Não usando encriptação","Nothing will be deleted. The backup size will grow with each change.":"Nada será eliminado. O tamanho da cópia de segurança crescerá com cada alteração.","OK":"Aceitar","Once there are more backups than the specified number, the oldest backups are deleted.":"Se existirem mais cópias de segurança do que o número especificado, as cópias de segurança mais antigas serão eliminadas.","OpenStack AuthURI":"URI de autenticação do OpenStack ","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Aberto","Operating System":"Sistema operativo","Operation":"Operação","Operations:":"Operações:","Optional authentication password":"Palavra-passe opcional para autenticação","Optional authentication username":"Nome de utilizador opcional para autenticação","Options":"Opções","Original location":"Localização original","Others":"Outras","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Com o tempo, as versões das cópias de segurança serão eliminadas automaticamente. Permanecerá uma cópia de segurança dos últimos 7 dias, das últimas 4 semanas e cada um dos últimos 12 meses. Haverá sempre pelo menos uma cópia de segurança.","Overwrite":"Substituir","Passphrase":"Frase-passe","Passphrase (if encrypted)":"Frase-passe (se encriptado)","Passphrase changed":"Frase-passe alterada","Passphrases are not matching":"Disparidade de frases-passe","Passphrases do not match":"As frases-passe não coincidem","Password":"Palavra-passe","Patching files with local blocks …":"A aplicar correcções aos ficheiros com blocos locais ...","Path":"Caminho","Path not found":"Caminho não encontrado","Path on server":"Caminho no servidor","Path or subfolder in the bucket":"Caminho ou sub-pasta no 'bucket'","Pause":"Pausa","Pause after startup or hibernation":"Pausa após o arranque ou hibernação","Pause options":"Opções de pausa","Permissions":"Permissões","Pick location":"Escolher localização","Point to your backup files and restore from there":"Apontar para os ficheiros da cópia de segurança e restaurar a partir de lá","Port":"Porta","Prevent tray icon automatic log-in":"Impedir autenticação automática com o ícone da barra de tarefas","Previous":"Anterior","Progress:":"Progresso:","ProjectID is optional if the bucket exist":"ID do projeto é opcional se o 'bucket' já existir","Proprietary":"Proprietário","Purge Phase":"Fase de purgar","Purging files complete!":"A purga dos ficheiros está terminada!","Purging files …":"A eliminar ficheiros ...","Rebuilding local database …":"A recriar a base de dados local ...","Recreate (delete and repair)":"Recriar (eliminar e reparar)","Recreate Database Phase":"Fase de recriar base de dados","Recreating database …":"A recriar a base de dados","Registering temporary backup …":"A registar a cópia de segurança emporária ...","Relative paths not allowed":"Caminhos relativos não são permitidos","Reload":"Recarregar","Remote":"Remoto","Remote Path":"Caminho remoto","Remote Repository":"Repositório remoto","Remote path":"Caminho remoto","Remote repository":"Repositório remoto","Remote volume size":"Remover tamanho do volume","Remove":"Remover","Remove option":"Remover opção","Removed files":"Ficheiros removidos","Repair":"Reparar","Repair Phase":"Fase de reparar","Repairing database …":"A reparar a base de dados ...","Repeat Passphrase":"Repetição de frase-passe","Reporting:":"Reporte:","Reset":"Repor","Restore":"Restaurar","Restore complete!":"Restauro terminado!","Restore files":"Restaurar ficheiros","Restore files …":"Restaurar ficheiros ...","Restore from":"Restaurar de","Restore from backup configuration":"Restaurar de uma configuração de cópia de segurança","Restore options":"Opções de restauro","Restore read/write permissions":"Restaurar permissões de leitura/escrita","Restored Files":"Ficheiros restaurados","Restored Folders":"Pastas restauradas","Restored Symlinks":"Ligações de ficheiros restauradas","Restoring files …":"A restaurar ficheiros ...","Resume":"Retomar","Rewritten File Lists":"Listas de ficheiros reescritos","Run again every":"Executar a cada","Run now":"Executar agora","Running commandline entry":"A executar a entrada na linha de comandos","Running task:":"Tarefa em execução:","Running …":"A executar ...","S3 Compatible":"Compatível com S3","Same as the base install version: {{channelname}}":"Igual à versão de instalação base: {{channelname}}","Sat":"Sáb","Satellite":"Satélite","Save":"Guardar","Save and repair":"Guardar e reparar","Save different versions with timestamp in file name":"Guardar versões diferentes com marcas de hora no nome do ficheiro","Save immediately":"Guardar imediatamente","Scanning existing files …":"A analisar ficheiros existentes ...","Scanning for local blocks …":"A analisar blocos locais ...","Schedule":"Agendamento","Search":"Pesquisa","Search for files":"Pesquisar ficheiros","Seconds":"Segundos","Select a log level and see messages as they happen:":"Selecione um nível de registos e veja as mensagens conforme elas aparecem:","Select files":"Selecionar ficheiros","Server":"Servidor","Server and port":"Servidor e porta","Server hostname or IP":"Nome ou IP do servidor","Server is currently paused,":"O servidor está em pausa,","Server is currently paused, do you want to resume now?":"O servidor está em pausa, deseja continuar agora?","Server password":"Palavra-passe do servidor","Server paused":"Servidor em pausa","Server state properties":"Propriedades do estado do servidor","Settings":"Definições","Show":"Mostrar","Show advanced editor":"Mostrar editor avançado","Show hidden folders":"Mostrar pastas ocultas","Show log":"Mostrar registo","Show log …":"Mostrar registo ...","Show treeview":"Mostrar em árvore","Sia server password":"Palavra-passe do servidor Sia","Smart backup retention":"Retenção de cópia de segurança inteligente","Some OpenStack providers allow an API key instead of a password and tenant name":"Alguns fornecedores OpenStack permitem uma chave de API em vez de uma palavra-passe e o tenant (projeto)","Some S3 providers might only be compatible with a certain client library":"Alguns fornecedores de S3 podem ser compatíveis apenas com uma determinada biblioteca de clientesSome S3 providers might only be compatible with a certain client library","Source Data":"Dados de origem","Source Files":"Ficheiros de origem","Source data":"Dados de origem","Source folders":"Pastas de origem","Source:":"Origem:","Specific builds for developers only. Not for use with important data.":"Versões específicas apenas para programadores. Não destinadas a serem utilizadas com dados importantes.","Standard protocols":"Protocolos padrão","Start":"Iniciar","Starting backup …":"A iniciar a cópia de segurança ...","Starting restore …":"A iniciar o restauro ...","Starting the restore process …":"A iniciar o processo de restauro ...","Stop after the current file":"Parar após o ficheiro atual","Stop running backup":"Parar cópia de segurança em execução","Stop running task":"Parar tarefa em execução","Stopping after the current file:":"A parar após o ficheiro atual:","Stopping task:":"Parar tarefa:","Storage Type":"Tipo de armazenamento","Storage class":"Classe de armazenamento","Storage class for creating a bucket":"Classe de armazenamento para criar um 'bucket'","Stored":"Guardado","Strong":"Forte","Success":"Sucesso","Sun":"Dom","Symbolic link":"Ligação simbólica","System Files":"Ficheiros de sistema","System default ({{levelname}})":"Predefinição ({{levelname}})","System files":"Ficheiros do sistema","System info":"Informações do sistema","System properties":"Propriedades do sistema","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Tarefa em execução","Temporary Files":"Ficheiros temporários","Temporary files":"Ficheiros temporários","Test Phase":"Fase de teste","Test connection":"Testar ligação","Testing permissions …":"A verificar permissões ...","Testing …":"A verificar ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"O campo '{{fieldname}}' contém um carácter inválido: {{character}} (valor: {{value}}, índice: {{pos}})","The backup is missing, has it been deleted?":"Falta a cópia de segurança. Será que foi eliminada?","The backup was temporary and does not exist anymore, so the log data is lost":"A cópia de segurança era temporária e já não existe, por isso os dados de registo foram perdidos","The bucket name should be all lower-case, convert automatically?":"O nome do 'bucket' deve ser todo em minúsculas. Converter automaticamente?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"A configuração deve ser mantida de forma segura. Tem a certeza de que quer guardar um ficheiro não encriptado contendo as suas palavras-passe?","The dark theme (by Michal)":"Tema escuro (por Michal)","The default blue on white theme (by Alex)":"Azul em tema claro (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"A pasta {{folder}} não existe.\nCriar agora?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"A chave do host foi alterada, verifique com o administrador do servidor se está correta, caso contrário pode ter sido vítima de um ataque MAN-IN-THE MIDDLE.\n\nDeseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" pela chave do host REPORTADA: {{key}}?","The passwords do not match":"As palavras-passe não coincidem","The path does not appear to exist, do you want to add it anyway?":"Parece que o caminho não existe, quer adicioná-lo mesmo assim?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"O caminho não termina com um caractere '{{dirsep}}, o que significa que incluiu um ficheiro e não uma pasta.\n\nQuer incluir o ficheiro especificado?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"O caminho deve ser um caminho absoluto, ou seja, deve começar com uma barra inclinada '/'","The region parameter is only applied when creating a new bucket":"O parâmetro de região só é aplicado ao criar um novo 'bucket'","The region parameter is only used when creating a bucket":"O parâmetro de região só é usado na criação de um 'bucket'","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Não foi possível validar o certificado do servidor.\nQuer aprovar o certificado SSL com o hash: {{hash}}?","The storage class affects the availability and price for a stored file":"A classe de armazenamento afeta a disponibilidade e o preço de um ficheiro armazenado","The target folder contains encrypted files, please supply the passphrase":"A pasta de destino contém ficheiros encriptados. Forneça a frase-passe","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"O utilizador tem muitas permissões. Quer criar um novo utilizador limitado, com permissões apenas para o caminho selecionado?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Esta cópia de segurança foi criada noutro sistema operativo. A restauração dos ficheiros sem especificar uma pasta de destino pode fazer com que os ficheiros sejam restaurados em locais inesperados. Tem a certeza que quer continuar sem escolher uma pasta de destino?","This month":"Este mês","This week":"Esta semana","Throttle settings":"Definições de velocidade","Thu":"Qui","Time":"Hora","To File":"Para ficheiro","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Para confirmar que deseja eliminar todos os ficheiros remotos para \"{{nome}}\", insira a palavra abaixo","To export without a passphrase, uncheck the \"Encrypt file\" box":"Para exportar sem uma frase-passe, desmarque a caixa \"Encriptar ficheiro\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Para evitar vários ataques baseados em DNS, o Duplicati limita os nomes de host permitidos aos que estão listados aqui. O acesso IP direto e o host local são sempre permitidos. Podem ser fornecidos vários nomes de host com um separador de ponto-e-vírgula. Se qualquer um dos nomes de host permitidos for um asterisco (*), todos os nomes de host serão permitidos e esse recurso será desativado. Se o campo estiver vazio, apenas o endereço IP e o acesso ao host local serão permitidos.","Today":"Hoje","Trust host certificate?":"Confiar no certificado do host?","Trust server certificate?":"Confiar no certificado do servidor?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Experimente as novas funcionalidades em que estamos a trabalhar. Atualmente, a versão mais estável disponível. Teste restaurar dados antes de usar isto em ambientes de produção.","Tue":"Terça","Type passphrase here.":"Digite a frase-passe aqui.","Type to highlight files":"Digite para destacar ficheiros","Unknown backup size and versions":"Tamanho e versões da cópia de segurança desconhecidos","Until resumed":"Até retormar","Update channel":"Canal de atualização","Update failed:":"Falha ao atualizar:","Updating with existing database":"A atualizar base de dados existente","Uploaded files":"Ficheiros enviados","Uploading verification file …":"A enviar ficheiro de verificação ...","Usage statistics":"Estatísticas de utilização","Usage statistics, warnings, errors, and crashes":"Estatísticas de utilização, avisos e erros","Use SSL":"Usar SSL","Use existing database?":"Usar base de dados existente?","Use weak passphrase":"Utilizar frase-passe fraca","Useless":"Inútil","User data":"Dados do utilizador","User domain name":"Nome do domínio do utilizador","User has too many permissions":"Utilizador com demasiadas permissões","User interface settings":"Definições da interface","Username":"Nome de utilizador","Vacuuming database …":"A limpar a base de dados ...","Validating …":"A validar ...","Verifications":"Verificações","Verify files":"A verificar ficheiros","Verifying answer":"A verificar resposta","Verifying backend data …":"A verificar dados remotos ...","Verifying files …":"A verificar ficheiros ...","Verifying remote data …":"A verificar dados remotos ...","Verifying restored files …":"A verificar ficheiros restaurados ...","Verifying …":"A verificar ...","Version ID":"ID da versão","Very strong":"Muito forte","Very weak":"Muito fraca","Visit us on":"Visite-nos em","WARNING: This will prevent you from restoring the data in the future.":"AVISO: isto impedirá que possa restaurar os dados no futuro.","Waiting for task to begin":"À espera para iniciar a tarefa","Waiting for upload to finish …":"A aguardar que o envio termine ...","Warnings, errors and crashes":"Avisos e erros","We recommend that you encrypt all backups stored outside your system":"Recomendamos que encripte todas as cópias de segurança armazenadas fora do seu sistema","Weak":"Fraca","Weak passphrase":"Frase-passe fraca","Wed":"Qua","Weeks":"Semanas","Where do you want to restore from?":"De onde quer restaurar?","Where do you want to restore the files to?":"Para onde quer restaurar os ficheiros?","Years":"Anos","Yes":"Sim","Yes, I have stored the passphrase safely":"Sim, eu armazenei a frase-passe de forma segura","Yes, I understand the risk":"Sim, eu entendo os riscos","Yes, I'm brave!":"Sim, sou valente!","Yes, please break my backup!":"Sim, por favor estraga a minha cópia de segurança!","Yesterday":"Ontem","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Está a alterar o caminho da base de dados para longe de uma base de dados existente.\nTem a certeza que quer isso?","You are currently running {{appname}} {{version}}":"Está a executar o {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Mudou o modo de encriptação. Isso pode estragar algo. Em vez disso é recomendável fazer uma cópia de segurança.","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Alterou a frase-passe, que não é suportada. Em vez disso é recomendável criar uma cópia de segurança.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Escolheu não encriptar a cópia de segurança. É recomendável encriptar todos os dados armazenados num servidor remoto.","You have chosen to restore to a new location, but not entered one":"Escolheu restaurar para uma localização distinta mas não a indicou","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Gerou uma frase-passe segura. Certifique-se que fez uma cópia da frase-passe, uma vez que os dados não podem ser recuperados se perder a frase-passe.","You must choose at least one source folder":"Tem que escolher, pelo menos, uma pasta de origem","You must enter a domain name to use v3 API":"Tem de introduzir um nome de domínio para usar a API v3","You must enter a name for the backup":"Tem que introduzir o nome para a cópia de segurança","You must enter a passphrase or disable encryption":"Tem de introduzir uma frase-passe ou desativar a encriptação","You must enter a password to use v3 API":"Tem de introduzir uma palavra-passe para usar a API v3","You must enter a positive number of backups to keep":"Tem que introduzir um número positivo para as cópias de segurança a manter","You must enter a tenant (aka project) name to use v3 API":"Te de introduzir um tenant (ou seja projeto) para usar a API v3","You must enter a valid duration for the time to keep backups":"Tem de introduzir uma duração de tempo válida durante a qual deve manter as cópias de segurança","You must enter a valid retention policy string":"Tem de inserir uma cadeia de política de retenção válida","You must fill in the password":"Tem que preencher uma palavra-passe","You must fill in the server name or address":"Tem que preencher o nome ou endereço do servidor","You must fill in the username":"Tem que preencher o nome de utilizador","You must fill in {{field}}":"Tem que preencher {{field}}","You must select or fill in the AuthURI":"Tem que selecionar ou preencher o AuthURI","You must select or fill in the server":"Tem que selecionar ou preencher o servidor","You must specify a path":"Tem que especificar o caminho","Your files and folders have been restored successfully.":"Os seus ficheiros e pastas foram restaurados com sucesso.","Your passphrase is easy to guess. Consider changing passphrase.":"A sua frase-passe é muito fraca. Deve alterar para uma mais forte.","bucket/folder/subfolder":"'bucket'/pasta/sub-pasta","byte":"byte","byte/s":"byte/s","custom":"personalizado","resume now":"retomar agora","unless you are explicitly specifying --group-id":"a não ser que esteja a especificar explicitamente --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} foi inicialmente desenvolvido por {{dev1}} e {{dev2}}. {{appname}} pode ser descarregado em {{websitename}}. {{appname}} é licenciado nos termos da {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} ficheiros ({{size}}) por enviar {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versão","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versões","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} versões"],"{{number}} Hour":"{{number}} hora","{{number}} Hours":"{{number}} horas","{{number}} Minutes":"{{number}} minutos","{{time}} (took {{duration}})":"{{time}} (demorou {{duration}})"}); + gettextCatalog.setStrings('ro', {"- pick an option -":"- alegeți o opțiune -","...loading...":"...se încarcă...","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"Politica AWS IAM","About":"Despre","About {{appname}}":"Despre {{appname}}","Access Key":"Cheie de acces","Access denied":"Acces interzis","Access to user interface":"Accesul la interfața cu utilizatorul","Account name":"Nume de cont","Add a new backup":"Adăugați o copie de rezervă nouă","Add a path directly":"Adăugați direct o cale","Add advanced option":"Adăugați opțiunea avansată","Add backup":"Adăugați o copie de rezervă","Add filter":"Adăugați un filtru","Add path":"Adaugă calea","Added":"Adăugat","Adjust bucket name?":"Modificați numele găleții?","Advanced Options":"Opțiuni avansate","Advanced options":"Opțiuni avansate","Advanced:":"Avansat:","All Hyper-V Machines":"Toate mașinile Hyper-V","All Microsoft SQL Databases":"Toate bazele de date Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Toate rapoartele de utilizare sunt trimise anonim și nu conțin informații personale. Acestea conțin informații despre hardware și sistemul de operare, tipul de backend, durata de copiere, dimensiunea generală a datelor sursă și datele similare. Ele nu conțin căi, nume de fișiere, nume de utilizator, parole sau alte informații sensibile similare.","Allow remote access (requires restart)":"Permiteți accesul de la distanță (necesită repornire)","Allowed days":"Zile permise","An existing file was found at the new location":"Un fișier existent a fost găsit la noua locație","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Un fișier existent a fost găsit la noua locație\nSigur doriți ca baza de date să indice un fișier existent?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"O bază de date locală existentă pentru stocare a fost găsită.\nReutilizarea bazei de date va permite instanțelor de linie de comandă și server să funcționeze pe aceeași stocare la distanță.\n\n Doriți să utilizați baza de date existentă?","Anonymous usage reports":"Rapoarte de utilizare anonime","Applications":"Aplicații","As Command-line":"Ca linie de comandă","AuthID":"authId","Authentication password":"Parola de autentificare","Authentication username":"Numele de utilizator de autentificare","Autogenerated passphrase":"Fraza de acces generată automat","B2 Application Key":"B2 cheie de aplicație","B2 Cloud Storage Account ID":"B2 ID-ul contului de stocare în cloud","B2 Cloud Storage Application Key":"B2 Cheia aplicației de stocare cloud","Back":"Înapoi","Backup destination":"Destinație de rezervă","Backup location":"Locație de rezervă","Backup:":"Copie de rezervă:","Beta":"Beta","Broken access":"Accesul spart","Browse":"Naviga","Browser default":"Browser default","Bucket create location":"Locația unde va fi creată găleata","Bucket name":"Numele găleții","Bucket storage class":"Clasa de stocare a găleții","Building list of files to restore …":"Creez lista de fișiere de restaurat ...","Building partial temporary database …":"Creez o bază de date parțială temporară ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Prin permiterea accesului de la distanță, se configurează serverul sa asculte cererile oricăror mașini din rețeaua ta. Dacă activezi această opțiune, asigură-te că folosești mereu calculatorul într-o rețea protejată de firewall.","Cache Files":"Încarcă fișierele în avans","Canary":"Canar","Cancel":"Anulare","Cannot move to existing file":"Nu se poate muta la fișierul existent","Changelog":"Jurnal de modificări","Changelog for {{appname}} {{version}}":"Jurnal de modificări pentru {{appname}} {{version}}","Check failed:":"Verificarea a eșuat:","Check for updates now":"Verifică acum actualizările","Checking for updates …":"Caut versiuni noi ...","Chose a storage type to get started":"Alege un tip de stocare pentru a începe","Click the AuthID link to create an AuthID":"Faceți clic pe linkul AuthID pentru a crea un AuthID","Click to set throttle options":"Faceți clic pentru a seta opțiunile de accelerație","Commandline …":"Linie de comandă ...","Compact Phase":"Etapa de compactare","Compact now":"Compactează acum","Compacting remote data …":"Se compactează datele de la distanță ...","Complete log":"Jurnal complet","Completing backup …":"Se finalizează copia de rezervă ...","Completing previous backup …":"Se finalizează copia de rezervă anterioară ...","Computer":"Calculator","Configuration file:":"Fișier de configurare:","Configuration:":"Configurare:","Configure a new backup":"Configurați o copie de rezervă nouă","Confirm delete":"Confirmă ștergerea","Confirm encryption passphrase":"Confirmă parola de criptare","Confirm passphrase":"Confirmă parola","Confirmation required":"Confirmare Necesară","Connect":"Conectează","Connect now":"Conectează acum","Connecting to server …":"Se conectează la server ...","Connection lost":"Conexiunea a fost pierdută","Connection worked!":"Conexiunea a funcționat!","Container name":"Numele containerului","Container region":"Zona containerului","Continue":"Continuă","Continue without encryption":"Continuă fără criptare","Copied!":"Copiată!","Copy":"Copiază","Copy Destination URL to Clipboard":"Copiați adresa URL de destinație în Clipboard","Copy failed. Please manually copy the URL":"Copierea a eșuat. Copiați manual adresa URL","Core options":"Opțiuni centrale","Counting ({{files}} files found, {{size}})":"Numărătoare ({{fișiere}} fișiere găsite, {{size}})","Crashes only":"Doar eșecuri","Create bug report …":"Creează un raport de defecțiune","Create folder?":"Creează director?","Created new limited user":"S-a creat un nou utilizator cu drepturi limitate","Creating bug report …":"Se creează un raport de defecțiuni ...","Creating new user with limited access …":"Se creează un nou utilizator cu acces limitat ...","Creating target folders …":"Se creează directoarele destinație ...","Creating temporary backup …":"Se creează o copie de rezervă temporară ...","Current action:":"Acțiunea curentă:","Current file:":"Fișierul curent:","Current version is {{versionname}} ({{versionnumber}})":"Versiunea curentă este {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Conector S3 personalizat","Custom authentication url":"Adresă de autentificare personalizată","Custom backup retention":"Durată de retenție a copiei de rezervă personalizată","Custom location ({{server}})":"Locația particularizată ({{server}})","Custom region for creating buckets":"Regiunea personalizată pentru crearea de cupe","Custom region value ({{region}})":"Valoarea pentru regiunea particularizată ({{region}})","Custom server url ({{server}})":"Adresa URL a serverului personalizat ({{server}})","Custom storage class ({{class}})":"Clase de stocare personalizate ({{class}})","Database …":"Bază de date ...","Days":"Zile","Default":"Mod implicit","Default ({{channelname}})":"Implicit ({{nume_canal}})","Default excludes":"Excluderi implicite","Default options":"Opțiunile prestabilite","Delete":"Șterge","Delete Phase (Old Backup Versions)":"Etapa de ștergere (Versiuni Vechi ale Copiei de Rezervă)","Delete backup":"Șterge copie de rezervă","Delete backups that are older than":"Șterge copiile de rezervă mai vechi de:","Delete local database":"Șterge baza de date locală","Delete remote files":"Șterge fișierele la distanță","Delete the local database":"Ștergeți baza de date locală","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Ștergeți fișierele {{filecount}} ({{file size}}) din spațiul de stocare de la distanță?","Delete …":"Șterge ...","Deleted":"Șters","Deleted Versions":"Versiuni șterse","Deleted files":"Fișiere șterse","Deleting remote files …":"Se șterg fișierele de la distanță ...","Deleting unwanted files …":"Se șterg fișierele nedorite ...","Description (optional)":"Descriere (opțional)","Description:":"Descriere:","Desktop":"Spațiul de lucru","Destination":"Destinaţie","Destination path":"Calea destinație","Disabled":"Inactiv","Dismiss":"Închide","Dismiss all":"Închide tot","Display and color theme":"Afișare și temă de culoare","Do you really want to delete the backup: \"{{name}}\" ?":"Chiar vrei să ștergi copia de rezervă: \"{{name}}\"?","Do you really want to delete the local database for: {{name}}":"Chiar vrei să ștergi baza de date locală pentru: {{name}}","Done":"Terminat","Download":"Descarcă","Downloaded files":"Fișierele descărcate","Downloading files …":"Se descarcă fișierele ...","Downloading update…":"Se descarcă actualizarea ...","Duplicate option {{opt}}":"Opțiunea de duplicare {{opt}}","Duplicati Website":"Site-ul web al Duplicati","Duplicati forum":"Forum-ul Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati va rula la pornire, dar va rămâne pe pauză pentru durata specificată. Duplicati va folosi resurse minime și nu va fi creată nici o copie de rezervă.","Duration":"Durată","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Fiecare copie de rezervă are o bază de date locală asociată cu aceasta, care stochează informații despre copia de siguranță la distanță de pe aparatul local.\n            Când ștergeți o copie de rezervă, puteți șterge și baza de date locală fără a afecta capacitatea de a restabili fișierele la distanță.\n            Dacă utilizați baza de date locală pentru copii de rezervă din linia de comandă, ar trebui să păstrați baza de date.","Edit as list":"Editați ca listă","Edit as text":"Editați ca text","Encrypt file":"Criptați fișierul","Encryption":"Criptarea","Encryption changed":"Criptarea a fost modificată","End":"Sfârșit","Enter URL":"Introdu URL-ul","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Înregistrează manual o strategie de retenție. Literele sunt D/W/Y oentru zile/săptămâni/ani și U pentru nelimitat. Sintaxa este: 7D:1D,4W:1W,36M:1M. Acest exemplu păstreză o copie de rezervă pentru fiecare zi din următoarele 7 zile, una pentru următoarele 4 săptămâni și una pentru fiecare din următoarele 36 de luni. Acest lucru poate fi scris astfel 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Introduceți fraza de acces, dacă există","Enter configuration details":"Introduceți detaliile de configurare","Enter encryption passphrase":"Introduceți expresia de acces pentru criptare","Enter expression here":"Introduceți expresia aici","Enter the destination path":"Introduceți calea de destinație","Error":"Eroare","Error!":"Eroare!","Errors and crashes":"Erori și accidente","Examined":"Examinat","Exclude":"Exclude","Exclude directories whose names contain":"Excludeți directoarele ale căror nume conțin","Exclude expression":"Excludeți expresia","Exclude file":"Excludeți fișierul","Exclude file extension":"Excludeți extensia de fișier","Exclude files whose names contain":"Excludeți fișierele ale căror nume conțin","Exclude folder":"Excludeți dosarul","Exclude regular expression":"Excludeți expresia regulată","Existing file found":"Fișierul existent găsit","Experimental":"Experimental","Export":"Export","Export backup configuration":"Exportați configurația de backup","Export configuration":"Exportați configurația","FTP (Alternative)":"FTP (alternativă)","Failed to build temporary database: {{message}}":"Eroare la crearea bazei de date temporare: {{message}}","Failed to connect:":"Eroare de conexiune:","Failed to connect: {{message}}":"Nu s-a putut conecta: {{message}}","Failed to delete:":"Nu sa șters:","Failed to fetch path information: {{message}}":"Nu s-a putut obține informații despre cale: {{message}}","Failed to read backup defaults:":"Nu au putut fi citite valorile implicite de rezervă:","Failed to restore files: {{message}}":"Nu sa reușit restaurarea fișierelor: {{message}}","Failed to save:":"Salvarea nu a reușit:","File":"Fişier","Files larger than:":"Fișiere mai mari decât:","Filters":"Filtre","Finished!":"Terminat!","First run setup":"Prima configurare","Folder":"Pliant","Folder path":"Dosarul de cale","Fri":"Vi","GByte":"GByte","GByte/s":"GByte / s","GCS Project ID":"ID de proiect GCS","General":"General","General backup settings":"Setări de rezervă generale","General options":"Optiuni generale","Generate":"Genera","Hidden files":"Fișiere ascunse","Hide":"Ascunde","Hide hidden folders":"Ascundeți folderele ascunse","Home":"Acasă","Hours":"ore","How do you want to handle existing files?":"Cum doriți să gestionați fișierele existente?","Hyper-V Machine":"Mașină Hyper-V","Hyper-V Machine:":"Mașina Hyper-V:","Hyper-V Machines":"Mașini Hyper-V","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Dacă o dată a fost ratată, lucrarea va funcționa cât mai curând posibil.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Dacă nu introduceți o cale, toate fișierele vor fi stocate în dosarul de conectare.\nEști sigur că asta vrei?","If you do not enter an API Key, the tenant name is required":"Dacă nu introduceți o cheie API, este necesar numele locatarului","Import":"Import","Import Destination URL":"Importați adresa URL de destinație","Import backup configuration":"Importați configurația de rezervă","Import from a file":"Importați dintr-un fișier","Include a file?":"Includeți un fișier?","Include expression":"Includeți expresia","Include regular expression":"Includeți expresia regulată","Incorrect answer, try again":"Răspuns incorect, încercați din nou","Information":"informație","Invalid characters in path":"Caractere nevalide în cale","Invalid retention time":"Timp de retenție nevalid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Este posibil să vă conectați la un FTP fără o parolă.\nSunteți sigur că serverul FTP acceptă login-urile fără parolă?","KByte":"kByte","KByte/s":"KByte / s","Language in user interface":"Limba în interfața cu utilizatorul","Last month":"Luna trecuta","Latest":"Cele mai recente","Libraries":"Biblioteci","Load a configuration from an exported job or a storage provider":"Încărcați o configurație dintr-o lucrare exportată sau dintr-un furnizor de stocare","Load destination from an exported job or a storage provider":"Încărcați destinația dintr-o lucrare exportată sau dintr-un furnizor de stocare","Load older data":"Încărcați date mai vechi","Local database path:":"Calea bazei de date locale:","Local storage":"Depozit local","Location":"Locație","Location where buckets are created":"Locația în care sunt create găleți","Log data for {{Backup.Backup.Name}}":"Date din jurnal pentru {{Backup.Backup.Name}} ","Log data from the server":"Datele din jurnal de pe server","Log out":"Deconectați-vă","MByte":"MByte","MByte/s":"MByte / s","Maintenance":"întreținere","Manually type path":"Trasează manual calea","Max download speed":"Viteză maximă de descărcare","Max upload speed":"Viteză maximă de încărcare","Menu":"Meniul","Microsoft SQL Database:":"Microsoft SQL Database:","Microsoft SQL Databases":"Baze de date Microsoft SQL","Minimum redundancy":"Redundanță minimă","Minimum redundancy is 1.0":"Redundanța minimă este de 1,0","Minutes":"Minute","Missing name":"Lipsește numele","Missing passphrase":"Fraza de acces lipsă","Missing sources":"Sursa lipsă","Mon":"Mon","Months":"Luni","Move existing database":"Mutați baza de date existentă","Move failed:":"Mutarea a eșuat:","My Documents":"Documentele mele","My Music":"Muzica mea","My Photos":"Fotografiile mele","My Pictures":"Pozele mele","Name":"Nume","Never":"Nu","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Numele noului utilizator este {{user}}.\nAu fost aprobate informațiile pentru a utiliza noul utilizator limitat","Next":"Următor →","Next scheduled run:":"Următorul programat:","Next scheduled task:":"Următoarea sarcină programată:","Next task:":"Următoarea sarcină:","Next time":"Data viitoare","No":"Nu","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Niciun certificat nu a fost specificat anterior, verificați cu administratorul serverului că cheia este corectă: {{key}}\n\nDoriți să aprobați cheia de gazdă raportată?","No editor found for the "{{backend}}" storage type":"Nu a fost găsit un editor pentru tipul de stocare 6118489 _ {{backend}} "","No encryption":"Nu există criptare","No items selected":"Nu au fost selectate elemente","No items to restore, please select one or more items":"Nu există elemente pentru restaurare, selectați unul sau mai multe elemente","No passphrase entered":"Nu a fost introdusă nici o expresie de acces","No scheduled tasks":"Nu există sarcini programate","Non-matching passphrase":"Fraza de acces fără potrivire","None / disabled":"Nici unul / dezactivat","OK":"O.K","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Operations:":"Operații:","Optional authentication password":"Parola de autentificare opțională","Optional authentication username":"Nume de utilizator opțional de autentificare","Options":"Opțiuni","Original location":"Locația originală","Others":"Alții","Overwrite":"Suprascriere","Passphrase":"o expresie de acces","Passphrase (if encrypted)":"Fraza de acces (dacă este criptată)","Passphrase changed":"Fraza de acces a fost modificată","Passphrases are not matching":"Frazele de acces nu se potrivesc","Password":"Parola","Path not found":"Calea nu a fost găsită","Path on server":"Cale pe server","Path or subfolder in the bucket":"Cale sau subfolder în găleată","Pause":"Pauză","Pause after startup or hibernation":"Întrerupeți după pornire sau hibernare","Pause options":"Opțiunile de întrerupere","Permissions":"Permisiuni","Pick location":"Alegeți locația","Point to your backup files and restore from there":"Indicați fișierele de rezervă și restaurați-le de acolo","Port":"Port","Previous":"Anterior","ProjectID is optional if the bucket exist":"ID-ul proiectului este opțional dacă există o cupă","Proprietary":"Proprietate","Recreate (delete and repair)":"Refaceți (ștergeți și reparați)","Relative paths not allowed":"Căile relative nu sunt permise","Reload":"Reîncarcă","Remote":"la distanta","Remove":"Elimina","Remove option":"Eliminați opțiunea","Repair":"Reparație","Repeat Passphrase":"Repetați expresia de acces","Reporting:":"Raportarea:","Reset":"restabili","Restore":"Restabili","Restore files":"Restaurați fișierele","Restore from":"Restaurați de la","Restore from backup configuration":"Restabiliți din configurația de backup","Restore options":"Restaurați opțiunile","Restore read/write permissions":"Restaurați permisiunile de citire / scriere","Resume":"Relua","Run again every":"Rulați din nou fiecare","Run now":"Fugiți acum","Running commandline entry":"Rulează intrarea în linia de comandă","Running task:":"Sarcina de funcționare:","S3 Compatible":"S3 Compatibil","Same as the base install version: {{channelname}}":"La fel ca versiunea de instalare de bază: {{channelname}}","Sat":"Sat","Save":"Salvați","Save and repair":"Salvați și reparați","Save different versions with timestamp in file name":"Salvați diferite versiuni cu marca de timp în numele fișierului","Save immediately":"Salvați imediat","Schedule":"Programa","Search":"Căutare","Search for files":"Căutați fișiere","Seconds":"secunde","Select a log level and see messages as they happen:":"Selectați un nivel de jurnal și vedeți mesajele așa cum se întâmplă:","Select files":"Selectati fisierele","Server":"Server","Server and port":"Server și port","Server hostname or IP":"Server hostname sau IP","Server is currently paused,":"Serverul este în prezent întrerupt,","Server is currently paused, do you want to resume now?":"Serverul este în prezent întrerupt, doriți să îl reluați acum?","Server password":"Parola serverului","Server paused":"Serverul a fost întrerupt","Server state properties":"Proprietăți stare server","Settings":"Setări","Show":"Spectacol","Show advanced editor":"Afișați editorul avansat","Show hidden folders":"Afișați dosarele ascunse","Show log":"Arată jurnal","Show treeview":"Afișați arborele","Sia server password":"Parola serverului Sia","Some OpenStack providers allow an API key instead of a password and tenant name":"Unii furnizori OpenStack permit o cheie API în locul unei parole și a unui nume de chiriaș","Source Data":"Datele sursă","Source data":"Datele sursă","Source folders":"Sursă de directoare","Source:":"Sursă:","Standard protocols":"Protocoale standard","Stop after the current file":"Opriți după fișierul curent","Stop running backup":"Nu mai rulați backupul","Stop running task":"Opriți executarea sarcinii","Stopping task:":"Oprire:","Storage Type":"Tip de stocare","Storage class":"Clasă de stocare","Storage class for creating a bucket":"Clasă de stocare pentru crearea unei găleți","Stored":"stocate","Strong":"Puternic","Success":"Succes","Sun":"Soare","Symbolic link":"Link-uri simbolice","System default ({{levelname}})":"Implicit în sistem ({{levelname}})","System files":"Fișiere de sistem","System info":"Informatie de sistem","System properties":"Proprietatile sistemului","TByte":"TByte","TByte/s":"TByte / s","Task is running":"Sarcina se execută","Temporary files":"Fișiere temporare","Test connection":"Test de conexiune","The bucket name should be all lower-case, convert automatically?":"Numele găleții ar trebui să fie toate literele mici, să se convertească automat?","The dark theme (by Michal)":"Tema intunecata (de Michal)","The default blue on white theme (by Alex)":"Culoarea albastră implicită pe alb (de Alex)","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Cheia gazdă a fost modificată, verificați-vă cu administratorul serverului dacă aceasta este corectă, altfel ați putea fi victima unui atac MAN-IN-THE-MIDDLE.\n\nDoriți să ÎNLOCUIți cheia gazdă CURRENT \"{{prev}}\" cu cheia gazdă REPORTED: {{key}}?","The path does not appear to exist, do you want to add it anyway?":"Calea nu pare să existe, vreți să o adăugați oricum?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Calea nu se termină cu un caracter {{dirsep}}, ceea ce înseamnă că includeți un fișier, nu un dosar.\n\nDoriți să includeți fișierul specificat?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Calea trebuie să fie o cale absolută, adică trebuie să pornească cu o slash '/'","The region parameter is only applied when creating a new bucket":"Parametrul regiune se aplică numai când se creează o nouă găleată","The region parameter is only used when creating a bucket":"Parametrul regiune este utilizat numai când creați o găleată","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Certificatul de server nu a putut fi validat.\nDoriți să aprobați certificatul SSL cu hash: {{hash}}?","The storage class affects the availability and price for a stored file":"Clasa de stocare afectează disponibilitatea și prețul unui fișier stocat","The target folder contains encrypted files, please supply the passphrase":"Dosarul țintă conține fișiere criptate, furnizați expresia de acces","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Utilizatorul are prea multe permisiuni. Doriți să creați un nou utilizator limitat, cu permisiuni numai pentru calea selectată?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Această copie de siguranță a fost creată pe un alt sistem de operare. Restaurarea fișierelor fără specificarea unui dosar de destinație poate determina refacerea fișierelor în locuri neașteptate. Sigur doriți să continuați fără a alege un dosar de destinație?","This month":"Luna aceasta","This week":"Săptămâna aceasta","Throttle settings":"Setările clapetei","Thu":"Thu","To File":"La dosar","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Pentru a confirma că doriți să ștergeți toate fișierele la distanță pentru \"{{name}}\", introduceți cuvântul pe care îl vedeți mai jos","To export without a passphrase, uncheck the \"Encrypt file\" box":"Pentru a exporta fără o expresie de acces, debifați caseta \"Criptare fișier\"","Today":"Astăzi","Trust host certificate?":"Trust gazdă certificat?","Trust server certificate?":"Certificat de server de încredere?","Tue":"Marti","Type to highlight files":"Tastați pentru a evidenția fișierele","Unknown backup size and versions":"Mărimea și versiunile de rezervă necunoscute","Until resumed":"Până la reluare","Update channel":"Actualizați canalul","Update failed:":"Actualizare esuata:","Updating with existing database":"Actualizarea cu baza de date existentă","Usage statistics":"Statistica utilizării","Usage statistics, warnings, errors, and crashes":"Statistici de utilizare, avertismente, erori și accidente","Use SSL":"Utilizați SSL","Use existing database?":"Utilizați baza de date existentă?","Use weak passphrase":"Utilizați fraza de acces slabă","Useless":"Inutil","User data":"Datele utilizatorului","User has too many permissions":"Utilizatorul are prea multe permisiuni","User interface settings":"Setările interfeței utilizatorului","Username":"Nume de utilizator","Verify files":"Verificați fișierele","Verifying answer":"Verificarea răspunsului","Very strong":"Foarte puternic","Very weak":"Foarte slab","Visit us on":"Vizitați-ne","WARNING: This will prevent you from restoring the data in the future.":"AVERTISMENT: Acest lucru vă va împiedica să restaurați datele în viitor.","Waiting for task to begin":"Se așteaptă ca sarcina să înceapă","Warnings, errors and crashes":"Avertizări, erori și accidente","We recommend that you encrypt all backups stored outside your system":"Vă recomandăm să criptați toate copiile de rezervă stocate în afara sistemului dvs.","Weak":"Slab","Weak passphrase":"Frază de acces slabă","Wed":"însura","Weeks":"săptămâni","Where do you want to restore from?":"De unde doriți să restaurați?","Where do you want to restore the files to?":"Unde doriți să restaurați fișierele?","Years":"Ani","Yes":"da","Yes, I have stored the passphrase safely":"Da, am stocat expresia de acces în siguranță","Yes, I'm brave!":"Da, sunt curajos!","Yes, please break my backup!":"Da, vă rog să întrerupeți backupul!","Yesterday":"Ieri","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Schimbați calea bazei de date departe de o bază de date existentă.\nEști sigur că asta vrei?","You are currently running {{appname}} {{version}}":"În prezent, executați {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Ați schimbat modul de criptare. Acest lucru poate sparge lucrurile. Sunteți încurajați să creați în schimb o copie de siguranță nouă","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Ați schimbat fraza de acces, care nu este acceptată. Sunteți încurajați să creați în schimb o copie de siguranță nouă.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Ați ales să nu criptați copia de rezervă. Criptarea este recomandată pentru toate datele stocate pe un server de la distanță.","You have chosen to restore to a new location, but not entered one":"Ați ales să restaurați o locație nouă, dar nu ați introdus una","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Ați generat o expresie de acces puternică. Asigurați-vă că ați făcut o copie sigură a expresiei de acces, deoarece datele nu pot fi recuperate dacă pierdeți expresia de acces.","You must choose at least one source folder":"Trebuie să alegeți cel puțin un dosar sursă","You must enter a name for the backup":"Trebuie să introduceți un nume pentru copia de rezervă","You must enter a passphrase or disable encryption":"Trebuie să introduceți o expresie de acces sau să dezactivați criptarea","You must enter a positive number of backups to keep":"Trebuie să introduceți un număr pozitiv de copii de rezervă pe care să le păstrați","You must enter a valid duration for the time to keep backups":"Trebuie să introduceți o durată valabilă pentru timpul necesar pentru a păstra copii de rezervă","You must fill in the password":"Trebuie să completați parola","You must fill in the server name or address":"Trebuie să completați numele sau adresa serverului","You must fill in the username":"Trebuie să completați numele de utilizator","You must fill in {{field}}":"Trebuie să completați {{field}}","You must select or fill in the AuthURI":"Trebuie să selectați sau să completați AuthURI","You must select or fill in the server":"Trebuie să selectați sau să completați serverul","You must specify a path":"Trebuie să specificați o cale","Your files and folders have been restored successfully.":"Fișierele și folderele dvs. au fost restaurate cu succes.","Your passphrase is easy to guess. Consider changing passphrase.":"Fraza de acces este ușor de ghicit. Luați în considerare schimbarea expresiei de acces.","bucket/folder/subfolder":"cupă pentru excavat / folder / subfolder","byte":"octet","byte/s":"byte / s","custom":"personalizat","resume now":"reluați acum","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} a fost dezvoltat în primul rând prin {{dev1}} și {{dev2}} . {{appname}} poate fi descărcat de la {{sitename}} . {{appname}} este licențiat sub {{licensename}} .","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fișiere ({{size}}) pentru a merge {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} versiune","{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} Versiuni","{{item.Backup.Metadata.TargetSizeString}} / {{$ count}} Versiuni"],"{{number}} Hour":"{{număr}} oră","{{number}} Minutes":"{{număr}} Minute","{{time}} (took {{duration}})":"{{time}} (a luat {{duration}})"}); + gettextCatalog.setStrings('ru', {"- pick an option -":"- выберите параметр -","...loading...":"...загрузка...","API key":"Ключ API","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"О программе","About {{appname}}":"О {{appname}}","Access Key":"Ключ доступа","Access denied":"Доступ запрещен","Access grant":"Разрешение на доступ","Access to user interface":"Доступ в веб-интерфейс","Account name":"Имя учётной записи","Add a new backup":"Создать новую резервную копию","Add a path directly":"Добавить путь непосредственно","Add advanced option":"Добавить расширенный параметр","Add backup":"Добавить резервную копию","Add filter":"Добавить фильтр","Add path":"Добавить путь","Added":"Добавлено","Adjust bucket name?":"Изменить имя блока?","Advanced Options":"Расширенные параметры","Advanced options":"Расширенные параметры","Advanced:":"Дополнительно:","All Hyper-V Machines":"Все виртуальные машины Hyper-V","All Microsoft SQL Databases":"Все базы данных Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Все отчеты отправляются анонимно и не включают каких-либо персональных данных. Они содержат информацию об аппаратной конфигурации и операционной системе, типе бэкэнда, продолжительности резервного копирования, а также общий размер резервируемых данных и другие подобные данные. Они не включают пути или имена файлов, имена пользователей, пароли или любую другую конфиденциальную информацию.","Allow remote access (requires restart)":"Разрешить удалённый доступ (потребуется перезапуск)","Allowed days":"Разрешенные дни","An existing file was found at the new location":"Существующий файл был найден по новому пути","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Существующий файл был найден по новому пути\nВы точно хотите, чтобы база данных указывала на существующий файл?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Была обнаружена локальная база данных для хранилища.\nПовторное использование базы данных позволит экземплярам командной строки и сервера работать на одном и том же удаленном хранилище.\n\n Вы хотите использовать существующую базу данных?","Anonymous usage reports":"Анонимные отчёты об использовании","Applications":"Приложения","As Command-line":"Как командная строка","AuthID":"AuthID","Authentication method":"Метод аутентификации","Authentication method ({{auth_method}})":"Метод аутентификации ({{auth_method}})","Authentication password":"Пароль для аутентификации","Authentication username":"Имя пользователя для аутентификации","Autogenerated passphrase":"Сгенерированный пароль","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Назад","Backup complete!":"Резервное копирование завершено!","Backup destination":"Хранение резервной копии","Backup location":"Расположение резервной копии","Backup retention":"Хранение копий","Backup:":"Резервная копия:","Beta":"Beta","Broken access":"Битый доступ","Browse":"Обзор","Browser default":"Браузер по-умолчанию","Bucket create location":"Место создания блока","Bucket name":"Имя блока","Bucket storage class":"Класс хранения блока","Building list of files to restore …":"Создание списка файлов для восстановления…","Building partial temporary database …":"Создание временной базы данных…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Разрешая удаленный доступ, сервер видит запросы от любого компьютера в вашей сети. Если Вы включили эту опцию, убедитесь, что используете компьютер в защищенной сети, где есть надежный Файрвол.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"По умолчанию значок в трее открывает пользовательский интерфейс сразу без ввода каких либо данных. Это удобно для быстрого доступа к интерфейсу, но не безопасно, так как любой может получить доступ к зашифрованным резервным копиям. Если вам такое не нравится, включите эту опцию, предварительно указав пароль выше. ","Cache Files":"Кеш файлы","Canary":"Canary","Cancel":"Отмена","Cannot move to existing file":"Не могу переместить в существующий файл","Changelog":"История изменений","Changelog for {{appname}} {{version}}":"Список изменений для {{appname}} {{version}}","Check failed:":"Проверка не удалась:","Check for updates now":"Проверить наличие обновлений","Checking for updates …":"Проверка обновлений...","Chose a storage type to get started":"Для начала выберите тип хранилища","Click the AuthID link to create an AuthID":"Нажмите на ссылку AuthID для создания AuthID","Click to set throttle options":"Нажмите, чтобы установить параметры ограничения скорости","Client library to use":"Использовать клиентскую библиотеку","Commandline …":"Командная строка...","Compact Phase":"Компактная фаза","Compact now":"Уплотнить сейчас","Compacting remote data …":"Сжатие удаленных данных…","Complete log":"Полный отчёт","Completing backup …":"Завершение резервного копирования…","Completing previous backup …":"Завершение предыдущего резервного копирования…","Computer":"Компьютер","Configuration file:":"Файл конфигурации:","Configuration:":"Настройка:","Configure a new backup":"Настройка новой резервной копии","Confirm delete":"Подтвердите удаление","Confirm encryption passphrase":"Подтвердите кодовую фразу шифрования","Confirm new password":"Подтверждение пароля","Confirm passphrase":"Подтвердите кодовую фразу","Confirmation required":"Необходимо подтверждение","Connect":"Подключение","Connect now":"Подключиться сейчас","Connecting to server …":"Подключение к серверу…","Connection lost":"Потеряно соединение","Connection worked!":"Подключение работает!","Container name":"Имя контейнера","Container region":"Регион контейнера","Continue":"Продолжить","Continue without encryption":"Продолжить без шифрования","Copied!":"Скопировано!","Copy":"Копировать","Copy Destination URL to Clipboard":"Скопировать URL-адрес назначения в буфер обмена","Copy failed. Please manually copy the URL":"Копирование не удалось. Скопируйте URL-адрес вручную","Core options":"Основные параметры","Counting ({{files}} files found, {{size}})":"Сканирование (найдено {{files}} файлов, {{size}})","Crashes only":"Только падения","Create bug report …":"Создать отчет об ошибке…","Create folder?":"Создать папку?","Created new limited user":"Создан новый ограниченный пользователь","Creating bug report …":"Создание отчета об ошибке…","Creating new user with limited access …":"Создание нового пользователя с ограниченным доступом…","Creating target folders …":"Создание целевых папок…","Creating temporary backup …":"Создание временной резервной копии…","Current action:":"Текущая операция:","Current file:":"Текущий файл:","Current version is {{versionname}} ({{versionnumber}})":"Текущая версия — {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Пользовательский S3 endpoint","Custom Satellite":"Пользовательский спутник","Custom Satellite ({{satellite}})":"Пользовательский спутник ({{satellite}})","Custom authentication url":"Пользовательский URL-адрес аутентификации","Custom backup retention":"Пользовательское","Custom location ({{server}})":"Пользовательское местоположение ({{server}})","Custom region for creating buckets":"Пользовательский регион для создания buckets","Custom region value ({{region}})":"Пользовательское значение региона ({{region}})","Custom server url ({{server}})":"Пользовательский URL-адрес сервера ({{server}})","Custom storage class ({{class}})":"Пользовательский класс хранения ({{class}})","Database …":"База данных…","Days":"Дней","Default":"По умолчанию","Default ({{channelname}})":"По умолчанию ({{channelname}})","Default excludes":"Исключения по-умолчанию","Default options":"Параметры по умолчанию","Delete":"Удалить","Delete Phase (Old Backup Versions)":"Этап удаления (старые версии резервного копирования)","Delete backup":"Удалить резервную копию","Delete backups that are older than":"Удалить копии старше","Delete local database":"Удалить локальную базу данных","Delete remote files":"Удалить файлы с диска","Delete the local database":"Удалить локальную базу данных","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Удалить {{filecount}} файлов ({{filesize}}) из удаленного хранилища?","Delete …":"Удалить…","Deleted":"Удалено","Deleted Versions":"Удалённые версии","Deleted files":"Удалённые файлы","Deleting remote files …":"Удаление \"удаленных\" файлов…","Deleting unwanted files …":"Удаление ненужных файлов…","Description (optional)":"Описание (опционально)","Description:":"Описание:","Desktop":"Рабочий стол","Destination":"Хранение","Destination path":"Путь назначения","Disabled":"Отключено","Dismiss":"Скрыть","Dismiss all":"Отклонить все","Display and color theme":"Отображение и цветовая тема","Do you really want to delete the backup: \"{{name}}\" ?":"Подтверждаете удаление плана резервного копирования: «{{name}}» ?","Do you really want to delete the local database for: {{name}}":"Вы действительно хотите удалить локальную базу данных для: {{name}}","Done":"Готово","Download":"Скачать","Downloaded files":"Загруженные файлы","Downloading files …":"Загрузка файлов…","Downloading update…":"Загрузка обновления…","Duplicate option {{opt}}":"Дублировать параметр {{opt}}","Duplicati Website":"Сайт Duplicati ","Duplicati forum":"Форум Duplicati","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati будет запускаться при старте системы, но останется приостановленным, используя минимум ресурсов и не выполняя резервное копирование.","Duration":"Продолжительность","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Каждый план резервного копирования создаёт локальную базу данных, в которой содержится информация о резервируемых файлах.\nУдаление плана резервного копирования и его локальной базы данных не влияет на возможность восстановления уже зарезервированных файлов.\nЕсли Вы планируете воспользоваться удаляемым планом в будущем через командную строку, то не рекомендуется удалять локальную базу данных.","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"Каждая резервная копия имеет локальную базу данных, которая хранит информацию о ней. Это ускоряет выполнение многих операций и сокращает объём передаваемых данных с удалённых серверов.","Edit as list":"Редактировать как список","Edit as text":"Редактировать как текст","Edit …":"Изменить... ","Encrypt file":"Шифровать файл","Encryption":"Шифрование","Encryption changed":"Шифрование изменено","Encryption passphrase":"Кодовая фраза для шифрования","End":"Конец","Enter URL":"Введите URL-адрес","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Схема такая. Есть заполнители D/W/Y/U соответсвенно день (D), неделя (W), год (Y), без ограничений (U). Например: 7D:1D,4W:1W,36M:1M\nВ этом примере сохраняется одна копия за каждые 7 дней, одна копия за 4 недели и одна копия за 36 месяцев. ","Enter backup passphrase, if any":"Введите пароль резервной копии, если таковой имеется","Enter configuration details":"Ввод сведений конфигурации","Enter encryption passphrase":"Введите пароль шифрования","Enter expression here":"Введите выражение здесь","Enter the destination path":"Введите путь назначения","Error":"Ошибка","Error!":"Ошибка!","Errors and crashes":"Ошибки и падения","Examined":"Проверено","Exclude":"Исключить","Exclude directories whose names contain":"Исключить каталоги, имена которых содержат","Exclude expression":"Выражение для исключения","Exclude file":"Исключить файл","Exclude file extension":"Исключить файловое расширение","Exclude files whose names contain":"Исключить файлы, имена которых содержат","Exclude filter group":"Исключить группу фильтров","Exclude folder":"Исключить папку","Exclude regular expression":"Регулярное выражение для исключения","Existing file found":"Найден существующий файл","Experimental":"Experimental","Export":"Экспорт","Export backup configuration":"Экспорт конфигурации резервного копирования","Export configuration":"Экспорт конфигурации","Export passwords":"Экспортировать пароли","Export …":"Экспорт...","Exporting …":"Экспортирование...","External link":"Внешняя ссылка","FTP (Alternative)":"FTP (Альтернативный)","Failed to build temporary database: {{message}}":"Не удалось построить временную базу данных: {{message}}","Failed to connect:":"Не удается подключиться:","Failed to connect: {{message}}":"Не удается подключиться: {{message}}","Failed to delete:":"Не удалось удалить:","Failed to fetch path information: {{message}}":"Не удалось получить сведения о пути: {{message}}","Failed to find backup:":"Не удалось найти резервную копию:","Failed to read backup defaults:":"Не удалось прочитать настройки по умолчанию для резервной копии:","Failed to restore files: {{message}}":"Не удалось восстановить файлы: {{message}}","Failed to save:":"Не удалось сохранить:","Fetching path information …":"Получение информации о пути…","File":"Файл","Files larger than:":"Файлы размером более:","Filters":"Фильтры","Finished!":"Готово!","First run setup":"Настройка при первом запуске","Folder":"Папка","Folder path":"Путь к папке","Fri":"Пт","GByte":"ГБ","GByte/s":"ГБ/сек","GCS Project ID":"GCS Project ID","General":"Общие","General backup settings":"Общие параметры резервного копирования","General options":"Основные параметры","Generate":"Сгенерировать","Generate IAM access policy":"Сгенерировать политики доступа IAM","Getting file versions …":"Получение версий файлов…","Group email":"Электронная почта группы","Hidden files":"Скрытые файлы","Hide":"Скрыть","Hide hidden folders":"Скрыть скрытые папки","Home":"Главная","Hostnames":"Имя хоста","Hours":"часов","How do you want to handle existing files?":"Как вы хотите обрабатывать существующие файлы?","Hyper-V Machine":"Hyper-V Машина","Hyper-V Machine:":"Hyper-V Машина:","Hyper-V Machines":"Hyper-V Машины","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Если дата была пропущена, задание будет выполнено как можно скорее.","If at least one newer backup is found, all backups older than this date are deleted.":"Если найдена резервная копия старше, чем указанное количество дней, недель и т.д., то они будут удалятся. ","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Если вы не введете путь, все файлы будут храниться в папке логина.\nВы уверены, что это то, что вы хотите?","If you do not enter an API Key, the tenant name is required":"Если вы не вводите ключ API, требуется имя арендатора","Import":"Импорт","Import Destination URL":"Импортировать URL-адрес назначения","Import backup configuration":"Импорт настройки резервной копии","Import from a file":"Импортировать из файла","Import metadata":"Импортировать метаданные","Importing …":"Импорт...","Include a file?":"Включить файл?","Include expression":"Выражение для включения","Include regular expression":"Регулярное выражение для включения","Incorrect answer, try again":"Неправильный ответ, попробуйте еще раз","Individual builds for developers only. Not for use with important data.":"Индивидуальные сборки только для разработчиков. Не рекомендуется использовать для сохранения важных данных.","Information":"Информация","Invalid characters in path":"Недопустимые символы в пути","Invalid retention time":"Недопустимое время хранения","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"К некоторым FTP возможно подключиться без пароля.\nВы уверены, что ваш FTP-сервер поддерживает вход без пароля?","KByte":"КБайт","KByte/s":"КБ/сек","Keep a specific number of backups":"Хранить в количестве","Keep all backups":"Хранить все копии","Keystone API version":"Версия Keystone API","Language in user interface":"Язык пользовательского интерфейса","Last month":"Последний месяц","Last successful backup:":"Последнее успешное резервное копирование:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Последнее успешное восстановление: {{time}} (took {{duration || '0 seconds'}})","Latest":"Последнее","Libraries":"Библиотеки","Listing backup dates …":"Отображать дату резервного копирования…","Listing remote files for purge …":"Показать список удаленных файлов после очистки…","Listing remote files …":"Вывод списка \"удаленных\" файлов…","Live":"Текущие","Load a configuration from an exported job or a storage provider":"Загрузить настройки из экспортированного задания или поставщика хранилища","Load destination from an exported job or a storage provider":"Загрузить назначение из экспортированного задания или поставщика хранилища","Load older data":"Загрузить ещё...","Loading …":"Загрузка...","Local database path:":"Путь локальной базы данных:","Local repository":"Локальный репозиторий","Local storage":"Локальное хранилище","Location":"Местоположение","Location where buckets are created":"Место где создаются buckets","Log data for {{Backup.Backup.Name}}":"Данные журнала для {{Backup.Backup.Name}}","Log data from the server":"Сообщения журнала сервера","Log out":"Выход","MByte":"Мбайт","MByte/s":"Мбайт/с","Maintenance":"Техническое обслуживание","Manually type path":"Ввести путь вручную","Max download speed":"Максимальная скорость загрузки","Max upload speed":"Максимальная скорость выгрузки","Menu":"Меню","Microsoft SQL Database:":"База данных Microsoft SQL:","Microsoft SQL Databases":"Баз данных Microsoft SQL","Minimum redundancy":"Минимальная избыточность","Minimum redundancy is 1.0":"Минимальная избыточность - 1.0","Minutes":"минут","Missing name":"Отсутствует имя","Missing passphrase":"Отсутствующие парольная фраза","Missing sources":"Отсутствуют источники","Modified":"Изменено","Mon":"Пн","Months":"Месяцев","Move existing database":"Перемещение существующей базы данных","Move failed:":"Перемещение не удалось:","My Documents":"Мои документы","My Music":"Моя музыка","My Photos":"Мои фотографии","My Pictures":"Мои Картинки","Name":"Имя","Never":"Никогда","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Новое имя пользователя — {{user}}.\nОбновлены учетные данные для использования нового пользователя с ограниченными правами","Next":"Далее","Next scheduled run:":"Следующий запуск:","Next scheduled task:":"Следующий запуск:","Next task:":"Следующая задача:","Next time":"В следующий раз","No":"Нет","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Сертификат не был указан ранее, пожалуйста проверьте с администратором сервера ключ: {{key}} \n\nВы хотите утвердить полученный ключ сервера?","No editor found for the "{{backend}}" storage type":"Не найден редактор для хранилища типа "{{backend}}"","No encryption":"Без шифрования","No items selected":"Элементы не выбраны","No items to restore, please select one or more items":"Нет элементов для восстановления, выберите один или несколько элементов","No passphrase entered":"Не введена кодовая фраза","No scheduled tasks":"Нет запланированных задач","Non-matching passphrase":"Кодовые фразы не совпадают","None / disabled":"Нет / отключено","Not using encryption":"Без шифрования","Nothing will be deleted. The backup size will grow with each change.":"Ничего не будет удалено. Размер резервной копии будет расти с каждым изменением.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"Когда количество резервных копий превышает указанное количество, самые старые резервные копии удаляются.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Открыто","Operating System":"Операционная Система","Operation":"Операция","Operations:":"Операции:","Optional authentication password":"Необязательный пароль аутентификации","Optional authentication username":"Необязательное имя пользователя","Options":"Параметры","Options added here are applied to all backups, but can be overridden in each individual backup.":"Указанные настройки будут применяться ко всем резервным копиям, но могут быть переопределены для каждой отдельной резервной копии.","Original location":"Исходное местоположение","Others":"Другие","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Со временем резервные копии будут удаляться автоматически. Останется по одной резервной копии за последние 7 дней, за последние 4 недели, за последний 12 месяцев. Всегда будет как минимум одна оставшаяся резервная копия.","Overwrite":"Перезаписать","Passphrase":"Кодовая фраза","Passphrase (if encrypted)":"Кодовая фраза (если зашифрован)","Passphrase changed":"Кодовая фраза изменена","Passphrases are not matching":"Кодовые фразы не совпадают","Passphrases do not match":"Парольные фразы не совпадают","Password":"Пароль","Patching files with local blocks …":"Исправление файлов локальными блоками…","Path":"Путь","Path not found":"Путь не найден","Path on server":"Путь на сервере","Path or subfolder in the bucket":"Путь или подпапка в bucket","Pause":"Пауза","Pause after startup or hibernation":"Отложенный запуск после включения или выхода из спящего режима","Pause options":"Параметры паузы","Permissions":"Разрешения","Pick location":"Выберите местоположение","Point to your backup files and restore from there":"Укажите место хранения резервной копии и восстановите данные из неё","Port":"Порт","Prevent tray icon automatic log-in":"Запретить автоматический вход из значка в трее","Previous":"Назад","Progress:":"Прогресс:","ProjectID is optional if the bucket exist":"ProjectID необязателен, если существует bucket","Proprietary":"Проприетарное","Purge Phase":"Стадия очистки","Purging files complete!":"Очистка файлов завершена!","Purging files …":"Очистка файлов...","Rebuilding local database …":"Восстановление локальной базы данных…","Recreate (delete and repair)":"Пересоздать (удалить и исправить)","Recreate Database Phase":"Этап восстановления базы данных","Recreating database …":"Восстановление базы данных…","Registering temporary backup …":"Регистрация временной резервной копии…","Relative paths not allowed":"Относительные пути не допускаются","Reload":"Обновить","Remote":"Удаленный","Remote Path":"Удаленный путь","Remote Repository":"Удаленный Репозиторий","Remote path":"Удаленный путь","Remote repository":"Удаленный репозиторий","Remote volume size":"Размер удаленного тома","Remove":"Удалить","Remove option":"Удалить параметр","Removed files":"Удаленные файлы","Repair":"Исправить","Repair Phase":"Период исправления","Repairing database …":"Восстановление базы данных…","Repeat Passphrase":"Повторить кодовую фразу","Reporting:":"Отчетность:","Reset":"Сбросить","Restore":"Восстановление","Restore complete!":"Восстановление завершено!","Restore files":"Восстановить файлы","Restore files …":"Восстановить файлы...","Restore from":"Восстановить из","Restore from backup configuration":"Восстановить из конфигурации резервной копии","Restore options":"Параметры восстановления","Restore read/write permissions":"Восстановить разрешения чтения/записи","Restored Files":"Восстановленные Файлы","Restored Folders":"Восстановленные Папки","Restored Symlinks":"Восстановленные Символические ссылки","Restoring files …":"Восстановление файлов…","Resume":"Продолжить","Rewritten File Lists":"Перезаписанные списки файлов","Run again every":"Запускать каждый","Run now":"Запустить сейчас","Running commandline entry":"Выполнение записи командной строки","Running task:":"Выполняемая задача:","Running …":"Запуск...","S3 Compatible":"S3 совместимый","Same as the base install version: {{channelname}}":"Такой же как в базовой версии: {{channelname}}","Sat":"Сб","Satellite":"Спутник","Save":"Сохранить","Save and repair":"Сохранить и исправить","Save different versions with timestamp in file name":"Сохранить различные версии с отметкой времени в имени файла","Save immediately":"Немедленно сохранить","Scanning existing files …":"Сканирование существующих файлов…","Scanning for local blocks …":"Сканирование локальных блоков…","Schedule":"Расписание","Search":"Поиск","Search for files":"Поиск файлов","Seconds":"Секунд","Select a log level and see messages as they happen:":"Выберите уровень журналирования для просмотра сообщений по мере их возникновения:","Select files":"Выбор файлов","Server":"Сервер","Server and port":"Сервер и порт","Server hostname or IP":"Имя сервера или IP","Server is currently paused,":"Сервер приостановлен,","Server is currently paused, do you want to resume now?":"Сервер в настоящее время приостановлен, вы хотите возобновить сейчас?","Server password":"Пароль сервера","Server paused":"Сервер приостановлен","Server state properties":"Свойства состояния сервера","Settings":"Настройки","Show":"Показать","Show advanced editor":"Текстовое отображение","Show hidden folders":"Показать скрытые папки","Show log":"Журнал","Show log …":"Показать журнал …","Show treeview":"Древовидное отображение","Sia server password":"Пароль сервера Sia","Smart backup retention":"Умное хранение копий","Some OpenStack providers allow an API key instead of a password and tenant name":"Некоторые провайдеры OpenStack позволяют использовать ключ API вместо имени клиента и пароля","Some S3 providers might only be compatible with a certain client library":"Некоторые поставщики S3 могут быть совместимы только с определенной клиентской библиотекой.","Source Data":"Исходные данные","Source Files":"Исходные Файлы","Source data":"Данные для резервирования","Source folders":"Исходные папки","Source:":"Источник:","Specific builds for developers only. Not for use with important data.":"Специальные сборки только для разработчиков. Не рекомендуется использовать для сохранения важных данных.","Standard protocols":"Стандартные протоколы","Start":"Начало","Starting backup …":"Запуск резервного копирования…","Starting restore …":"Начало восстановления…","Starting the restore process …":"Запуск процесса восстановления…","Stop after the current file":"Остановиться после текущего файла","Stop running backup":"Остановить резервное копирование","Stop running task":"Остановить задачу","Stopping after the current file:":"Остановка после текущего файла:","Stopping task:":"Остановка задачи:","Storage Type":"Тип хранилища","Storage class":"Класс хранилища","Storage class for creating a bucket":"Класс хранения для создания bucket","Stored":"Сохраненные","Strong":"Сильный","Success":"Успех","Sun":"Вс","Symbolic link":"Символическая ссылка","System Files":"Системные Файлы","System default ({{levelname}})":"По умолчанию ({{levelname}})","System files":"Системные файлы","System info":"Информация о системе","System properties":"Свойства системы","TByte":"ТБайт","TByte/s":"ТБайт/s","Task is running":"Выполняется задача","Temporary Files":"Временные Файлы","Temporary files":"Временные файлы","Test Phase":"Этап проверки","Test connection":"Проверить доступ","Testing permissions …":"Проверка разрешений…","Testing …":"Тестирование…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Поле '{{fieldname}}' содержит недопустимый символ: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Резервная копия не найдена. Возможно удалена.","The backup was temporary and does not exist anymore, so the log data is lost":"Резервная копия была временной и больше не существует, поэтому данные журнала отсутствуют.","The bucket name should be all lower-case, convert automatically?":"Имя bucket должно быть строчным, преобразовать автоматически?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Конфигурация должна быть защищена. Вы уверены, что хотите сохранить незашифрованным файл, в котором содержатся ваши пароли?","The dark theme (by Michal)":"Тёмная тема (от Michael)","The default blue on white theme (by Alex)":"Стандартная тема синий на белом (от Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Папка {{folder}} не существует. \nСоздать сейчас?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Ключ узла изменился, пожалуйста, проверьте у администратора сервера так ли это, в противном случае вы можете быть жертвой атаки MAN-IN-THE-MIDDLE.\n\nВы хотите ЗАМЕНИТЬ ваш ТЕКУЩИЙ ключ узла «{{prev}}» ПОЛУЧЕННЫМ ключом хоста: {{key}}?","The passwords do not match":"Пароли не совпадают","The path does not appear to exist, do you want to add it anyway?":"Путь, по-видимому, не существует, вы всё равно хотите его добавить?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Путь не заканчивается символом «{{dirsep}}», что означает, что вы включаете файл, а не папку.\n\nВы хотите включить указанный файл?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Путь должен быть абсолютным, то есть он должен начинаться с косой черты «/»","The region parameter is only applied when creating a new bucket":"Параметр «регион» применяется только при создании нового bucket","The region parameter is only used when creating a bucket":"Параметр «регион» используется только при создании bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Не удалось проверить сертификат сервера.\nВы хотите утвердить SSL-сертификат с хэшом: {{hash}}?","The storage class affects the availability and price for a stored file":"Класс хранилища влияет на доступность и цену сохраненного файла","The target folder contains encrypted files, please supply the passphrase":"Целевая папка содержит зашифрованные файлы, пожалуйста, укажите кодовую фразу","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Пользователь имеет слишком много прав. Вы хотите создать нового пользователя с ограниченными правами, с разрешениями только на выбранный путь?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Эта резервная копия была создана в другой операционной системе. Восстановление файлов без указания папки назначения может повлечь восстановление файлов в неожиданных местах. Вы уверены, что вы хотите продолжить без выбора папки назначения?","This month":"В этом месяце","This week":"На этой неделе","Throttle settings":"Параметры ограничения скорости","Thu":"Чт","Time":"Время","To File":"В файл","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Чтобы подтвердить, что вы хотите удалить все дистанционные файлы для «{{name}}», введите слово, которое вы видите ниже","To export without a passphrase, uncheck the \"Encrypt file\" box":"Чтобы экспортировать без кодовой фразы, снимите флажок «Зашифровать файл»","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Чтобы предотвратить различные атаки на основе DNS, Duplicati ограничивает допустимые имена хостов перечисленными здесь. Всегда разрешен прямой IP-доступ и localhost. Несколько имен хостов могут быть указаны через точку с запятой. Для доступа с любого хоста, указываем звездочку (*). Если оставить поле пустым, разрешен только IP-адрес и доступ к локальному хосту.","Today":"Сегодня","Trust host certificate?":"Доверять сертификату хоста?","Trust server certificate?":"Доверять сертификату сервера?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Попробуйте новые функции, над которыми мы работаем. На данный момент самая стабильная из доступных версий. Проведите тестовое восстановление данных перед использованием в производственной или в корпоративной сфере.","Tue":"Вт","Type passphrase here.":"Введите здесь кодовую фразу.","Type to highlight files":"Напишите для выделения файлов","Unknown backup size and versions":"Неизвестные размер резервной копии и версии","Until resumed":"До возобновления","Update channel":"Канал обновлений","Update failed:":"Обновление не удалось:","Updating with existing database":"Обновление с существующей базой данных","Uploaded files":"Загруженные файлы","Uploading verification file …":"Загрузить проверочный файл…","Usage statistics":"Статистика использования","Usage statistics, warnings, errors, and crashes":"Статистика использования, предупреждения, ошибки и падения","Use SSL":"Использовать SSL","Use existing database?":"Использовать существующую базу данных?","Use weak passphrase":"Использовать слабую кодовую фразу","Useless":"Бесполезно","User data":"Данные пользователя","User domain name":"Доменное имя пользователя","User has too many permissions":"Пользователь имеет слишком много разрешений","User interface settings":"Настройки интерфейса","Username":"Имя пользователя","Vacuuming database …":"Очистка базы данных…","Validating …":"Проверка…","Verifications":"Проверено","Verify files":"Проверить файлы","Verifying answer":"Проверка ответа","Verifying backend data …":"Проверка внутренних данных …","Verifying files …":"Проверка файлов…","Verifying remote data …":"Проверка удаленных данных…","Verifying restored files …":"Проверка восстановленных файлов…","Verifying …":"Проверка…","Version ID":"Version ID","Very strong":"Очень надёжный","Very weak":"Очень слабый","Visit us on":"Посетите нас на","WARNING: This will prevent you from restoring the data in the future.":"ВНИМАНИЕ: Файлы с диска удаляются навсегда в обход корзины!","Waiting for task to begin":"Ожидание начала задачи","Waiting for upload to finish …":"Ожидание завершения выгрузки…","Warnings, errors and crashes":"Предупреждения, ошибки и падения","We recommend that you encrypt all backups stored outside your system":"Мы рекомендуем зашифровать все резервные копии, хранящиеся вне вашей системы","Weak":"Слабый","Weak passphrase":"Слабая кодовая фраза","Wed":"Ср","Weeks":"Недель","Where do you want to restore from?":"Откуда вы хотите восстановить данные?","Where do you want to restore the files to?":"Куда вы хотите восстановить файлы?","Years":"Лет","Yes":"Да","Yes, I have stored the passphrase safely":"Да, я надёжно сохранил кодовую фразу","Yes, I understand the risk":"Да, я принимаю риск","Yes, I'm brave!":"Да, я смелый!","Yes, please break my backup!":"Да, пожалуйста, сломайте мою резервную копию!","Yesterday":"Вчера","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Вы меняете путь базы данных отличный от существующей базы данных.\nВы уверены, что это то, что вы хотите?","You are currently running {{appname}} {{version}}":"Вы используете {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Вы изменили режим шифрования. Это может что-нибудь сломать. Вместо этого вам лучше создать новую резервную копию","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Вы изменили кодовую фразу, но это не поддерживается. Вместо этого вам стоит создать новую резервную копию.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Вы выбрали не шифровать резервную копию. Шифрование рекомендовано для всех данных, хранящихся на удаленном сервере.","You have chosen to restore to a new location, but not entered one":"Вы выбрали новое место для восстановления, но не ввели его","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Вы использовали сильную парольную фразу. Пожалуйста, убедитесь, что вы надёжно сохранили парольную фразу, ибо восстановление данных невозможно в случае её утраты.","You must choose at least one source folder":"Вы должны выбрать по крайней мере одну исходную папку","You must enter a domain name to use v3 API":"Вы должны ввести доменное имя, чтобы использовать v3 API","You must enter a name for the backup":"Вам необходимо ввести имя резервной копии","You must enter a passphrase or disable encryption":"Вы должны ввести кодовую фразу или отключить шифрование","You must enter a password to use v3 API":"Вы должны ввести пароль, чтобы использовать v3 API","You must enter a positive number of backups to keep":"Необходимо ввести положительное число резервных копий для хранения","You must enter a tenant (aka project) name to use v3 API":"Вы должны ввести имя проекта, чтобы использовать v3 API","You must enter a valid duration for the time to keep backups":"Необходимо ввести допустимый срок времени хранения резервных копий","You must enter a valid retention policy string":"Необходимо ввести допустимое значение политики хранения","You must fill in the password":"Вы должны заполнить пароль","You must fill in the server name or address":"Вы должны заполнить имя сервера или адрес","You must fill in the username":"Вы должны заполнить имя пользователя","You must fill in {{field}}":"Вы должны заполнить {{field}}","You must select or fill in the AuthURI":"Вы должны выбрать или заполнить AuthURI","You must select or fill in the server":"Вы должны выбрать или заполнить сервер","You must specify a path":"Вы должны указать путь","Your files and folders have been restored successfully.":"Ваши файлы и папки были восстановлены успешно.","Your passphrase is easy to guess. Consider changing passphrase.":"Вашу кодовую фразу легко отгадать. Подумайте об изменении кодовой фразы.","bucket/folder/subfolder":"bucket/папка/подпапка","byte":"байт","byte/s":"байт/сек","custom":"пользовательские","resume now":"возобновить сейчас","unless you are explicitly specifying --group-id":"если вы явно не указываете --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"Основными разработчиками {{appname}} являются {{dev1}} и {{dev2}}. Последняя версия {{appname}} может быть загружена с сайта {{websitename}}. {{appname}} распространяется под лицензией {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} файлов ({{size}}) впереди {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версия","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версии","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версий","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} версий"],"{{number}} Hour":"{{number}} Часов","{{number}} Hours":"{{number}} Часов","{{number}} Minutes":"{{number}} минут","{{time}} (took {{duration}})":"{{time}} (заняло {{duration}})"}); gettextCatalog.setStrings('sk_SK', {"- pick an option -":"- zadajte voľbu -","...loading...":"...načítavam...","AWS Access ID":"AWS prístupové ID","AWS Access Key":"AWS prístupový kľúč","AWS IAM Policy":"AWS IAM Pravidlá","About":"O","About {{appname}}":"O {{appname}}","Access Key":"Prístupový kľúč","Access denied":"Prístup zakázaný","Access to user interface":"Prístup k používateľskému rozhraniu","Account name":"Užívateľské meno","Add a new backup":"Pridať novú zálohu","Add a path directly":"Pridajte cestu priamo","Add advanced option":"Pridať rozšírenú možnosť","Allowed days":"Povolené dni","AuthID":"AuthID","Authentication password":"Prístupové heslo","Authentication username":"Prístupové užívateľské meno","Autogenerated passphrase":"Autogenerácia hesla","Back":"Späť","Backup:":"Záloha:","Beta":"Beta","Canary":"Canary","Computer":"Počítač","Configuration:":"Konfigurácia:","Confirm encryption passphrase":"Potvrdenie šifrovacej frázy","Continue":"Pokračovať","Continue without encryption":"Pokračovať bez šifrovania","Copied!":"Skopírované!","Create folder?":"Vytvoriť adresár?","Days":"Dni","Delete":"Zmazať","Delete backup":"Zmazať zálohu","Do you really want to delete the backup: \"{{name}}\" ?":"Ozaj chcete zmazať zálohu: \"{{name}}\" ?","Duplicati Website":"Duplicati stránky","Encryption":"Šifrovanie","Enter URL":"Zadaj URL","Enter encryption passphrase":"Vložte šifrovacie heslo","Error":"Chyba","Error!":"Chyba!","Path":"Cesta"}); gettextCatalog.setStrings('sk', {"- pick an option -":"- vybrať možnosť -","...loading...":"...nahrávam...","AWS Access ID":"AWS Prístupové ID","AWS Access Key":"AWS Prístupový kľúč","AWS IAM Policy":"AWS IAM Politika","About":"o","About {{appname}}":"O {{appname}}","Access Key":"Prístupový kľúč","Access denied":"Prístup zamietnutý","Access to user interface":"Prístup k používateľskému rozhraniu","Account name":"Názov účtu","Add a new backup":"Pridať novú zálohu","Add a path directly":"Pridajte cestu priamo","Add advanced option":"Pridať rozšírenú možnosť","Add backup":"Pridať zálohu","Add filter":"Pridať filter","Add path":"Pridať cestu","Adjust bucket name?":"Nastaviť názov sektoru?","Advanced Options":"Pokročilé nastavenia","Advanced options":"Pokročilé nastavenia","Advanced:":"Pokročilé:","All Hyper-V Machines":"Všetky stroje Hyper-V","All Microsoft SQL Databases":"Všetky databázy Microsoft SQL","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Všetky správy o používaní sa odosielajú anonymne a neobsahujú žiadne osobné údaje. Obsahujú informácie o hardvéri a operačnom systéme, druhu backendu, trvaní zálohovania, celkovej veľkosti zdrojových dát a podobných údajov. Neobsahujú cesty, názvy súborov, používateľské mená, heslá ani podobné citlivé informácie.","Allow remote access (requires restart)":"Povoliť vzdialený prístup (vyžaduje reštart)","Allowed days":"Povolené dni","An existing file was found at the new location":"Existujúci súbor bol nájdený na novom mieste","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Existujúci súbor bol nájdený na novom mieste\nNaozaj chcete, aby databáza smerovala k existujúcemu súboru?"}); - gettextCatalog.setStrings('sr_RS', {"- pick an option -":"- odaberite opciju -","...loading...":"...učitavanje...","API key":"API ključ","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"O nama","About {{appname}}":"O aplikaciji {{appname}}","Access Key":"Pristupni ključ - access key","Access denied":"Pristup odbijen","Access grant":"Dozvola za pristup","Access to user interface":"Pristup korisničkom interfejsu","Account name":"Korisničko ime","Add a new backup":"Dodaj novu rezervnu kopiju","Add a path directly":"Dodajte direktno putanju","Add advanced option":"Dodaj naprednu opciju","Add backup":"Dodaj rezervnu kopiju","Add filter":"Dodaj filter","Add path":"Dodaj putanju","Added":"Dodato","Adjust bucket name?":"Prilagodi ime segment-a?","Advanced Options":"Napredne opcije","Advanced options":"Napredne opcije","Advanced:":"Napredno:","All Hyper-V Machines":"Sve Hyper-V mašine","All Microsoft SQL Databases":"Sve Microsoft SQL baze podataka","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Svi izveštaji o korišćenju se šalju anonimno i ne sadrže nikakve lične podatke. Oni sadrže informacije o hardveru i operativnom sistemu, tipu pozadine, trajanju rezervne kopije, ukupnoj veličini izvornih podataka i sličnim podacima. Ne sadrže putanje, imena datoteka, korisnička imena, lozinke ili slične osetljive informacije.","Allow remote access (requires restart)":"Dozvoli udaljeni pristup (zahteva restartovanje)","Allowed days":"Dozvoljeni dani","An existing file was found at the new location":"Postojeća datoteka je pronađena na novoj lokaciji","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Postojeća datoteka je pronađena na novoj lokaciji\nDa li ste sigurni da želite da baza podataka ukazuje na postojeću datoteku?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Pronađena je u skladištu postojeća lokalna baza.\nBaza se ponovo može koristit sa komandne linije i serverske instance na istom skladištu.\n\nDa li želite da koristite postojeću bazu?","Anonymous usage reports":"Anonimni izveštaj o korišćenju","Applications":"Aplikacije","As Command-line":"Kao komandna linija","AuthID":"AuthID","Authentication method":"Metoda autentifikacije","Authentication method ({{auth_method}})":"Metoda autentifikacije ({{auth_method}})","Authentication password":"Lozinka za autentifikaciju","Authentication username":"Korisničko ime za autentifikaciju","Autogenerated passphrase":"Automatski generisana pristupna lozinka","B2 Application ID":"B2 ID aplikacije","B2 Application Key":"B2 aplikacioni ključ","B2 Cloud Storage Account ID":"B2 ID naloga za skladište u oblaku","B2 Cloud Storage Application ID":"B2 ID aplikacije za skladište u oblaku","B2 Cloud Storage Application Key":"B2 ključ aplikacije za skladište u oblaku","Back":"Nazad","Backup complete!":"Rezervna kopija je završena!","Backup destination":"Odredište rezervne kopije","Backup location":"Lokacija rezervne kopije","Backup retention":"Čuvanje rezervne kopije","Backup:":"Rezervna kopija:","Beta":"Beta","Broken access":"Neispravan pristup","Browse":"Pregledaj","Browser default":"Podrazumvani pretraživač","Bucket create location":"Segment kreira lokaciju","Bucket name":"Ime segmenta","Bucket storage class":"Klasa skladištenja segment-a","Building list of files to restore …":"Pravljnje liste fajlova za vraćanje ...","Building partial temporary database …":"Pravljenje delimične privremene baze podataka ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Dozvoljavajući daljinski pristup, server sluša zahteve sa bilo koje mašine na vašoj mreži. Ako omogućite ovu opciju, uverite se da uvek koristite računar na bezbednoj mreži zaštićenoj zaštitnim zidom.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Podrazumevano, ikona u traci otvara korisnički interfejs sa tokenom koji otključava korisnički interfejs. Ovo osigurava da možete pristupiti korisničkom interfejsu sa ikone na traci, dok od drugih zahtevate da unesu lozinku. Ako želite da se mora uneti lozinka, čak i kada pristupate korisničkom interfejsu sa ikone na traci, omogućite ovu opciju.","Cache Files":"Keš fajlovi","Canary":"Canary","Cancel":"Otkaži","Cannot move to existing file":"Nemoguće premestiti u postojeću datoteku","Changelog":"Dnevnik promena","Changelog for {{appname}} {{version}}":"Dnevnik promena za {{appname}} {{version}}","Check failed:":"Provera nije uspela:","Check for updates now":"Proveri ažuriranja odmah","Checking for updates …":"Provera ažuriranja …","Chose a storage type to get started":"Izaberite tip skladištenja da biste započeli","Click the AuthID link to create an AuthID":"Kliknite na vezu AuthID da biste kreirali AuthID","Click to set throttle options":"Kliknite da biste podesili opcije prigušivanja funkcije","Client library to use":"Klijentska biblioteka za korišćenje","Commandline …":"Komandna linija …","Compact Phase":"Faza sažimanja","Compact now":"Sažmi sada","Compacting remote data …":"Sažimanje udaljenih podataka ...","Complete log":"Kompletiram dnevnik","Completing backup …":"Kompletiranje rezervne kopije","Completing previous backup …":"Kompletiranje prethodne rezervne kopije","Computer":"Računar","Configuration file:":"Datoteka sa podešavanjima:","Configuration:":"Podešavanja:","Configure a new backup":"Konfigurišite novu rezervnu kopiju","Confirm delete":"Potvrdi brisanje","Confirm encryption passphrase":"Potvrdite pristupnu frazu lozinke za šifrovanje","Confirm passphrase":"Potvrdite pristupnu frazu lozinke","Confirmation required":"Neophodna potvrda","Connect":"Poveži","Connect now":"Poveži odmah","Connecting to server …":"Povezivanje na server …","Connection lost":"Veza izgubljena","Connection worked!":"Veza je radila!","Container name":"Naziv kontejnera","Container region":"Region kontejnera","Continue":"Nastavi","Continue without encryption":"Nastavi bez šifrovanja","Copied!":"Prekopirano!","Copy":"Kopiraj","Copy Destination URL to Clipboard":"Kopiraj odredišni URL u privremenu memoriju","Copy failed. Please manually copy the URL":"Kopiranje nije uspelo. Molimo ručno kopirajte URL","Core options":"Osnovne opcije","Counting ({{files}} files found, {{size}})":"Brojanjem ({{files}} fajlova pronađeno, {{size}})","Crashes only":"Samo srušeni","Create bug report …":"Kreira se izveštaj o greškama ...","Create folder?":"Napraviti fasciklu?","Created new limited user":"Napravljen novi korisnik sa ograničenjima","Creating bug report …":"Kreira se izveštaj o greškama ...","Creating new user with limited access …":"Pravljenje novog korisnika sa ograničenim pristupom …","Creating target folders …":"Pravljenje ciljnih foldera ...","Creating temporary backup …":"Pravljenje privremene rezervne kopije …","Current action:":"Trenutna akcija:","Current file:":"Trenutni fajl:","Current version is {{versionname}} ({{versionnumber}})":"Trenutna verzija je {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Prilagođena krajnja tačka S3","Custom Satellite":"Prilagođeni satelit","Custom Satellite ({{satellite}})":"Prilagođeni satelit ({{satellite}})","Custom authentication url":"Prilagođeni URL za autentifikaciju","Custom backup retention":"Prilagođeno zadržavanje rezervne kopije","Custom location ({{server}})":"Prilagođena lokacija ({{server}})","Custom region for creating buckets":"Prilagođeni region za pravljenje segmenata","Custom region value ({{region}})":"Prilagođena vrednost regiona ({{region}})","Custom server url ({{server}})":"Prilagođeni URL servera ({{server}})","Custom storage class ({{class}})":"Prilagođena klasa skladištenja ({{class}})","Database …":"Baza podataka ...","Days":"Dana","Default":"Podrazumevano","Default ({{channelname}})":"Podrazumevano ({{channelname}})","Default excludes":"Podrazumevano isključuje","Default options":"Podrazumevane opcije","Delete":"Obriši","Delete Phase (Old Backup Versions)":"Faza brisanja (stare verzije rezervne kopije)","Delete backup":"Obriši backup","Delete backups that are older than":"Izbrisati rezervne kopije koje su starije od","Delete local database":"Obriši lokalnu bazu podataka","Delete remote files":"Obriši udaljene datoteke","Delete the local database":"Obriši lokalnu bazu podataka","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Obrisati {{filecount}} datoteka ({{filesize}}) iz udaljenog skladišta?","Delete …":"Brisanje …","Deleted":"Izbrisano","Deleted Versions":"Izbrisane verzije","Deleted files":"Izbrisani fajlovi","Deleting remote files …":"Brisanje udaljenih fajlova …","Deleting unwanted files …":"Brisanje neželjenih fajlova …","Description (optional)":"Opis (opciono)","Description:":"Opis:","Desktop":"Radna površina","Destination":"Odredište","Destination path":"Putanja odredišta","Disabled":"Onemogućeno","Dismiss":"Odbaci","Dismiss all":"Odbaci sve","Display and color theme":"Ekran i tema boja","Do you really want to delete the backup: \"{{name}}\" ?":"Da li zaista želite da obrišete rezervnu kopiju: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Da li zaista želiš da obrišeš lokalnu bazu podataka za: {{name}}","Done":"Završi","Download":"Preuzmi","Downloaded files":"Preuzeti fajlovi","Downloading files …":"Preuzimanje fajlova …","Downloading update…":"Preuzimanje ažuriranja…","Duplicate option {{opt}}":"Duplikat opcije {{opt}}","Duplicati Website":"Duplicati veb sajt","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati će se pokrenuti kada se startuje, ali će ostati u pauziranom stanju sve vreme. Duplicati će zauzeti minimalne sistemske resurse i neće praviti rezervne kopije.","Duration":"Trajanje","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Svaka rezervna kopija ima lokalnu bazu podataka koja je povezana sa njom, koja čuva informacije o udaljenoj rezervnoj kopiji na lokalnoj mašini.\nKada brišete rezervnu kopiju, takođe možete izbrisati lokalnu bazu podataka bez uticaja na mogućnost vraćanja udaljenih fajlova.\nAko koristite lokalnu bazu podataka za rezervne kopije sa komandne linije, trebalo bi da zadržite bazu podataka.","Edit as list":"Izmeni kao listu","Edit as text":"Izmeni kao tekst","Edit …":"Izmeni ...","Encrypt file":"Šifrujte fajl","Encryption":"Šifrovanje","Encryption changed":"Šifrovanje promenjeno","Encryption passphrase":"Šifrovanje pristupne fraze","End":"Kraj","Enter URL":"Unesi URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ručno unesite strategiju zadržavanja. Čuvari mesta su D/W/Y za dane/sedmice/godine i U za neograničeno. Sintaksa je: 7D:1D,4W:1W,36M:1M. Ovaj primer čuva jednu rezervnu kopiju za svaki od narednih 7 dana, jednu za svaku od naredne 4 nedelje i jednu za svaki od narednih 36 meseci. Ovo se takođe može napisati kao 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Unesite frazu lozinke rezervne kopije, ako postoji","Enter configuration details":"Unesite detalje konfiguracije","Enter encryption passphrase":"Unesite frazu lozinke enkripcije","Enter expression here":"Ovde unesite izraz","Enter the destination path":"Unesite odredišnu putanju","Error":"Greška","Error!":"Greška!","Errors and crashes":"Greške i rušenja","Examined":"Ispitano","Exclude":"Izuzmi","Exclude directories whose names contain":"Izuzmite direktorijume čija imena sadrže","Exclude expression":"Izuzmi izraz","Exclude file":"Izuzmi fajl","Exclude file extension":"Izuzmi ekstenziju fajla","Exclude files whose names contain":"Izuzmi fajlove čija imena sadrže","Exclude filter group":"Izuzmi grupu filtera","Exclude folder":"Izuzmi fasciklu","Exclude regular expression":"Isključi regularni izraz","Existing file found":"Pronađen je postojeći fajl","Experimental":"Eksperimentalno","Export":"Izvezi","Export backup configuration":"Izvezi podešavanja rezervne kopije","Export configuration":"Izvezi podešavanja","Export passwords":"Izvezi lozinke","Export …":"Izvoz ...","Exporting …":"Izvozim ...","External link":"Spoljašnja veza","FTP (Alternative)":"FTP (Alternativno)","Failed to build temporary database: {{message}}":"Pravljenje privremene baze podataka nije uspelo: {{message}}","Failed to connect:":"Neuspelo povezivanje:","Failed to connect: {{message}}":"Neuspelo povezivanje: {{message}}","Failed to delete:":"Brisanje nije uspelo:","Failed to fetch path information: {{message}}":"Nije uspelo preuzimanje informacija o putanji: {{message}}","Failed to find backup:":"Pronalaženje rezervne kopije nije uspelo:","Failed to read backup defaults:":"Čitanje podrazumevanih rezervnih kopija nije uspelo:","Failed to restore files: {{message}}":"Vraćanje fajlova nije uspelo: {{message}}","Failed to save:":"Čuvanje nije uspelo:","Fetching path information …":"Preuzimanje informacija o putanji …","File":"Fajl","Files larger than:":"Fajlovi veći od:","Filters":"Filteri","Finished!":"Završeno!","First run setup":"Podešavanje za prvo pokretanje","Folder":"Fascikla","Folder path":"Putanja do fascikle","Fri":"Pet","GByte":"GBajt","GByte/s":"GBajt/s","GCS Project ID":"GCS ID projekta","General":"Generalno","General backup settings":"Opšta podešavanja rezervnih kopija","General options":"Generalne opcije","Generate":"Generiši","Getting file versions …":"Dohvatanje verzija fajla ...","Group email":"Grupna e-pošta","Hidden files":"Skriveni fajlovi","Hide":"Sakrij","Hide hidden folders":"Sakrij skrivene fascikle","Home":"Glavna","Hostnames":"Imena hostova","Hours":"Sati","How do you want to handle existing files?":"Kako želite da rukujete postojećim fajlovima?","Hyper-V Machine":"Hyper-V mašina","Hyper-V Machine:":"Hyper-V mašina:","Hyper-V Machines":"Hyper-V mašine","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ako je neki datum propušten, posao će biti pokrenut što je pre moguće.","If at least one newer backup is found, all backups older than this date are deleted.":"Ako se pronađe bar jedna novija rezervna kopija, sve rezervne kopije starije od ovog datuma se brišu.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ako ne unesete putanju, svi fajlovi će biti sačuvani u fascikli za prijavu.\nJeste li sigurni da je to ono što želite?","If you do not enter an API Key, the tenant name is required":"Ako ne unesete API ključ, potrebno je ime zakupca","Import":"Uvoz","Import Destination URL":"Uvezite odredišnu URL adresu","Import backup configuration":"Uvezite konfiguraciju rezervne kopije","Import from a file":"Uvezi iz fajla","Import metadata":"Uvezite metapodatke","Importing …":"Uvoz ...","Include a file?":"Uključiti fajl?","Include expression":"Uključite izraz","Include regular expression":"Uključite regularni izraz","Incorrect answer, try again":"Netačan odgovor, pokušajte ponovo","Individual builds for developers only. Not for use with important data.":"Pojedinačne verzije samo za programere. Nije za upotrebu sa važnim podacima.","Information":"Informacije","Invalid characters in path":"Nevažeći znakovi u putanji","Invalid retention time":"Nevažeće vreme zadržavanja","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Moguće je povezati se na neki FTP bez lozinke.\nDa li ste sigurni da vaš FTP server podržava prijavljivanje bez lozinke?","KByte":"KBajt","KByte/s":"KBajt/s","Keep a specific number of backups":"Čuvajte određeni broj rezervnih kopija","Keep all backups":"Čuvajte sve rezervne kopije","Keystone API version":"Keystone API verzija","Language in user interface":"Jezik u korisničkom interfejsu","Last month":"Prošlog meseca","Last successful backup:":"Poslednja uspešna rezervna kopija:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Poslednje uspešno vraćanje: {{time}} (trajalo je {{duration || '0 seconds'}})","Latest":"Najnovije","Libraries":"Biblioteke","Listing backup dates …":"Navođenje datuma rezervnih kopija …","Listing remote files for purge …":"Lista udaljenih fajlova za čišćenje …","Listing remote files …":"Lista udaljenih fajlova ...","Live":"Uživo","Load a configuration from an exported job or a storage provider":"Učitajte konfiguraciju iz izvezenog posla ili dobavljača skladišta","Load destination from an exported job or a storage provider":"Učitajte odredište iz izvezenog posla ili dobavljača skladišta","Load older data":"Učitaj starije podatke","Loading …":"Učitavanje ...","Local Repository":"Lokalno spremište","Local database path:":"Putanja lokalne baze podataka:","Local repository":"Lokalno skladište","Local storage":"Lokalno skladište","Location":"Lokacija","Location where buckets are created":"Lokacija na kojoj se kreiraju segmenti","Log data for {{Backup.Backup.Name}}":"Podaci evidencije za {{Backup.Backup.Name}}","Log data from the server":"Evidentirajte podatke sa servera","Log out":"Odjavi se","MByte":"MBajt","MByte/s":"MBajt/s","Maintenance":"Održavanje","Manually type path":"Ručno unesite putanju","Max download speed":"Maksimalna brzina preuzimanja","Max upload speed":"Maksimalna brzina otpremanja","Menu":"Meni","Microsoft SQL Database:":"Microsoft SQL baza podataka:","Microsoft SQL Databases":"Microsoft SQL baze podataka","Minimum redundancy":"Minimalna redundantnost","Minimum redundancy is 1.0":"Minimalna redundantnost je 1.0","Minutes":"Minute","Missing name":"Nedostaje naziv","Missing passphrase":"Nedostaje fraza lozinke","Missing sources":"Nedostaju izvori","Modified":"Modifikovano","Mon":"Pon","Months":"Meseci","Move existing database":"Premesti postojeću bazu podataka","Move failed:":"Premeštanje nije uspelo:","My Documents":"Moji dokumenti","My Music":"Moja muzika","My Photos":"Moje fotografije","My Pictures":"Moje slike","Name":"Naziv","Never":"Nikad","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Novo korisničko ime je {{user}}.\nAžurirani akreditivi za korišćenje novog korisnika sa ograničenjem","Next":"Sledeće","Next scheduled run:":"Sledeće zakazano pokretanje:","Next scheduled task:":"Sledeći zakazan zadatak:","Next task:":"Sledeći zadatak:","Next time":"Sledeći put","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nijedan sertifikat prethodno nije naveden, proverite kod administratora servera da li je ključ tačan: {{key}}\n\nDa li želite da odobrite prijavljeni ključ hosta?","No editor found for the "{{backend}}" storage type":"Nije pronađen nijedan uređivač za "{{backend}}" tip skladištenja","No encryption":"Bez šifrovanja","No items selected":"Nema izabranih stavki","No items to restore, please select one or more items":"Nema stavki za vraćanje, izaberite jednu ili više stavki","No passphrase entered":"Lozinka nije uneta","No scheduled tasks":"Nema zakazanih zadataka","Non-matching passphrase":"Pristupna fraza koja se ne podudara","None / disabled":"Ništa / onemogućeno","Not using encryption":"Ne koristi šifrovanje","Nothing will be deleted. The backup size will grow with each change.":"Ništa neće biti izbrisano. Veličina rezervne kopije će rasti sa svakom promenom.","OK":"U redu","Once there are more backups than the specified number, the oldest backups are deleted.":"Kada ima više rezervnih kopija od navedenog broja, najstarije rezervne kopije se brišu.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Otvoren","Operating System":"Operativni sistem","Operation":"Operacija","Operations:":"Operacije:","Optional authentication password":"Opciona lozinka za autentifikaciju","Optional authentication username":"Opciono korisničko ime za autentifikaciju","Options":"Opcije","Original location":"Originalna lokacija","Others":"Ostalo","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Vremenom će rezervne kopije biti automatski izbrisane. Ostaće po jedna rezervna kopija za svaku od poslednjih 7 dana, svaku od poslednje 4 nedelje, svaku od poslednjih 12 meseci. Uvek će biti najmanje jedna preostala rezervna kopija.","Overwrite":"Prepiši","Passphrase":"Lozinka","Passphrase (if encrypted)":"Lozinka (ako je šifrovano)","Passphrase changed":"Lozinka promenjena","Passphrases are not matching":"Lozinke se ne poklapaju","Passphrases do not match":"Pristupne fraze se ne podudaraju","Password":"Lozinka","Patching files with local blocks …":"Zakrpa fajlova sa lokalnim blokovima …","Path":"Putanja","Path not found":"Putanja nije pronađena","Path on server":"Putanja na serveru","Path or subfolder in the bucket":"Putanja ili podfascikla u segment-u","Pause":"Pauza","Pause after startup or hibernation":"Pauziraj nakon pokretanja ili hibernacije","Pause options":"Opcije pauze","Permissions":"Dozvole","Pick location":"Izaberite lokaciju","Point to your backup files and restore from there":"Postavite pokazivač na svoje rezervne kopije fajlova i vratite ih odatle","Port":"Port","Prevent tray icon automatic log-in":"Sprečite automatsko prijavljivanje ikonom na traci","Previous":"Prethodno","Progress:":"Napredak:","ProjectID is optional if the bucket exist":"ID projekta je opcioni ako segment postoji","Proprietary":"Vlasnički","Purge Phase":"Faza čišćenja","Purging files complete!":"Čišćenje fajlova je završeno!","Purging files …":"Čišćenje fajlova …","Rebuilding local database …":"Ponovno kreiranje lokalne baze podataka …","Recreate (delete and repair)":"Ponovo kreirajte (izbrišite i popravite)","Recreate Database Phase":"Ponovo kreirajte fazu baze podataka","Recreating database …":"Ponovo kreiranje baze podataka …","Registering temporary backup …":"Registrovanje privremene rezervne kopije …","Relative paths not allowed":"Relativne putanje nisu dozvoljene","Reload":"Učitaj ponovo","Remote":"Udaljeno","Remote Path":"Udaljena putanja","Remote Repository":"Udaljeno spremište","Remote path":"Udaljena putanja","Remote repository":"Udaljeno spremište","Remote volume size":"Veličina udljenog volumena","Remove":"Ukloni","Remove option":"Ukloni opciju","Removed files":"Ukloni fajlove","Repair":"Popravi","Repair Phase":"Popravi fazu","Repairing database …":"Popravljanje baze podataka …","Repeat Passphrase":"Ponovite lozinku","Reporting:":"Izveštavanje:","Reset":"Resetovanje","Restore":"Vrati","Restore complete!":"Vraćanje je završeno!","Restore files":"Vrati fajlove","Restore files …":"Vraćanje fajlova ...","Restore from":"Vrati iz","Restore from backup configuration":"Vrati iz podešavanja rezervne kopije","Restore options":"Vrati opcije","Restore read/write permissions":"Vrati dozvole za čitanje i upis","Restored Files":"Vraćeni fajlovi","Restored Folders":"Vraćene fascikle","Restored Symlinks":"Vraćeni Symlinks","Restoring files …":"Vraćanje fajlova ...","Resume":"Nastavi","Rewritten File Lists":"Prepisane liste fajlova","Run again every":"Izvrši ponovo svaki","Run now":"Izvrši sad","Running commandline entry":"Izvrši unos komandne linije","Running task:":"Izvršavanje zadatka:","Running …":"Izvršavanje ...","S3 Compatible":"S3 kompatibilno","Same as the base install version: {{channelname}}":"Isto kao i verzija osnovne instalacije: {{channelname}}","Sat":"Sub","Satellite":"Satelit","Save":"Sačuvaj","Save and repair":"Sačuvaj i popravi","Save different versions with timestamp in file name":"Sačuvaj drugu verziju sa vremenom u nazivu fajla","Save immediately":"Sačuvaj odmah","Scanning existing files …":"Skeniranje postojećih fajlova …","Scanning for local blocks …":"Skeniranje lokalnih blokova ...","Schedule":"Raspored","Search":"Pretraga","Search for files":"Pretraga fajlova","Seconds":"Sekunde","Select a log level and see messages as they happen:":"Izaberite nivo dnevnika i pogledajte poruke kako se dešavaju:","Select files":"Izaberite fajlove","Server":"Server","Server and port":"Server i port","Server hostname or IP":"Ime servera ili IP adresa","Server is currently paused,":"Server je trenutno pauziran,","Server is currently paused, do you want to resume now?":"Server je trenutno pauziran, da li želite da nastavite odmah?","Server password":"Lozinka servera","Server paused":"Server je pauziran","Server state properties":"Opcije stanja servera","Settings":"Podešavanja","Show":"Prikaži","Show advanced editor":"Prikaži napredni editor","Show hidden folders":"Prikaži skrivene fascikle","Show log":"Prikaži dnevnik","Show log …":"Prikazujem dnevnik ...","Show treeview":"Prikazujem izled stabla","Sia server password":"Lozinka za Sia server","Smart backup retention":"Pametno čuvanje rezervne kopije","Some OpenStack providers allow an API key instead of a password and tenant name":"Neki OpenStack provajderi dozvoljavaju API ključ umesto lozinke i imena zakupca","Some S3 providers might only be compatible with a certain client library":"Neki S3 provajderi mogu biti kompatibilni samo sa određenom bibliotekom klijenata","Source Data":"Izvorni podaci","Source Files":"Izvorni fajlovi","Source data":"Izvorni podaci","Source folders":"Izvorne fascikle","Source:":"Izvor:","Specific builds for developers only. Not for use with important data.":"Posebne verzije samo za programere. Nije za upotrebu sa važnim podacima.","Standard protocols":"Standardni protokoli","Start":"Start","Starting backup …":"Startujem rezervnu kopiju ...","Starting restore …":"Startujem obnavljanje ...","Starting the restore process …":"Startujem proces obnavljanja ...","Stop after current file":"Zaustavi nakon trenutnog fajla","Stop after the current file":"Zaustavi nakon trenutnog fajla","Stop now":"Zaustavi odmah","Stop running backup":"Zaustavi pokrenutu rezervnu kopiju","Stop running task":"Zaustavi pokrenuti zadatak","Stopping after the current file:":"Zaustavljanje nakon trenutnog fajla:","Stopping task:":"Zaustavljanje zadatka:","Storage Type":"Tip skladišta","Storage class":"Klasa skladišta","Storage class for creating a bucket":"Klasa skladišta za kreiranje segment-a","Stored":"Uskladišteno","Strong":"Jaka","Success":"Uspešno","Sun":"Ned","Symbolic link":"Simbolička veza","System Files":"Sistemski fajlovi","System default ({{levelname}})":"Podrazumevani sistem ({{levelname}})","System files":"Sistemski fajlovi","System info":"Sistemske informacije","System properties":"Osobine sistema","TByte":"TBajt","TByte/s":"TBajt/s","Task is running":"Zadatak se izvršava","Temporary Files":"Privremeni fajlovi","Temporary files":"Privremene fajlovi","Test Phase":"Faza testiranje","Test connection":"Ispitaj vezu","Testing permissions …":"Ispitivanje dozvola ...","Testing …":"Ispitivanje ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Polje '{{fieldname}}' sadrži nevažeći znak: {{character}} (vrednost: {{value}}, indeks: {{pos}})","The backup is missing, has it been deleted?":"Nedostaje rezervna kopija, da li je izbrisana?","The backup was temporary and does not exist anymore, so the log data is lost":"Rezervna kopija je bila privremena i više ne postoji, tako da su podaci dnevnika izgubljeni","The bucket name should be all lower-case, convert automatically?":"Naziv segmenta treba da bude malim slovima, da li da se automatski konvertuje?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfiguraciju treba čuvati na sigurnom. Da li ste sigurni da želite da sačuvate nešifrovani fajl koji sadrži vaše lozinke?","The dark theme (by Michal)":"Tamna tema (napravio Michal)","The default blue on white theme (by Alex)":"Podrazumevana tema plavo na belom (napravio Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Fascikla {{folder}} ne postoji.\nKreirate je sada?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Ključ hosta se promenio, proverite kod administratora servera da li je to tačno, inače biste mogli da budete žrtva napada MAN-IN-THE-MIDDLE.\n\nDa li želite da ZAMENITE svoj TRENUTNI ključ hosta \"{{prev}}\" sa PRIJAVLJENIM ključem hosta: {{key}}?","The passwords do not match":"Lozinke se ne poklapaju","The path does not appear to exist, do you want to add it anyway?":"Putanja izgleda ne postoji, da li svejedno želite da je dodate?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Putanja se ne završava znakom '{{dirsep}}', što znači da uključujete fajl, a ne fasciklu.\n\nDa li želite da uključite navedeni fajl?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Putanja mora biti apsolutna putanja, tj. mora da počinje sa kosom crtom unapred '/'","The region parameter is only applied when creating a new bucket":"Parametar regiona se primenjuje samo pri kreiranju novog segmenta","The region parameter is only used when creating a bucket":"Parametar regiona se kreira samo kada se koristi segment","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Sertifikat servera nije mogao biti proveren.\nDa li želite da odobrite SSL sertifikat sa hešom: {{hash}}?","The storage class affects the availability and price for a stored file":"Klasa skladišta utiče na dostupnost i cenu za uskladišteni fajl","The target folder contains encrypted files, please supply the passphrase":"Ciljana fasckla sadrži šifrovane fajlove, molimo unesite pristupnu frazu","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Korisnik ima previše dozvola, Da li želite da napravite novog ograničenog korisnika, samo sa dozvolama za izabranu putanju?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ova rezervna kopija je napravljena na drugom operativnom sistemu. Vraćanje fajlova bez navođenja odredišne fascikle može dovesti do vraćanja fajlova na neočekivana mesta. Da li ste sigurni da želite da nastavite bez odabira odredišne fascikle?","This month":"Ovog meseca","This week":"Ove sedmice","Throttle settings":"Podešavanja regulacije","Thu":"Čet","Time":"Vreme","To File":"U datoteku","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Da potvrdite da želite obrisati sve udaljene datoteke sa imenom \"{{name}}\", molimo unesite reč koju vidite ispod","To export without a passphrase, uncheck the \"Encrypt file\" box":"Za izvoz bez lozinke, polje \"Šifruj datoteku\" ne treba da bude označeno","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Da bi sprečio različite napade zasnovane na DNS-u, Duplicati ograničava dozvoljena imena hostova na ona koja su ovde navedena. Direktan IP pristup i lokalni host je uvek dozvoljen. Višestruka imena hostova mogu biti isporučena sa tačkom i zarezom. Ako je neko od dozvoljenih imena hostova zvezdica (*), sva imena hostova su dozvoljena i ova funkcija je onemogućena. Ako je polje prazno, dozvoljen je samo pristup IP adresi i lokalnom hostu.","Today":"Danas","Trust host certificate?":"Verujete sertifikatu hosta?","Trust server certificate?":"Veruj sertifikatu servera?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Isprobajte nove funkcije na kojima radimo. Trenutno najstabilnija dostupna verzija. Testirajte podatke za vraćanje pre nego što ih upotrebite u proizvodnim okruženjima.","Tue":"Uto","Type passphrase here.":"Ovde unesite pristupnu frazu.","Type to highlight files":"Ukucajte da biste istakli fajlove","Unknown backup size and versions":"Nepoznata veličina i verzije rezervne kopije","Until resumed":"Dok se ne nastavi","Update channel":"Ažurirajte kanal","Update failed:":"Ažuriranje nije uspelo:","Updating with existing database":"Ažuriranje sa postojećom bazom podataka","Uploaded files":"Otpremanje fajlova","Uploading verification file …":"Otpremanje fajla za verifikaciju …","Usage statistics":"Statistika upotrebe","Usage statistics, warnings, errors, and crashes":"Statistika korišćenja, upozorenja, greške i rušenja","Use SSL":"Koristi SSL","Use existing database?":"Koristi postojeću bazu podataka?","Use weak passphrase":"Koristi slabu lozinku","Useless":"Beskorisno","User data":"Podaci o korisniku","User domain name":"Ime korisničkog domena","User has too many permissions":"Korisnik ima previše dozvola","User interface settings":"Podešavanja korisničkog interfejsa","Username":"Korisničko ime","Vacuuming database …":"Usisavanje baze podataka …","Validating …":"Provera valjanosti ...","Verifications":"Provere","Verify files":"Proveri datoteke","Verifying answer":"Proveravanje odgovora","Verifying backend data …":"Provra pozadinskih podataka ...","Verifying files …":"Provera fajlova ...","Verifying remote data …":"Provera udaljenih podataka ...","Verifying restored files …":"Provera vraćenih fajlova ...","Verifying …":"Provera ...","Version ID":"ID verzije","Very strong":"Veoma jaka","Very weak":"Veoma slaba","Visit us on":"Posetite nas na","WARNING: This will prevent you from restoring the data in the future.":"UPOZORENJE: Ovo će vas sprečiti da vratite podatke u budućnosti.","Waiting for task to begin":"Čekanje na početak zadatka","Waiting for upload to finish …":"Čeka se da se otpremanje završi …","Warnings, errors and crashes":"Upozorenja, greške i padovi","We recommend that you encrypt all backups stored outside your system":"Preporučujemo da šifrujete sve backup-ove uskladištene van Vašeg sistema","Weak":"Slaba","Weak passphrase":"Slaba lozinka","Wed":"Sre","Weeks":"Sedmica","Where do you want to restore from?":"Odakle želite da vratite?","Where do you want to restore the files to?":"Gde želite da vratite fajlove?","Years":"Godina","Yes":"Da","Yes, I have stored the passphrase safely":"Da, uskladištio sam lozinku bezbedno","Yes, I understand the risk":"Da, razumem rizik","Yes, I'm brave!":"Da, hrabar sam!","Yes, please break my backup!":"Da, molim te pauziraj moju rezervnu kopiju!","Yesterday":"Juče","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Menjate putanju baze podataka dalje od postojeće baze podataka.\nJeste li sigurni da je to ono što želite?","You are currently running {{appname}} {{version}}":"Trenutno koristite {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Možete da zaustavite pravljenje rezervne kopije nakon što se završi bilo koji fajl koji je trenutno u toku.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Možete odmah zaustaviti zadatak ili dozvoliti procesu da nastavi sa trenutnim fajlom, a zatim ga zaustavi.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Promenili ste režim šifrovanja. Ovo bi moglo biti loš izbor. Preporučujemo vam da umesto toga napravite novu rezervnu kopiju","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Promenili ste pristupnu frazu lozinke, koja nije podržana. Preporučujemo vam da umesto toga napravite novu rezervnu kopiju.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Izabrali ste da ne šifrujete rezervnu kopiju. Šifrovanje se preporučuje za sve podatke uskladištene na udaljenom serveru.","You have chosen to restore to a new location, but not entered one":"Odabrali ste da vratite na novu lokaciju, ali niste je uneli","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Generisali ste jaku pristupnu frazu lozinke. Uverite se da ste napravili bezbednu kopiju pristupne fraze lozinke, jer podaci ne mogu da se povrate ako izgubite pristupnu frazu lozinke.","You must choose at least one source folder":"Morate odabrati najmanje jednu izvornu fasciklu","You must enter a domain name to use v3 API":"Morate uneti naziv domena da biste koristili v3 API","You must enter a name for the backup":"Morate uneti naziv za rezervnu kopiju","You must enter a passphrase or disable encryption":"Morate uneti lozinku ili isključiti šifrovanje","You must enter a password to use v3 API":"Morate uneti lozinku da biste koristili v3 API","You must enter a positive number of backups to keep":"Morate da unesete važeće vreme trajanje za čuvanje rezervnih kopija","You must enter a tenant (aka project) name to use v3 API":"Morate da unesete ime zakupca (aka projekta) da biste koristili v3 API","You must enter a valid duration for the time to keep backups":"Morate da unesete važeće vreme trajanja za čuvanja rezervnih kopija","You must enter a valid retention policy string":"Morate da unesete važeći niz politike retencije","You must fill in the password":"Morate uneti lozinku","You must fill in the server name or address":"Morate uneti naziv servera ili adresu","You must fill in the username":"Morate uneti korisničko ime","You must fill in {{field}}":"Morate uneti {{field}}","You must select or fill in the AuthURI":"Morate izabrati ili uneti AuthURI","You must select or fill in the server":"Morate izabrati ili uneti server","You must specify a path":"Morate navesti putanju","Your files and folders have been restored successfully.":"Vaše datoteke i fascikle su uspešno vraćene.","Your passphrase is easy to guess. Consider changing passphrase.":"Vaša lozinka je laka za pogađanje. Razmislite o promeni lozinke.","bucket/folder/subfolder":"segment/fascikla/podfascikla","byte":"bajt","byte/s":"bajt/ova","custom":"poručen","resume now":"nastavi odmah","unless you are explicitly specifying --group-id":"osim ako izričito ne navedete --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} su prvenstveno razvili {{dev1}} i {{dev2}}. {{appname}} se može preuzeti sa {{websitename}}. {{appname}} je licenciran pod {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fajlovi ({{size}}) da ide {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije"],"{{number}} Hour":"{{number}} sati","{{number}} Hours":"{{number}} sati","{{number}} Minutes":"{{number}} minuta","{{time}} (took {{duration}})":"{{time}} (trajalo {{duration}})"}); - gettextCatalog.setStrings('sv_SE', {"- pick an option -":"- välj ett alternativ -","...loading...":"...laddar...","API key":"API-nyckel","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Om","About {{appname}}":"Om {{appname}}","Access Key":"Åtkomstnyckel","Access denied":"Åtkomst nekad","Access grant":"Åtkomst beviljad","Access to user interface":"Access till användarinterface","Account name":"Kontonamn","Add a new backup":"Lägg till ny säkerhetskopia","Add a path directly":"Lägg till direkt sökväg","Add advanced option":"Lägg till avancerade val","Add backup":"Lägg till säkerhetskopia","Add filter":"Lägg till filter","Add path":"Lägg till sökväg","Added":"Sparad","Adjust bucket name?":"Justera \"bucket name\"?","Advanced Options":"Avancerade tillägg","Advanced options":"Avancerade tillägg","Advanced:":"Avancerat:","All Hyper-V Machines":"Alla Hyper-V datorer","All Microsoft SQL Databases":"Alla Microsoft SQL-databaser","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alla användningsrapporter skickas anonymt och innehåller ingen personlig information. De innehåller information om hårdvara och operativsystem, typ av backend, säkerhetskopieringstid, övergripande storlek på källdata och liknande data. De innehåller inte sökvägar, filnamn, användarnamn, lösenord eller liknande känslig information.","Allow remote access (requires restart)":"Tillåt fjärrstyrning (kräver omstart)","Allowed days":"Tillåtna dagar","An existing file was found at the new location":"En existerande fil hittades på den nya platsen","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"En existerande fil hittades på den nya platsen. Är du säker att databasen skall peka till en existerande fil?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"En befintlig lokal databas för lagringen har hittats.\nÅteranvändning av databasen gör att kommandorads- och serverinstanserna kan arbeta på samma fjärrlagring.\n\nVill du använda den befintliga databasen?","Anonymous usage reports":"Anonym användarrapport","Applications":"Applikationer","As Command-line":"Som kommandorad","AuthID":"AuthID","Authentication method":"Autentiseringsmetod","Authentication method ({{auth_method}})":"Autentiseringsmetod ({{auth_method}})","Authentication password":"Autentiseringslösenord","Authentication username":"Autentiseringsanvändarnamn","Autogenerated passphrase":"Autogenererat lösenord","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Åter","Backup complete!":"Säkerhetskopieringen är klar!","Backup destination":"Destination till säkerhetskopia","Backup location":"Plats för säkerhetskopia","Backup retention":"Backup-bibehållning","Backup:":"Säkerhetskopia:","Beta":"Beta","Broken access":"Trasig åtkomst","Browse":"Bläddra","Browser default":"Webbläsarens standard","Bucket create location":"Bucket skapa plats","Bucket name":"Bucket namn","Bucket storage class":"Bucket förvaringsklass","Building list of files to restore …":"Skapar lista med filer för återskapande ...","Building partial temporary database …":"Skapar tillfällig databas ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Genom att tillåta fjärråtkomst lyssnar servern på förfrågningar från vilken maskin som helst i ditt nätverk. Om du aktiverar det här alternativet, se till att du alltid använder datorn i ett säkert brandvägg-skyddat nätverk.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Som standard öppnar tray-icon användargränssnittet med en token som låser upp användargränssnittet. Detta säkerställer att du kan komma åt användargränssnittet från ikonen i fältet, samtidigt som du kräver att andra anger ett lösenord. Om du föredrar att behöva skriva in lösenordet, även när du kommer åt användargränssnittet från ikonen i fältet, aktivera det här alternativet.","Cache Files":"Cachefiler","Canary":"Kanariefågel","Cancel":"Avbryt","Cannot move to existing file":"Kan inte flytta till befintlig fil","Changelog":"Ändringslogg","Changelog for {{appname}} {{version}}":"Ändringslogg för {{appname}} {{version}}","Check failed:":"Kontroll misslyckades:","Check for updates now":"Kontrollera uppdateringar nu","Checking for updates …":"Kontrollerar uppdateringar ...","Chose a storage type to get started":"Välj en lagringstyp för att börja","Click the AuthID link to create an AuthID":"Klicka på AuthID-länken för att skapa ett AuthID","Click to set throttle options":"Klicka för att välja begränsningsalternativ","Client library to use":"Klientbibliotek att använda","Commandline …":"Kommandorad ...","Compact Phase":"Kompakt Fas","Compact now":"Komprimera nu","Compacting remote data …":"Komprimerar fjärrdata …","Complete log":"Komplett logg","Completing backup …":"Slutför säkerhetskopieringen...","Completing previous backup …":"Slutför tidigare säkerhetskopiering …","Computer":"Dator","Configuration file:":"Konfigurationsfil:","Configuration:":"Konfiguration:","Configure a new backup":"Konfigurera en ny säkerhetskopia","Confirm delete":"Bekräfta borttagning","Confirm encryption passphrase":"Bekräfta krypteringslösenord","Confirm passphrase":"Bekräfta lösenfras","Confirmation required":"Bekräftelse beövs","Connect":"Anslut","Connect now":"Anslut nu","Connecting to server …":"Ansluter till server ...","Connection lost":"Anslutning avbruten","Connection worked!":"Anslutning OK!","Container name":"Behållarnamn","Container region":"Behållarregion","Continue":"Fortsätt","Continue without encryption":"Fortsätt utan kryptering","Copied!":"Kopierad!","Copy":"Kopia","Copy Destination URL to Clipboard":"Kopiera mål-URL till urklipp","Copy failed. Please manually copy the URL":"Kopering misslyckades, var vänlig kopiera URLen manuellt","Core options":"Kärnalternativ","Counting ({{files}} files found, {{size}})":"Beräknar ({{files}} filer hittade, {{size}})","Crashes only":"Endast kraschar","Create bug report …":"Skapa buggrapport","Create folder?":"Skapa mapp?","Created new limited user":"Skapa ny begränsad användare","Creating bug report …":"Skapar felrapport ...","Creating new user with limited access …":"Skapar ny användare med begränsad åtkomst …","Creating target folders …":"Skapar målmappar …","Creating temporary backup …":"Skapar temporär säkerhetskopia ...","Current action:":"Nuvarande åtgärd:","Current file:":"Nuvarande fil:","Current version is {{versionname}} ({{versionnumber}})":"Aktuell version är {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Anpassad S3-slutpunkt","Custom Satellite":"Anpassad Satellit","Custom Satellite ({{satellite}})":"Anpassad Satellit ({{satellite}})","Custom authentication url":"Anpassad autentiseringsadress","Custom backup retention":"Anpassad backup-bibehållning","Custom location ({{server}})":"Anpassad plats ({{server}})","Custom region for creating buckets":"Anpassad region för att skapa buckets","Custom region value ({{region}})":"Anpassat värde för region ({{region}})","Custom server url ({{server}})":"Anpassad serveradress ({{server}})","Custom storage class ({{class}})":"Anpassad lagringsklass ({{class}})","Database …":"Databas ...","Days":"Dagar","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standard exkluderingar","Default options":"Standardalternativ","Delete":"Radera","Delete Phase (Old Backup Versions)":"Ta bort fas (gamla säkerhetskopieringsversioner)","Delete backup":"Radera säkerhetskopia","Delete backups that are older than":"Radera säkerhetskopior äldre än","Delete local database":"Radera lokal databas","Delete remote files":"Radera målfiler","Delete the local database":"Radera lokal databas","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Ta bort {{filecount}} filer ({{filesize}}) från fjärrmålet?","Delete …":"Radera ...","Deleted":"Raderade","Deleted Versions":"Raderade Versioner","Deleted files":"Raderade filer","Deleting remote files …":"Raderar fjärrfiler ...","Deleting unwanted files …":"Raderar oönskade filer...","Description (optional)":"Beskrivning (valfritt)","Description:":"Beskrivning:","Desktop":"Skrivbord","Destination":"Destination","Destination path":"Målsökväg","Disabled":"Avstängd","Dismiss":"Avfärda","Dismiss all":"Avfärda allt","Display and color theme":"Visnings- och färgtema","Do you really want to delete the backup: \"{{name}}\" ?":"Vill du verkligen radera säkerhetskopia för: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Vill du verkligen radera den lokala databasen för: {{name}}","Done":"Klart","Download":"Ladda ner","Downloaded files":"Nedladdade filer","Downloading files …":"Laddar ner filer ...","Downloading update…":"Laddar ner uppdatering ...","Duplicate option {{opt}}":"Duplicera alternativ {{opt}}","Duplicati Website":"Duplicatis webbsida","Duplicati forum":"Duplicatis forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati kommer att köras när den startas, men förblir i pausat tillstånd under hela tiden. Duplicati kommer att uppta minimala systemresurser och inga säkerhetskopior kommer att köras.","Duration":"Varaktighet","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Varje backup har en lokal databas som är associerad med den, som lagrar information om fjärrfilerna på den lokala maskinen.\nNär du tar bort en säkerhetskopia kan du också ta bort den lokala databasen utan att påverka möjligheten att återställa fjärrfilerna.\nOm du använder den lokala databasen för säkerhetskopior från kommandoraden bör du behålla databasen.","Edit as list":"Ändra som lista","Edit as text":"Ändra som text","Edit …":"Ändra ...","Encrypt file":"Kryptera fil","Encryption":"Kryptering","Encryption changed":"Kryptering förändrad","Encryption passphrase":"Ange krypteringslösenord","End":"Slut","Enter URL":"Ange URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ange en backupstategi manuellt. Användbara tecken är D/W/Y för dagar/veckor/år och U för obegränsat. Tillåten syntax är: 7D:1D,4W:1W,36M:1M. Detta exempel behåller en backup för var 7:e dag, en för var 4:e vecka och en för var 36:e månad. Detta kan också skriva som 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Ange lösenordsfras, om tillämpligt","Enter configuration details":"Ange konfigurationsdetaljer","Enter encryption passphrase":"Ange krypteringslösenord","Enter expression here":"Ange uttryck här","Enter the destination path":"Ange målsökväg","Error":"Fel","Error!":"Fel!","Errors and crashes":"Fel och kraschar","Examined":"Granska","Exclude":"Exkludera","Exclude directories whose names contain":"Exkludera kataloger vars namn innehåller","Exclude expression":"Uteslut enligt uttryck","Exclude file":"Exkludera fil","Exclude file extension":"Uteslut filändelse","Exclude files whose names contain":"Uteslut filer vars namn innehåller","Exclude filter group":"Uteslut filtergrupp","Exclude folder":"Uteslut mapp","Exclude regular expression":"Uteslut enligt reguljärt uttryck","Existing file found":"Filen existerar redan","Experimental":"Experimentell","Export":"Exportera","Export backup configuration":"Exportera konfiguration för säkerhetskopia","Export configuration":"Exportera konfiguration","Export passwords":"Exportera lösenord","Export …":"Exportera ...","Exporting …":"Exporterar ...","External link":"Extern länk","FTP (Alternative)":"FTP (alternativ)","Failed to build temporary database: {{message}}":"Misslyckades med att skapa tillfällig databas: {{message}}","Failed to connect:":"Misslyckades med att ansluta:","Failed to connect: {{message}}":"Misslyckades med att ansluta: {{message}}","Failed to delete:":"Misslyckades med att radera:","Failed to fetch path information: {{message}}":"Misslyckades med att hämta sökvägsinformation: {{message}}","Failed to find backup:":"Misslyckades med att hitta säkerhetskopia:","Failed to read backup defaults:":"Misslyckades med att läsa standardinställningarna för säkerhetskopia:","Failed to restore files: {{message}}":"Misslyckades med att återställa filer: {{message}}","Failed to save:":"Misslyckades med att spara:","Fetching path information …":"Hämtar sökvägsinformation …","File":"Fil","Files larger than:":"Filer större än:","Filters":"Filter","Finished!":"Klar!","First run setup":"Nyinstallationsinställningar","Folder":"Mapp","Folder path":"Mappsökväg","Fri":"Fre","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Projekt-ID","General":"Generellt","General backup settings":"Allmän inställningar för säkerhetskopia","General options":"Generella inställningar","Generate":"Skapa","Getting file versions …":"Hämtar filversioner ...","Group email":"Grupp-epost","Hidden files":"Gömda filer","Hide":"Dölj","Hide hidden folders":"Visa dolda mappar","Home":"Hem","Hostnames":"Värdnamn","Hours":"Timmar","How do you want to handle existing files?":"Hur vill du hantera existerande filer?","Hyper-V Machine":"HyperV-maskin","Hyper-V Machine:":"HyperV-maskin:","Hyper-V Machines":"HyperV-maskiner","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Om ett tillfälle missades görs uppgiften så fort som möjligt.","If at least one newer backup is found, all backups older than this date are deleted.":"Om minst en nyare säkerhetskopia finns, kommer alla säkerhetskopior äldre än detta datum att raderas.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Om du inte anger en sökväg kommer alla filer att lagras i inloggningsmappen.\nÄr du säker på detta?","If you do not enter an API Key, the tenant name is required":"Om du inte anger en API-nyckel krävs \"tenant name\"","Import":"Importera","Import Destination URL":"Importera destinationsadress","Import backup configuration":"Importera konfiguration för säkerhetskopia","Import from a file":"Importera från en fil","Import metadata":"Importera metadata","Importing …":"Importerar …","Include a file?":"Inkludera en fil?","Include expression":"Inkludera enligt uttryck","Include regular expression":"Inkludera enligt reguljärt uttryck","Incorrect answer, try again":"Felaktigt svar, försök igen","Individual builds for developers only. Not for use with important data.":"Individuella versioner endast för utvecklare. Ej för användning med viktig data.","Information":"Information","Invalid characters in path":"Ogiltiga tecken i sökvägen","Invalid retention time":"Ogiltig bibehållningstid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Det är möjligt att ansluta till vissa FTP utan ett lösenord.\nÄr du säker på att din FTP-server stöder lösenordsfria inloggningar?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Behåll ett visst antal säkerhetskopior","Keep all backups":"Behåll alla säkerhetskopior","Keystone API version":"Keystone API-version","Language in user interface":"Språk i användargränssnittet","Last month":"Förra månaden","Last successful backup:":"Senaste lyckade säkerhetskopiering:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Senaste lyckade återställning: {{tid}} (tog {{varaktighet || '0 sekunder'}})","Latest":"Senaste","Libraries":"Bibliotek","Listing backup dates …":"Listar datum för säkerhetskopia …","Listing remote files for purge …":"Listar fjärrfiler för rensning …","Listing remote files …":"Listar fjärrfiler ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Hämta konfiguration från en exporterad rutin eller en lagringstjänst","Load destination from an exported job or a storage provider":"Hämta mål från en exporterad rutin eller en lagringstjänst","Load older data":"Hämta äldre data","Loading …":"Laddar ...","Local Repository":"Lokalt arkiv","Local database path:":"Sökväg till lokal databas:","Local repository":"Lokalt arkiv","Local storage":"Lokal lagring","Location":"Plats","Location where buckets are created":"Plats där buckets skapas","Log data for {{Backup.Backup.Name}}":"Logg-data för {{Backup.Backup.Name}}","Log data from the server":"Logg data från servern","Log out":"Logga ut","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Underhåll","Manually type path":"Skriv sökväg manuellt","Max download speed":"Max nedladdningshastighet","Max upload speed":"Max uppladdningshastighet","Menu":"Meny","Microsoft SQL Database:":"Microsoft SQL-databas:","Microsoft SQL Databases":"Microsoft SQL-databaser","Minimum redundancy":"Minsta redundans","Minimum redundancy is 1.0":"Minsta redundans är 1,0","Minutes":"Minuter","Missing name":"Saknar namn","Missing passphrase":"Saknar lösenfras ","Missing sources":"Saknade källor","Modified":"Ändrad","Mon":"Mån","Months":"Månader","Move existing database":"Flytta existerande databas","Move failed:":"Flytten misslyckades:","My Documents":"Mina Dokument","My Music":"Min Musi","My Photos":"Mina Foton","My Pictures":"Mina Bilder","Name":"Namn","Never":"Aldrig","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nytt användarnamn är {{user}}.\nUppdaterade användaruppgifter för att använda den nya begränsade användaren","Next":"Nästa","Next scheduled run:":"Nästa schemalagda körning:","Next scheduled task:":"Nästa schemalagda uppgift:","Next task:":"Nästa uppgift:","Next time":"Nästa gång","No":"Nej","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Inget certifikat har angetts tidigare, kontrollera med serveradministratören att nyckeln är korrekt: {{key}}\n\nVill du godkänna den rapporterade värdnyckeln?","No editor found for the "{{backend}}" storage type":"Ingen redigerare hittades för "{{backend}}" lagringstyp","No encryption":"Ingen kryptering","No items selected":"Inga objekt har valts","No items to restore, please select one or more items":"Inga objekt att återställa, välj ett eller flera objekt","No passphrase entered":"Ingen lösenfras har angetts","No scheduled tasks":"Inga schemalagda uppgifter","Non-matching passphrase":"Lösenfras som inte matchar","None / disabled":"Ingen / inaktiverad","Not using encryption":"Använder inte kryptering","Nothing will be deleted. The backup size will grow with each change.":"Ingenting kommer att raderas. Storleken på säkerhetskopieringen kommer att växa med varje ändring.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"När det finns fler säkerhetskopior än det angivna antalet, raderas de äldsta säkerhetskopiorna.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Öppnad","Operating System":"Operativsystem","Operation":"Operation","Operations:":"Operationer:","Optional authentication password":"Valfritt lösenord för autentisering","Optional authentication username":"Valfritt användarnamn för autentisering","Options":"Alternativ","Original location":"Ursprunglig plats","Others":"Andra","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Med tiden kommer säkerhetskopior att raderas automatiskt. Det kommer att finnas kvar en säkerhetskopia för var och en av de senaste 7 dagarna, var och en av de senaste 4 veckorna, var och en av de senaste 12 månaderna. Det kommer alltid att finnas minst en säkerhetskopia kvar.","Overwrite":"Skriva över","Passphrase":"Lösenfras","Passphrase (if encrypted)":"Lösenfras (om krypterad)","Passphrase changed":"Lösenfras ändrad","Passphrases are not matching":"Lösenfraser matchar inte","Passphrases do not match":"Lösenfraser matchar inte","Password":"Lösenord","Patching files with local blocks …":"Patchar filer med lokala block...","Path":"Sökväg","Path not found":"Sökvägen hittades inte","Path on server":"Sökväg på servern","Path or subfolder in the bucket":"Sökväg eller undermapp i bucket","Pause":"Paus","Pause after startup or hibernation":"Pausa efter uppstart eller viloläge","Pause options":"Pausalternativ","Permissions":"Behörigheter","Pick location":"Välj plats","Point to your backup files and restore from there":"Peka på dina säkerhetskopior och återställ därifrån","Port":"Port","Prevent tray icon automatic log-in":"Förhindra att tray-icon automatiskt loggar in","Previous":"Tidigare","Progress:":"Framsteg:","ProjectID is optional if the bucket exist":"ProjectID är valfritt om bucket finns","Proprietary":"Proprietär","Purge Phase":"Rensningsfas","Purging files complete!":"Rensning av filer klar!","Purging files …":"Rensar filer...","Rebuilding local database …":"Bygger om lokal databas...","Recreate (delete and repair)":"Återskapa (ta bort och reparera)","Recreate Database Phase":"Återskapa Databas Fasen","Recreating database …":"Återskapar databas...","Registering temporary backup …":"Registrerar tillfällig säkerhetskopia …","Relative paths not allowed":"Relativa sökvägar är inte tillåtna","Reload":"Ladda om","Remote":"Fjärr","Remote Path":"Fjärrsökväg ","Remote Repository":"Fjärr Repository","Remote path":"Fjärrsökväg ","Remote repository":"Fjärr repository","Remote volume size":"Fjärr-volymstorlek","Remove":"Ta bort","Remove option":"Ta bort alternativ","Removed files":"Borttagna filer","Repair":"Reparera","Repair Phase":"Reparations Fas","Repairing database …":"Reparerar databas ...","Repeat Passphrase":"Upprepa lösenfrasen","Reporting:":"Rapportering:","Reset":"Återställa","Restore":"Återställ","Restore complete!":"Återställningen är klar!","Restore files":"Återställningen filer","Restore files …":"Återställer filer …","Restore from":"Återställ från","Restore from backup configuration":"Återställ från konfiguration av säkerhetskopia","Restore options":"Återställ alternativ","Restore read/write permissions":"Återställ läs-/skrivbehörigheter","Restored Files":"Återställda filer","Restored Folders":"Återställda mappar","Restored Symlinks":"Återställda symbollänkar","Restoring files …":"Återställer filer...","Resume":"Försätt","Rewritten File Lists":"Omskrivna fillistor","Run again every":"Kör igen varje","Run now":"Kör nu","Running commandline entry":"Kör kommandoradspost","Running task:":"Pågående uppgift:","Running …":"Pågående ... ","S3 Compatible":"S3 Kompatibel","Same as the base install version: {{channelname}}":"Samma som basinstallationsversionen: {{channelname}}","Sat":"Lör","Satellite":"Satellit","Save":"Spara","Save and repair":"Spara och reparera","Save different versions with timestamp in file name":"Spara olika versioner med tidsstämpel i filnamnet","Save immediately":"Spara omedelbart","Scanning existing files …":"Skannar befintliga filer...","Scanning for local blocks …":"Söker efter lokala block …","Schedule":"Schema","Search":"Sök","Search for files":"Sök efter filer","Seconds":"Sekunder","Select a log level and see messages as they happen:":"Välj en logg-nivå och se meddelanden när de händer:","Select files":"Välj filer","Server":"Server","Server and port":"Server och port","Server hostname or IP":"Server värdnamn eller IP","Server is currently paused,":"Servern är för närvarande pausad,","Server is currently paused, do you want to resume now?":"Servern är för närvarande pausad, vill du återuppta nu?","Server password":"Server lösenord","Server paused":"Servern pausad","Server state properties":"Serverstatusegenskaper","Settings":"Inställningar","Show":"Visa","Show advanced editor":"Visa avancerad redigerare","Show hidden folders":"Visa dolda mappar","Show log":"Visa logg","Show log …":"Visa logg ...","Show treeview":"Visa träd-vy","Sia server password":"Sia-server lösenord","Smart backup retention":"Smart backup-bibehållning","Some OpenStack providers allow an API key instead of a password and tenant name":"Vissa OpenStack-leverantörer tillåter en API-nyckel istället för ett lösenord och \"tenant name\"","Some S3 providers might only be compatible with a certain client library":"Vissa S3-leverantörer kanske bara är kompatibla med ett visst klientbibliotek","Source Data":"Källdata","Source Files":"Källfiler","Source data":"Källdata","Source folders":"Källmappar","Source:":"Källa:","Specific builds for developers only. Not for use with important data.":"Specifika versioner endast för utvecklare. Ej för användning med viktig data.","Standard protocols":"Standardprotokoll","Start":"Start","Starting backup …":"Startar säkerhetskopiering ...","Starting restore …":"Startar återställning ...","Starting the restore process …":"Startar återställningsprocessen ...","Stop after current file":"Stoppa efter aktuell fil","Stop after the current file":"Stoppa efter den aktuella filen","Stop now":"Stoppa nu","Stop running backup":"Avsluta säkerhetskopiering","Stop running task":"Sluta köra uppgiften","Stopping after the current file:":"Stoppa efter den aktuella filen:","Stopping task:":"Stoppa uppgift:","Storage Type":"Lagringstyp","Storage class":"Förvarings-klass","Storage class for creating a bucket":"Förvaringsklass för att skapa en bucket","Stored":"Lagrat","Strong":"Stark","Success":"Framgång","Sun":"Sön","Symbolic link":"Symbolisk länk","System Files":"Systemfiler","System default ({{levelname}})":"Systemstandard ({{levelname}})","System files":"Systemfiler","System info":"System information","System properties":"Systemegenskaper","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Uppgiften pågår","Temporary Files":"Tillfälliga filer","Temporary files":"Tillfälliga filer","Test Phase":"Test Fas","Test connection":"Testa anslutningen","Testing permissions …":"Testar behörigheter...","Testing …":"Testar ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Fältet '{{fieldname}}' innehåller ett ogiltigt tecken: {{character}} (värde: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Säkerhetskopia saknas, har den tagits bort?","The backup was temporary and does not exist anymore, so the log data is lost":"Säkerhetskopian var tillfällig och existerar inte längre, så logg-data går förlorad","The bucket name should be all lower-case, convert automatically?":"Namnet på bucket borde vara gemener, konvertera automatiskt?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfigurationen bör förvaras säker. Är du säker på att du vill spara en okrypterad fil som innehåller dina lösenord?","The dark theme (by Michal)":"Det mörka temat (av Michal)","The default blue on white theme (by Alex)":"Standardtemat för blått på vitt (av Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Mappen {{folder}} finns inte.\nSkapa det nu?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Värdnyckeln har ändrats, kontrollera med serveradministratören om detta är korrekt, annars kan du bli offer för en MAN-IN-MIDDLE-attack.\n\nVill du ERSÄTTA din AKTUELLA värdnyckel \"{{prev}}\" med den RAPPORTERADE värdnyckeln: {{key}}?","The passwords do not match":"Lösenorden matchar inte","The path does not appear to exist, do you want to add it anyway?":"Sökvägen verkar inte existera, vill du lägga till den ändå?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Sökvägen slutar inte med tecknet '{{dirsep}}', vilket betyder att du inkluderar en fil, inte en mapp.\n\nVill du inkludera den angivna filen ändå?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Sökvägen måste vara en absolut väg, dvs den måste börja med ett snedstreck '/'","The region parameter is only applied when creating a new bucket":"Regionparametern tillämpas endast när en ny bucket skapas","The region parameter is only used when creating a bucket":"Regionparametern används endast när du skapar en bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Servercertifikatet kunde inte valideras.\nVill du godkänna SSL-certifikatet med hashen: {{hash}}?","The storage class affects the availability and price for a stored file":"Lagringsklassen påverkar tillgängligheten och priset för en lagrad fil","The target folder contains encrypted files, please supply the passphrase":"Målmappen innehåller redan krypterade filer, vänligen ange lösenfrasen","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Användaren har för många behörigheter. Vill du skapa en ny begränsad användare, med endast behörigheter till den valda sökvägen?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Denna säkerhetskopia skapades på ett annat operativsystem. Att återställa filer utan att ange en målmapp kan göra att filer återställs på oväntade platser. Är du säker på att du vill fortsätta utan att välja en målmapp?","This month":"Denna månad","This week":"Denna vecka","Throttle settings":"Inställningar för Hastighetsbegränsningar ","Thu":"Tors","Time":"Tid","To File":"Till Arkiv","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"För att bekräfta att du vill ta bort alla fjärrfiler för \"{{name}}\", skriv in ordet du ser nedan","To export without a passphrase, uncheck the \"Encrypt file\" box":"För att exportera utan en lösenordsfras, avmarkera rutan \"Kryptera fil\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"För att förhindra olika DNS-baserade attacker, begränsar Duplicati de tillåtna värdnamnen till de som listas här. Direkt IP-åtkomst och lokal värd är alltid tillåten. Flera värdnamn kan förses med en semikolonseparator. Om något av de tillåtna värdnamnen är en asterisk (*), är alla värdnamn tillåtna och den här funktionen är inaktiverad. Om fältet är tomt tillåts endast IP-adress och lokal värdåtkomst.","Today":"I dag","Trust host certificate?":"Lita på värdcertifikat?","Trust server certificate?":"Lita på servercertifikat?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Testa de nya funktionerna som vi arbetar med. För närvarande den mest stabila versionen som finns. Testa Återställ data innan du använder detta i produktionsmiljöer.","Tue":"Tis","Type passphrase here.":"Skriv lösenordsfras här.","Type to highlight files":"Skriv för att markera filer","Unknown backup size and versions":"Okänd storlek och versioner av säkerhetskopia","Until resumed":"Tills den återupptas","Update channel":"Uppdatera kanal","Update failed:":"Uppdateringen misslyckades:","Updating with existing database":"Uppdatering med befintlig databas","Uploaded files":"Uppladdade filer","Uploading verification file …":"Laddar upp verifieringsfil …","Usage statistics":"Användningsstatistik","Usage statistics, warnings, errors, and crashes":"Användningsstatistik, varningar, fel och krascher","Use SSL":"Använd SSL","Use existing database?":"Använd befintlig databas?","Use weak passphrase":"Använd svag lösenfras","Useless":"Oanvändbar","User data":"Användardata","User domain name":"Användardomännamn","User has too many permissions":"Användaren har för många behörigheter","User interface settings":"Användargränssnittet inställningar","Username":"Användarnamn","Vacuuming database …":"Dammsugar databas …","Validating …":"Validerar …","Verifications":"Verifieringar","Verify files":"Verifiera filer","Verifying answer":"Verifierar svar","Verifying backend data …":"Verifierar backend-data …","Verifying files …":"Verifierar filer ...","Verifying remote data …":"Verifierar fjärrdata …","Verifying restored files …":"Verifierar återställda filer...","Verifying …":"Verifierar ...","Version ID":"Versions-ID","Very strong":"Väldigt stark","Very weak":"Väldigt svag","Visit us on":"Besök oss på","WARNING: This will prevent you from restoring the data in the future.":"VARNING: Detta kommer att förhindra dig från att återställa data i framtiden.","Waiting for task to begin":"Väntar på att uppgiften ska börja","Waiting for upload to finish …":"Väntar på att uppladdningen ska slutföras ...","Warnings, errors and crashes":"Varningar, fel och krascher","We recommend that you encrypt all backups stored outside your system":"Vi rekommenderar att du krypterar alla säkerhetskopior som lagras utanför ditt system","Weak":"Svag","Weak passphrase":"Svag lösenfras","Wed":"Ons","Weeks":"Veckor","Where do you want to restore from?":"Var vill du återställa från?","Where do you want to restore the files to?":"Var vill du återställa filerna?","Years":"År","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, jag har lagrat lösenfrasen säkert","Yes, I understand the risk":"Ja, jag förstår risken","Yes, I'm brave!":"Ja, jag är modig!","Yes, please break my backup!":"Ja, snälla bryt min säkerhetskopia!","Yesterday":"I går","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Du ändrar databassökvägen från en befintlig databas.\nÄr du säker på detta?","You are currently running {{appname}} {{version}}":"Du kör för närvarande {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"Du kan stoppa säkerhetskopieringen efter att alla pågående filuppladdningar har slutförts.","You can stop the task immediately, or allow the process to continue its current file and then stop.":"Du kan stoppa uppgiften omedelbart eller tillåta processen att fortsätta sin nuvarande fil och sedan stoppa.","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Du har ändrat krypteringsläget. Det här kan ta sönder saker. Du uppmuntras att skapa en ny säkerhetskopia istället","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Du har ändrat lösenfrasen, som inte stöds. Du uppmuntras att skapa en ny säkerhetskopia istället.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Du har valt att inte kryptera säkerhetskopian. Kryptering rekommenderas för all data som lagras på en fjärrserver.","You have chosen to restore to a new location, but not entered one":"Du har valt att återställa till en ny plats, men inte angett någon","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Du har genererat en stark lösenfras. Se till att du har gjort en säker kopia av lösenfrasen, eftersom data inte kan återställas om du tappar bort lösenfrasen.","You must choose at least one source folder":"Du måste välja minst en källmapp","You must enter a domain name to use v3 API":"Du måste ange ett domännamn för att använda v3 API","You must enter a name for the backup":"Du måste ange ett namn för säkerhetskopian","You must enter a passphrase or disable encryption":"Du måste ange en lösenfras eller inaktivera kryptering","You must enter a password to use v3 API":"Du måste ange ett lösenord för att använda v3 API","You must enter a positive number of backups to keep":"Du måste ange ett positivt antal säkerhetskopior för att behålla","You must enter a tenant (aka project) name to use v3 API":"Du måste ange ett tenant (aka project) för att använda v3 API","You must enter a valid duration for the time to keep backups":"Du måste ange en giltig varaktighet för hur länge säkerhetskopior sparas ","You must enter a valid retention policy string":"Du måste ange en giltig lagrings-policysträng","You must fill in the password":"Du måste fylla i lösenordet","You must fill in the server name or address":"Du måste fylla i serverns namn eller adress","You must fill in the username":"Du måste fylla i användarnamnet","You must fill in {{field}}":"Du måste fylla i {{field}}","You must select or fill in the AuthURI":"Du måste välja eller fylla i AuthURI","You must select or fill in the server":"Du måste välja eller fylla i uppgifterna för servern","You must specify a path":"Du måste ange en sökväg","Your files and folders have been restored successfully.":"Dina filer och mappar har återställts.","Your passphrase is easy to guess. Consider changing passphrase.":"Din lösenfras är lätt att gissa. Överväg att ändra lösenordsfras.","bucket/folder/subfolder":"bucket/mapp/undermapp","byte":"byte","byte/s":"byte/s","custom":"anpassad","resume now":"återuppta nu","unless you are explicitly specifying --group-id":"om du inte uttryckligen anger --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} utvecklades främst av {{dev1}} och {{dev2}}. {{appname}} kan laddas ner från {{websitename}}. {{appname}} är licensierad under {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} filer ({{size}}) att gå {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Timme","{{number}} Hours":"{{number}} Timmar","{{number}} Minutes":"{{number}} Minuter","{{time}} (took {{duration}})":"{{time}} (tog {{duration}})"}); + gettextCatalog.setStrings('sr_RS', {"- pick an option -":"- odaberite opciju -","...loading...":"...učitavanje...","API key":"API ključ","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"O nama","About {{appname}}":"O aplikaciji {{appname}}","Access Key":"Pristupni ključ - access key","Access denied":"Pristup odbijen","Access grant":"Dozvola za pristup","Access to user interface":"Pristup korisničkom interfejsu","Account name":"Korisničko ime","Add a new backup":"Dodaj novu rezervnu kopiju","Add a path directly":"Dodajte direktno putanju","Add advanced option":"Dodaj naprednu opciju","Add backup":"Dodaj rezervnu kopiju","Add filter":"Dodaj filter","Add path":"Dodaj putanju","Added":"Dodato","Adjust bucket name?":"Prilagodi ime segment-a?","Advanced Options":"Napredne opcije","Advanced options":"Napredne opcije","Advanced:":"Napredno:","All Hyper-V Machines":"Sve Hyper-V mašine","All Microsoft SQL Databases":"Sve Microsoft SQL baze podataka","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Svi izveštaji o korišćenju se šalju anonimno i ne sadrže nikakve lične podatke. Oni sadrže informacije o hardveru i operativnom sistemu, tipu pozadine, trajanju rezervne kopije, ukupnoj veličini izvornih podataka i sličnim podacima. Ne sadrže putanje, imena datoteka, korisnička imena, lozinke ili slične osetljive informacije.","Allow remote access (requires restart)":"Dozvoli udaljeni pristup (zahteva restartovanje)","Allowed days":"Dozvoljeni dani","An existing file was found at the new location":"Postojeća datoteka je pronađena na novoj lokaciji","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"Postojeća datoteka je pronađena na novoj lokaciji\nDa li ste sigurni da želite da baza podataka ukazuje na postojeću datoteku?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"Pronađena je u skladištu postojeća lokalna baza.\nBaza se ponovo može koristit sa komandne linije i serverske instance na istom skladištu.\n\nDa li želite da koristite postojeću bazu?","Anonymous usage reports":"Anonimni izveštaj o korišćenju","Applications":"Aplikacije","As Command-line":"Kao komandna linija","AuthID":"AuthID","Authentication method":"Metoda autentifikacije","Authentication method ({{auth_method}})":"Metoda autentifikacije ({{auth_method}})","Authentication password":"Lozinka za autentifikaciju","Authentication username":"Korisničko ime za autentifikaciju","Autogenerated passphrase":"Automatski generisana pristupna lozinka","B2 Application ID":"B2 ID aplikacije","B2 Application Key":"B2 aplikacioni ključ","B2 Cloud Storage Account ID":"B2 ID naloga za skladište u oblaku","B2 Cloud Storage Application ID":"B2 ID aplikacije za skladište u oblaku","B2 Cloud Storage Application Key":"B2 ključ aplikacije za skladište u oblaku","Back":"Nazad","Backup complete!":"Rezervna kopija je završena!","Backup destination":"Odredište rezervne kopije","Backup location":"Lokacija rezervne kopije","Backup retention":"Čuvanje rezervne kopije","Backup:":"Rezervna kopija:","Beta":"Beta","Broken access":"Neispravan pristup","Browse":"Pregledaj","Browser default":"Podrazumvani pretraživač","Bucket create location":"Segment kreira lokaciju","Bucket name":"Ime segmenta","Bucket storage class":"Klasa skladištenja segment-a","Building list of files to restore …":"Pravljnje liste fajlova za vraćanje ...","Building partial temporary database …":"Pravljenje delimične privremene baze podataka ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Dozvoljavajući daljinski pristup, server sluša zahteve sa bilo koje mašine na vašoj mreži. Ako omogućite ovu opciju, uverite se da uvek koristite računar na bezbednoj mreži zaštićenoj zaštitnim zidom.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Podrazumevano, ikona u traci otvara korisnički interfejs sa tokenom koji otključava korisnički interfejs. Ovo osigurava da možete pristupiti korisničkom interfejsu sa ikone na traci, dok od drugih zahtevate da unesu lozinku. Ako želite da se mora uneti lozinka, čak i kada pristupate korisničkom interfejsu sa ikone na traci, omogućite ovu opciju.","Cache Files":"Keš fajlovi","Canary":"Canary","Cancel":"Otkaži","Cannot move to existing file":"Nemoguće premestiti u postojeću datoteku","Changelog":"Dnevnik promena","Changelog for {{appname}} {{version}}":"Dnevnik promena za {{appname}} {{version}}","Check failed:":"Provera nije uspela:","Check for updates now":"Proveri ažuriranja odmah","Checking for updates …":"Provera ažuriranja …","Chose a storage type to get started":"Izaberite tip skladištenja da biste započeli","Click the AuthID link to create an AuthID":"Kliknite na vezu AuthID da biste kreirali AuthID","Click to set throttle options":"Kliknite da biste podesili opcije prigušivanja funkcije","Client library to use":"Klijentska biblioteka za korišćenje","Commandline …":"Komandna linija …","Compact Phase":"Faza sažimanja","Compact now":"Sažmi sada","Compacting remote data …":"Sažimanje udaljenih podataka ...","Complete log":"Kompletiram dnevnik","Completing backup …":"Kompletiranje rezervne kopije","Completing previous backup …":"Kompletiranje prethodne rezervne kopije","Computer":"Računar","Configuration file:":"Datoteka sa podešavanjima:","Configuration:":"Podešavanja:","Configure a new backup":"Konfigurišite novu rezervnu kopiju","Confirm delete":"Potvrdi brisanje","Confirm encryption passphrase":"Potvrdite pristupnu frazu lozinke za šifrovanje","Confirm passphrase":"Potvrdite pristupnu frazu lozinke","Confirmation required":"Neophodna potvrda","Connect":"Poveži","Connect now":"Poveži odmah","Connecting to server …":"Povezivanje na server …","Connection lost":"Veza izgubljena","Connection worked!":"Veza je radila!","Container name":"Naziv kontejnera","Container region":"Region kontejnera","Continue":"Nastavi","Continue without encryption":"Nastavi bez šifrovanja","Copied!":"Prekopirano!","Copy":"Kopiraj","Copy Destination URL to Clipboard":"Kopiraj odredišni URL u privremenu memoriju","Copy failed. Please manually copy the URL":"Kopiranje nije uspelo. Molimo ručno kopirajte URL","Core options":"Osnovne opcije","Counting ({{files}} files found, {{size}})":"Brojanjem ({{files}} fajlova pronađeno, {{size}})","Crashes only":"Samo srušeni","Create bug report …":"Kreira se izveštaj o greškama ...","Create folder?":"Napraviti fasciklu?","Created new limited user":"Napravljen novi korisnik sa ograničenjima","Creating bug report …":"Kreira se izveštaj o greškama ...","Creating new user with limited access …":"Pravljenje novog korisnika sa ograničenim pristupom …","Creating target folders …":"Pravljenje ciljnih foldera ...","Creating temporary backup …":"Pravljenje privremene rezervne kopije …","Current action:":"Trenutna akcija:","Current file:":"Trenutni fajl:","Current version is {{versionname}} ({{versionnumber}})":"Trenutna verzija je {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Prilagođena krajnja tačka S3","Custom Satellite":"Prilagođeni satelit","Custom Satellite ({{satellite}})":"Prilagođeni satelit ({{satellite}})","Custom authentication url":"Prilagođeni URL za autentifikaciju","Custom backup retention":"Prilagođeno zadržavanje rezervne kopije","Custom location ({{server}})":"Prilagođena lokacija ({{server}})","Custom region for creating buckets":"Prilagođeni region za pravljenje segmenata","Custom region value ({{region}})":"Prilagođena vrednost regiona ({{region}})","Custom server url ({{server}})":"Prilagođeni URL servera ({{server}})","Custom storage class ({{class}})":"Prilagođena klasa skladištenja ({{class}})","Database …":"Baza podataka ...","Days":"Dana","Default":"Podrazumevano","Default ({{channelname}})":"Podrazumevano ({{channelname}})","Default excludes":"Podrazumevano isključuje","Default options":"Podrazumevane opcije","Delete":"Obriši","Delete Phase (Old Backup Versions)":"Faza brisanja (stare verzije rezervne kopije)","Delete backup":"Obriši backup","Delete backups that are older than":"Izbrisati rezervne kopije koje su starije od","Delete local database":"Obriši lokalnu bazu podataka","Delete remote files":"Obriši udaljene datoteke","Delete the local database":"Obriši lokalnu bazu podataka","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Obrisati {{filecount}} datoteka ({{filesize}}) iz udaljenog skladišta?","Delete …":"Brisanje …","Deleted":"Izbrisano","Deleted Versions":"Izbrisane verzije","Deleted files":"Izbrisani fajlovi","Deleting remote files …":"Brisanje udaljenih fajlova …","Deleting unwanted files …":"Brisanje neželjenih fajlova …","Description (optional)":"Opis (opciono)","Description:":"Opis:","Desktop":"Radna površina","Destination":"Odredište","Destination path":"Putanja odredišta","Disabled":"Onemogućeno","Dismiss":"Odbaci","Dismiss all":"Odbaci sve","Display and color theme":"Ekran i tema boja","Do you really want to delete the backup: \"{{name}}\" ?":"Da li zaista želite da obrišete rezervnu kopiju: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Da li zaista želiš da obrišeš lokalnu bazu podataka za: {{name}}","Done":"Završi","Download":"Preuzmi","Downloaded files":"Preuzeti fajlovi","Downloading files …":"Preuzimanje fajlova …","Downloading update…":"Preuzimanje ažuriranja…","Duplicate option {{opt}}":"Duplikat opcije {{opt}}","Duplicati Website":"Duplicati veb sajt","Duplicati forum":"Duplicati forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati će se pokrenuti kada se startuje, ali će ostati u pauziranom stanju sve vreme. Duplicati će zauzeti minimalne sistemske resurse i neće praviti rezervne kopije.","Duration":"Trajanje","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Svaka rezervna kopija ima lokalnu bazu podataka koja je povezana sa njom, koja čuva informacije o udaljenoj rezervnoj kopiji na lokalnoj mašini.\nKada brišete rezervnu kopiju, takođe možete izbrisati lokalnu bazu podataka bez uticaja na mogućnost vraćanja udaljenih fajlova.\nAko koristite lokalnu bazu podataka za rezervne kopije sa komandne linije, trebalo bi da zadržite bazu podataka.","Edit as list":"Izmeni kao listu","Edit as text":"Izmeni kao tekst","Edit …":"Izmeni ...","Encrypt file":"Šifrujte fajl","Encryption":"Šifrovanje","Encryption changed":"Šifrovanje promenjeno","Encryption passphrase":"Šifrovanje pristupne fraze","End":"Kraj","Enter URL":"Unesi URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ručno unesite strategiju zadržavanja. Čuvari mesta su D/W/Y za dane/sedmice/godine i U za neograničeno. Sintaksa je: 7D:1D,4W:1W,36M:1M. Ovaj primer čuva jednu rezervnu kopiju za svaki od narednih 7 dana, jednu za svaku od naredne 4 nedelje i jednu za svaki od narednih 36 meseci. Ovo se takođe može napisati kao 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Unesite frazu lozinke rezervne kopije, ako postoji","Enter configuration details":"Unesite detalje konfiguracije","Enter encryption passphrase":"Unesite frazu lozinke enkripcije","Enter expression here":"Ovde unesite izraz","Enter the destination path":"Unesite odredišnu putanju","Error":"Greška","Error!":"Greška!","Errors and crashes":"Greške i rušenja","Examined":"Ispitano","Exclude":"Izuzmi","Exclude directories whose names contain":"Izuzmite direktorijume čija imena sadrže","Exclude expression":"Izuzmi izraz","Exclude file":"Izuzmi fajl","Exclude file extension":"Izuzmi ekstenziju fajla","Exclude files whose names contain":"Izuzmi fajlove čija imena sadrže","Exclude filter group":"Izuzmi grupu filtera","Exclude folder":"Izuzmi fasciklu","Exclude regular expression":"Isključi regularni izraz","Existing file found":"Pronađen je postojeći fajl","Experimental":"Eksperimentalno","Export":"Izvezi","Export backup configuration":"Izvezi podešavanja rezervne kopije","Export configuration":"Izvezi podešavanja","Export passwords":"Izvezi lozinke","Export …":"Izvoz ...","Exporting …":"Izvozim ...","External link":"Spoljašnja veza","FTP (Alternative)":"FTP (Alternativno)","Failed to build temporary database: {{message}}":"Pravljenje privremene baze podataka nije uspelo: {{message}}","Failed to connect:":"Neuspelo povezivanje:","Failed to connect: {{message}}":"Neuspelo povezivanje: {{message}}","Failed to delete:":"Brisanje nije uspelo:","Failed to fetch path information: {{message}}":"Nije uspelo preuzimanje informacija o putanji: {{message}}","Failed to find backup:":"Pronalaženje rezervne kopije nije uspelo:","Failed to read backup defaults:":"Čitanje podrazumevanih rezervnih kopija nije uspelo:","Failed to restore files: {{message}}":"Vraćanje fajlova nije uspelo: {{message}}","Failed to save:":"Čuvanje nije uspelo:","Fetching path information …":"Preuzimanje informacija o putanji …","File":"Fajl","Files larger than:":"Fajlovi veći od:","Filters":"Filteri","Finished!":"Završeno!","First run setup":"Podešavanje za prvo pokretanje","Folder":"Fascikla","Folder path":"Putanja do fascikle","Fri":"Pet","GByte":"GBajt","GByte/s":"GBajt/s","GCS Project ID":"GCS ID projekta","General":"Generalno","General backup settings":"Opšta podešavanja rezervnih kopija","General options":"Generalne opcije","Generate":"Generiši","Getting file versions …":"Dohvatanje verzija fajla ...","Group email":"Grupna e-pošta","Hidden files":"Skriveni fajlovi","Hide":"Sakrij","Hide hidden folders":"Sakrij skrivene fascikle","Home":"Glavna","Hostnames":"Imena hostova","Hours":"Sati","How do you want to handle existing files?":"Kako želite da rukujete postojećim fajlovima?","Hyper-V Machine":"Hyper-V mašina","Hyper-V Machine:":"Hyper-V mašina:","Hyper-V Machines":"Hyper-V mašine","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Ako je neki datum propušten, posao će biti pokrenut što je pre moguće.","If at least one newer backup is found, all backups older than this date are deleted.":"Ako se pronađe bar jedna novija rezervna kopija, sve rezervne kopije starije od ovog datuma se brišu.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Ako ne unesete putanju, svi fajlovi će biti sačuvani u fascikli za prijavu.\nJeste li sigurni da je to ono što želite?","If you do not enter an API Key, the tenant name is required":"Ako ne unesete API ključ, potrebno je ime zakupca","Import":"Uvoz","Import Destination URL":"Uvezite odredišnu URL adresu","Import backup configuration":"Uvezite konfiguraciju rezervne kopije","Import from a file":"Uvezi iz fajla","Import metadata":"Uvezite metapodatke","Importing …":"Uvoz ...","Include a file?":"Uključiti fajl?","Include expression":"Uključite izraz","Include regular expression":"Uključite regularni izraz","Incorrect answer, try again":"Netačan odgovor, pokušajte ponovo","Individual builds for developers only. Not for use with important data.":"Pojedinačne verzije samo za programere. Nije za upotrebu sa važnim podacima.","Information":"Informacije","Invalid characters in path":"Nevažeći znakovi u putanji","Invalid retention time":"Nevažeće vreme zadržavanja","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Moguće je povezati se na neki FTP bez lozinke.\nDa li ste sigurni da vaš FTP server podržava prijavljivanje bez lozinke?","KByte":"KBajt","KByte/s":"KBajt/s","Keep a specific number of backups":"Čuvajte određeni broj rezervnih kopija","Keep all backups":"Čuvajte sve rezervne kopije","Keystone API version":"Keystone API verzija","Language in user interface":"Jezik u korisničkom interfejsu","Last month":"Prošlog meseca","Last successful backup:":"Poslednja uspešna rezervna kopija:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Poslednje uspešno vraćanje: {{time}} (trajalo je {{duration || '0 seconds'}})","Latest":"Najnovije","Libraries":"Biblioteke","Listing backup dates …":"Navođenje datuma rezervnih kopija …","Listing remote files for purge …":"Lista udaljenih fajlova za čišćenje …","Listing remote files …":"Lista udaljenih fajlova ...","Live":"Uživo","Load a configuration from an exported job or a storage provider":"Učitajte konfiguraciju iz izvezenog posla ili dobavljača skladišta","Load destination from an exported job or a storage provider":"Učitajte odredište iz izvezenog posla ili dobavljača skladišta","Load older data":"Učitaj starije podatke","Loading …":"Učitavanje ...","Local database path:":"Putanja lokalne baze podataka:","Local repository":"Lokalno skladište","Local storage":"Lokalno skladište","Location":"Lokacija","Location where buckets are created":"Lokacija na kojoj se kreiraju segmenti","Log data for {{Backup.Backup.Name}}":"Podaci evidencije za {{Backup.Backup.Name}}","Log data from the server":"Evidentirajte podatke sa servera","Log out":"Odjavi se","MByte":"MBajt","MByte/s":"MBajt/s","Maintenance":"Održavanje","Manually type path":"Ručno unesite putanju","Max download speed":"Maksimalna brzina preuzimanja","Max upload speed":"Maksimalna brzina otpremanja","Menu":"Meni","Microsoft SQL Database:":"Microsoft SQL baza podataka:","Microsoft SQL Databases":"Microsoft SQL baze podataka","Minimum redundancy":"Minimalna redundantnost","Minimum redundancy is 1.0":"Minimalna redundantnost je 1.0","Minutes":"Minute","Missing name":"Nedostaje naziv","Missing passphrase":"Nedostaje fraza lozinke","Missing sources":"Nedostaju izvori","Modified":"Modifikovano","Mon":"Pon","Months":"Meseci","Move existing database":"Premesti postojeću bazu podataka","Move failed:":"Premeštanje nije uspelo:","My Documents":"Moji dokumenti","My Music":"Moja muzika","My Photos":"Moje fotografije","My Pictures":"Moje slike","Name":"Naziv","Never":"Nikad","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Novo korisničko ime je {{user}}.\nAžurirani akreditivi za korišćenje novog korisnika sa ograničenjem","Next":"Sledeće","Next scheduled run:":"Sledeće zakazano pokretanje:","Next scheduled task:":"Sledeći zakazan zadatak:","Next task:":"Sledeći zadatak:","Next time":"Sledeći put","No":"Ne","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Nijedan sertifikat prethodno nije naveden, proverite kod administratora servera da li je ključ tačan: {{key}}\n\nDa li želite da odobrite prijavljeni ključ hosta?","No editor found for the "{{backend}}" storage type":"Nije pronađen nijedan uređivač za "{{backend}}" tip skladištenja","No encryption":"Bez šifrovanja","No items selected":"Nema izabranih stavki","No items to restore, please select one or more items":"Nema stavki za vraćanje, izaberite jednu ili više stavki","No passphrase entered":"Lozinka nije uneta","No scheduled tasks":"Nema zakazanih zadataka","Non-matching passphrase":"Pristupna fraza koja se ne podudara","None / disabled":"Ništa / onemogućeno","Not using encryption":"Ne koristi šifrovanje","Nothing will be deleted. The backup size will grow with each change.":"Ništa neće biti izbrisano. Veličina rezervne kopije će rasti sa svakom promenom.","OK":"U redu","Once there are more backups than the specified number, the oldest backups are deleted.":"Kada ima više rezervnih kopija od navedenog broja, najstarije rezervne kopije se brišu.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Otvoren","Operating System":"Operativni sistem","Operation":"Operacija","Operations:":"Operacije:","Optional authentication password":"Opciona lozinka za autentifikaciju","Optional authentication username":"Opciono korisničko ime za autentifikaciju","Options":"Opcije","Original location":"Originalna lokacija","Others":"Ostalo","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Vremenom će rezervne kopije biti automatski izbrisane. Ostaće po jedna rezervna kopija za svaku od poslednjih 7 dana, svaku od poslednje 4 nedelje, svaku od poslednjih 12 meseci. Uvek će biti najmanje jedna preostala rezervna kopija.","Overwrite":"Prepiši","Passphrase":"Lozinka","Passphrase (if encrypted)":"Lozinka (ako je šifrovano)","Passphrase changed":"Lozinka promenjena","Passphrases are not matching":"Lozinke se ne poklapaju","Passphrases do not match":"Pristupne fraze se ne podudaraju","Password":"Lozinka","Patching files with local blocks …":"Zakrpa fajlova sa lokalnim blokovima …","Path":"Putanja","Path not found":"Putanja nije pronađena","Path on server":"Putanja na serveru","Path or subfolder in the bucket":"Putanja ili podfascikla u segment-u","Pause":"Pauza","Pause after startup or hibernation":"Pauziraj nakon pokretanja ili hibernacije","Pause options":"Opcije pauze","Permissions":"Dozvole","Pick location":"Izaberite lokaciju","Point to your backup files and restore from there":"Postavite pokazivač na svoje rezervne kopije fajlova i vratite ih odatle","Port":"Port","Prevent tray icon automatic log-in":"Sprečite automatsko prijavljivanje ikonom na traci","Previous":"Prethodno","Progress:":"Napredak:","ProjectID is optional if the bucket exist":"ID projekta je opcioni ako segment postoji","Proprietary":"Vlasnički","Purge Phase":"Faza čišćenja","Purging files complete!":"Čišćenje fajlova je završeno!","Purging files …":"Čišćenje fajlova …","Rebuilding local database …":"Ponovno kreiranje lokalne baze podataka …","Recreate (delete and repair)":"Ponovo kreirajte (izbrišite i popravite)","Recreate Database Phase":"Ponovo kreirajte fazu baze podataka","Recreating database …":"Ponovo kreiranje baze podataka …","Registering temporary backup …":"Registrovanje privremene rezervne kopije …","Relative paths not allowed":"Relativne putanje nisu dozvoljene","Reload":"Učitaj ponovo","Remote":"Udaljeno","Remote Path":"Udaljena putanja","Remote Repository":"Udaljeno spremište","Remote path":"Udaljena putanja","Remote repository":"Udaljeno spremište","Remote volume size":"Veličina udljenog volumena","Remove":"Ukloni","Remove option":"Ukloni opciju","Removed files":"Ukloni fajlove","Repair":"Popravi","Repair Phase":"Popravi fazu","Repairing database …":"Popravljanje baze podataka …","Repeat Passphrase":"Ponovite lozinku","Reporting:":"Izveštavanje:","Reset":"Resetovanje","Restore":"Vrati","Restore complete!":"Vraćanje je završeno!","Restore files":"Vrati fajlove","Restore files …":"Vraćanje fajlova ...","Restore from":"Vrati iz","Restore from backup configuration":"Vrati iz podešavanja rezervne kopije","Restore options":"Vrati opcije","Restore read/write permissions":"Vrati dozvole za čitanje i upis","Restored Files":"Vraćeni fajlovi","Restored Folders":"Vraćene fascikle","Restored Symlinks":"Vraćeni Symlinks","Restoring files …":"Vraćanje fajlova ...","Resume":"Nastavi","Rewritten File Lists":"Prepisane liste fajlova","Run again every":"Izvrši ponovo svaki","Run now":"Izvrši sad","Running commandline entry":"Izvrši unos komandne linije","Running task:":"Izvršavanje zadatka:","Running …":"Izvršavanje ...","S3 Compatible":"S3 kompatibilno","Same as the base install version: {{channelname}}":"Isto kao i verzija osnovne instalacije: {{channelname}}","Sat":"Sub","Satellite":"Satelit","Save":"Sačuvaj","Save and repair":"Sačuvaj i popravi","Save different versions with timestamp in file name":"Sačuvaj drugu verziju sa vremenom u nazivu fajla","Save immediately":"Sačuvaj odmah","Scanning existing files …":"Skeniranje postojećih fajlova …","Scanning for local blocks …":"Skeniranje lokalnih blokova ...","Schedule":"Raspored","Search":"Pretraga","Search for files":"Pretraga fajlova","Seconds":"Sekunde","Select a log level and see messages as they happen:":"Izaberite nivo dnevnika i pogledajte poruke kako se dešavaju:","Select files":"Izaberite fajlove","Server":"Server","Server and port":"Server i port","Server hostname or IP":"Ime servera ili IP adresa","Server is currently paused,":"Server je trenutno pauziran,","Server is currently paused, do you want to resume now?":"Server je trenutno pauziran, da li želite da nastavite odmah?","Server password":"Lozinka servera","Server paused":"Server je pauziran","Server state properties":"Opcije stanja servera","Settings":"Podešavanja","Show":"Prikaži","Show advanced editor":"Prikaži napredni editor","Show hidden folders":"Prikaži skrivene fascikle","Show log":"Prikaži dnevnik","Show log …":"Prikazujem dnevnik ...","Show treeview":"Prikazujem izled stabla","Sia server password":"Lozinka za Sia server","Smart backup retention":"Pametno čuvanje rezervne kopije","Some OpenStack providers allow an API key instead of a password and tenant name":"Neki OpenStack provajderi dozvoljavaju API ključ umesto lozinke i imena zakupca","Some S3 providers might only be compatible with a certain client library":"Neki S3 provajderi mogu biti kompatibilni samo sa određenom bibliotekom klijenata","Source Data":"Izvorni podaci","Source Files":"Izvorni fajlovi","Source data":"Izvorni podaci","Source folders":"Izvorne fascikle","Source:":"Izvor:","Specific builds for developers only. Not for use with important data.":"Posebne verzije samo za programere. Nije za upotrebu sa važnim podacima.","Standard protocols":"Standardni protokoli","Start":"Start","Starting backup …":"Startujem rezervnu kopiju ...","Starting restore …":"Startujem obnavljanje ...","Starting the restore process …":"Startujem proces obnavljanja ...","Stop after the current file":"Zaustavi nakon trenutnog fajla","Stop running backup":"Zaustavi pokrenutu rezervnu kopiju","Stop running task":"Zaustavi pokrenuti zadatak","Stopping after the current file:":"Zaustavljanje nakon trenutnog fajla:","Stopping task:":"Zaustavljanje zadatka:","Storage Type":"Tip skladišta","Storage class":"Klasa skladišta","Storage class for creating a bucket":"Klasa skladišta za kreiranje segment-a","Stored":"Uskladišteno","Strong":"Jaka","Success":"Uspešno","Sun":"Ned","Symbolic link":"Simbolička veza","System Files":"Sistemski fajlovi","System default ({{levelname}})":"Podrazumevani sistem ({{levelname}})","System files":"Sistemski fajlovi","System info":"Sistemske informacije","System properties":"Osobine sistema","TByte":"TBajt","TByte/s":"TBajt/s","Task is running":"Zadatak se izvršava","Temporary Files":"Privremeni fajlovi","Temporary files":"Privremene fajlovi","Test Phase":"Faza testiranje","Test connection":"Ispitaj vezu","Testing permissions …":"Ispitivanje dozvola ...","Testing …":"Ispitivanje ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Polje '{{fieldname}}' sadrži nevažeći znak: {{character}} (vrednost: {{value}}, indeks: {{pos}})","The backup is missing, has it been deleted?":"Nedostaje rezervna kopija, da li je izbrisana?","The backup was temporary and does not exist anymore, so the log data is lost":"Rezervna kopija je bila privremena i više ne postoji, tako da su podaci dnevnika izgubljeni","The bucket name should be all lower-case, convert automatically?":"Naziv segmenta treba da bude malim slovima, da li da se automatski konvertuje?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfiguraciju treba čuvati na sigurnom. Da li ste sigurni da želite da sačuvate nešifrovani fajl koji sadrži vaše lozinke?","The dark theme (by Michal)":"Tamna tema (napravio Michal)","The default blue on white theme (by Alex)":"Podrazumevana tema plavo na belom (napravio Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Fascikla {{folder}} ne postoji.\nKreirate je sada?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Ključ hosta se promenio, proverite kod administratora servera da li je to tačno, inače biste mogli da budete žrtva napada MAN-IN-THE-MIDDLE.\n\nDa li želite da ZAMENITE svoj TRENUTNI ključ hosta \"{{prev}}\" sa PRIJAVLJENIM ključem hosta: {{key}}?","The passwords do not match":"Lozinke se ne poklapaju","The path does not appear to exist, do you want to add it anyway?":"Putanja izgleda ne postoji, da li svejedno želite da je dodate?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Putanja se ne završava znakom '{{dirsep}}', što znači da uključujete fajl, a ne fasciklu.\n\nDa li želite da uključite navedeni fajl?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Putanja mora biti apsolutna putanja, tj. mora da počinje sa kosom crtom unapred '/'","The region parameter is only applied when creating a new bucket":"Parametar regiona se primenjuje samo pri kreiranju novog segmenta","The region parameter is only used when creating a bucket":"Parametar regiona se kreira samo kada se koristi segment","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Sertifikat servera nije mogao biti proveren.\nDa li želite da odobrite SSL sertifikat sa hešom: {{hash}}?","The storage class affects the availability and price for a stored file":"Klasa skladišta utiče na dostupnost i cenu za uskladišteni fajl","The target folder contains encrypted files, please supply the passphrase":"Ciljana fasckla sadrži šifrovane fajlove, molimo unesite pristupnu frazu","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Korisnik ima previše dozvola, Da li želite da napravite novog ograničenog korisnika, samo sa dozvolama za izabranu putanju?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Ova rezervna kopija je napravljena na drugom operativnom sistemu. Vraćanje fajlova bez navođenja odredišne fascikle može dovesti do vraćanja fajlova na neočekivana mesta. Da li ste sigurni da želite da nastavite bez odabira odredišne fascikle?","This month":"Ovog meseca","This week":"Ove sedmice","Throttle settings":"Podešavanja regulacije","Thu":"Čet","Time":"Vreme","To File":"U datoteku","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"Da potvrdite da želite obrisati sve udaljene datoteke sa imenom \"{{name}}\", molimo unesite reč koju vidite ispod","To export without a passphrase, uncheck the \"Encrypt file\" box":"Za izvoz bez lozinke, polje \"Šifruj datoteku\" ne treba da bude označeno","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"Da bi sprečio različite napade zasnovane na DNS-u, Duplicati ograničava dozvoljena imena hostova na ona koja su ovde navedena. Direktan IP pristup i lokalni host je uvek dozvoljen. Višestruka imena hostova mogu biti isporučena sa tačkom i zarezom. Ako je neko od dozvoljenih imena hostova zvezdica (*), sva imena hostova su dozvoljena i ova funkcija je onemogućena. Ako je polje prazno, dozvoljen je samo pristup IP adresi i lokalnom hostu.","Today":"Danas","Trust host certificate?":"Verujete sertifikatu hosta?","Trust server certificate?":"Veruj sertifikatu servera?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Isprobajte nove funkcije na kojima radimo. Trenutno najstabilnija dostupna verzija. Testirajte podatke za vraćanje pre nego što ih upotrebite u proizvodnim okruženjima.","Tue":"Uto","Type passphrase here.":"Ovde unesite pristupnu frazu.","Type to highlight files":"Ukucajte da biste istakli fajlove","Unknown backup size and versions":"Nepoznata veličina i verzije rezervne kopije","Until resumed":"Dok se ne nastavi","Update channel":"Ažurirajte kanal","Update failed:":"Ažuriranje nije uspelo:","Updating with existing database":"Ažuriranje sa postojećom bazom podataka","Uploaded files":"Otpremanje fajlova","Uploading verification file …":"Otpremanje fajla za verifikaciju …","Usage statistics":"Statistika upotrebe","Usage statistics, warnings, errors, and crashes":"Statistika korišćenja, upozorenja, greške i rušenja","Use SSL":"Koristi SSL","Use existing database?":"Koristi postojeću bazu podataka?","Use weak passphrase":"Koristi slabu lozinku","Useless":"Beskorisno","User data":"Podaci o korisniku","User domain name":"Ime korisničkog domena","User has too many permissions":"Korisnik ima previše dozvola","User interface settings":"Podešavanja korisničkog interfejsa","Username":"Korisničko ime","Vacuuming database …":"Usisavanje baze podataka …","Validating …":"Provera valjanosti ...","Verifications":"Provere","Verify files":"Proveri datoteke","Verifying answer":"Proveravanje odgovora","Verifying backend data …":"Provra pozadinskih podataka ...","Verifying files …":"Provera fajlova ...","Verifying remote data …":"Provera udaljenih podataka ...","Verifying restored files …":"Provera vraćenih fajlova ...","Verifying …":"Provera ...","Version ID":"ID verzije","Very strong":"Veoma jaka","Very weak":"Veoma slaba","Visit us on":"Posetite nas na","WARNING: This will prevent you from restoring the data in the future.":"UPOZORENJE: Ovo će vas sprečiti da vratite podatke u budućnosti.","Waiting for task to begin":"Čekanje na početak zadatka","Waiting for upload to finish …":"Čeka se da se otpremanje završi …","Warnings, errors and crashes":"Upozorenja, greške i padovi","We recommend that you encrypt all backups stored outside your system":"Preporučujemo da šifrujete sve backup-ove uskladištene van Vašeg sistema","Weak":"Slaba","Weak passphrase":"Slaba lozinka","Wed":"Sre","Weeks":"Sedmica","Where do you want to restore from?":"Odakle želite da vratite?","Where do you want to restore the files to?":"Gde želite da vratite fajlove?","Years":"Godina","Yes":"Da","Yes, I have stored the passphrase safely":"Da, uskladištio sam lozinku bezbedno","Yes, I understand the risk":"Da, razumem rizik","Yes, I'm brave!":"Da, hrabar sam!","Yes, please break my backup!":"Da, molim te pauziraj moju rezervnu kopiju!","Yesterday":"Juče","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Menjate putanju baze podataka dalje od postojeće baze podataka.\nJeste li sigurni da je to ono što želite?","You are currently running {{appname}} {{version}}":"Trenutno koristite {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Promenili ste režim šifrovanja. Ovo bi moglo biti loš izbor. Preporučujemo vam da umesto toga napravite novu rezervnu kopiju","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Promenili ste pristupnu frazu lozinke, koja nije podržana. Preporučujemo vam da umesto toga napravite novu rezervnu kopiju.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Izabrali ste da ne šifrujete rezervnu kopiju. Šifrovanje se preporučuje za sve podatke uskladištene na udaljenom serveru.","You have chosen to restore to a new location, but not entered one":"Odabrali ste da vratite na novu lokaciju, ali niste je uneli","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Generisali ste jaku pristupnu frazu lozinke. Uverite se da ste napravili bezbednu kopiju pristupne fraze lozinke, jer podaci ne mogu da se povrate ako izgubite pristupnu frazu lozinke.","You must choose at least one source folder":"Morate odabrati najmanje jednu izvornu fasciklu","You must enter a domain name to use v3 API":"Morate uneti naziv domena da biste koristili v3 API","You must enter a name for the backup":"Morate uneti naziv za rezervnu kopiju","You must enter a passphrase or disable encryption":"Morate uneti lozinku ili isključiti šifrovanje","You must enter a password to use v3 API":"Morate uneti lozinku da biste koristili v3 API","You must enter a positive number of backups to keep":"Morate da unesete važeće vreme trajanje za čuvanje rezervnih kopija","You must enter a tenant (aka project) name to use v3 API":"Morate da unesete ime zakupca (aka projekta) da biste koristili v3 API","You must enter a valid duration for the time to keep backups":"Morate da unesete važeće vreme trajanja za čuvanja rezervnih kopija","You must enter a valid retention policy string":"Morate da unesete važeći niz politike retencije","You must fill in the password":"Morate uneti lozinku","You must fill in the server name or address":"Morate uneti naziv servera ili adresu","You must fill in the username":"Morate uneti korisničko ime","You must fill in {{field}}":"Morate uneti {{field}}","You must select or fill in the AuthURI":"Morate izabrati ili uneti AuthURI","You must select or fill in the server":"Morate izabrati ili uneti server","You must specify a path":"Morate navesti putanju","Your files and folders have been restored successfully.":"Vaše datoteke i fascikle su uspešno vraćene.","Your passphrase is easy to guess. Consider changing passphrase.":"Vaša lozinka je laka za pogađanje. Razmislite o promeni lozinke.","bucket/folder/subfolder":"segment/fascikla/podfascikla","byte":"bajt","byte/s":"bajt/ova","custom":"poručen","resume now":"nastavi odmah","unless you are explicitly specifying --group-id":"osim ako izričito ne navedete --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} su prvenstveno razvili {{dev1}} i {{dev2}}. {{appname}} se može preuzeti sa {{websitename}}. {{appname}} je licenciran pod {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} fajlovi ({{size}}) da ide {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} verzije"],"{{number}} Hour":"{{number}} sati","{{number}} Hours":"{{number}} sati","{{number}} Minutes":"{{number}} minuta","{{time}} (took {{duration}})":"{{time}} (trajalo {{duration}})"}); + gettextCatalog.setStrings('sv_SE', {"- pick an option -":"- välj ett alternativ -","...loading...":"...laddar...","API key":"API-nyckel","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"Om","About {{appname}}":"Om {{appname}}","Access Key":"Åtkomstnyckel","Access denied":"Åtkomst nekad","Access grant":"Åtkomst beviljad","Access to user interface":"Access till användarinterface","Account name":"Kontonamn","Add a new backup":"Lägg till ny säkerhetskopia","Add a path directly":"Lägg till direkt sökväg","Add advanced option":"Lägg till avancerade val","Add backup":"Lägg till säkerhetskopia","Add filter":"Lägg till filter","Add path":"Lägg till sökväg","Added":"Sparad","Adjust bucket name?":"Justera \"bucket name\"?","Advanced Options":"Avancerade tillägg","Advanced options":"Avancerade tillägg","Advanced:":"Avancerat:","All Hyper-V Machines":"Alla Hyper-V datorer","All Microsoft SQL Databases":"Alla Microsoft SQL-databaser","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"Alla användningsrapporter skickas anonymt och innehåller ingen personlig information. De innehåller information om hårdvara och operativsystem, typ av backend, säkerhetskopieringstid, övergripande storlek på källdata och liknande data. De innehåller inte sökvägar, filnamn, användarnamn, lösenord eller liknande känslig information.","Allow remote access (requires restart)":"Tillåt fjärrstyrning (kräver omstart)","Allowed days":"Tillåtna dagar","An existing file was found at the new location":"En existerande fil hittades på den nya platsen","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"En existerande fil hittades på den nya platsen. Är du säker att databasen skall peka till en existerande fil?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"En befintlig lokal databas för lagringen har hittats.\nÅteranvändning av databasen gör att kommandorads- och serverinstanserna kan arbeta på samma fjärrlagring.\n\nVill du använda den befintliga databasen?","Anonymous usage reports":"Anonym användarrapport","Applications":"Applikationer","As Command-line":"Som kommandorad","AuthID":"AuthID","Authentication method":"Autentiseringsmetod","Authentication method ({{auth_method}})":"Autentiseringsmetod ({{auth_method}})","Authentication password":"Autentiseringslösenord","Authentication username":"Autentiseringsanvändarnamn","Autogenerated passphrase":"Autogenererat lösenord","B2 Application ID":"B2 Application ID","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage Account ID","B2 Cloud Storage Application ID":"B2 Cloud Storage Application ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"Åter","Backup complete!":"Säkerhetskopieringen är klar!","Backup destination":"Destination till säkerhetskopia","Backup location":"Plats för säkerhetskopia","Backup retention":"Backup-bibehållning","Backup:":"Säkerhetskopia:","Beta":"Beta","Broken access":"Trasig åtkomst","Browse":"Bläddra","Browser default":"Webbläsarens standard","Bucket create location":"Bucket skapa plats","Bucket name":"Bucket namn","Bucket storage class":"Bucket förvaringsklass","Building list of files to restore …":"Skapar lista med filer för återskapande ...","Building partial temporary database …":"Skapar tillfällig databas ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"Genom att tillåta fjärråtkomst lyssnar servern på förfrågningar från vilken maskin som helst i ditt nätverk. Om du aktiverar det här alternativet, se till att du alltid använder datorn i ett säkert brandvägg-skyddat nätverk.","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"Som standard öppnar tray-icon användargränssnittet med en token som låser upp användargränssnittet. Detta säkerställer att du kan komma åt användargränssnittet från ikonen i fältet, samtidigt som du kräver att andra anger ett lösenord. Om du föredrar att behöva skriva in lösenordet, även när du kommer åt användargränssnittet från ikonen i fältet, aktivera det här alternativet.","Cache Files":"Cachefiler","Canary":"Kanariefågel","Cancel":"Avbryt","Cannot move to existing file":"Kan inte flytta till befintlig fil","Changelog":"Ändringslogg","Changelog for {{appname}} {{version}}":"Ändringslogg för {{appname}} {{version}}","Check failed:":"Kontroll misslyckades:","Check for updates now":"Kontrollera uppdateringar nu","Checking for updates …":"Kontrollerar uppdateringar ...","Chose a storage type to get started":"Välj en lagringstyp för att börja","Click the AuthID link to create an AuthID":"Klicka på AuthID-länken för att skapa ett AuthID","Click to set throttle options":"Klicka för att välja begränsningsalternativ","Client library to use":"Klientbibliotek att använda","Commandline …":"Kommandorad ...","Compact Phase":"Kompakt Fas","Compact now":"Komprimera nu","Compacting remote data …":"Komprimerar fjärrdata …","Complete log":"Komplett logg","Completing backup …":"Slutför säkerhetskopieringen...","Completing previous backup …":"Slutför tidigare säkerhetskopiering …","Computer":"Dator","Configuration file:":"Konfigurationsfil:","Configuration:":"Konfiguration:","Configure a new backup":"Konfigurera en ny säkerhetskopia","Confirm delete":"Bekräfta borttagning","Confirm encryption passphrase":"Bekräfta krypteringslösenord","Confirm passphrase":"Bekräfta lösenfras","Confirmation required":"Bekräftelse beövs","Connect":"Anslut","Connect now":"Anslut nu","Connecting to server …":"Ansluter till server ...","Connection lost":"Anslutning avbruten","Connection worked!":"Anslutning OK!","Container name":"Behållarnamn","Container region":"Behållarregion","Continue":"Fortsätt","Continue without encryption":"Fortsätt utan kryptering","Copied!":"Kopierad!","Copy":"Kopia","Copy Destination URL to Clipboard":"Kopiera mål-URL till urklipp","Copy failed. Please manually copy the URL":"Kopering misslyckades, var vänlig kopiera URLen manuellt","Core options":"Kärnalternativ","Counting ({{files}} files found, {{size}})":"Beräknar ({{files}} filer hittade, {{size}})","Crashes only":"Endast kraschar","Create bug report …":"Skapa buggrapport","Create folder?":"Skapa mapp?","Created new limited user":"Skapa ny begränsad användare","Creating bug report …":"Skapar felrapport ...","Creating new user with limited access …":"Skapar ny användare med begränsad åtkomst …","Creating target folders …":"Skapar målmappar …","Creating temporary backup …":"Skapar temporär säkerhetskopia ...","Current action:":"Nuvarande åtgärd:","Current file:":"Nuvarande fil:","Current version is {{versionname}} ({{versionnumber}})":"Aktuell version är {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"Anpassad S3-slutpunkt","Custom Satellite":"Anpassad Satellit","Custom Satellite ({{satellite}})":"Anpassad Satellit ({{satellite}})","Custom authentication url":"Anpassad autentiseringsadress","Custom backup retention":"Anpassad backup-bibehållning","Custom location ({{server}})":"Anpassad plats ({{server}})","Custom region for creating buckets":"Anpassad region för att skapa buckets","Custom region value ({{region}})":"Anpassat värde för region ({{region}})","Custom server url ({{server}})":"Anpassad serveradress ({{server}})","Custom storage class ({{class}})":"Anpassad lagringsklass ({{class}})","Database …":"Databas ...","Days":"Dagar","Default":"Standard","Default ({{channelname}})":"Standard ({{channelname}})","Default excludes":"Standard exkluderingar","Default options":"Standardalternativ","Delete":"Radera","Delete Phase (Old Backup Versions)":"Ta bort fas (gamla säkerhetskopieringsversioner)","Delete backup":"Radera säkerhetskopia","Delete backups that are older than":"Radera säkerhetskopior äldre än","Delete local database":"Radera lokal databas","Delete remote files":"Radera målfiler","Delete the local database":"Radera lokal databas","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"Ta bort {{filecount}} filer ({{filesize}}) från fjärrmålet?","Delete …":"Radera ...","Deleted":"Raderade","Deleted Versions":"Raderade Versioner","Deleted files":"Raderade filer","Deleting remote files …":"Raderar fjärrfiler ...","Deleting unwanted files …":"Raderar oönskade filer...","Description (optional)":"Beskrivning (valfritt)","Description:":"Beskrivning:","Desktop":"Skrivbord","Destination":"Destination","Destination path":"Målsökväg","Disabled":"Avstängd","Dismiss":"Avfärda","Dismiss all":"Avfärda allt","Display and color theme":"Visnings- och färgtema","Do you really want to delete the backup: \"{{name}}\" ?":"Vill du verkligen radera säkerhetskopia för: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"Vill du verkligen radera den lokala databasen för: {{name}}","Done":"Klart","Download":"Ladda ner","Downloaded files":"Nedladdade filer","Downloading files …":"Laddar ner filer ...","Downloading update…":"Laddar ner uppdatering ...","Duplicate option {{opt}}":"Duplicera alternativ {{opt}}","Duplicati Website":"Duplicatis webbsida","Duplicati forum":"Duplicatis forum","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati kommer att köras när den startas, men förblir i pausat tillstånd under hela tiden. Duplicati kommer att uppta minimala systemresurser och inga säkerhetskopior kommer att köras.","Duration":"Varaktighet","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"Varje backup har en lokal databas som är associerad med den, som lagrar information om fjärrfilerna på den lokala maskinen.\nNär du tar bort en säkerhetskopia kan du också ta bort den lokala databasen utan att påverka möjligheten att återställa fjärrfilerna.\nOm du använder den lokala databasen för säkerhetskopior från kommandoraden bör du behålla databasen.","Edit as list":"Ändra som lista","Edit as text":"Ändra som text","Edit …":"Ändra ...","Encrypt file":"Kryptera fil","Encryption":"Kryptering","Encryption changed":"Kryptering förändrad","Encryption passphrase":"Ange krypteringslösenord","End":"Slut","Enter URL":"Ange URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"Ange en backupstategi manuellt. Användbara tecken är D/W/Y för dagar/veckor/år och U för obegränsat. Tillåten syntax är: 7D:1D,4W:1W,36M:1M. Detta exempel behåller en backup för var 7:e dag, en för var 4:e vecka och en för var 36:e månad. Detta kan också skriva som 1W:1D,1M:1W,3Y:1M.","Enter backup passphrase, if any":"Ange lösenordsfras, om tillämpligt","Enter configuration details":"Ange konfigurationsdetaljer","Enter encryption passphrase":"Ange krypteringslösenord","Enter expression here":"Ange uttryck här","Enter the destination path":"Ange målsökväg","Error":"Fel","Error!":"Fel!","Errors and crashes":"Fel och kraschar","Examined":"Granska","Exclude":"Exkludera","Exclude directories whose names contain":"Exkludera kataloger vars namn innehåller","Exclude expression":"Uteslut enligt uttryck","Exclude file":"Exkludera fil","Exclude file extension":"Uteslut filändelse","Exclude files whose names contain":"Uteslut filer vars namn innehåller","Exclude filter group":"Uteslut filtergrupp","Exclude folder":"Uteslut mapp","Exclude regular expression":"Uteslut enligt reguljärt uttryck","Existing file found":"Filen existerar redan","Experimental":"Experimentell","Export":"Exportera","Export backup configuration":"Exportera konfiguration för säkerhetskopia","Export configuration":"Exportera konfiguration","Export passwords":"Exportera lösenord","Export …":"Exportera ...","Exporting …":"Exporterar ...","External link":"Extern länk","FTP (Alternative)":"FTP (alternativ)","Failed to build temporary database: {{message}}":"Misslyckades med att skapa tillfällig databas: {{message}}","Failed to connect:":"Misslyckades med att ansluta:","Failed to connect: {{message}}":"Misslyckades med att ansluta: {{message}}","Failed to delete:":"Misslyckades med att radera:","Failed to fetch path information: {{message}}":"Misslyckades med att hämta sökvägsinformation: {{message}}","Failed to find backup:":"Misslyckades med att hitta säkerhetskopia:","Failed to read backup defaults:":"Misslyckades med att läsa standardinställningarna för säkerhetskopia:","Failed to restore files: {{message}}":"Misslyckades med att återställa filer: {{message}}","Failed to save:":"Misslyckades med att spara:","Fetching path information …":"Hämtar sökvägsinformation …","File":"Fil","Files larger than:":"Filer större än:","Filters":"Filter","Finished!":"Klar!","First run setup":"Nyinstallationsinställningar","Folder":"Mapp","Folder path":"Mappsökväg","Fri":"Fre","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Projekt-ID","General":"Generellt","General backup settings":"Allmän inställningar för säkerhetskopia","General options":"Generella inställningar","Generate":"Skapa","Getting file versions …":"Hämtar filversioner ...","Group email":"Grupp-epost","Hidden files":"Gömda filer","Hide":"Dölj","Hide hidden folders":"Visa dolda mappar","Home":"Hem","Hostnames":"Värdnamn","Hours":"Timmar","How do you want to handle existing files?":"Hur vill du hantera existerande filer?","Hyper-V Machine":"HyperV-maskin","Hyper-V Machine:":"HyperV-maskin:","Hyper-V Machines":"HyperV-maskiner","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"Om ett tillfälle missades görs uppgiften så fort som möjligt.","If at least one newer backup is found, all backups older than this date are deleted.":"Om minst en nyare säkerhetskopia finns, kommer alla säkerhetskopior äldre än detta datum att raderas.","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"Om du inte anger en sökväg kommer alla filer att lagras i inloggningsmappen.\nÄr du säker på detta?","If you do not enter an API Key, the tenant name is required":"Om du inte anger en API-nyckel krävs \"tenant name\"","Import":"Importera","Import Destination URL":"Importera destinationsadress","Import backup configuration":"Importera konfiguration för säkerhetskopia","Import from a file":"Importera från en fil","Import metadata":"Importera metadata","Importing …":"Importerar …","Include a file?":"Inkludera en fil?","Include expression":"Inkludera enligt uttryck","Include regular expression":"Inkludera enligt reguljärt uttryck","Incorrect answer, try again":"Felaktigt svar, försök igen","Individual builds for developers only. Not for use with important data.":"Individuella versioner endast för utvecklare. Ej för användning med viktig data.","Information":"Information","Invalid characters in path":"Ogiltiga tecken i sökvägen","Invalid retention time":"Ogiltig bibehållningstid","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"Det är möjligt att ansluta till vissa FTP utan ett lösenord.\nÄr du säker på att din FTP-server stöder lösenordsfria inloggningar?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"Behåll ett visst antal säkerhetskopior","Keep all backups":"Behåll alla säkerhetskopior","Keystone API version":"Keystone API-version","Language in user interface":"Språk i användargränssnittet","Last month":"Förra månaden","Last successful backup:":"Senaste lyckade säkerhetskopiering:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"Senaste lyckade återställning: {{tid}} (tog {{varaktighet || '0 sekunder'}})","Latest":"Senaste","Libraries":"Bibliotek","Listing backup dates …":"Listar datum för säkerhetskopia …","Listing remote files for purge …":"Listar fjärrfiler för rensning …","Listing remote files …":"Listar fjärrfiler ...","Live":"Live","Load a configuration from an exported job or a storage provider":"Hämta konfiguration från en exporterad rutin eller en lagringstjänst","Load destination from an exported job or a storage provider":"Hämta mål från en exporterad rutin eller en lagringstjänst","Load older data":"Hämta äldre data","Loading …":"Laddar ...","Local database path:":"Sökväg till lokal databas:","Local repository":"Lokalt arkiv","Local storage":"Lokal lagring","Location":"Plats","Location where buckets are created":"Plats där buckets skapas","Log data for {{Backup.Backup.Name}}":"Logg-data för {{Backup.Backup.Name}}","Log data from the server":"Logg data från servern","Log out":"Logga ut","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"Underhåll","Manually type path":"Skriv sökväg manuellt","Max download speed":"Max nedladdningshastighet","Max upload speed":"Max uppladdningshastighet","Menu":"Meny","Microsoft SQL Database:":"Microsoft SQL-databas:","Microsoft SQL Databases":"Microsoft SQL-databaser","Minimum redundancy":"Minsta redundans","Minimum redundancy is 1.0":"Minsta redundans är 1,0","Minutes":"Minuter","Missing name":"Saknar namn","Missing passphrase":"Saknar lösenfras ","Missing sources":"Saknade källor","Modified":"Ändrad","Mon":"Mån","Months":"Månader","Move existing database":"Flytta existerande databas","Move failed:":"Flytten misslyckades:","My Documents":"Mina Dokument","My Music":"Min Musi","My Photos":"Mina Foton","My Pictures":"Mina Bilder","Name":"Namn","Never":"Aldrig","New user name is {{user}}.\nUpdated credentials to use the new limited user":"Nytt användarnamn är {{user}}.\nUppdaterade användaruppgifter för att använda den nya begränsade användaren","Next":"Nästa","Next scheduled run:":"Nästa schemalagda körning:","Next scheduled task:":"Nästa schemalagda uppgift:","Next task:":"Nästa uppgift:","Next time":"Nästa gång","No":"Nej","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"Inget certifikat har angetts tidigare, kontrollera med serveradministratören att nyckeln är korrekt: {{key}}\n\nVill du godkänna den rapporterade värdnyckeln?","No editor found for the "{{backend}}" storage type":"Ingen redigerare hittades för "{{backend}}" lagringstyp","No encryption":"Ingen kryptering","No items selected":"Inga objekt har valts","No items to restore, please select one or more items":"Inga objekt att återställa, välj ett eller flera objekt","No passphrase entered":"Ingen lösenfras har angetts","No scheduled tasks":"Inga schemalagda uppgifter","Non-matching passphrase":"Lösenfras som inte matchar","None / disabled":"Ingen / inaktiverad","Not using encryption":"Använder inte kryptering","Nothing will be deleted. The backup size will grow with each change.":"Ingenting kommer att raderas. Storleken på säkerhetskopieringen kommer att växa med varje ändring.","OK":"OK","Once there are more backups than the specified number, the oldest backups are deleted.":"När det finns fler säkerhetskopior än det angivna antalet, raderas de äldsta säkerhetskopiorna.","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"Öppnad","Operating System":"Operativsystem","Operation":"Operation","Operations:":"Operationer:","Optional authentication password":"Valfritt lösenord för autentisering","Optional authentication username":"Valfritt användarnamn för autentisering","Options":"Alternativ","Original location":"Ursprunglig plats","Others":"Andra","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"Med tiden kommer säkerhetskopior att raderas automatiskt. Det kommer att finnas kvar en säkerhetskopia för var och en av de senaste 7 dagarna, var och en av de senaste 4 veckorna, var och en av de senaste 12 månaderna. Det kommer alltid att finnas minst en säkerhetskopia kvar.","Overwrite":"Skriva över","Passphrase":"Lösenfras","Passphrase (if encrypted)":"Lösenfras (om krypterad)","Passphrase changed":"Lösenfras ändrad","Passphrases are not matching":"Lösenfraser matchar inte","Passphrases do not match":"Lösenfraser matchar inte","Password":"Lösenord","Patching files with local blocks …":"Patchar filer med lokala block...","Path":"Sökväg","Path not found":"Sökvägen hittades inte","Path on server":"Sökväg på servern","Path or subfolder in the bucket":"Sökväg eller undermapp i bucket","Pause":"Paus","Pause after startup or hibernation":"Pausa efter uppstart eller viloläge","Pause options":"Pausalternativ","Permissions":"Behörigheter","Pick location":"Välj plats","Point to your backup files and restore from there":"Peka på dina säkerhetskopior och återställ därifrån","Port":"Port","Prevent tray icon automatic log-in":"Förhindra att tray-icon automatiskt loggar in","Previous":"Tidigare","Progress:":"Framsteg:","ProjectID is optional if the bucket exist":"ProjectID är valfritt om bucket finns","Proprietary":"Proprietär","Purge Phase":"Rensningsfas","Purging files complete!":"Rensning av filer klar!","Purging files …":"Rensar filer...","Rebuilding local database …":"Bygger om lokal databas...","Recreate (delete and repair)":"Återskapa (ta bort och reparera)","Recreate Database Phase":"Återskapa Databas Fasen","Recreating database …":"Återskapar databas...","Registering temporary backup …":"Registrerar tillfällig säkerhetskopia …","Relative paths not allowed":"Relativa sökvägar är inte tillåtna","Reload":"Ladda om","Remote":"Fjärr","Remote Path":"Fjärrsökväg ","Remote Repository":"Fjärr Repository","Remote path":"Fjärrsökväg ","Remote repository":"Fjärr repository","Remote volume size":"Fjärr-volymstorlek","Remove":"Ta bort","Remove option":"Ta bort alternativ","Removed files":"Borttagna filer","Repair":"Reparera","Repair Phase":"Reparations Fas","Repairing database …":"Reparerar databas ...","Repeat Passphrase":"Upprepa lösenfrasen","Reporting:":"Rapportering:","Reset":"Återställa","Restore":"Återställ","Restore complete!":"Återställningen är klar!","Restore files":"Återställningen filer","Restore files …":"Återställer filer …","Restore from":"Återställ från","Restore from backup configuration":"Återställ från konfiguration av säkerhetskopia","Restore options":"Återställ alternativ","Restore read/write permissions":"Återställ läs-/skrivbehörigheter","Restored Files":"Återställda filer","Restored Folders":"Återställda mappar","Restored Symlinks":"Återställda symbollänkar","Restoring files …":"Återställer filer...","Resume":"Försätt","Rewritten File Lists":"Omskrivna fillistor","Run again every":"Kör igen varje","Run now":"Kör nu","Running commandline entry":"Kör kommandoradspost","Running task:":"Pågående uppgift:","Running …":"Pågående ... ","S3 Compatible":"S3 Kompatibel","Same as the base install version: {{channelname}}":"Samma som basinstallationsversionen: {{channelname}}","Sat":"Lör","Satellite":"Satellit","Save":"Spara","Save and repair":"Spara och reparera","Save different versions with timestamp in file name":"Spara olika versioner med tidsstämpel i filnamnet","Save immediately":"Spara omedelbart","Scanning existing files …":"Skannar befintliga filer...","Scanning for local blocks …":"Söker efter lokala block …","Schedule":"Schema","Search":"Sök","Search for files":"Sök efter filer","Seconds":"Sekunder","Select a log level and see messages as they happen:":"Välj en logg-nivå och se meddelanden när de händer:","Select files":"Välj filer","Server":"Server","Server and port":"Server och port","Server hostname or IP":"Server värdnamn eller IP","Server is currently paused,":"Servern är för närvarande pausad,","Server is currently paused, do you want to resume now?":"Servern är för närvarande pausad, vill du återuppta nu?","Server password":"Server lösenord","Server paused":"Servern pausad","Server state properties":"Serverstatusegenskaper","Settings":"Inställningar","Show":"Visa","Show advanced editor":"Visa avancerad redigerare","Show hidden folders":"Visa dolda mappar","Show log":"Visa logg","Show log …":"Visa logg ...","Show treeview":"Visa träd-vy","Sia server password":"Sia-server lösenord","Smart backup retention":"Smart backup-bibehållning","Some OpenStack providers allow an API key instead of a password and tenant name":"Vissa OpenStack-leverantörer tillåter en API-nyckel istället för ett lösenord och \"tenant name\"","Some S3 providers might only be compatible with a certain client library":"Vissa S3-leverantörer kanske bara är kompatibla med ett visst klientbibliotek","Source Data":"Källdata","Source Files":"Källfiler","Source data":"Källdata","Source folders":"Källmappar","Source:":"Källa:","Specific builds for developers only. Not for use with important data.":"Specifika versioner endast för utvecklare. Ej för användning med viktig data.","Standard protocols":"Standardprotokoll","Start":"Start","Starting backup …":"Startar säkerhetskopiering ...","Starting restore …":"Startar återställning ...","Starting the restore process …":"Startar återställningsprocessen ...","Stop after the current file":"Stoppa efter den aktuella filen","Stop running backup":"Avsluta säkerhetskopiering","Stop running task":"Sluta köra uppgiften","Stopping after the current file:":"Stoppa efter den aktuella filen:","Stopping task:":"Stoppa uppgift:","Storage Type":"Lagringstyp","Storage class":"Förvarings-klass","Storage class for creating a bucket":"Förvaringsklass för att skapa en bucket","Stored":"Lagrat","Strong":"Stark","Success":"Framgång","Sun":"Sön","Symbolic link":"Symbolisk länk","System Files":"Systemfiler","System default ({{levelname}})":"Systemstandard ({{levelname}})","System files":"Systemfiler","System info":"System information","System properties":"Systemegenskaper","TByte":"TByte","TByte/s":"TByte/s","Task is running":"Uppgiften pågår","Temporary Files":"Tillfälliga filer","Temporary files":"Tillfälliga filer","Test Phase":"Test Fas","Test connection":"Testa anslutningen","Testing permissions …":"Testar behörigheter...","Testing …":"Testar ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"Fältet '{{fieldname}}' innehåller ett ogiltigt tecken: {{character}} (värde: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"Säkerhetskopia saknas, har den tagits bort?","The backup was temporary and does not exist anymore, so the log data is lost":"Säkerhetskopian var tillfällig och existerar inte längre, så logg-data går förlorad","The bucket name should be all lower-case, convert automatically?":"Namnet på bucket borde vara gemener, konvertera automatiskt?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"Konfigurationen bör förvaras säker. Är du säker på att du vill spara en okrypterad fil som innehåller dina lösenord?","The dark theme (by Michal)":"Det mörka temat (av Michal)","The default blue on white theme (by Alex)":"Standardtemat för blått på vitt (av Alex)","The folder {{folder}} does not exist.\nCreate it now?":"Mappen {{folder}} finns inte.\nSkapa det nu?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"Värdnyckeln har ändrats, kontrollera med serveradministratören om detta är korrekt, annars kan du bli offer för en MAN-IN-MIDDLE-attack.\n\nVill du ERSÄTTA din AKTUELLA värdnyckel \"{{prev}}\" med den RAPPORTERADE värdnyckeln: {{key}}?","The passwords do not match":"Lösenorden matchar inte","The path does not appear to exist, do you want to add it anyway?":"Sökvägen verkar inte existera, vill du lägga till den ändå?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"Sökvägen slutar inte med tecknet '{{dirsep}}', vilket betyder att du inkluderar en fil, inte en mapp.\n\nVill du inkludera den angivna filen ändå?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"Sökvägen måste vara en absolut väg, dvs den måste börja med ett snedstreck '/'","The region parameter is only applied when creating a new bucket":"Regionparametern tillämpas endast när en ny bucket skapas","The region parameter is only used when creating a bucket":"Regionparametern används endast när du skapar en bucket","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"Servercertifikatet kunde inte valideras.\nVill du godkänna SSL-certifikatet med hashen: {{hash}}?","The storage class affects the availability and price for a stored file":"Lagringsklassen påverkar tillgängligheten och priset för en lagrad fil","The target folder contains encrypted files, please supply the passphrase":"Målmappen innehåller redan krypterade filer, vänligen ange lösenfrasen","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"Användaren har för många behörigheter. Vill du skapa en ny begränsad användare, med endast behörigheter till den valda sökvägen?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"Denna säkerhetskopia skapades på ett annat operativsystem. Att återställa filer utan att ange en målmapp kan göra att filer återställs på oväntade platser. Är du säker på att du vill fortsätta utan att välja en målmapp?","This month":"Denna månad","This week":"Denna vecka","Throttle settings":"Inställningar för Hastighetsbegränsningar ","Thu":"Tors","Time":"Tid","To File":"Till Arkiv","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"För att bekräfta att du vill ta bort alla fjärrfiler för \"{{name}}\", skriv in ordet du ser nedan","To export without a passphrase, uncheck the \"Encrypt file\" box":"För att exportera utan en lösenordsfras, avmarkera rutan \"Kryptera fil\"","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"För att förhindra olika DNS-baserade attacker, begränsar Duplicati de tillåtna värdnamnen till de som listas här. Direkt IP-åtkomst och lokal värd är alltid tillåten. Flera värdnamn kan förses med en semikolonseparator. Om något av de tillåtna värdnamnen är en asterisk (*), är alla värdnamn tillåtna och den här funktionen är inaktiverad. Om fältet är tomt tillåts endast IP-adress och lokal värdåtkomst.","Today":"I dag","Trust host certificate?":"Lita på värdcertifikat?","Trust server certificate?":"Lita på servercertifikat?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"Testa de nya funktionerna som vi arbetar med. För närvarande den mest stabila versionen som finns. Testa Återställ data innan du använder detta i produktionsmiljöer.","Tue":"Tis","Type passphrase here.":"Skriv lösenordsfras här.","Type to highlight files":"Skriv för att markera filer","Unknown backup size and versions":"Okänd storlek och versioner av säkerhetskopia","Until resumed":"Tills den återupptas","Update channel":"Uppdatera kanal","Update failed:":"Uppdateringen misslyckades:","Updating with existing database":"Uppdatering med befintlig databas","Uploaded files":"Uppladdade filer","Uploading verification file …":"Laddar upp verifieringsfil …","Usage statistics":"Användningsstatistik","Usage statistics, warnings, errors, and crashes":"Användningsstatistik, varningar, fel och krascher","Use SSL":"Använd SSL","Use existing database?":"Använd befintlig databas?","Use weak passphrase":"Använd svag lösenfras","Useless":"Oanvändbar","User data":"Användardata","User domain name":"Användardomännamn","User has too many permissions":"Användaren har för många behörigheter","User interface settings":"Användargränssnittet inställningar","Username":"Användarnamn","Vacuuming database …":"Dammsugar databas …","Validating …":"Validerar …","Verifications":"Verifieringar","Verify files":"Verifiera filer","Verifying answer":"Verifierar svar","Verifying backend data …":"Verifierar backend-data …","Verifying files …":"Verifierar filer ...","Verifying remote data …":"Verifierar fjärrdata …","Verifying restored files …":"Verifierar återställda filer...","Verifying …":"Verifierar ...","Version ID":"Versions-ID","Very strong":"Väldigt stark","Very weak":"Väldigt svag","Visit us on":"Besök oss på","WARNING: This will prevent you from restoring the data in the future.":"VARNING: Detta kommer att förhindra dig från att återställa data i framtiden.","Waiting for task to begin":"Väntar på att uppgiften ska börja","Waiting for upload to finish …":"Väntar på att uppladdningen ska slutföras ...","Warnings, errors and crashes":"Varningar, fel och krascher","We recommend that you encrypt all backups stored outside your system":"Vi rekommenderar att du krypterar alla säkerhetskopior som lagras utanför ditt system","Weak":"Svag","Weak passphrase":"Svag lösenfras","Wed":"Ons","Weeks":"Veckor","Where do you want to restore from?":"Var vill du återställa från?","Where do you want to restore the files to?":"Var vill du återställa filerna?","Years":"År","Yes":"Ja","Yes, I have stored the passphrase safely":"Ja, jag har lagrat lösenfrasen säkert","Yes, I understand the risk":"Ja, jag förstår risken","Yes, I'm brave!":"Ja, jag är modig!","Yes, please break my backup!":"Ja, snälla bryt min säkerhetskopia!","Yesterday":"I går","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"Du ändrar databassökvägen från en befintlig databas.\nÄr du säker på detta?","You are currently running {{appname}} {{version}}":"Du kör för närvarande {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"Du har ändrat krypteringsläget. Det här kan ta sönder saker. Du uppmuntras att skapa en ny säkerhetskopia istället","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"Du har ändrat lösenfrasen, som inte stöds. Du uppmuntras att skapa en ny säkerhetskopia istället.","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"Du har valt att inte kryptera säkerhetskopian. Kryptering rekommenderas för all data som lagras på en fjärrserver.","You have chosen to restore to a new location, but not entered one":"Du har valt att återställa till en ny plats, men inte angett någon","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"Du har genererat en stark lösenfras. Se till att du har gjort en säker kopia av lösenfrasen, eftersom data inte kan återställas om du tappar bort lösenfrasen.","You must choose at least one source folder":"Du måste välja minst en källmapp","You must enter a domain name to use v3 API":"Du måste ange ett domännamn för att använda v3 API","You must enter a name for the backup":"Du måste ange ett namn för säkerhetskopian","You must enter a passphrase or disable encryption":"Du måste ange en lösenfras eller inaktivera kryptering","You must enter a password to use v3 API":"Du måste ange ett lösenord för att använda v3 API","You must enter a positive number of backups to keep":"Du måste ange ett positivt antal säkerhetskopior för att behålla","You must enter a tenant (aka project) name to use v3 API":"Du måste ange ett tenant (aka project) för att använda v3 API","You must enter a valid duration for the time to keep backups":"Du måste ange en giltig varaktighet för hur länge säkerhetskopior sparas ","You must enter a valid retention policy string":"Du måste ange en giltig lagrings-policysträng","You must fill in the password":"Du måste fylla i lösenordet","You must fill in the server name or address":"Du måste fylla i serverns namn eller adress","You must fill in the username":"Du måste fylla i användarnamnet","You must fill in {{field}}":"Du måste fylla i {{field}}","You must select or fill in the AuthURI":"Du måste välja eller fylla i AuthURI","You must select or fill in the server":"Du måste välja eller fylla i uppgifterna för servern","You must specify a path":"Du måste ange en sökväg","Your files and folders have been restored successfully.":"Dina filer och mappar har återställts.","Your passphrase is easy to guess. Consider changing passphrase.":"Din lösenfras är lätt att gissa. Överväg att ändra lösenordsfras.","bucket/folder/subfolder":"bucket/mapp/undermapp","byte":"byte","byte/s":"byte/s","custom":"anpassad","resume now":"återuppta nu","unless you are explicitly specifying --group-id":"om du inte uttryckligen anger --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} utvecklades främst av {{dev1}} och {{dev2}}. {{appname}} kan laddas ner från {{websitename}}. {{appname}} är licensierad under {{licensename}}.","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} filer ({{size}}) att gå {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":["{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Versions"],"{{number}} Hour":"{{number}} Timme","{{number}} Hours":"{{number}} Timmar","{{number}} Minutes":"{{number}} Minuter","{{time}} (took {{duration}})":"{{time}} (tog {{duration}})"}); gettextCatalog.setStrings('th', {"- pick an option -":"- เลือกตัวเลือก -","...loading...":"...กำลังดึงข้อมูล...","About":"เกี่ยวกับ","About {{appname}}":"เกี่ยวกับ {{appname}}","Access Key":"กุญแจเข้าถึง","Access denied":"การเข้าถึงถูกปฏิเสธ","Access to user interface":"การเข้าถึงส่วนติดต่อผู้ใช้","Account name":"ชื่อบัญชี","Add a new backup":"เพิ่มการสำรองข้อมูลใหม่","Add advanced option":"เพิ่มตัวเลือกขั้นสูง","Add backup":"เพิ่มข้อมูลสำรอง","Add filter":"เพิ่มตัวกรอง","Add path":"เพิ่ม path","Added":"เพิ่มแล้ว","Adjust bucket name?":"ปรับแก้ชื่อถัง?","Advanced Options":"ตัวเลือกขั้นสูง","Advanced options":"ตัวเลือกขั้นสูง:","Advanced:":"ขั้นสูง:","All Hyper-V Machines":"เครื่อง Hyper-V ทั้งหมด","All Microsoft SQL Databases":"ฐานข้อมูล Microsoft SQL ทั้งหมด","Allow remote access (requires restart)":"อนุญาตการเข้าถึงจากทางไกล (จำเป็นต้องปิดเครื่องแล้วเปิดใหม่)","Allowed days":"วันที่อนุญาต","AuthID":"AuthID","Back":"กลับ","Backup destination":"ปลายทางข้อมูลสำรอง","Backup location":"ตำแหน่งข้อมูลสำรอง","Backup:":"ข้อมูลสำรอง:","Beta":"เบต้า","Broken access":"การเข้าถึงเสียหาย","Browse":"ดู","Browser default":"ค่ามาตรฐานของเบราว์เซอร์","Cancel":"ยกเลิก","Changelog":"ปูมความเปลี่ยนแปลง","Check failed:":"การตรวจสอบล้มเหลว:","Check for updates now":"ตรวจหาการปรับปรุงตอนนี้","Computer":"คอมพิวเตอร์","Configuration:":"การตั้งค่า:","Configure a new backup":"ตั้งค่าข้อมูลสำรองอันใหม่","Confirm delete":"ยืนยันการลบ","Confirmation required":"จำเป็นต้องได้รับการยืนยัน","Connect":"เชื่อมต่อ","Connect now":"เชื่อมต่อเดี๋ยวนี้","Continue":"ทำต่อ","Copied!":"คัดลอกแล้ว!","Copy Destination URL to Clipboard":"คัดลอก URL ปลายทางไปยังคลิปบอร์ด","Create folder?":"สร้างโฟลเดอร์?","Created new limited user":"สร้างผู้ใช้จำกัดสิทธิ์คนใหม่","Days":"วัน","Default":"ปริยาย","Default options":"ตัวเลือกมาตรฐาน","Delete":"ลบ","Delete backup":"ลบข้อมูลสำรอง","Delete local database":"ลบฐานข้อมูลในเครื่อง","Delete remote files":"ลบแฟ้มทางไกล","Delete the local database":"ลบฐานข้อมูลในเครื่อง","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"ลบ {{filecount}} แฟ้ม ({{filesize}}) จากที่เก็บข้อมูลทางไกล?","Desktop":"เดสก์ทอป","Destination":"ปลายทาง","Disabled":"ปิดใช้","Dismiss":"รับทราบ","Display and color theme":"การแสดงผลและชุดสี","Done":"เสร็จ","Download":"ดาวน์โหลด","Encrypt file":"เข้ารหัสลับแฟ้ม","Encryption":"การเข้ารหัสลับ","Encryption changed":"การเข้ารหัสลับถูกเปลี่ยนแล้ว","Enter URL":"ใส่ URL","Enter encryption passphrase":"ใส่วลีรหัสผ่านเข้ารหัสลับ","Error":"ผิดพลาด","Error!":"ผิดพลาด!","Errors and crashes":"ผิดพลาดและพัง","Exclude":"ไม่นับรวม","Exclude directories whose names contain":"ไม่นับรวมไดเกทอรีที่ในชื่อมี","Exclude file":"ไม่นับรวมแฟ้ม","Exclude file extension":"ไม่นับรวมสกุลแฟ้ม","Exclude files whose names contain":"ไม่นับรวมแฟ้มที่ในชื่อมี","Exclude folder":"ไม่นับรวมโฟลเดอร์","Exclude regular expression":"ไม่นับรวมตาม regular expression","Export":"ส่งออก","Export configuration":"ส่งออกการตั้งค่า","FTP (Alternative)":"FTP (ทางเลือก)","Failed to delete:":"การลบล้มเหลว:","File":"แฟ้ม","Files larger than:":"แฟ้มที่ใหญ่กว่า:","Filters":"ตัวกรอง","Finished!":"เสร็จสิ้น!","Folder":"โฟลเดอร์","Fri":"ศุกร์","GByte":"กิกะไบต์","GByte/s":"กิกะไบต์/วิ","General":"ทั่วไป","General backup settings":"การตั้งค่าข้อมูลสำรองทั่วไป","General options":"ตัวเลือกทั่วไป","Generate":"สร้าง","Hidden files":"แฟ้มที่ซ่อนอยู่","Hide":"ซ่อน","Hide hidden folders":"ซ่อนโฟลเดอร์ที่ถูกซ่อน","Home":"เหย้า","Hours":"ชั่วโมง","ID:":"ID:","Import":"นำเข้า","Import Destination URL":"นำเข้า URL ปลายทาง","Import backup configuration":"นำเข้าการตั้งค่าข้อมูลสำรอง","Import from a file":"นำเข้าจากแฟ้ม","Include a file?":"นับรวมแฟ้ม?","KByte":"กิโลไบต์","KByte/s":"กิโลไบต์/วิ","Language in user interface":"ภาษาในส่วนติดต่อผู้ใช้","Last month":"เดือนที่แล้ว","Latest":"ล่าสุด","Live":"สด","Load older data":"เรียกข้อมูลที่เก่ากว่า","Local storage":"ที่เก็บข้อมูลในท้องถิ่น","Location":"ที่ตั้ง","Log out":"ลงชื่อออก","MByte":"เมกะไบต์","MByte/s":"เมกะไบต์/วิ","Maintenance":"การบำรุงรักษา","Menu":"เมนู","Minutes":"นาที","Mon":"จ","Months":"เดือน","Next":"ถัดไป","No":"ไม่","No encryption":"ไม่เข้ารหัสลับ","OK":"ตกลง","Opened":"เปิดแล้ว","Options":"ตัวเลือก","Original location":"ตำแหน่งที่ตั้งตั้งต้น","Others":"อื่นๆ","Overwrite":"เขียนทับ","Passphrase":"วลีรหัสผ่าน","Passphrase (if encrypted)":"วลีรหัสผ่าน (ถ้าเข้ารหัสลับ)","Passphrase changed":"เปลี่ยนวลีรหัสผ่านแล้ว","Passphrases are not matching":"วลีรหัสผ่านไม่ตรงกัน","Passphrases do not match":"วลีรหัสผ่านไม่ตรง","Password":"รหัสผ่าน","Pause":"หยุดชั่วคราว","Previous":"ก่อหน้า","Progress:":"คืบหน้า:","Remote":"ทางไกล","Repair":"ซ่อม","This month":"เดือนนี้","This week":"สัปดาห์นี้","Thu":"พฤ","Time":"เวลา"}); - gettextCatalog.setStrings('zh_CN', {"(interrupted)":"(中断)","- pick an option -":"- 选择一个选项 -","...loading...":"…正在加载…","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"注意: 只要您连接到您的主机,Sia稍后仍会提升冗余。"," Edit as text":" 以文本编辑"," Edit as text":" 以文本编辑","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

由于无效的身份验证,连接服务器被拒绝。

\n

重新登录,或者从托盘图标重新打开页面(如果适用)。

","API key":"API 密钥","AWS Access ID":"AWS 访问 ID","AWS Access Key":"AWS 访问密钥","AWS IAM Policy":"AWS IAM 策略","About":"关于","About {{appname}}":"关于 {{appname}}","Access Key":"访问密钥","Access denied":"访问被拒绝","Access grant":"访问授权","Access to user interface":"用户界面访问","Account name":"帐户名","Add a new backup":"添加新备份","Add a path directly":"直接添加路径","Add advanced option":"添加高级选项","Add backup":"新增备份","Add filter":"添加过滤条件","Add path":"添加路径","Added":"已添加","Adjust bucket name?":"调整 bucket 名称?","Advanced Options":"高级选项","Advanced options":"高级选项","Advanced:":"高级:","Aliyun OSS Endpoint":"Aliyun OSS Endpoint","Aliyun OSS documents and resources":"阿里云OSS文档和资源","All Hyper-V Machines":"所有 Hyper-V 机器","All Microsoft SQL Databases":"所有 Microsoft SQL 数据库","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"所有的使用情况报告都是匿名发送,不含任何个人信息。 其中包括硬件、操作系统、后端类型、备份时长、备份源大小以及类似数据,但不包括路径、文件名、用户名、密码或类似的敏感信息。","Allow remote access (requires restart)":"允许远程访问 (需要重启)","Allowed days":"允许的日期","An existing file was found at the new location":"新位置已经存在文件","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"新位置已经存在文件\n您确定要将数据库指向已存在的文件?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"发现该存储在本地已存在数据库\n重新使用该数据库将使用命令行或服务器实例工作在相同的存储\n您希望使用已有的数据库吗?","Anonymous usage reports":"匿名使用报告","Applications":"应用","As Command-line":"导出为命令行","AuthID":"授权 ID","Authentication method":"认证方法","Authentication method ({{auth_method}})":"认证方法 ({{auth_method}})","Authentication password":"认证密码","Authentication username":"认证用户名","Autogenerated passphrase":"自动生成的密码","Automatically run backups":"自动运行备份","B2 Application ID":"B2 应用 ID","B2 Application Key":"B2 应用密钥","B2 Cloud Storage Account ID":"B2 云存储帐户 ID","B2 Cloud Storage Application ID":"B2 云存储应用 ID","B2 Cloud Storage Application Key":"B2 云存储应用密钥","Back":"返回","Backend modules:

{{item.Key}}

":"后端模块:

{{item.Key}}

","Backup complete!":"备份完成!","Backup destination":"备份保存位置","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"备份已加密,但没有可用的密码短语。请在下方输入一个密码短语以用于恢复您的文件。或者在GPG加密的情况下,留空以让gpg通过调用您系统的钥匙串来检索密码短语。","Backup location":"备份位置","Backup retention":"备份保留策略","Backup:":"备份数据:","Beta":"Beta","Broken access":"访问中断","Browse":"浏览","Browser default":"浏览器默认","Bucket create location":"Bucket 创建位置","Bucket name":"Bucket 名称","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Bucket名称只能包含3到63个字符并且只能包含小写字母、数字、句点和破折号。","Bucket region":"Bucket 区域","Bucket region ap-guangzhou":"Bucket 区域 ap-guangzhou","Bucket storage class":"Bucket 存储类型","Bucket, format: BucketName-APPID":"Bucket, 格式: BucketName-APPID","Building list of files to restore …":"正在构建文件还原列表…","Building partial temporary database …":"正在构建部分临时数据库…","Busy …":"繁忙…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"允许远程访问,服务器监听并允许来自你网络上任何机器的请求。启用此项,请确保您的网络启用了安全防火墙保护。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"默认情况下,托盘图标将使用令牌打开用户界面,而不是解锁用户界面。这能确保从托盘图标访问用户界面,同时要求其他人输入密码。如果您希望从托盘图标访问用户界面也要输入密码,也请启用此选项。","Cache Files":"缓存文件","Canary":"Canary","Cancel":"取消","Cannot include \"{{text}}\"":"不能包含 \"{{text}}\"","Cannot move to existing file":"不能移动到已有文件","Cannot specify filter include or excludes in extra options":"不能在额外选项中指定过滤器的包含或排除","Change server passphrase":"更改服务器密码短语","Changelog":"更新日志","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} 更新日志","Check failed:":"检查失败:","Check for updates now":"立即检查更新","Checking for updates …":"正在检查更新…","Checking …":"正在检查…","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"选择1.0以获得快速备份,1.5以获得相当可靠的备份,2.0以获得更安全的上传但备份速度较慢","Chose a storage type to get started":"选择存储类型以开始","Click the AuthID link to create an AuthID":"点击\"授权 ID\"链接来创建一个授权 ID","Click to set throttle options":"点击配置限流","Client library to use":"使用的客户端库","Command":"命令","Commandline arguments":"命令行参数","Commandline …":"命令行...","Compact Phase":"压实阶段","Compact now":"立即压实","Compacting remote data …":"正在压实远程数据…","Complete log":"完整日志","Completing backup …":"正在完成备份…","Completing previous backup …":"正在完成上次备份…","Compression modules:

{{item.Key}}

":"压缩模块:

{{item.Key}}

","Computer":"计算机","Configuration file:":"配置文件:","Configuration:":"配置:","Configure a new backup":"配置新备份","Confirm delete":"确认删除","Confirm encryption passphrase":"确认加密密码","Confirm new password":"确认新密码","Confirm passphrase":"确认密码","Confirmation required":"需要确认","Connect":"连接","Connect now":"立即连接","Connecting to server …":"正在连接服务器…","Connecting to task …":"正在连接到任务…","Connecting …":"正在连接…","Connection lost":"连接中断","Connection worked!":"连接正常!","Container name":"容器名称","Container region":"容器区域","Continue":"继续","Continue without encryption":"继续且不启用加密","Copied!":"已复制!","Copy":"复制","Copy Destination URL to Clipboard":"复制地址到剪贴板","Copy URL":"复制URL","Copy failed. Please manually copy the URL":"复制失败,请手动复制该地址","Copy log":"复制日志","Core options":"核心选项","Counting ({{files}} files found, {{size}})":"正在计算 (已找到 {{files}} 个文件,{{size}})","Crashes only":"仅崩溃","Create bug report …":"创建问题报告…","Create folder?":"创建文件夹?","Created new limited user":"受限用户已创建","Creating bug report …":"正在创建问题报告…","Creating new user with limited access …":"正在创建受限用户…","Creating target folders …":"正在创建目标文件夹…","Creating temporary backup …":"正在创建临时备份…","Creating user …":"正在创建用户…","Current action:":"当前操作:","Current file:":"当前文件:","Current version is {{versionname}} ({{versionnumber}})":"当前版本为 {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"自定义 S3 端点","Custom Satellite":"自定义卫星","Custom Satellite ({{satellite}})":"自定义卫星 ({{satellite}})","Custom authentication url":"自定义认证地址","Custom backup retention":"自定义备份保留策略","Custom bucket storage class":"自定义bucket存储类","Custom location ({{server}})":"自定义区域 ({{server}})","Custom region for creating buckets":"自定义创建 Bucket 的地区","Custom region value ({{region}})":"自定义地区 ({{region}})","Custom server url ({{server}})":"自定义服务器地址 ({{server}})","Custom storage class ({{class}})":"自定义存储类别 ({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"已废弃: {{getDeprecationMessage(item)}}","Database …":"数据库…","Days":"天","Default":"默认","Default ({{channelname}})":"默认 ({{channelname}})","Default excludes":"默认排除规则","Default options":"默认选项","Default value: \"{{getDefaultValue(item)}}\"":"默认值: \"{{getDefaultValue(item)}}\"","Delete":"删除","Delete Phase (Old Backup Versions)":"删除阶段 (旧版本备份)","Delete backup":"删除备份","Delete backups that are older than":"删除早于条件的备份","Delete local database":"删除本地数据库","Delete remote files":"删除远程文件","Delete the local database":"删除本地数据库","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"从远程存储中删除 {{filecount}} 个文件 ({{filesize}}) ?","Delete …":"删除…","Deleted":"已删除","Deleted Versions":"已删除版本","Deleted files":"已删除文件","Deleting remote files …":"正在删除远程文件…","Deleting unwanted files …":"正在删除不需要的文件…","Description (optional)":"描述 (可选)","Description:":"描述:","Desktop":"桌面","Destination":"目标位置","Destination path":"目标路径","Direct restore from backup files …":"从备份文件直接恢复…","Directory path":"目录路径","Disabled":"已禁用","Dismiss":"忽略","Dismiss all":"忽略所有","Display and color theme":"显示和颜色主题","Do you really want to delete the backup: \"{{name}}\" ?":"您确定要删除备份:\"{{name}}\"吗 ?","Do you really want to delete the local database for: {{name}}":"您确定要删除 \"{{name}}\" 的本地数据库吗 ?","Domain name":"域名","Done":"完成","Download":"下载","Downloaded files":"已下载文件","Downloading files …":"正在下载文件…","Downloading update…":"正在下载更新…","Duplicate option {{opt}}":"Duplicati 选项 {{opt}}","Duplicati Website":"Duplicati 网站","Duplicati forum":"Duplicati 论坛","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicati需要用密码短语进行保护,并且已经为您生成了一个随机密码短语。\n如果您从托盘图标打开Duplicati,则不需要密码短语,但如果您计划从其他位置打开它,则需要设置一个您知道的密码短语。\n您现在想要设置一个密码短语吗?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati 将在启动后运行,但会保持在暂停状态。Duplicati 会使用最小的系统资源,并且不会运行任何备份。","Duration":"时间","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"每个备份都有一个关联的本地数据库,用来存储远程备份相关的信息。\n删除一个备份时,您也可以删除其本地数据库,这不会影响从远程文件中恢复数据。\n但如果你通过命令行进行备份,您应当保留此数据库。","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"每个备份都有一个与之关联的本地数据库,该数据库存储了有关本地机器上远程备份的信息。这使得执行许多操作变得更快,并减少了每次操作需要下载的数据量。","Edit as list":"以列表形式编辑","Edit as text":"以文本形式编辑","Edit …":"编辑…","Email address of the Office 365 group":"Office 365群组的电子邮件地址","Encrypt file":"加密文件","Encryption":"加密方式","Encryption changed":"加密方式已更改","Encryption modules:

{{item.Key}}

":"加密模块:

{{item.Key}}

","Encryption passphrase":"加密密码","Encryption passphrase (for verification)":"加密密码(用于验证)","End":"结束","Enter URL":"输入URL","Enter a backup destination URL:":"输入备份目标URL:","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"请手动输入备份保留策略。占位符 D/W/Y 代表 日/星期/年,U 代表 永久。语法为 7D:1D,4W:1W,36M:1M,这个例子保留7天中每天一份,4个星期中每星期一份,36个月中每月一份,也可以写成 1W:1D,1M:1W,3Y:1M","Enter a url, or click the "Target URL >" link":"输入一个网址,或者点击"目标网址>"链接","Enter backup passphrase, if any":"输入备份密码 (若存在)","Enter configuration details":"进入详细配置","Enter encryption passphrase":"输入加密密码","Enter expression here":"在此输入表达式","Enter one argument per line without quotes, e.g. *.txt":"每行输入一个参数,不带引号,例如:*.txt","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"以命令行格式每行输入一个选项,例如:--dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"以命令行格式每行输入一个选项,例如:{0}","Enter the destination path":"输入目标路径","Error":"错误","Error!":"错误!","Errors and crashes":"错误和崩溃","Examined":"已检查","Exclude":"排除","Exclude directories whose names contain":"排除文件夹,名称包括","Exclude expression":"排除表达式","Exclude file":"排除文件","Exclude file extension":"排除文件扩展名","Exclude files whose names contain":"排除文件,名称包括","Exclude filter group":"排除过滤条件集","Exclude folder":"排除文件夹","Exclude regular expression":"排除正则表达式","Existing file found":"发现已存在文件","Experimental":"Experimental","Export":"导出","Export backup configuration":"导出备份配置","Export configuration":"导出配置","Export passwords":"导出密码","Export …":"导出…","Exporting …":"正在导出…","External link":"外部链接","FTP (Alternative)":"FTP (备选)","Failed to build temporary database: {{message}}":"构建临时数据库失败: {{message}}","Failed to connect:":"连接失败:","Failed to connect: {{message}}":"连接失败:{{message}}","Failed to delete:":"删除失败:","Failed to fetch path information: {{message}}":"获取路径信息失败: {{message}}","Failed to find backup:":"查找备份失败:","Failed to get bug report URL: {{message}}":"获取错误报告URL失败: {{message}}","Failed to import: {{message}}":"导入失败: {{message}}","Failed to read backup defaults:":"读取备份默认设置失败:","Failed to read file: {{message}}":"读取文件失败: {{message}}","Failed to restore files: {{message}}":"恢复文件失败: {{message}}","Failed to save:":"保存失败:","Fatal error, no statistics collected":"致命错误,未收集到统计信息","Fetching path information …":"获取路径信息…","File":"文件","Files larger than:":"文件大于","Filters":"过滤条件","Finished!":"已完成!","First run setup":"初始配置","Folder":"文件夹","Folder in the bucket":"bucket中的文件夹","Folder path":"文件夹路径","Folder path name":"文件夹路径名称","Fri":"周五","Full destination path, including the server name, but without https":"完整的目标路径,包括服务器名称,但不包括https","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS 项目 ID","General":"常规","General backup settings":"常规备份设置","General options":"常规选项","Generate":"生成","Generate IAM access policy":"生成 IAM 访问策略","Getting file versions …":"正在获取文件版本...","Group email":"群组邮箱","Hidden files":"隐藏文件","Hide":"隐藏","Hide hidden folders":"隐藏被隐藏的文件夹","Home":"首页","Hostnames":"主机名","Hours":"小时","How do you want to handle existing files?":"您想怎样处理已存在的文件?","Hyper-V Machine":"Hyper-V 虚拟机","Hyper-V Machine:":"Hyper-V 虚拟机:","Hyper-V Machines":"Hyper-V 虚拟机","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果错过了时间,任务将尽快运行。","If at least one newer backup is found, all backups older than this date are deleted.":"如果有更新的备份存在,早于此日期的备份将被删除。","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"如果备份和远程存储不同步,Duplicati将要求您执行修复操作以同步数据库。如果修复不成功,您可以删除本地数据库并重新生成。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"如果备份文件没有自动下载,请右键点击并选择"另存为…"。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"如果备份文件没有自动下载,请右键点击并选择"另存为…"。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"如果没有输入路径,所有文件将存储在登录文件夹。\n确定这是您想要的吗?","If you do not enter an API Key, the tenant name is required":"如果您不输入 API 密钥,则需要输入租户名称","If you want to use the backup later, you can export the configuration before deleting it.":"如果您以后还想使用此备份,可以在删除之前先导出配置。","Import":"导入","Import Destination URL":"导入目标URL","Import URL":"导入URL","Import backup configuration":"导入备份配置","Import from a file":"从文件导入","Import metadata":"导入元数据","Importing …":"正在导入…","Include a file?":"包含一个文件?","Include expression":"包含表达式","Include regular expression":"包含正则表达式","Incorrect answer, try again":"验证失败,请重试","Individual builds for developers only. Not for use with important data.":"面向开发者的单个构建,不适用于重要数据","Information":"信息","Interrupted, no statistics collected":"中断,未收集统计信息","Invalid characters in path":"路径中包含无效字符","Invalid retention time":"无效的保留时间","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"可以在无密码的情况下连接到一些 FTP\n您确定您的 FTP 服务器支持无密码登录吗?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"保留指定版本数","Keep all backups":"永久保留","Keystone API version":"Keystone API 版本","Language in user interface":"界面语言","Last month":"上月","Last successful backup:":"上次成功备份于:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"上次成功恢复于:{{time}} (耗时 {{duration || '0 秒'}})","Latest":"最新","Libraries":"第三方库","Listing backup dates …":"正在列出备份日期…","Listing remote files for purge …":"正在列出需要清除的远程文件…","Listing remote files …":"正在列出远程文件…","Live":"实时","Load a configuration from an exported job or a storage provider":"从已导出的任务文件或者存储提供商处加载配置","Load destination from an exported job or a storage provider":"从已导出的任务文件或存储提供商处加载目标位置","Load older data":"加载之前的数据","Loading remote storage usage …":"正在加载远程存储使用情况…","Loading …":"正在加载…","Local Repository":"本地仓库","Local database for {{Backup.Backup.Name}}…loading…":"本地数据库用于 {{Backup.Backup.Name}}…加载中…","Local database path:":"本地数据库路径:","Local repository":"本地仓库","Local storage":"本地存储","Location":"位置","Location where buckets are created":"创建 Bucket 的位置","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}} 的日志数据","Log data from the server":"来自服务器的日志数据","Log in":"登录","Log out":"退出登录","MByte":"MB","MByte/s":"MB/s","Maintenance":"维护","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"确保rclone在您的环境变量中,或者通过高级选项将位置添加到rclone。","Manual":"手册","Manual update found:":"手动更新:","Manually type path":"手动输入路径","Max download speed":"最大下载速度","Max upload speed":"最大上传速度","Menu":"菜单","Microsoft SQL Database:":"Microsoft SQL 数据库:","Microsoft SQL Databases":"Microsoft SQL 数据库","Minimum redundancy":"最小冗余","Minimum redundancy is 1.0":"最小冗余为 1.0","Minutes":"分钟","Missing name":"缺少名称","Missing passphrase":"缺少密码","Missing sources":"缺少源数据","Modified":"已修改","Mon":"周一","Months":"月","Move existing database":"移动已有数据库","Move failed:":"移动失败:","My Documents":"我的文档","My Music":"我的音乐","My Photos":"我的照片","My Pictures":"我的图片","Name":"名称","Never":"从不","New Password":"新密码","New update found: {{message}}":"新的更新: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新用户名为 {{user}}\n已为新的受限用户更新证书","Next":"下一步","Next scheduled run:":"下一次计划运行于:","Next scheduled task:":"下一次计划任务:","Next task:":"下一次任务:","Next time":"下一次运行时间:","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"之前未指定证书,请与服务器管理员确认密钥 {{key}} 是否正确\n\n您是否要允许该主机密钥吗?","No editor found for the "{{backend}}" storage type":"未找到 "{{backend}}" 存储类型的编辑器","No encryption":"无加密","No items selected":"未选中项目","No items to restore, please select one or more items":"未恢复项目,请至少选择一项","No passphrase entered":"未输入密码","No scheduled tasks":"暂无计划任务","Non-matching passphrase":"密码不匹配","None / disabled":"无 / 禁用","Not using encryption":"未使用加密","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"请注意,速度是以bytes为单位输入的,而线路速度通常以bits为蛋王。两者使用 8 的倍数进行转换,这样8 mbit/s的线路相当于1 MByte/s","Nothing will be deleted. The backup size will grow with each change.":"不会清理任何备份,备份大小将持续增长","OK":"确定","OSS Access Key ID":"Aliyun OSS Access Key ID","OSS Access Key Secret":"Aliyun OSS Access Key Secret","OSS Bucket Region":"Aliyun OSS Bucket区域","OSS Bucket name":"Aliyun OSS Bucket名称","OSS Endpoint":"Aliyun OSS Endpoint","OSS Path or subfolder in the bucket":"Aliyun OSS路径或bucket的子文件夹","OSS Region":"Aliyun OSS 区域","Official releases":"官方发布","Once there are more backups than the specified number, the oldest backups are deleted.":"一旦备份版本数超过此值,最旧的备份将被清理","OpenStack AuthURI":"OpenStack 认证地址","OpenStack Object Storage / Swift":"OpenStack 对象存储 / Swift","Opened":"已打开","Openstack API key are not supported in v3 keystone API":"Openstack API key 在 v3 keystone API 中不受支持","Operating System":"操作系统","Operation":"操作","Operations:":"操作:","Optional API key":"API key(可选)","Optional authentication password":"认证密码(可选)","Optional authentication username":"认证用户名(可选)","Optional region":"区域(可选)","Optional tenant name":"租户名称(可选)","Options":"选项","Options added here are applied to all backups, but can be overridden in each individual backup.":"在此添加的选项适用于所有备份,但每个备份中可以单独设置来覆盖此选项","Original location":"原位置","Others":"其它","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"随着时间,备份将被自动清理。这将保留最近7天中每天一份,最近4个星期中每星期一份,最近12个月中每月一份。同时,保证总是至少存在一个备份。","Overwrite":"覆盖","Passphrase":"密码","Passphrase (if encrypted)":"密码 (若启用加密)","Passphrase changed":"密码已更改","Passphrases are not matching":"密码不匹配","Passphrases do not match":"密码不匹配","Password":"密码","Patching files with local blocks …":"正在使用本地块修补文件…","Path":"路径","Path not found":"路径未找到","Path on server":"服务器上路径","Path or subfolder in the bucket":"Bucket 中路径或子文件夹","Pause":"暂停","Pause after startup or hibernation":"开机或休眠后暂停","Pause options":"暂停选项","Permissions":"权限","Pick location":"选择位置","Please select a file to import":"请选择一个导入的文件","Point to your backup files and restore from there":"指向您的备份文件,将从中恢复","Port":"端口","Prevent tray icon automatic log-in":"保持托盘图标自动登录","Previous":"上一步","Progress:":"进度:","ProjectID is optional if the bucket exist":"若 Bucket 存在, 则项目ID 可选","Proprietary":"专有","Purge Phase":"清除阶段","Purging files complete!":"清除文件完成!","Purging files …":"正在清除文件...","Rebuilding local database …":"正在重新构建本地数据库…","Recreate (delete and repair)":"重建 (删除并修复)","Recreate Database Phase":"重建数据库阶段","Recreating database …":"正在重建数据库…","Registering temporary backup …":"正在注册临时备份…","Relative paths not allowed":"不允许相对路径","Reload":"重新加载","Remote":"远程","Remote Path":"远程路径","Remote Repository":"远程仓库","Remote path":"远程路径","Remote repository":"远程仓库","Remote volume size":"远程卷大小","Remove":"移除","Remove option":"移除选项","Removed files":"已删除文件","Repair":"修复","Repair Phase":"修复阶段","Repairing database …":"正在修复数据库…","Repeat Passphrase":"重复密码","Reporting:":"报告:","Reset":"重置","Restore":"恢复","Restore complete!":"恢复完成!","Restore files":"恢复文件","Restore files from:":"从以下位置恢复文件:","Restore files …":"恢复文件…","Restore from":"恢复自","Restore from backup configuration":"从备份配置中恢复","Restore from configuration …":"从配置中恢复…","Restore options":"恢复选项","Restore read/write permissions":"恢复读写权限","Restored Files":"已恢复文件","Restored Folders":"已恢复目录","Restored Symlinks":"已恢复符号链接","Restoring files …":"正在恢复文件…","Resume":"恢复运行","Rewritten File Lists":"重写文件列表","Run again every":"重复运行每","Run now":"立即运行","Running commandline entry":"正在运行命令行","Running task:":"运行中的任务:","Running …":"正在运行…","Running … stop now":"正在运行… 立即停止","S3 Compatible":"S3 兼容","Same as the base install version: {{channelname}}":"与当前安装版本一致:{{channelname}}","Sat":"周六","Satellite":"卫星","Save":"保存","Save and repair":"保存并修复","Save different versions with timestamp in file name":"保存不同版本 (文件名中添加时间戳)","Save immediately":"立即保存","Scanning existing files …":"正在扫描存在的文件…","Scanning for local blocks …":"正在扫描本地文件块…","Schedule":"计划","Search":"搜索","Search for files":"搜索文件","Seconds":"秒","Select a log level and see messages as they happen:":"选择日志级别并实时查看","Select files":"选择文件","Server":"服务器","Server and port":"服务器与端口","Server hostname or IP":"服务器主机名或 IP","Server is currently paused,":"服务器暂停中,","Server is currently paused, resume now":"服务器当前已暂停, 立即恢复","Server is currently paused, do you want to resume now?":"服务器目前已暂停,您想立即恢复运行吗?","Server password":"服务器密码","Server paused":"服务器已暂停","Server state properties":"服务器状态","Settings":"设置","Show":"查看","Show advanced editor":"显示高级编辑器","Show hidden folders":"显示隐藏文件夹","Show log":"日志","Show log …":"查看日志…","Show treeview":"显示树状视图","Sia server password":"Sia 服务器密码","Smart backup retention":"智能备份保留策略","Some OpenStack providers allow an API key instead of a password and tenant name":"一些 OpenStack 提供商允许使用 API 密钥,而不是租户名称和密码","Some S3 providers might only be compatible with a certain client library":"一些 S3 提供商可能只与某个客户端库兼容","Source Data":"源数据","Source Files":"源文件","Source data":"源数据","Source folders":"源文件夹","Source:":"源数据:","Specific builds for developers only. Not for use with important data.":"面向开发者的特定构建,不适用于重要数据","Standard protocols":"标准协议","Start":"开始","Starting backup …":"准备开始备份…","Starting restore …":"准备开始恢复…","Starting the restore process …":"正在开始恢复操作…","Stop after current file":"当前文件完成后停止","Stop after the current file":"当前文件完成后停止","Stop now":"立即停止","Stop running backup":"停止正在运行的备份","Stop running task":"停止正在运行的任务","Stopping after the current file:":"当前文件完成后停止:","Stopping task:":"正在停止任务:","Storage Type":"存储类型","Storage class":"存储类别","Storage class for creating a bucket":"创建 Bucket 的存储类别","Stored":"存档","Strong":"强度高","Success":"成功","Sun":"周日","Symbolic link":"符号链接","System Files":"系统文件","System default ({{levelname}})":"默认 ({{levelname}})","System files":"系统文件","System info":"系统信息","System properties":"系统属性","TByte":"TB","TByte/s":"TB/s","Target URL >":"目标 URL >","Target path. Example: /backup":"目标路径. 例如: /backup","Task is running":"任务正在运行中","Temporary Files":"临时文件","Temporary files":"临时文件","Tenant name":"租户名","Tencent Cloud Account APPID":"腾讯云账号APPID","Tencent Cloud COS documents and resources":"腾讯云COS文档和资源","Test Phase":"测试阶段","Test connection":"测试连接","Testing connection …":"测试连接中…","Testing permissions …":"正在测试权限…","Testing …":"正在测试…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"字段 '{{fieldname}}' 包含无效字符:{{character}} (值: {{value}}, 位置: {{pos}})","The backup is missing, has it been deleted?":"此备份缺失,是否已经被删除?","The backup was temporary and does not exist anymore, so the log data is lost":"这是已经不存在的临时备份,因此没有日志数据","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"备份将被分割成多个称为卷的文件。您可以在此设置单个卷文件的最大大小。更多信息,请参见此页面。","The bucket name should be all lower-case, convert automatically?":"Bucket 名称应当是全小写,需要自动转换吗?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"配置应该注意安全。您确定要将含有您密码的配置保存为不加密的文件吗?","The connection to the server is lost, attempting again in {{time}} …":"与服务器的连接丢失,将在{{time}}后再次尝试…","The dark theme (by Michal)":"黑色主题 (by Michal)","The default blue on white theme (by Alex)":"默认蓝白主题 (by Alex)","The encryption passphrases do not match":"加密密码不匹配","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"文件大小为{{size}},超过了指定的最大指定值。如果文件大小减小,它将会包含在未来的备份中。","The folder {{folder}} does not exist.\nCreate it now?":"文件夹 {{folder}} 不存在\n是否现在创建?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"主机密钥已更改,请与服务器管理员确认其是否正确,否则您可能正在被中间人攻击。\n\n您想要把现有主机密钥 \"{{prev}}\" 替换为 {{key}} 吗?","The passwords do not match":"密码不匹配","The path does not appear to exist, do you want to add it anyway?":"路径似乎不存在,您确定要添加它吗?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"该路径没有以 '{{dirsep}}' 字符结尾,这表示您指定的是一个文件而不是文件夹。\n您确定想要包含指定文件吗?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"路径必须为绝对路径,也就是说必须以斜线 '/' 开头","The region parameter is only applied when creating a new bucket":"\"地区\" 参数只在创建新 Bucket 时生效","The region parameter is only used when creating a bucket":"\"地区\" 参数只在创建新 Bucket 时使用","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"服务器证书验证失败\n您想要允许该哈希值为 {{hash}} 的 SSL 证书吗?","The storage class affects the availability and price for a stored file":"存储类别影响文件可用性和价格","The target folder contains encrypted files, please supply the passphrase":"目标文件夹包含加密文件,请提供密码","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"该用户权限太多,您想要创建一个只能访问所选路径的受限用户吗?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"该备份创建于其他操作系统上。恢复时不指定目标文件夹可能会使文件恢复到未知的位置。您确定不指定目标文件夹继续吗?","This month":"本月","This week":"本周","Throttle settings":"限流设置","Thu":"周四","Time":"时间 ","To File":"导出为文件","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"为确认您要删除 \"{{name}}\" 的所有远程文件,请输入以下字母","To export without a passphrase, uncheck the \"Encrypt file\" box":"如果不想使用密码加密导出的文件,请去除勾选\"加密文件\"","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"为防止bucket命名冲突,建议在bucket名称前加上您的账户ID。是否自动添加?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"为了防止各种基于 DNS 的攻击,Duplicati 将仅允许此处列出的主机名。直接使用 IP 和 localhost 访问是始终允许的。可以使用分号分隔多个主机名,星号 (*) 代表允许所有主机名,同时禁用所有限制。如果该字段为空,则仅允许 IP 地址和本地主机访问。","Today":"今天","Trust host certificate?":"信任主机证书?","Trust server certificate?":"信任服务器证书?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"尝试我们正在开发的新功能。这是当前最稳定的版本。在生产环境使用前,请先测试恢复数据。","Tue":"周二","Type passphrase here.":"在这里输入密码。","Type to highlight files":"输入以高亮文件","Unknown backup size and versions":"未知的备份大小和版本","Until resumed":"直到手动恢复运行","Update {{state.updatedVersion}} is available. Download now":"更新 {{state.updatedVersion}} 可用。立即下载","Update channel":"更新分支","Update failed:":"更新失败:","Updating with existing database":"正在更新存在的数据库","Uploaded files":"已上传文件","Uploading verification file …":"正在上传校验文件…","Usage statistics":"使用情况统计","Usage statistics, warnings, errors, and crashes":"使用情况统计、警告、错误和崩溃","Use SSL":"启用 SSL","Use existing database?":"使用已存在的数据库?","Use weak passphrase":"确定使用弱密码","Useless":"无用","User data":"用户数据","User domain name":"用户域名称","User has too many permissions":"用户权限太多","User interface settings":"界面设置","Username":"用户名","Vacuuming database …":"正在清理数据库…","Validating …":"正在验证…","Verifications":"验证","Verify encryption passphrase":"验证加密密码","Verify files":"校验文件","Verifying answer":"正在验证","Verifying backend data …":"正在校验后端数据…","Verifying files …":"正在校验文件…","Verifying remote data …":"正在校验远程数据…","Verifying restored files …":"正在校验恢复后的文件…","Verifying …":"正在校验…","Version ID":"版本 ID","Very strong":"强度非常高","Very weak":"强度非常低","Visit us on":"了解我们","WARNING: The remote database is found to be in use by the commandline library.":"警告:远程数据库被发现正被命令行使用。","WARNING: This will prevent you from restoring the data in the future.":"警告:这将阻止您将来恢复数据","Waiting for task to begin":"等待任务开始…","Waiting for task to start …":"等待任务启动…","Waiting for upload to finish …":"等待上传完成…","Warnings, errors and crashes":"警告、错误和崩溃","We recommend that you encrypt all backups stored outside your system":"我们建议您加密所有保存在第三方系统中的数据","Weak":"强度低","Weak passphrase":"弱密码","Wed":"周三","Weeks":"周","Where do you want to restore from?":"您想从哪里恢复呢?","Where do you want to restore the files to?":"您想把文件恢复到哪里?","Years":"年","Yes":"是","Yes, I have stored the passphrase safely":"是,我已将密码安全保存","Yes, I understand the risk":"是,我理解该风险","Yes, I'm brave!":"是,我无所谓","Yes, please break my backup!":"是,请清除我的备份","Yesterday":"昨天","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"您正在更改现有数据库路径。\n您确定要这么做吗?","You are currently running {{appname}} {{version}}":"当前正在运行 {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"您可以立即停止备份,将在当前上传的任意文件完成后停止。","You can stop the task immediately, or allow the process to continue its current file and then stop.":"您可以立即停止任务,或在当前文件处理完成后停止。","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"您已经更改了加密方式,这可能破坏备份。您应当创建一份新的备份。","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"您已经更改了密码,这是不支持的操作。您应当创建一份新的备份。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您已选择不加密备份,建议加密所有存储在远程服务器上的数据。","You have chosen to restore to a new location, but not entered one":"您选择了恢复到新位置,但没有指定具体位置","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"您已经生成了一个强密码。确保您已经安全记录下了该密码,否则,如果您丢失了该密码,数据将无法恢复。","You must choose at least one source folder":"您必须至少一个源文件夹","You must enter a domain name to use v3 API":"您必须输入域名称以使用 v3 API","You must enter a name for the backup":"您必须输入备份名称","You must enter a passphrase or disable encryption":"您必须输入加密密码或禁用加密","You must enter a password to use v3 API":"您必须输入密码以使用 v3 API","You must enter a positive number of backups to keep":"您输入要保留的版本数必须为正数","You must enter a tenant (aka project) name to use v3 API":"您必须输入租户名称(即项目)以使用 v3 API","You must enter a tenant name if you do not provide an API key":"如果您不提供API key,则必须输入租户名称","You must enter a valid duration for the time to keep backups":"您必须输入有效的期限来保留备份","You must enter a valid retention policy string":"您必须输入一个有效的保留策略","You must enter either a password or an API key":"您必须输入密码或API key","You must enter either a password or an API key, not both":"您必须输入密码或API key,两者不能同时都输入","You must fill in the password":"您必须填写密码","You must fill in the server name or address":"您必须填写服务器主机名或地址","You must fill in the username":"您必须填写用户名","You must fill in {{field}}":"您必须填写 {{field}}","You must select or fill in the AuthURI":"您必须选择或填写认证地址","You must select or fill in the server":"您必须选择或填写服务器","You must specify a path":"您必须指定路径","You should fill in {{field}} {{reason}}":"您应该填写{{field}} {{reason}}","Your files and folders have been restored successfully.":"您的文件和文件夹已经恢复成功。","Your passphrase is easy to guess. Consider changing passphrase.":"您的密码很容易被猜到,请考虑更换密码。","bucket/folder/subfolder":"Bucket / 文件夹 / 子文件夹","byte":"B","byte/s":"B/s","custom":"自定义","failed":"失败","local repository, e.g. local":"本地仓库,例如:local","remote path, e.g. backup":"远程路径,例如:backup","remote repository, e.g. remote":"远程仓库,例如:remote","resume now":"立即恢复运行","unless you are explicitly specifying --group-id":"除非您明确指定 --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} 主要由 {{dev1}} 和 {{dev2}} 开发. {{appname}} 可以从 {{websitename}} 下载. {{appname}} 采用 {{licensename}} 授权.","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}} 正在使用以下第三方库:","{{files}} files ({{size}}) to go {{speed_txt}}":"剩余 {{files}} 个文件 ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 个版本","{{number}} Hour":"{{number}} 小时","{{number}} Hours":"{{number}} 小时","{{number}} Minutes":"{{number}} 分钟","{{time}} (took {{duration}})":"{{time}} (耗时 {{duration}})"}); - gettextCatalog.setStrings('zh_HK', {"- pick an option -":"選擇一個選項","...loading...":"...載入中...","AWS IAM Policy":"AWS IAM 原則","About":"關於","About {{appname}}":"關於 {{appname}}","Access denied":"存取被拒","Account name":"用戶名","Add a new backup":"加入新的備份","Add a path directly":"直接加入路徑","Add advanced option":"新增進階選項","Add backup":"新增備份","Add filter":"新增過濾器","Add path":"加入路徑","Advanced Options":"進階選項","Advanced options":"進階選項","Advanced:":"進階:","All Hyper-V Machines":"所有Hyper-V機器","All Microsoft SQL Databases":"所有Microsoft SQL數據庫","Allow remote access (requires restart)":"允許遠端存取(需要重新啟動)","Allowed days":"允許日子","An existing file was found at the new location":"在新的位置上發現有檔案存在","Anonymous usage reports":"匿名使用報告","AuthID":"AuthID","Authentication password":"認證密碼","Authentication username":"認證用戶名","Autogenerated passphrase":"自動產生密碼","Back":"返回","Backup destination":"備份目的地","Backup location":"備份位置","Backup:":"備份:","Beta":"Beta","Browse":"瀏覽","Browser default":"瀏覽預設","Bucket create location":"Bucket 建立位置","Bucket name":"Bucket 名稱","Bucket storage class":"Bucket 儲存等級","Canary":"Canary","Cancel":"Cancel","Changelog":"更新日誌","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} 更新日誌","Check failed:":"檢查失敗:","Check for updates now":"立即檢查更新","Compact now":"立即壓縮","Computer":"電腦","Configuration file:":"設定檔案:","Configuration:":"設定:","Configure a new backup":"設定新備份","Confirm delete":"確認刪除","Confirm encryption passphrase":"確認加密密碼","Confirmation required":"需要確認","Connect":"連接","Connect now":"立即連接","Connection lost":"連接中斷","Connection worked!":"連接成功!","Container name":"容器名稱","Container region":"容器區域","Continue":"繼續","Continue without encryption":"繼續但不加密","Copied!":"已複製!","Copy Destination URL to Clipboard":"複製目的地網址到剪貼簿","Copy failed. Please manually copy the URL":"複製失敗。請手動複製網址","Counting ({{files}} files found, {{size}})":"點算中(找到 {{files}} 個檔案,{{size}})","Create folder?":"建立資料夾?","Created new limited user":"已建立受限制的使用者","Current version is {{versionname}} ({{versionnumber}})":"現時版本 {{versionname}} ({{versionnumber}})","Custom location ({{server}})":"自訂位置({{server}})","Custom server url ({{server}})":"自訂伺服器地址({{server}})","Days":"Days","Default":"預設","Default ({{channelname}})":"預設 ({{channelname}})","Default options":"預設選項","Delete":"刪除","Delete backup":"刪除備份","Delete local database":"刪除本地資料庫","Delete remote files":"刪除遠端檔案","Delete the local database":"刪除本地資料庫","Desktop":"桌面","Destination":"目的地","Disabled":"已停用","Dismiss":"略過","Display and color theme":"顯示及顏色主題","Do you really want to delete the backup: \"{{name}}\" ?":"您真的確定要刪除備份: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"您真的確定要刪除 \"{{name}}\" 的本地數據庫?","Done":"完成","Download":"下載","Duplicate option {{opt}}":"Duplicati 選項 {{opt}}","Duplicati Website":"Duplicati 網站","Duplicati forum":"Duplicati 討論區","Encrypt file":"加密檔案","Enter URL":"輸入網址","Enter backup passphrase, if any":"輸入備份密碼(如有)","Enter encryption passphrase":"輸入加密密碼","Enter the destination path":"輸入目的地路徑","Error":"錯誤","Error!":"錯誤!","Exclude":"排除","Exclude directories whose names contain":"排除含有此名稱的資料夾","Exclude expression":"排除表達式","Exclude file":"排除檔案","Exclude file extension":"排除副檔名","Exclude files whose names contain":"排除含有此名稱的檔案","Exclude folder":"排除資料夾","Exclude regular expression":"排除正規表達式","Existing file found":"找到已存在的檔案","Experimental":"實驗性","Export":"匯出","Export backup configuration":"匯出備份設定","Export configuration":"匯出設定","FTP (Alternative)":"FTP(備用)","Failed to build temporary database: {{message}}":"建立臨時資籵庫失敗:{{message}}","Failed to connect:":"連接失敗:","Failed to connect: {{message}}":"連接失敗:{{message}}","Failed to delete:":"刪除失敗:","Failed to fetch path information: {{message}}":"無法取得路徑資料:{{message}}","Failed to read backup defaults:":"讀取預設備份失敗:","Failed to restore files: {{message}}":"還原檔案失敗:{{message}}","Failed to save:":"儲存失敗:","File":"檔案","Files larger than:":"檔案大於","Filters":"過濾器","Finished!":"已完成!","Folder":"資籵夾","Folder path":"資料夾路徑","Fri":"星期五","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"一般","General backup settings":"一般備份設定","General options":"一般設定","Generate":"產生","Generate IAM access policy":"產生 IAM 存取原則","Hidden files":"隱藏的檔案","Hide":"隱藏","Hide hidden folders":"不顯示隱藏的資料夾","Home":"首頁","Hours":"小時","How do you want to handle existing files?":"您想怎樣處理已存在的檔案?","Hyper-V Machine":"Hyper-V 機器","Hyper-V Machine:":"Hyper-V 機器:","Hyper-V Machines":"Hyper-V 機器","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果錯過了時間,將儘快執行工作。","Import":"匯入","Import Destination URL":"匯入目的地網址","Import backup configuration":"匯入備份設定","Import from a file":"從檔案匯入","Include a file?":"包括一個檔案?","Include expression":"包括表達式","Include regular expression":"包括正規表達式","Incorrect answer, try again":"答案錯誤,請重試","Information":"訊息","Invalid characters in path":"路徑中有無效的字符","Invalid retention time":"無效的保留時間","KByte":"KByte","KByte/s":"KByte/s","Language in user interface":"界面語言","Last month":"上個月","Latest":"最新","Live":"即時","Load older data":"載入舊資料","Local database path:":"本地資料庫路徑:","Local storage":"本地儲存","Location":"位置","Log data from the server":"來自伺服器的記錄","Log out":"登出","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"維護","Manually type path":"手動輸入路徑","Max download speed":"最高下載速度","Max upload speed":"最高上傳速度","Menu":"選單","Microsoft SQL Database:":"Microsoft SQL 資料庫:","Microsoft SQL Databases":"Microsoft SQL 資料庫","Minutes":"分鐘","Missing name":"沒有名稱","Missing passphrase":"沒有密碼","Missing sources":"沒有來源","Mon":"星期一","Months":"月","Move existing database":"移動現時的資料庫","Move failed:":"移動失敗:","My Documents":"我的文件","My Music":"我的音樂","My Photos":"我的相片","My Pictures":"我的圖片","Name":"名稱","Never":"永不","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新用戶為 {{username}}。\n已更新憑證以使用該受管制用戶","Next":"下一步","Next scheduled run:":"下次預定報行的時間:","Next scheduled task:":"下次預定報行的工作:","Next task:":"下次的工作:","Next time":"下次執行時間:","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"先前並未指定過證書,請與伺服管理員驗證此密匙是否正確:{key}}\n\n您要接受這個主題密匙嗎?","No encryption":"無加密","No items selected":"沒有選擇任何項目","No items to restore, please select one or more items":"沒有需要還原的項目,請擇一個或以上的項目","No passphrase entered":"沒有輸入密碼","No scheduled tasks":"沒有預定的工作","Non-matching passphrase":"密碼不正確","None / disabled":"沒有/已停用","OK":"確定","Options":"選項","Others":"Others","Overwrite":"覆蓋","Passphrase":"密碼","Passphrase (if encrypted)":"密碼(如已加密)","Passphrase changed":"已更改密碼","Passphrases are not matching":"密碼不相同","Password":"密碼","Path":"路徑","Path not found":"找不到路徑","Path on server":"伺服器上路徑","Pause":"暫停","Pause after startup or hibernation":"啟動或休眠後暫停","Pause options":"暫停選項","Permissions":"權限","Pick location":"選擇位置","Port":"埠","Previous":"Previous","Recreate (delete and repair)":"重建(刪除及修復)","Remote":"遠端","Remove":"移除","Remove option":"移除選項","Repair":"修復","Repeat Passphrase":"重覆密碼","Reporting:":"報告︰","Reset":"重設","Restore":"還原","Restore files":"還原檔案","Restore from":"從...還原檔案","Restore from backup configuration":"從備份設定還原","Restore options":"還原選項","Resume":"繼續","Run again every":"每...重覆執行","Run now":"立即執行","Running task:":"正在執行工作:","S3 Compatible":"S3 相容","Sat":"星期六","Save":"儲存","Save and repair":"儲存並修復","Save immediately":"立即儲存","Schedule":"排程","Search":"搜尋","Search for files":"搜尋檔案","Seconds":"秒","Select files":"選擇檔案","Server":"伺服器","Server and port":"伺服器與連接埠","Server hostname or IP":"伺服器名稱或 IP","Server is currently paused,":"伺服器目前已暫停,","Server is currently paused, do you want to resume now?":"伺服器暫停中,您要現在立即繼續嗎?","Server password":"伺服器密碼","Server paused":"伺服器已暫停","Server state properties":"伺服器狀態","Settings":"設定","Show":"顯示","Show advanced editor":"顯示進階編輯","Show hidden folders":"顯示隱藏的資料夾","Show log":"顯示記錄","Show treeview":"顯示樹狀檢視","Sia server password":"Sia 伺服器密碼","Source Data":"來源資料","Source data":"來源資料","Source folders":"來源資料夾","Source:":"來源:","Standard protocols":"標準通訊協定","Stop after the current file":"現時檔案完成後停止","Stop now":"立即停止","Stop running backup":"停止正在進行的備份","Stop running task":"停止正在進行的工作","Stopping task:":"停止工作中:","Storage Type":"儲存類型","Storage class":"儲存等級","Stored":"已儲存","Strong":"強","Success":"成功","Sun":"星期日","Symbolic link":"符號連結","System default ({{levelname}})":"系統預設({{levelname}})","System files":"系統檔案","System info":"系統資訊","System properties":"系統內容","TByte":"TByte","TByte/s":"TByte/s","Task is running":"工作執行中","Temporary files":"暫存檔案","Test connection":"測試連線","The dark theme (by Michal)":"深色主題(Michai設計)","The default blue on white theme (by Alex)":"預設的藍白色主題(Alexi設計)","This month":"本月","This week":"本週","Thu":"星期四","To File":"到檔案","Today":"今日","Trust server certificate?":"信任伺服器證書?","Tue":"星期二","Until resumed":"直至手動繼續","Update channel":"更新頻道","Update failed:":"更新失敗:","Use SSL":"使用 SSL","Use weak passphrase":"使用強度為弱的密碼","Useless":"不使用","Username":"使用者","Verify files":"驗證檔案","Verifying answer":"驗證答案中...","Very strong":"十分強","Very weak":"十分弱","Weak passphrase":"弱密碼","Wed":"星期三","Weeks":"星期","Years":"年","Yes":"是","Yesterday":"Yesterday","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您選擇了不加密備份。建議備份所有儲存在遠端伺服器上資料。","You must fill in the server name or address":"您必須填寫伺服器名稱或地址","You must select or fill in the server":"您必須選擇或填寫伺服器","byte":"byte","byte/s":"byte/s","custom":"custom","resume now":"立即繼續","{{number}} Hour":"{{number}} 小時","{{number}} Minutes":"{{number}} 分鐘","{{time}} (took {{duration}})":"{{time}} (花費 {{duration}})"}); - gettextCatalog.setStrings('zh_TW', {"- pick an option -":"選擇一個項目","...loading...":"...載入中...","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"關於","About {{appname}}":"關於 {{appname}}","Access Key":"Access Key","Access denied":"拒絕存取","Access to user interface":"進入使用者介面","Account name":"帳號名稱","Add a new backup":"新增備份","Add a path directly":"直接增加資料路徑","Add advanced option":"加入進階選項","Add backup":"備份","Add filter":"加入篩選條件","Add path":"加入路徑","Added":"已加入","Adjust bucket name?":"調整 bucket 名稱?","Advanced Options":"進階選項","Advanced options":"進階選項","Advanced:":"進階:","All Hyper-V Machines":"全部 Hyper-V 主機","All Microsoft SQL Databases":"全部 Microsoft SQL 資料庫","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"全部的使用報告都是採匿名發送,不包含任何個人資訊。這份報告中包含有關硬體以及作業系統資訊、後端類型、備份時間、來源資料的總容量與相關資訊。當中將不會包含路徑、檔名、帳號、密碼或類似的敏感資訊。","Allow remote access (requires restart)":"允許遠端存取(需要重新啟動)","Allowed days":"允許日","An existing file was found at the new location":"新的位置發現已既有檔案存在","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"新的位置發現已既有檔案存在,您要將資料庫指向其中一個既有檔案嗎?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"儲存區發現既有的的本機資料庫已存在。\n重新使用資料庫將可以讓您使用命令列和伺服器服務用在同樣的遠端儲存區。\n\n您希望使用既有的資料庫嗎?","Anonymous usage reports":"匿名使用報告","Applications":"Applications","As Command-line":"顯示為 Command-Line","AuthID":"AuthID","Authentication password":"認證密碼","Authentication username":"認證名稱","Autogenerated passphrase":"自動產生密碼","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage 帳號 ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"返回","Backup complete!":"備份完成。","Backup destination":"備份目的地","Backup location":"備份位置","Backup retention":"保留備份數目","Backup:":"備份:","Beta":"測試版 (Beta)","Broken access":"故障連線","Browse":"瀏覽","Browser default":"瀏覽器預設","Bucket create location":"Bucket 建立位置","Bucket name":"Bucket 名稱","Bucket storage class":"Bucket 儲存等級","Building list of files to restore …":"正在建立還原的檔案清單 ...","Building partial temporary database …":"正在建立部份暫存資料庫 ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"允許遠端存取,伺服器間接收來自網路中任何主機的連線。如果啟用了這個選項,請確認已經使用防火牆保護好您網路中的主機。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"在預設情況下,點選系統列 (Tray) 圖示將會直接打開登入介面,而非直接解鎖進入管理介面。除了您從系統列圖示進入的是登入介面,也可以確保當其它人使用時也需要輸入密碼。如果您喜歡輸入密碼才能進入介面的話,啟用這個選項將是適合您的選擇。","Cache Files":"快取檔案","Canary":"Canary","Cancel":"取消","Cannot move to existing file":"無法搬移已存在檔案","Changelog":"更新記錄","Changelog for {{appname}} {{version}}":"更新記錄:{{appname}} {{version}}","Check failed:":"檢查失敗:","Check for updates now":"現在檢查更新","Checking for updates …":"檢查更新中 ...","Chose a storage type to get started":"選擇儲存區類型,然後開始","Click the AuthID link to create an AuthID":"按下 AuthID 連結來建立一組 AuthID","Click to set throttle options":"點這裡進入頻寬限制設定","Commandline …":"命令列 ...","Compact Phase":"壓縮階段","Compact now":"立即緊密壓縮","Compacting remote data …":"正在緊密壓縮遠端資料 ...","Complete log":"完整記錄","Completing backup …":"正在完成備份 ...","Completing previous backup …":"正在完成上一次備份 ...","Computer":"電腦","Configuration file:":"設定檔:","Configuration:":"設定:","Configure a new backup":"設定一個新備份","Confirm delete":"確認刪除","Confirm encryption passphrase":"確認加密密碼","Confirm passphrase":"確認密碼","Confirmation required":"需要確認","Connect":"連線","Connect now":"立即連線","Connecting to server …":"正在連線到伺服器 ...","Connection lost":"連線失敗","Connection worked!":"連線已建立!","Container name":"容器名稱","Container region":"容器區域","Continue":"繼續","Continue without encryption":"不加密並繼續","Copied!":"已複製","Copy":"複製","Copy Destination URL to Clipboard":"複製目標 URL 至剪貼簿","Copy failed. Please manually copy the URL":"複製失敗。請手動複製 URL","Core options":"核心選項","Counting ({{files}} files found, {{size}})":"正在計算 ({{files}} 個檔案, {{size}})","Crashes only":"只有當機","Create bug report …":"建立問題報告 ...","Create folder?":"建立資料夾?","Created new limited user":"建立新的受限使用者","Creating bug report …":"正在建立問題報告 ...","Creating new user with limited access …":"正在建立有限制存取的新使用者 ...","Creating target folders …":"正在建立目標資料夾 ...","Creating temporary backup …":"正在建立暫存備份 ...","Current action:":"目前動作:","Current file:":"目前檔案:","Current version is {{versionname}} ({{versionnumber}})":"目前版本 {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"自訂 S3 進入點","Custom authentication url":"自訂授權 URL","Custom backup retention":"自訂備份保留規則","Custom location ({{server}})":"自訂位置 ({{server}})","Custom region for creating buckets":"自定區域以建立 Bucket ","Custom region value ({{region}})":"自訂區域 Value ({{region}})","Custom server url ({{server}})":"自訂伺服器 URL ({{server}})","Custom storage class ({{class}})":"自訂儲存等級 ({{class}})","Database …":"資料庫 ...","Days":"日","Default":"預設","Default ({{channelname}})":"預設 ({{channelname}})","Default excludes":"預設排除","Default options":"預設選項","Delete":"刪除","Delete Phase (Old Backup Versions)":"刪除階段 (舊版本備份)","Delete backup":"刪除備份","Delete backups that are older than":"刪除指定條件以前的備份","Delete local database":"刪除本機資料庫","Delete remote files":"刪除遠端檔案","Delete the local database":"刪除本機資料庫","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"刪除遠端儲存區的 {{filecount}} 個檔案 ({{filesize}}) 嗎?","Delete …":"刪除 ...","Deleted":"已刪除","Deleted Versions":"已刪除版本","Deleted files":"已刪除檔案","Deleting remote files …":"正在刪除遠端檔案 ...","Deleting unwanted files …":"正在刪除不需要的檔案 ...","Description (optional)":"說明 (可省略)","Description:":"說明:","Desktop":"桌面","Destination":"目的地","Destination path":"目的路徑","Disabled":"取消","Dismiss":"忽略","Dismiss all":"全部忽略","Display and color theme":"佈景主題設定","Do you really want to delete the backup: \"{{name}}\" ?":"您真的要刪除 \"{{name}}\" 這個備份?","Do you really want to delete the local database for: {{name}}":"您真的要刪除 {{name}} 這個本機資料庫?","Done":"完成","Download":"下載","Downloaded files":"已下載檔案","Downloading files …":"正在下載檔案 ...","Downloading update…":"正在下載更新 ...","Duplicate option {{opt}}":"重複選項 {{opt}}","Duplicati Website":"Duplicati 官方網站","Duplicati forum":"Duplicati 論壇","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati 將於作業系統啟動後執行,但將會保持在暫停狀態。此時 Duplicati 將以最少資源使用率的情況下常駐,不會進行備份作業。","Duration":"時間","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"每個備份都會關聯到一個本機資料庫,裡面儲存有備份目的地的相關資訊。\n 當您刪除備份時,您可以只刪除本機資料庫而不影響恢復備份目的地備份檔的還原能力。\n 如果您使用本機資料庫做命令列方式備份,您將資料庫保留好。","Edit as list":"編輯清單","Edit as text":"編輯文字內容","Edit …":"編輯 ...","Encrypt file":"加密檔案","Encryption":"加密方式","Encryption changed":"加密方式已變更","End":"結束","Enter URL":"輸入 URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"手動輸入備份保留原則。可用關鍵字 D/W/Y,分別代表 日/週/年。語法如下:7D:1D,4W:1W,36M:1M。上述例子表示,每7日保留1份,每4週保留1份,每36個月保留1份。您也可以寫成 1W:1D,1M:1W,3Y:1M。","Enter backup passphrase, if any":"輸入備份密碼,如果有的話","Enter configuration details":"進入設定細節","Enter encryption passphrase":"輸入加密密碼","Enter expression here":"在這裡輸入運算式","Enter the destination path":"輸入目的地路徑","Error":"錯誤","Error!":"錯誤!","Errors and crashes":"錯誤與當機","Examined":"已檢查","Exclude":"例外","Exclude directories whose names contain":"排除目錄名稱含有","Exclude expression":"排除表示式","Exclude file":"例外檔案","Exclude file extension":"例外副檔名","Exclude files whose names contain":"排除檔案名稱包含有","Exclude filter group":"例外篩選群組","Exclude folder":"例外資料夾","Exclude regular expression":"排除的正規表示式","Existing file found":"檔案已存在","Experimental":"實驗版 (Experimental)","Export":"匯出","Export backup configuration":"匯出備份設定","Export configuration":"匯出設定","Export passwords":"匯出密碼","Export …":"匯出 ...","Exporting …":"正在匯出 ...","External link":"外部連結","FTP (Alternative)":"FTP (替代)","Failed to build temporary database: {{message}}":"建立暫存資料庫失敗:{{message}}","Failed to connect:":"連線失敗:","Failed to connect: {{message}}":"連線失敗:{{message}}","Failed to delete:":"刪除失敗:","Failed to fetch path information: {{message}}":"列取路徑資訊失敗: {{message}}","Failed to find backup:":"尋找備份失敗:","Failed to read backup defaults:":"讀取備份預設值失敗︰","Failed to restore files: {{message}}":"還原檔案失敗:{{message}}","Failed to save:":"儲存失敗:","Fetching path information …":"正在列舉路徑資訊 ...","File":"檔案","Files larger than:":"檔案大小超過:","Filters":"篩選","Finished!":"已完成!","First run setup":"執行初始化設定","Folder":"資料夾","Folder path":"資料夾路徑","Fri":"週五","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"一般","General backup settings":"一般備份設定","General options":"一般選項","Generate":"產生","Generate IAM access policy":"產生 IAM access policy","Getting file versions …":"正在取得檔案版本 ...","Group email":"群組郵件","Hidden files":"隱藏檔案","Hide":"隱藏","Hide hidden folders":"隱藏目錄","Home":"首頁","Hostnames":"主機名稱","Hours":"小時","How do you want to handle existing files?":"您如何處理既有檔案?","Hyper-V Machine":"Hyper-V 主機","Hyper-V Machine:":"Hyper-V 主機:","Hyper-V Machines":"Hyper-V 主機","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果已錯過時間,將儘可能快速進行這個工作。","If at least one newer backup is found, all backups older than this date are deleted.":"如果有更新的備份存在,則刪除比這個日期早的所有備份。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"如果沒有輸入路徑,將會儲存所有檔案在登入資料夾。\n確定這是您要的嗎?","If you do not enter an API Key, the tenant name is required":"If you do not enter an API Key, the tenant name is required","Import":"匯入","Import Destination URL":"匯入目的地 URL","Import backup configuration":"匯入備份設定","Import from a file":"從檔案匯入","Import metadata":"匯入 metadata","Importing …":"正在匯入 ...","Include a file?":"包含檔案?","Include expression":"包含表示式","Include regular expression":"包含正則表示式","Incorrect answer, try again":"回應不正確,請重試一次","Individual builds for developers only. Not for use with important data.":"僅針對開發人員的個別組建版本,請不要使用在重要資料上。","Information":"資訊","Invalid characters in path":"路徑有無法使用的字元","Invalid retention time":"保留時間無效","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"可以在無密碼的情況下連接到 FTP。\n您確定您的 FTP 伺服器支援無密碼登錄嗎?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"保留指定份數的備份","Keep all backups":"保留所有備份","Keystone API version":"Keystone API 版本","Language in user interface":"使用者介面語言","Last month":"上個月","Last successful backup:":"上一次成功備份:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"上一次成功還原:{{time}} (took {{duration || '0 seconds'}})","Latest":"最新","Libraries":"函式庫","Listing backup dates …":"正在列出備份日期 ...","Listing remote files for purge …":"正在列出要清除的遠端檔案...","Listing remote files …":"正在列出遠端檔案 ...","Live":"即時","Load a configuration from an exported job or a storage provider":"從匯出的備份作業或儲存區來載入組態設定","Load destination from an exported job or a storage provider":"從匯出的備份作業或儲存區來載入備份目的地","Load older data":"載入較舊的資料","Loading …":"載入中 ...","Local Repository":"本機 Repository","Local database path:":"本機資料庫路徑:","Local repository":"本機 repository","Local storage":"本機儲存區","Location":"位置","Location where buckets are created":"建立 Buckets 的位置","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}} 的記錄資料","Log data from the server":"伺服器上的記錄","Log out":"登出","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"維護","Manually type path":"手動輸入路徑","Max download speed":"最大下載速度","Max upload speed":"最大上傳速度","Menu":"功能","Microsoft SQL Database:":"Microsoft SQL 資料庫:","Microsoft SQL Databases":"Microsoft SQL 資料庫","Minimum redundancy":"Minimum redundancy","Minimum redundancy is 1.0":"Minimum redundancy is 1.0","Minutes":"分鐘","Missing name":"遺失名稱","Missing passphrase":"遺失密碼","Missing sources":"遺失來源","Modified":"已修改","Mon":"週一","Months":"月","Move existing database":"搬移已存在資料庫","Move failed:":"搬移失敗:","My Documents":"My Documents","My Music":"My Music","My Photos":"My Photos","My Pictures":"My Pictures","Name":"名稱","Never":"從未","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新使用者名稱是 {{user}}.\n更新憑證以使用新的受限使用者帳號","Next":"下一頁","Next scheduled run:":"下一次排程執行:","Next scheduled task:":"下一個排程工作:","Next task:":"下一個工作:","Next time":"下一次","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?","No editor found for the "{{backend}}" storage type":"找不到 "{{backend}}" 儲存區類型","No encryption":"不加密","No items selected":"沒有選擇","No items to restore, please select one or more items":"沒有要還原的項目,請至少選擇一個項目","No passphrase entered":"沒有輸入密碼","No scheduled tasks":"沒有排程工作","Non-matching passphrase":"密碼不相符","None / disabled":"無 / 取消","Not using encryption":"未使用加密","Nothing will be deleted. The backup size will grow with each change.":"什麼都不刪除。備份大小將隨著每次異動而持續增長。","OK":"確定","Once there are more backups than the specified number, the oldest backups are deleted.":"當備份數量超過指定數目,最舊的備份將被刪除。","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"已開啟","Operating System":"作業系統","Operation":"作業","Operations:":"作業:","Optional authentication password":"(非必要)認證密碼","Optional authentication username":"(非必要)認證帳號","Options":"選項","Original location":"原始位置","Others":"其它","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"智慧保留模式,兼具長時間保存與短時間份數考量。保留每7天、每4週、每12個月均有一份備份。","Overwrite":"覆寫","Passphrase":"密碼","Passphrase (if encrypted)":"密碼 (如果已加密)","Passphrase changed":"密碼已變更","Passphrases are not matching":"密碼不相符","Passphrases do not match":"密碼不相符","Password":"密碼","Patching files with local blocks …":"使用本機區塊修復檔案中 ...","Path":"路徑","Path not found":"找不到路徑","Path on server":"伺服器路徑","Path or subfolder in the bucket":"Bucket 裡的路徑或子資料夾","Pause":"暫停","Pause after startup or hibernation":"當啟動或休眠後暫停","Pause options":"暫停選項","Permissions":"權限","Pick location":"選擇位置","Point to your backup files and restore from there":"指向您的備份檔案,將會由此還原","Port":"連接埠","Prevent tray icon automatic log-in":"關閉從系統列 (Tray) 圖示自動登入","Previous":"上一頁","Progress:":"正在處理:","ProjectID is optional if the bucket exist":"ProjectID is optional if the bucket exist","Proprietary":"雲端服務","Purge Phase":"清除階段","Purging files complete!":"檔案清除完成!","Purging files …":"正在清理檔案 ...","Rebuilding local database …":"正在重建本機資料庫 ...","Recreate (delete and repair)":"重新建立(刪除並修復)","Recreate Database Phase":"重建資料庫階段","Recreating database …":"正在重建資料庫 ...","Registering temporary backup …":"正在註冊暫時備份 ...","Relative paths not allowed":"不允許使用相對路徑","Reload":"重新載入","Remote":"遠端","Remote Path":"遠端 Path","Remote Repository":"遠端 Repository","Remote path":"遠端 path","Remote repository":"遠端 repository","Remote volume size":"遠端區塊大小","Remove":"移除","Remove option":"移除選項","Removed files":"檔案已移除","Repair":"修復","Repair Phase":"修復階段","Repairing database …":"正在修復資料庫 ...","Repeat Passphrase":"重複密碼","Reporting:":"報告︰","Reset":"重置","Restore":"還原","Restore complete!":"還原完成!","Restore files":"還原檔案","Restore files …":"還原檔案 ...","Restore from":"還原檔案從 ","Restore from backup configuration":"從備份設定檔還原","Restore options":"還原選項","Restore read/write permissions":"還原讀/寫權限","Restored Files":"已還原檔案","Restored Folders":"已還原資料夾","Restored Symlinks":"已還原符號連結","Restoring files …":"正在還原檔案 ...","Resume":"繼續","Rewritten File Lists":"覆寫檔案清單","Run again every":"重複執行於每","Run now":"立即執行","Running commandline entry":"Running commandline entry","Running task:":"正在執行工作:","Running …":"正在執行 ...","S3 Compatible":"S3 相容","Same as the base install version: {{channelname}}":"與目前已安裝版本相同: {{channelname}}","Sat":"週六","Save":"儲存","Save and repair":"儲存並修復","Save different versions with timestamp in file name":"在檔案名稱中儲存不同版本的時間戳記","Save immediately":"立即儲存","Scanning existing files …":"正在掃描已存在檔案 ...","Scanning for local blocks …":"正在掃描本機區塊 ...","Schedule":"排程","Search":"搜尋","Search for files":"搜尋檔案","Seconds":"秒","Select a log level and see messages as they happen:":"選擇一個記錄等級以查看訊息︰","Select files":"選擇檔案","Server":"伺服器","Server and port":"伺服器與連接埠","Server hostname or IP":"伺服器名稱或 IP","Server is currently paused,":"伺服器目前已暫停,","Server is currently paused, do you want to resume now?":"伺服器目前已暫停,請問您現在要繼續嗎?","Server password":"伺服器密碼","Server paused":"伺服器目前已暫停","Server state properties":"伺服器狀態屬性","Settings":"設定","Show":"顯示","Show advanced editor":"顯示進階編輯器","Show hidden folders":"顯示隱藏資料夾","Show log":"顯示記錄","Show log …":"顯示記錄 ...","Show treeview":"顯示樹狀清單","Sia server password":"Sia 伺服器密碼","Smart backup retention":"智慧管理備份數","Some OpenStack providers allow an API key instead of a password and tenant name":"某些 OpenStack 供應商允許 API Key 而不用密碼與 Tenant 名稱","Source Data":"來源資料","Source Files":"來源檔案","Source data":"來源資料","Source folders":"來源資料夾","Source:":"來源:","Specific builds for developers only. Not for use with important data.":"僅針對開發人員的特定組建版本,請不要使用在重要資料上。","Standard protocols":"標準通訊協定","Start":"開始","Starting backup …":"正在開始備份 ...","Starting restore …":"正在開始還原...","Starting the restore process …":"正在開始還原程序 ...","Stop after current file":"這個檔案完成後停止","Stop after the current file":"這個檔案完成後停止","Stop now":"立即停止","Stop running backup":"停止正在進行的備份","Stop running task":"停止正在進行的工作","Stopping after the current file:":"正在等檔案完成後停止:","Stopping task:":"正在停止工作:","Storage Type":"儲存區類型","Storage class":"儲存區等級","Storage class for creating a bucket":"建立 Bucket 的儲存類型","Stored":"儲存","Strong":"強","Success":"成功","Sun":"週日","Symbolic link":"符號連結","System Files":"系統檔案","System default ({{levelname}})":"系統預設 ({{levelname}})","System files":"系統檔案","System info":"系統資訊","System properties":"系統屬性","TByte":"TByte","TByte/s":"TByte/s","Task is running":"工作正在執行","Temporary Files":"暫存檔案","Temporary files":"暫存檔案","Test Phase":"測試階段","Test connection":"測試連線","Testing permissions …":"正在測試權限 ...","Testing …":"測試中 ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"在 '{{fieldname}}' 欄位當中有無效字元: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"這個備份已遺失,是否要刪除?","The backup was temporary and does not exist anymore, so the log data is lost":"這是已經不存在的臨時備份,因此已無記錄資料。","The bucket name should be all lower-case, convert automatically?":"Bucket 名稱應該全部小寫,要自動轉換嗎?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"設定應該注意安全,您確定將含有密碼的設定儲存為不加密的檔案嗎?","The dark theme (by Michal)":"深色主題 (by Michal)","The default blue on white theme (by Alex)":"預設白色主題 (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"資料夾 {{folder}} 不存在,是否立即建立?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"主機金鑰已變更,如果是正確的請您與伺服器管理員聯繫,否則您可能已遭受中間人攻擊。\n\n你想要更換原先的主機金鑰 \"{{prev}}\" 到 {{key}} 嗎?","The passwords do not match":"密碼不符","The path does not appear to exist, do you want to add it anyway?":"路徑似乎不存在,無論如何你都要加入嗎?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"這個路徑的尾端沒有 '{{dirsep}}' 字元,這表示您指定的是檔案而非資料夾。\n\n您確認是要指定這個檔案嗎?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"必須是絕對路徑,也就是說必須以斜線開頭 '/'","The region parameter is only applied when creating a new bucket":"區域參數只有在建立新 Bucket 時套用","The region parameter is only used when creating a bucket":"區域參數只使用在在建立新 Bucket 時","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"伺服器無法驗證。\n您要使用這個 SSL 憑證 {{hash}} 嗎?","The storage class affects the availability and price for a stored file":"儲存區類型會影響到可用性以及... 價格","The target folder contains encrypted files, please supply the passphrase":"目的資料夾中包含加密檔案,請提供密碼","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"這個使用者擁有太多權限,您是否要建立另一個新的使用者,只具備指定路徑的權限?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"這個備份是在另一個作業系統上建立的,在不指定目標資料夾的情況下還原檔案,可能會讓檔案還原到您預期外的地方,請問您是否仍確定繼續而不重新指定資料夾?","This month":"本月","This week":"本週","Throttle settings":"頻寬限制設定","Thu":"週四","Time":"時間","To File":"到檔案","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"確認要刪除所有的遠端檔案 \"{{name}}\",請輸入下面的單字","To export without a passphrase, uncheck the \"Encrypt file\" box":"若要無密碼匯出,請不要勾選\"加密檔案\"核取方塊","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"為了避免基於 DNS 的攻擊,Duplicati 可以用主機名稱作為連接的來源限制。\n直接使用 IP 與 localhost 是內建允許的方式。\n若有多個主機名稱,可以用分號 (;) 做為分隔,如果使用萬用字元 (*),則表示所有主機名稱均可以連線至 Duplicaiti,等於關閉此功能;如果內容為空,則只允許使用 IP 與 localhost 進行連線。","Today":"今天","Trust host certificate?":"信任主機憑證?","Trust server certificate?":"信任伺服器憑證?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"嘗試我們正在開發中的新功能。這是目前最穩定的版本,要在正式環境使用此功能之前,請先測試是否可以正確還原資料。","Tue":"週二","Type passphrase here.":"在此這輸入密碼。","Type to highlight files":"輸入字串,符合的檔名會以粗體字方式標示","Unknown backup size and versions":"未知的備份大小與版本","Until resumed":"手動繼續","Update channel":"更新頻道","Update failed:":"更新失敗:","Updating with existing database":"正在更新既有資料庫 ...","Uploaded files":"已上傳檔案","Uploading verification file …":"正在上傳驗證檔案 ...","Usage statistics":"使用統計","Usage statistics, warnings, errors, and crashes":"使用統計、警告、錯誤與當機","Use SSL":"使用 SSL","Use existing database?":"使用已存在資料庫?","Use weak passphrase":"使用低強度密碼","Useless":"不使用","User data":"使用者資料","User domain name":"使用者網域名稱","User has too many permissions":"使用者有太多權限","User interface settings":"使用者介面設定","Username":"使用者","Vacuuming database …":"正在清理資料庫 ...","Validating …":"驗證中 ...","Verifications":"驗證","Verify files":"驗證檔案","Verifying answer":"驗證答案","Verifying backend data …":"正在驗證後端資料 ...","Verifying files …":"正在驗證檔案 ...","Verifying remote data …":"正在驗證遠端資料 ...","Verifying restored files …":"正在驗證已還原檔案 ...","Verifying …":"驗證中 ...","Version ID":"版本 ID","Very strong":"非常強","Very weak":"非常弱","Visit us on":"造訪我們","WARNING: This will prevent you from restoring the data in the future.":"警告︰ 這將會阻止您日後還原資料。","Waiting for task to begin":"正在等待工作開始","Waiting for upload to finish …":"等待上傳完成中 ...","Warnings, errors and crashes":"警告、錯誤與當機","We recommend that you encrypt all backups stored outside your system":"我們建議,您將放在您自己控管系統以外的備份都進行加密","Weak":"弱","Weak passphrase":"弱密碼","Wed":"週三","Weeks":"週","Where do you want to restore from?":"您要從那裡還原?","Where do you want to restore the files to?":"您要還原檔案到哪裡?","Years":"年","Yes":"是","Yes, I have stored the passphrase safely":"是,我已安全的儲存密碼","Yes, I understand the risk":"是的,我理解這個風險","Yes, I'm brave!":"是的,我敢!","Yes, please break my backup!":"是,請中斷我的備份!","Yesterday":"昨天","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"您正在變更現有資料庫的路徑。\n您確定這是您想要的嗎?","You are currently running {{appname}} {{version}}":"您正在執行 {{appname}} {{version}}","You can stop the backup after any file uploads currently in progress have finished.":"您可以立即停止備份,將在目前檔案上傳完成後停止。","You can stop the task immediately, or allow the process to continue its current file and then stop.":"您可以立即停止備份作業,或是讓備份作業進行至目前檔案完成後再停止。","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"您已變更加密模式。這可能導致資料損毀。我們建議您建立一個新的備份","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"您變更加密密碼,這個動作不被支援。我們建議您建立一個新的備份。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您已選擇備份不加密。建議您應將存在遠端伺服器上的資料予以加密。","You have chosen to restore to a new location, but not entered one":"您已經選擇還原到新的位置,但還沒輸入位置資訊","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"您已經產生足夠強度的密碼。請確保您已經另外備份好這組密碼,若您遺失這組密碼,您的資料將無法還原。","You must choose at least one source folder":"您至少要選擇一個來源資料夾","You must enter a domain name to use v3 API":"您必須輸入網域名稱以使用 v3 API","You must enter a name for the backup":"您必須輸入備份名稱","You must enter a passphrase or disable encryption":"您必須輸入密碼或取消加密","You must enter a password to use v3 API":"您必須輸入密碼以使用 v3 API","You must enter a positive number of backups to keep":"您必須輸入正數,備份才能保存","You must enter a tenant (aka project) name to use v3 API":"您必須輸入 tenant (或 project) 名稱以使用 v3 API","You must enter a valid duration for the time to keep backups":"您必須輸入有效的起迄時間來保留備份","You must fill in the password":"您必須輸入密碼","You must fill in the server name or address":"您必須填寫伺服器名稱或位址","You must fill in the username":"您必須填寫使用者名稱","You must fill in {{field}}":"您必須填寫 {{field}}","You must select or fill in the AuthURI":"您必須選擇或填寫 AuthURI","You must select or fill in the server":"您必須選擇或填寫伺服器","You must specify a path":"您必須指定一個路徑","Your files and folders have been restored successfully.":"您的檔案與資料夾已成功還原。","Your passphrase is easy to guess. Consider changing passphrase.":"您的密碼很容易被猜到。請考慮變更密碼。","bucket/folder/subfolder":"bucket/folder/subfolder","byte":"byte","byte/s":"byte/s","custom":"自訂","resume now":"立即繼續","unless you are explicitly specifying --group-id":"除非您明確的指定 --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} 主要是由 {{dev1}} 以及 {{dev2}} 所開發。 {{appname}} 可以從 {{websitename}} 下載取得。 {{appname}} 採用 {{licensename}} 授權。","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} 個檔案 ({{size}}) 正在傳輸 {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 個版本","{{number}} Hour":"{{number}} 小時","{{number}} Hours":"{{number}} 小時","{{number}} Minutes":"{{number}} 分鐘","{{time}} (took {{duration}})":"{{time}} (花費 {{duration}})"}); + gettextCatalog.setStrings('zh_CN', {"(interrupted)":"(中断)","- pick an option -":"- 选择一个选项 -","...loading...":"…正在加载…","Note: Sia will still boost redundancy later as long as you're connected to your hosts.":"注意: 只要您连接到您的主机,Sia稍后仍会提升冗余。"," Edit as text":" 以文本编辑"," Edit as text":" 以文本编辑","

Connection to server was rejected due to invalid authentication.

\n

Log in again, or re-open the page from the TrayIcon (if applicable)

":"

由于无效的身份验证,连接服务器被拒绝。

\n

重新登录,或者从托盘图标重新打开页面(如果适用)。

","API key":"API 密钥","AWS Access ID":"AWS 访问 ID","AWS Access Key":"AWS 访问密钥","AWS IAM Policy":"AWS IAM 策略","About":"关于","About {{appname}}":"关于 {{appname}}","Access Key":"Access Key","Access denied":"访问被拒绝","Access grant":"访问授权","Access to user interface":"用户界面访问","Account name":"帐户名","Add a new backup":"添加新备份","Add a path directly":"直接添加路径","Add advanced option":"添加高级选项","Add backup":"新增备份","Add filter":"添加过滤条件","Add path":"添加路径","Added":"已添加","Adjust bucket name?":"调整 bucket 名称?","Advanced Options":"高级选项","Advanced options":"高级选项","Advanced:":"高级:","Aliyun OSS Endpoint":"Aliyun OSS Endpoint","Aliyun OSS documents and resources":"阿里云OSS文档和资源","All Hyper-V Machines":"所有 Hyper-V 机器","All Microsoft SQL Databases":"所有 Microsoft SQL 数据库","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"所有的使用情况报告都是匿名发送,不含任何个人信息。 其中包括硬件、操作系统、后端类型、备份时长、备份源大小以及类似数据,但不包括路径、文件名、用户名、密码或类似的敏感信息。","Allow remote access (requires restart)":"允许远程访问 (需要重启)","Allowed days":"允许的日期","An existing file was found at the new location":"新位置已经存在文件","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"新位置已经存在文件\n您确定要将数据库指向已存在的文件?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"发现该存储在本地已存在数据库\n重新使用该数据库将使用命令行或服务器实例工作在相同的存储\n您希望使用已有的数据库吗?","Anonymous usage reports":"匿名使用报告","Applications":"应用","As Command-line":"导出为命令行","AuthID":"授权 ID","Authentication method":"认证方法","Authentication method ({{auth_method}})":"认证方法 ({{auth_method}})","Authentication password":"认证密码","Authentication username":"认证用户名","Autogenerated passphrase":"自动生成的密码","Automatically run backups":"自动运行备份","B2 Application ID":"B2 应用 ID","B2 Application Key":"B2 应用密钥","B2 Cloud Storage Account ID":"B2 云存储帐户 ID","B2 Cloud Storage Application ID":"B2 云存储应用 ID","B2 Cloud Storage Application Key":"B2 云存储应用密钥","Back":"返回","Backend modules:

{{item.Key}}

":"后端模块:

{{item.Key}}

","Backup complete!":"备份完成!","Backup destination":"备份保存位置","Backup is encrypted but no passphrase is available. Type a passphrase below to use for restoring your files, or, in case of GPG encryption, leave blank to let gpg retrieve the passphrase by invoking your system's keychain.":"备份已加密,但没有可用的密码短语。请在下方输入一个密码短语以用于恢复您的文件。或者在GPG加密的情况下,留空以让gpg通过调用您系统的钥匙串来检索密码短语。","Backup location":"备份位置","Backup retention":"备份保留策略","Backup:":"备份数据:","Beta":"Beta","Broken access":"访问中断","Browse":"浏览","Browser default":"浏览器默认","Bucket create location":"Bucket 创建位置","Bucket name":"Bucket 名称","Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes":"Bucket名称只能包含3到63个字符并且只能包含小写字母、数字、句点和破折号。","Bucket region":"Bucket 区域","Bucket region ap-guangzhou":"Bucket 区域 ap-guangzhou","Bucket storage class":"Bucket 存储类型","Bucket, format: BucketName-APPID":"Bucket, 格式: BucketName-APPID","Building list of files to restore …":"正在构建文件还原列表…","Building partial temporary database …":"正在构建部分临时数据库…","Busy …":"繁忙…","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"允许远程访问,服务器监听并允许来自你网络上任何机器的请求。启用此项,请确保您的网络启用了安全防火墙保护。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"默认情况下,托盘图标将使用令牌打开用户界面,而不是解锁用户界面。这能确保从托盘图标访问用户界面,同时要求其他人输入密码。如果您希望从托盘图标访问用户界面也要输入密码,也请启用此选项。","Cache Files":"缓存文件","Canary":"Canary","Cancel":"取消","Cannot include \"{{text}}\"":"不能包含 \"{{text}}\"","Cannot move to existing file":"不能移动到已有文件","Cannot specify filter include or excludes in extra options":"不能在额外选项中指定过滤器的包含或排除","Change server passphrase":"更改服务器密码短语","Changelog":"更新日志","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} 更新日志","Check failed:":"检查失败:","Check for updates now":"立即检查更新","Checking for updates …":"正在检查更新…","Checking …":"正在检查…","Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.":"选择1.0以获得快速备份,1.5以获得相当可靠的备份,2.0以获得更安全的上传但备份速度较慢","Chose a storage type to get started":"选择存储类型以开始","Click the AuthID link to create an AuthID":"点击\"授权 ID\"链接来创建一个授权 ID","Click to set throttle options":"点击配置限流","Client library to use":"使用的客户端库","Command":"命令","Commandline arguments":"命令行参数","Commandline …":"命令行...","Compact Phase":"压实阶段","Compact now":"立即压实","Compacting remote data …":"正在压实远程数据…","Complete log":"完整日志","Completing backup …":"正在完成备份…","Completing previous backup …":"正在完成上次备份…","Compression modules:

{{item.Key}}

":"压缩模块:

{{item.Key}}

","Computer":"计算机","Configuration file:":"配置文件:","Configuration:":"配置:","Configure a new backup":"配置新备份","Confirm delete":"确认删除","Confirm encryption passphrase":"确认加密密码","Confirm new password":"确认新密码","Confirm passphrase":"确认密码","Confirmation required":"需要确认","Connect":"连接","Connect now":"立即连接","Connecting to server …":"正在连接服务器…","Connecting to task …":"正在连接到任务…","Connecting …":"正在连接…","Connection lost":"连接中断","Connection worked!":"连接正常!","Container name":"容器名称","Container region":"容器区域","Continue":"继续","Continue without encryption":"继续且不启用加密","Copied!":"已复制!","Copy":"复制","Copy Destination URL to Clipboard":"复制地址到剪贴板","Copy URL":"复制URL","Copy failed. Please manually copy the URL":"复制失败,请手动复制该地址","Copy log":"复制日志","Core options":"核心选项","Counting ({{files}} files found, {{size}})":"正在计算 (已找到 {{files}} 个文件,{{size}})","Crashes only":"仅崩溃","Create bug report …":"创建问题报告…","Create folder?":"创建文件夹?","Created new limited user":"受限用户已创建","Creating bug report …":"正在创建问题报告…","Creating new user with limited access …":"正在创建受限用户…","Creating target folders …":"正在创建目标文件夹…","Creating temporary backup …":"正在创建临时备份…","Creating user …":"正在创建用户…","Current action:":"当前操作:","Current file:":"当前文件:","Current version is {{versionname}} ({{versionnumber}})":"当前版本为 {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"自定义 S3 端点","Custom Satellite":"自定义卫星","Custom Satellite ({{satellite}})":"自定义卫星 ({{satellite}})","Custom authentication url":"自定义认证地址","Custom backup retention":"自定义备份保留策略","Custom bucket storage class":"自定义bucket存储类","Custom location ({{server}})":"自定义区域 ({{server}})","Custom region for creating buckets":"自定义创建 Bucket 的地区","Custom region value ({{region}})":"自定义地区 ({{region}})","Custom server url ({{server}})":"自定义服务器地址 ({{server}})","Custom storage class ({{class}})":"自定义存储类别 ({{class}})","DEPRECATED: {{getDeprecationMessage(item)}}":"已废弃: {{getDeprecationMessage(item)}}","Database …":"数据库…","Days":"天","Default":"默认","Default ({{channelname}})":"默认 ({{channelname}})","Default excludes":"默认排除规则","Default options":"默认选项","Default value: \"{{getDefaultValue(item)}}\"":"默认值: \"{{getDefaultValue(item)}}\"","Delete":"删除","Delete Phase (Old Backup Versions)":"删除阶段 (旧版本备份)","Delete backup":"删除备份","Delete backups that are older than":"删除早于条件的备份","Delete local database":"删除本地数据库","Delete remote files":"删除远程文件","Delete the local database":"删除本地数据库","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"从远程存储中删除 {{filecount}} 个文件 ({{filesize}}) ?","Delete …":"删除…","Deleted":"已删除","Deleted Versions":"已删除版本","Deleted files":"已删除文件","Deleting remote files …":"正在删除远程文件…","Deleting unwanted files …":"正在删除不需要的文件…","Description (optional)":"描述 (可选)","Description:":"描述:","Desktop":"桌面","Destination":"目标位置","Destination path":"目标路径","Direct restore from backup files …":"从备份文件直接恢复…","Directory path":"目录路径","Disabled":"已禁用","Dismiss":"忽略","Dismiss all":"忽略所有","Display and color theme":"显示和颜色主题","Do you really want to delete the backup: \"{{name}}\" ?":"您确定要删除备份:\"{{name}}\"吗 ?","Do you really want to delete the local database for: {{name}}":"您确定要删除 \"{{name}}\" 的本地数据库吗 ?","Domain name":"域名","Done":"完成","Download":"下载","Downloaded files":"已下载文件","Downloading files …":"正在下载文件…","Downloading update…":"正在下载更新…","Duplicate option {{opt}}":"Duplicati 选项 {{opt}}","Duplicati Website":"Duplicati 网站","Duplicati forum":"Duplicati 论坛","Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?":"Duplicati需要用密码短语进行保护,并且已经为您生成了一个随机密码短语。\n如果您从托盘图标打开Duplicati,则不需要密码短语,但如果您计划从其他位置打开它,则需要设置一个您知道的密码短语。\n您现在想要设置一个密码短语吗?","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati 将在启动后运行,但会保持在暂停状态。Duplicati 会使用最小的系统资源,并且不会运行任何备份。","Duration":"时间","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"每个备份都有一个关联的本地数据库,用来存储远程备份相关的信息。\n删除一个备份时,您也可以删除其本地数据库,这不会影响从远程文件中恢复数据。\n但如果你通过命令行进行备份,您应当保留此数据库。","Each backup has a local database associated with it, which stores information about the remote backup on the local machine. This makes it faster to perform many operations, and reduces the amount of data that needs to be downloaded for each operation.":"每个备份都有一个与之关联的本地数据库,该数据库存储了有关本地机器上远程备份的信息。这使得执行许多操作变得更快,并减少了每次操作需要下载的数据量。","Edit as list":"以列表形式编辑","Edit as text":"以文本形式编辑","Edit …":"编辑…","Email address of the Office 365 group":"Office 365群组的电子邮件地址","Encrypt file":"加密文件","Encryption":"加密方式","Encryption changed":"加密方式已更改","Encryption modules:

{{item.Key}}

":"加密模块:

{{item.Key}}

","Encryption passphrase":"加密密码","Encryption passphrase (for verification)":"加密密码(用于验证)","End":"结束","Enter URL":"输入URL","Enter a backup destination URL:":"输入备份目标URL:","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"请手动输入备份保留策略。占位符 D/W/Y 代表 日/星期/年,U 代表 永久。语法为 7D:1D,4W:1W,36M:1M,这个例子保留7天中每天一份,4个星期中每星期一份,36个月中每月一份,也可以写成 1W:1D,1M:1W,3Y:1M","Enter a url, or click the "Target URL >" link":"输入一个网址,或者点击"目标网址>"链接","Enter backup passphrase, if any":"输入备份密码 (若存在)","Enter configuration details":"进入详细配置","Enter encryption passphrase":"输入加密密码","Enter expression here":"在此输入表达式","Enter one argument per line without quotes, e.g. *.txt":"每行输入一个参数,不带引号,例如:*.txt","Enter one option per line in command-line format, e.g. --dblock-size=100MB":"以命令行格式每行输入一个选项,例如:--dblock-size=100MB","Enter one option per line in command-line format, e.g. {0}":"以命令行格式每行输入一个选项,例如:{0}","Enter the destination path":"输入目标路径","Error":"错误","Error!":"错误!","Errors and crashes":"错误和崩溃","Examined":"已检查","Exclude":"排除","Exclude directories whose names contain":"排除文件夹,名称包括","Exclude expression":"排除表达式","Exclude file":"排除文件","Exclude file extension":"排除文件扩展名","Exclude files whose names contain":"排除文件,名称包括","Exclude filter group":"排除过滤条件集","Exclude folder":"排除文件夹","Exclude regular expression":"排除正则表达式","Existing file found":"发现已存在文件","Experimental":"Experimental","Export":"导出","Export backup configuration":"导出备份配置","Export configuration":"导出配置","Export passwords":"导出密码","Export …":"导出…","Exporting …":"正在导出…","External link":"外部链接","FTP (Alternative)":"FTP (备选)","Failed to build temporary database: {{message}}":"构建临时数据库失败: {{message}}","Failed to connect:":"连接失败:","Failed to connect: {{message}}":"连接失败:{{message}}","Failed to delete:":"删除失败:","Failed to fetch path information: {{message}}":"获取路径信息失败: {{message}}","Failed to find backup:":"查找备份失败:","Failed to get bug report URL: {{message}}":"获取错误报告URL失败: {{message}}","Failed to import: {{message}}":"导入失败: {{message}}","Failed to read backup defaults:":"读取备份默认设置失败:","Failed to read file: {{message}}":"读取文件失败: {{message}}","Failed to restore files: {{message}}":"恢复文件失败: {{message}}","Failed to save:":"保存失败:","Fatal error, no statistics collected":"致命错误,未收集到统计信息","Fetching path information …":"获取路径信息…","File":"文件","Files larger than:":"文件大于","Filters":"过滤条件","Finished!":"已完成!","First run setup":"初始配置","Folder":"文件夹","Folder in the bucket":"bucket中的文件夹","Folder path":"文件夹路径","Folder path name":"文件夹路径名称","Fri":"周五","Full destination path, including the server name, but without https":"完整的目标路径,包括服务器名称,但不包括https","GByte":"GB","GByte/s":"GB/s","GCS Project ID":"GCS 项目 ID","General":"常规","General backup settings":"常规备份设置","General options":"常规选项","Generate":"生成","Generate IAM access policy":"生成 IAM 访问策略","Getting file versions …":"正在获取文件版本...","Group email":"群组邮箱","Hidden files":"隐藏文件","Hide":"隐藏","Hide hidden folders":"隐藏被隐藏的文件夹","Home":"首页","Hostnames":"主机名","Hours":"小时","How do you want to handle existing files?":"您想怎样处理已存在的文件?","Hyper-V Machine":"Hyper-V 虚拟机","Hyper-V Machine:":"Hyper-V 虚拟机:","Hyper-V Machines":"Hyper-V 虚拟机","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果错过了时间,任务将尽快运行。","If at least one newer backup is found, all backups older than this date are deleted.":"如果有更新的备份存在,早于此日期的备份将被删除。","If the backup and the remote storage is out of sync, Duplicati will require that you perform a repair operation to synchronize the database. If the repair is unsuccessful, you can delete the local database and re-generate.":"如果备份和远程存储不同步,Duplicati将要求您执行修复操作以同步数据库。如果修复不成功,您可以删除本地数据库并重新生成。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"如果备份文件没有自动下载,请右键点击并选择"另存为…"。","If the backup file was not downloaded automatically, right click and choose "Save as …".":"如果备份文件没有自动下载,请右键点击并选择"另存为…"。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"如果没有输入路径,所有文件将存储在登录文件夹。\n确定这是您想要的吗?","If you do not enter an API Key, the tenant name is required":"如果您不输入 API 密钥,则需要输入租户名称","If you want to use the backup later, you can export the configuration before deleting it.":"如果您以后还想使用此备份,可以在删除之前先导出配置。","Import":"导入","Import Destination URL":"导入目标URL","Import URL":"导入URL","Import backup configuration":"导入备份配置","Import from a file":"从文件导入","Import metadata":"导入元数据","Importing …":"正在导入…","Include a file?":"包含一个文件?","Include expression":"包含表达式","Include regular expression":"包含正则表达式","Incorrect answer, try again":"验证失败,请重试","Individual builds for developers only. Not for use with important data.":"面向开发者的单个构建,不适用于重要数据","Information":"信息","Interrupted, no statistics collected":"中断,未收集统计信息","Invalid characters in path":"路径中包含无效字符","Invalid retention time":"无效的保留时间","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"可以在无密码的情况下连接到一些 FTP\n您确定您的 FTP 服务器支持无密码登录吗?","KByte":"KB","KByte/s":"KB/s","Keep a specific number of backups":"保留指定版本数","Keep all backups":"永久保留","Keystone API version":"Keystone API 版本","Language in user interface":"界面语言","Last month":"上月","Last successful backup:":"上次成功备份于:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"上次成功恢复于:{{time}} (耗时 {{duration || '0 秒'}})","Latest":"最新","Libraries":"第三方库","Listing backup dates …":"正在列出备份日期…","Listing remote files for purge …":"正在列出需要清除的远程文件…","Listing remote files …":"正在列出远程文件…","Live":"实时","Load a configuration from an exported job or a storage provider":"从已导出的任务文件或者存储提供商处加载配置","Load destination from an exported job or a storage provider":"从已导出的任务文件或存储提供商处加载目标位置","Load older data":"加载之前的数据","Loading remote storage usage …":"正在加载远程存储使用情况…","Loading …":"正在加载…","Local database for {{Backup.Backup.Name}}…loading…":"本地数据库用于 {{Backup.Backup.Name}}…加载中…","Local database path:":"本地数据库路径:","Local repository":"本地仓库","Local storage":"本地存储","Location":"位置","Location where buckets are created":"创建 Bucket 的位置","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}} 的日志数据","Log data from the server":"来自服务器的日志数据","Log in":"登录","Log out":"退出登录","MByte":"MB","MByte/s":"MB/s","Maintenance":"维护","Make sure that rclone is in your path, or add the location to rclone via the advanced options.":"确保rclone在您的环境变量中,或者通过高级选项将位置添加到rclone。","Manual":"手册","Manual update found:":"手动更新:","Manually type path":"手动输入路径","Max download speed":"最大下载速度","Max upload speed":"最大上传速度","Menu":"菜单","Microsoft SQL Database:":"Microsoft SQL 数据库:","Microsoft SQL Databases":"Microsoft SQL 数据库","Minimum redundancy":"最小冗余","Minimum redundancy is 1.0":"最小冗余为 1.0","Minutes":"分钟","Missing name":"缺少名称","Missing passphrase":"缺少密码","Missing sources":"缺少源数据","Modified":"已修改","Mon":"周一","Months":"月","Move existing database":"移动已有数据库","Move failed:":"移动失败:","My Documents":"我的文档","My Music":"我的音乐","My Photos":"我的照片","My Pictures":"我的图片","Name":"名称","Never":"从不","New Password":"新密码","New update found: {{message}}":"新的更新: {{message}}","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新用户名为 {{user}}\n已为新的受限用户更新证书","Next":"下一步","Next scheduled run:":"下一次计划运行于:","Next scheduled task:":"下一次计划任务:","Next task:":"下一次任务:","Next time":"下一次运行时间:","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"之前未指定证书,请与服务器管理员确认密钥 {{key}} 是否正确\n\n您是否要允许该主机密钥吗?","No editor found for the "{{backend}}" storage type":"未找到 "{{backend}}" 存储类型的编辑器","No encryption":"无加密","No items selected":"未选中项目","No items to restore, please select one or more items":"未恢复项目,请至少选择一项","No passphrase entered":"未输入密码","No scheduled tasks":"暂无计划任务","Non-matching passphrase":"密码不匹配","None / disabled":"无 / 禁用","Not using encryption":"未使用加密","Note that speeds are entered in bytes, and line speeds are typically reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line is equivalent to 1 MByte/s.":"注意:速度是以bytes为单位输入的,而线路速度通常以bits为单位。两者使用 8 的倍数进行转换。换言之,8 mbit/s的线路相当于1 MByte/s","Nothing will be deleted. The backup size will grow with each change.":"不会清理任何备份,备份大小将持续增长","OK":"确定","OSS Access Key ID":"Aliyun OSS Access Key ID","OSS Access Key Secret":"Aliyun OSS Access Key Secret","OSS Bucket Region":"Aliyun OSS Bucket区域","OSS Bucket name":"Aliyun OSS Bucket名称","OSS Endpoint":"Aliyun OSS Endpoint","OSS Path or subfolder in the bucket":"Aliyun OSS路径或bucket的子文件夹","OSS Region":"Aliyun OSS 区域","Official releases":"官方发布","Once there are more backups than the specified number, the oldest backups are deleted.":"一旦备份版本数超过此值,最旧的备份将被清理","OpenStack AuthURI":"OpenStack 认证地址","OpenStack Object Storage / Swift":"OpenStack 对象存储 / Swift","Opened":"已打开","Openstack API key are not supported in v3 keystone API":"Openstack API key 在 v3 keystone API 中不受支持","Operating System":"操作系统","Operation":"操作","Operations:":"操作:","Optional API key":"API key(可选)","Optional authentication password":"认证密码(可选)","Optional authentication username":"认证用户名(可选)","Optional region":"区域(可选)","Optional tenant name":"租户名称(可选)","Options":"选项","Options added here are applied to all backups, but can be overridden in each individual backup.":"在此添加的选项适用于所有备份,但每个备份中可以单独设置来覆盖此选项","Original location":"原位置","Others":"其它","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"随着时间,备份将被自动清理。这将保留最近7天中每天一份,最近4个星期中每星期一份,最近12个月中每月一份。同时,保证总是至少存在一个备份。","Overwrite":"覆盖","Passphrase":"密码","Passphrase (if encrypted)":"密码 (若启用加密)","Passphrase changed":"密码已更改","Passphrases are not matching":"密码不匹配","Passphrases do not match":"密码不匹配","Password":"密码","Patching files with local blocks …":"正在使用本地块修补文件…","Path":"路径","Path not found":"路径未找到","Path on server":"服务器上路径","Path or subfolder in the bucket":"Bucket 中路径或子文件夹","Pause":"暂停","Pause after startup or hibernation":"开机或休眠后暂停","Pause options":"暂停选项","Permissions":"权限","Pick location":"选择位置","Please select a file to import":"请选择一个导入的文件","Point to your backup files and restore from there":"指向您的备份文件,将从中恢复","Port":"端口","Prevent tray icon automatic log-in":"保持托盘图标自动登录","Previous":"上一步","Progress:":"进度:","ProjectID is optional if the bucket exist":"若 Bucket 存在, 则项目ID 可选","Proprietary":"专有","Purge Phase":"清除阶段","Purging files complete!":"清除文件完成!","Purging files …":"正在清除文件...","Rebuilding local database …":"正在重新构建本地数据库…","Recreate (delete and repair)":"重建 (删除并修复)","Recreate Database Phase":"重建数据库阶段","Recreating database …":"正在重建数据库…","Registering temporary backup …":"正在注册临时备份…","Relative paths not allowed":"不允许相对路径","Reload":"重新加载","Remote":"远程","Remote Path":"远程路径","Remote Repository":"远程仓库","Remote path":"远程路径","Remote repository":"远程仓库","Remote volume size":"远程卷大小","Remove":"移除","Remove option":"移除选项","Removed files":"已删除文件","Repair":"修复","Repair Phase":"修复阶段","Repairing database …":"正在修复数据库…","Repeat Passphrase":"重复密码","Reporting:":"报告:","Reset":"重置","Restore":"恢复","Restore complete!":"恢复完成!","Restore files":"恢复文件","Restore files from:":"从以下位置恢复文件:","Restore files …":"恢复文件…","Restore from":"恢复自","Restore from backup configuration":"从备份配置中恢复","Restore from configuration …":"从配置中恢复…","Restore options":"恢复选项","Restore read/write permissions":"恢复读写权限","Restored Files":"已恢复文件","Restored Folders":"已恢复目录","Restored Symlinks":"已恢复符号链接","Restoring files …":"正在恢复文件…","Resume":"恢复运行","Rewritten File Lists":"重写文件列表","Run again every":"重复运行每","Run now":"立即运行","Running commandline entry":"正在运行命令行","Running task:":"运行中的任务:","Running …":"正在运行…","Running … stop now":"正在运行… 立即停止","S3 Compatible":"S3 兼容","Same as the base install version: {{channelname}}":"与当前安装版本一致:{{channelname}}","Sat":"周六","Satellite":"卫星","Save":"保存","Save and repair":"保存并修复","Save different versions with timestamp in file name":"保存不同版本 (文件名中添加时间戳)","Save immediately":"立即保存","Scanning existing files …":"正在扫描存在的文件…","Scanning for local blocks …":"正在扫描本地文件块…","Schedule":"计划","Search":"搜索","Search for files":"搜索文件","Seconds":"秒","Select a log level and see messages as they happen:":"选择日志级别并实时查看","Select files":"选择文件","Server":"服务器","Server and port":"服务器与端口","Server hostname or IP":"服务器主机名或 IP","Server is currently paused,":"服务器暂停中,","Server is currently paused, resume now":"服务器当前已暂停, 立即恢复","Server is currently paused, do you want to resume now?":"服务器目前已暂停,您想立即恢复运行吗?","Server password":"服务器密码","Server paused":"服务器已暂停","Server state properties":"服务器状态","Settings":"设置","Show":"查看","Show advanced editor":"显示高级编辑器","Show hidden folders":"显示隐藏文件夹","Show log":"日志","Show log …":"查看日志…","Show treeview":"显示树状视图","Sia server password":"Sia 服务器密码","Smart backup retention":"智能备份保留策略","Some OpenStack providers allow an API key instead of a password and tenant name":"一些 OpenStack 提供商允许使用 API 密钥,而不是租户名称和密码","Some S3 providers might only be compatible with a certain client library":"一些 S3 提供商可能只与某个客户端库兼容","Source Data":"源数据","Source Files":"源文件","Source data":"源数据","Source folders":"源文件夹","Source:":"源数据:","Specific builds for developers only. Not for use with important data.":"面向开发者的特定构建,不适用于重要数据","Standard protocols":"标准协议","Start":"开始","Starting backup …":"准备开始备份…","Starting restore …":"准备开始恢复…","Starting the restore process …":"正在开始恢复操作…","Stop after the current file":"当前文件完成后停止","Stop running backup":"停止正在运行的备份","Stop running task":"停止正在运行的任务","Stopping after the current file:":"当前文件完成后停止:","Stopping task:":"正在停止任务:","Storage Type":"存储类型","Storage class":"存储类别","Storage class for creating a bucket":"创建 Bucket 的存储类别","Stored":"存档","Strong":"强度高","Success":"成功","Sun":"周日","Symbolic link":"符号链接","System Files":"系统文件","System default ({{levelname}})":"默认 ({{levelname}})","System files":"系统文件","System info":"系统信息","System properties":"系统属性","TByte":"TB","TByte/s":"TB/s","Target URL >":"目标 URL >","Target path. Example: /backup":"目标路径. 例如: /backup","Task is running":"任务正在运行中","Temporary Files":"临时文件","Temporary files":"临时文件","Tenant name":"租户名","Tencent Cloud Account APPID":"腾讯云账号APPID","Tencent Cloud COS documents and resources":"腾讯云COS文档和资源","Test Phase":"测试阶段","Test connection":"测试连接","Testing connection …":"测试连接中…","Testing permissions …":"正在测试权限…","Testing …":"正在测试…","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"字段 '{{fieldname}}' 包含无效字符:{{character}} (值: {{value}}, 位置: {{pos}})","The backup is missing, has it been deleted?":"此备份缺失,是否已经被删除?","The backup was temporary and does not exist anymore, so the log data is lost":"这是已经不存在的临时备份,因此没有日志数据","The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.":"备份将被分割成多个称为卷的文件。您可以在此设置单个卷文件的最大大小。更多信息,请参见此页面。","The bucket name should be all lower-case, convert automatically?":"Bucket 名称应当是全小写,需要自动转换吗?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"配置应该注意安全。您确定要将含有您密码的配置保存为不加密的文件吗?","The connection to the server is lost, attempting again in {{time}} …":"与服务器的连接丢失,将在{{time}}后再次尝试…","The dark theme (by Michal)":"黑色主题 (by Michal)","The default blue on white theme (by Alex)":"默认蓝白主题 (by Alex)","The encryption passphrases do not match":"加密密码不匹配","The file size is {{size}}, larger than the maximum specified size. If the file size decreases, it will be included in future backups.":"文件大小为{{size}},超过了指定的最大指定值。如果文件大小减小,它将会包含在未来的备份中。","The folder {{folder}} does not exist.\nCreate it now?":"文件夹 {{folder}} 不存在\n是否现在创建?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"主机密钥已更改,请与服务器管理员确认其是否正确,否则您可能正在被中间人攻击。\n\n您想要把现有主机密钥 \"{{prev}}\" 替换为 {{key}} 吗?","The passwords do not match":"密码不匹配","The path does not appear to exist, do you want to add it anyway?":"路径似乎不存在,您确定要添加它吗?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"该路径没有以 '{{dirsep}}' 字符结尾,这表示您指定的是一个文件而不是文件夹。\n您确定想要包含指定文件吗?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"路径必须为绝对路径,也就是说必须以斜线 '/' 开头","The region parameter is only applied when creating a new bucket":"\"地区\" 参数只在创建新 Bucket 时生效","The region parameter is only used when creating a bucket":"\"地区\" 参数只在创建新 Bucket 时使用","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"服务器证书验证失败\n您想要允许该哈希值为 {{hash}} 的 SSL 证书吗?","The storage class affects the availability and price for a stored file":"存储类别影响文件可用性和价格","The target folder contains encrypted files, please supply the passphrase":"目标文件夹包含加密文件,请提供密码","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"该用户权限太多,您想要创建一个只能访问所选路径的受限用户吗?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"该备份创建于其他操作系统上。恢复时不指定目标文件夹可能会使文件恢复到未知的位置。您确定不指定目标文件夹继续吗?","This month":"本月","This week":"本周","Throttle settings":"限流设置","Thu":"周四","Time":"时间 ","To File":"导出为文件","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"为确认您要删除 \"{{name}}\" 的所有远程文件,请输入以下字母","To export without a passphrase, uncheck the \"Encrypt file\" box":"如果不想使用密码加密导出的文件,请去除勾选\"加密文件\"","To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?":"为防止bucket命名冲突,建议在bucket名称前加上您的账户ID。是否自动添加?","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"为了防止各种基于 DNS 的攻击,Duplicati 将仅允许此处列出的主机名。直接使用 IP 和 localhost 访问是始终允许的。可以使用分号分隔多个主机名,星号 (*) 代表允许所有主机名,同时禁用所有限制。如果该字段为空,则仅允许 IP 地址和本地主机访问。","Today":"今天","Trust host certificate?":"信任主机证书?","Trust server certificate?":"信任服务器证书?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"尝试我们正在开发的新功能。这是当前最稳定的版本。在生产环境使用前,请先测试恢复数据。","Tue":"周二","Type passphrase here.":"在这里输入密码。","Type to highlight files":"输入以高亮文件","Unknown backup size and versions":"未知的备份大小和版本","Until resumed":"直到手动恢复运行","Update {{state.updatedVersion}} is available. Download now":"更新 {{state.updatedVersion}} 可用。立即下载","Update channel":"更新分支","Update failed:":"更新失败:","Updating with existing database":"正在更新存在的数据库","Uploaded files":"已上传文件","Uploading verification file …":"正在上传校验文件…","Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics.":"使用报告帮助我们改善用户体验并评估新功能的影响。我们使用它们来生成public usage statistics。","Usage statistics":"使用情况统计","Usage statistics, warnings, errors, and crashes":"使用情况统计、警告、错误和崩溃","Use SSL":"启用 SSL","Use existing database?":"使用已存在的数据库?","Use weak passphrase":"确定使用弱密码","Useless":"无用","User data":"用户数据","User domain name":"用户域名称","User has too many permissions":"用户权限太多","User interface settings":"界面设置","Username":"用户名","Vacuuming database …":"正在清理数据库…","Validating …":"正在验证…","Verifications":"验证","Verify encryption passphrase":"验证加密密码","Verify files":"校验文件","Verifying answer":"正在验证","Verifying backend data …":"正在校验后端数据…","Verifying files …":"正在校验文件…","Verifying remote data …":"正在校验远程数据…","Verifying restored files …":"正在校验恢复后的文件…","Verifying …":"正在校验…","Version ID":"版本 ID","Very strong":"强度非常高","Very weak":"强度非常低","Visit us on":"了解我们","WARNING: The remote database is found to be in use by the commandline library.":"警告:远程数据库被发现正被命令行使用。","WARNING: This will prevent you from restoring the data in the future.":"警告:这将阻止您将来恢复数据","Waiting for task to begin":"等待任务开始…","Waiting for task to start …":"等待任务启动…","Waiting for upload to finish …":"等待上传完成…","Warnings, errors and crashes":"警告、错误和崩溃","We recommend that you encrypt all backups stored outside your system":"我们建议您加密所有保存在第三方系统中的数据","Weak":"强度低","Weak passphrase":"弱密码","Wed":"周三","Weeks":"周","Where do you want to restore from?":"您想从哪里恢复呢?","Where do you want to restore the files to?":"您想把文件恢复到哪里?","Years":"年","Yes":"是","Yes, I have stored the passphrase safely":"是,我已将密码安全保存","Yes, I understand the risk":"是,我理解该风险","Yes, I'm brave!":"是,我无所谓","Yes, please break my backup!":"是,请清除我的备份","Yesterday":"昨天","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"您正在更改现有数据库路径。\n您确定要这么做吗?","You are currently running {{appname}} {{version}}":"当前正在运行 {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"您已经更改了加密方式,这可能破坏备份。您应当创建一份新的备份。","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"您已经更改了密码,这是不支持的操作。您应当创建一份新的备份。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您已选择不加密备份,建议加密所有存储在远程服务器上的数据。","You have chosen to restore to a new location, but not entered one":"您选择了恢复到新位置,但没有指定具体位置","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"您已经生成了一个强密码。确保您已经安全记录下了该密码,否则,如果您丢失了该密码,数据将无法恢复。","You must choose at least one source folder":"您必须至少一个源文件夹","You must enter a domain name to use v3 API":"您必须输入域名称以使用 v3 API","You must enter a name for the backup":"您必须输入备份名称","You must enter a passphrase or disable encryption":"您必须输入加密密码或禁用加密","You must enter a password to use v3 API":"您必须输入密码以使用 v3 API","You must enter a positive number of backups to keep":"您输入要保留的版本数必须为正数","You must enter a tenant (aka project) name to use v3 API":"您必须输入租户名称(即项目)以使用 v3 API","You must enter a tenant name if you do not provide an API key":"如果您不提供API key,则必须输入租户名称","You must enter a valid duration for the time to keep backups":"您必须输入有效的期限来保留备份","You must enter a valid retention policy string":"您必须输入一个有效的保留策略","You must enter either a password or an API key":"您必须输入密码或API key","You must enter either a password or an API key, not both":"您必须输入密码或API key,两者不能同时都输入","You must fill in the password":"您必须填写密码","You must fill in the server name or address":"您必须填写服务器主机名或地址","You must fill in the username":"您必须填写用户名","You must fill in {{field}}":"您必须填写 {{field}}","You must select or fill in the AuthURI":"您必须选择或填写认证地址","You must select or fill in the server":"您必须选择或填写服务器","You must specify a path":"您必须指定路径","You should fill in {{field}} {{reason}}":"您应该填写{{field}} {{reason}}","Your files and folders have been restored successfully.":"您的文件和文件夹已经恢复成功。","Your passphrase is easy to guess. Consider changing passphrase.":"您的密码很容易被猜到,请考虑更换密码。","bucket/folder/subfolder":"Bucket / 文件夹 / 子文件夹","byte":"B","byte/s":"B/s","custom":"自定义","failed":"失败","remote path, e.g. backup":"远程路径,例如:backup","remote repository, e.g. remote":"远程仓库,例如:remote","resume now":"立即恢复运行","unless you are explicitly specifying --group-id":"除非您明确指定 --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} 主要由 {{dev1}} 和 {{dev2}} 开发. {{appname}} 可以从 {{websitename}} 下载. {{appname}} 采用 {{licensename}} 授权.","{{brandingService.appName}} is using the following third party libraries:":"{{brandingService.appName}} 正在使用以下第三方库:","{{files}} files ({{size}}) to go {{speed_txt}}":"剩余 {{files}} 个文件 ({{size}}) {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 个版本","{{number}} Hour":"{{number}} 小时","{{number}} Hours":"{{number}} 小时","{{number}} Minutes":"{{number}} 分钟","{{time}} (took {{duration}})":"{{time}} (耗时 {{duration}})"}); + gettextCatalog.setStrings('zh_HK', {"- pick an option -":"選擇一個選項","...loading...":"...載入中...","AWS IAM Policy":"AWS IAM 原則","About":"關於","About {{appname}}":"關於 {{appname}}","Access denied":"存取被拒","Account name":"用戶名","Add a new backup":"加入新的備份","Add a path directly":"直接加入路徑","Add advanced option":"新增進階選項","Add backup":"新增備份","Add filter":"新增過濾器","Add path":"加入路徑","Advanced Options":"進階選項","Advanced options":"進階選項","Advanced:":"進階:","All Hyper-V Machines":"所有Hyper-V機器","All Microsoft SQL Databases":"所有Microsoft SQL數據庫","Allow remote access (requires restart)":"允許遠端存取(需要重新啟動)","Allowed days":"允許日子","An existing file was found at the new location":"在新的位置上發現有檔案存在","Anonymous usage reports":"匿名使用報告","AuthID":"AuthID","Authentication password":"認證密碼","Authentication username":"認證用戶名","Autogenerated passphrase":"自動產生密碼","Back":"返回","Backup destination":"備份目的地","Backup location":"備份位置","Backup:":"備份:","Beta":"Beta","Browse":"瀏覽","Browser default":"瀏覽預設","Bucket create location":"Bucket 建立位置","Bucket name":"Bucket 名稱","Bucket storage class":"Bucket 儲存等級","Canary":"Canary","Cancel":"Cancel","Changelog":"更新日誌","Changelog for {{appname}} {{version}}":"{{appname}} {{version}} 更新日誌","Check failed:":"檢查失敗:","Check for updates now":"立即檢查更新","Compact now":"立即壓縮","Computer":"電腦","Configuration file:":"設定檔案:","Configuration:":"設定:","Configure a new backup":"設定新備份","Confirm delete":"確認刪除","Confirm encryption passphrase":"確認加密密碼","Confirmation required":"需要確認","Connect":"連接","Connect now":"立即連接","Connection lost":"連接中斷","Connection worked!":"連接成功!","Container name":"容器名稱","Container region":"容器區域","Continue":"繼續","Continue without encryption":"繼續但不加密","Copied!":"已複製!","Copy Destination URL to Clipboard":"複製目的地網址到剪貼簿","Copy failed. Please manually copy the URL":"複製失敗。請手動複製網址","Counting ({{files}} files found, {{size}})":"點算中(找到 {{files}} 個檔案,{{size}})","Create folder?":"建立資料夾?","Created new limited user":"已建立受限制的使用者","Current version is {{versionname}} ({{versionnumber}})":"現時版本 {{versionname}} ({{versionnumber}})","Custom location ({{server}})":"自訂位置({{server}})","Custom server url ({{server}})":"自訂伺服器地址({{server}})","Days":"Days","Default":"預設","Default ({{channelname}})":"預設 ({{channelname}})","Default options":"預設選項","Delete":"刪除","Delete backup":"刪除備份","Delete local database":"刪除本地資料庫","Delete remote files":"刪除遠端檔案","Delete the local database":"刪除本地資料庫","Desktop":"桌面","Destination":"目的地","Disabled":"已停用","Dismiss":"略過","Display and color theme":"顯示及顏色主題","Do you really want to delete the backup: \"{{name}}\" ?":"您真的確定要刪除備份: \"{{name}}\" ?","Do you really want to delete the local database for: {{name}}":"您真的確定要刪除 \"{{name}}\" 的本地數據庫?","Done":"完成","Download":"下載","Duplicate option {{opt}}":"Duplicati 選項 {{opt}}","Duplicati Website":"Duplicati 網站","Duplicati forum":"Duplicati 討論區","Encrypt file":"加密檔案","Enter URL":"輸入網址","Enter backup passphrase, if any":"輸入備份密碼(如有)","Enter encryption passphrase":"輸入加密密碼","Enter the destination path":"輸入目的地路徑","Error":"錯誤","Error!":"錯誤!","Exclude":"排除","Exclude directories whose names contain":"排除含有此名稱的資料夾","Exclude expression":"排除表達式","Exclude file":"排除檔案","Exclude file extension":"排除副檔名","Exclude files whose names contain":"排除含有此名稱的檔案","Exclude folder":"排除資料夾","Exclude regular expression":"排除正規表達式","Existing file found":"找到已存在的檔案","Experimental":"實驗性","Export":"匯出","Export backup configuration":"匯出備份設定","Export configuration":"匯出設定","FTP (Alternative)":"FTP(備用)","Failed to build temporary database: {{message}}":"建立臨時資籵庫失敗:{{message}}","Failed to connect:":"連接失敗:","Failed to connect: {{message}}":"連接失敗:{{message}}","Failed to delete:":"刪除失敗:","Failed to fetch path information: {{message}}":"無法取得路徑資料:{{message}}","Failed to read backup defaults:":"讀取預設備份失敗:","Failed to restore files: {{message}}":"還原檔案失敗:{{message}}","Failed to save:":"儲存失敗:","File":"檔案","Files larger than:":"檔案大於","Filters":"過濾器","Finished!":"已完成!","Folder":"資籵夾","Folder path":"資料夾路徑","Fri":"星期五","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"一般","General backup settings":"一般備份設定","General options":"一般設定","Generate":"產生","Generate IAM access policy":"產生 IAM 存取原則","Hidden files":"隱藏的檔案","Hide":"隱藏","Hide hidden folders":"不顯示隱藏的資料夾","Home":"首頁","Hours":"小時","How do you want to handle existing files?":"您想怎樣處理已存在的檔案?","Hyper-V Machine":"Hyper-V 機器","Hyper-V Machine:":"Hyper-V 機器:","Hyper-V Machines":"Hyper-V 機器","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果錯過了時間,將儘快執行工作。","Import":"匯入","Import Destination URL":"匯入目的地網址","Import backup configuration":"匯入備份設定","Import from a file":"從檔案匯入","Include a file?":"包括一個檔案?","Include expression":"包括表達式","Include regular expression":"包括正規表達式","Incorrect answer, try again":"答案錯誤,請重試","Information":"訊息","Invalid characters in path":"路徑中有無效的字符","Invalid retention time":"無效的保留時間","KByte":"KByte","KByte/s":"KByte/s","Language in user interface":"界面語言","Last month":"上個月","Latest":"最新","Live":"即時","Load older data":"載入舊資料","Local database path:":"本地資料庫路徑:","Local storage":"本地儲存","Location":"位置","Log data from the server":"來自伺服器的記錄","Log out":"登出","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"維護","Manually type path":"手動輸入路徑","Max download speed":"最高下載速度","Max upload speed":"最高上傳速度","Menu":"選單","Microsoft SQL Database:":"Microsoft SQL 資料庫:","Microsoft SQL Databases":"Microsoft SQL 資料庫","Minutes":"分鐘","Missing name":"沒有名稱","Missing passphrase":"沒有密碼","Missing sources":"沒有來源","Mon":"星期一","Months":"月","Move existing database":"移動現時的資料庫","Move failed:":"移動失敗:","My Documents":"我的文件","My Music":"我的音樂","My Photos":"我的相片","My Pictures":"我的圖片","Name":"名稱","Never":"永不","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新用戶為 {{username}}。\n已更新憑證以使用該受管制用戶","Next":"下一步","Next scheduled run:":"下次預定報行的時間:","Next scheduled task:":"下次預定報行的工作:","Next task:":"下次的工作:","Next time":"下次執行時間:","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"先前並未指定過證書,請與伺服管理員驗證此密匙是否正確:{key}}\n\n您要接受這個主題密匙嗎?","No encryption":"無加密","No items selected":"沒有選擇任何項目","No items to restore, please select one or more items":"沒有需要還原的項目,請擇一個或以上的項目","No passphrase entered":"沒有輸入密碼","No scheduled tasks":"沒有預定的工作","Non-matching passphrase":"密碼不正確","None / disabled":"沒有/已停用","OK":"確定","Options":"選項","Others":"Others","Overwrite":"覆蓋","Passphrase":"密碼","Passphrase (if encrypted)":"密碼(如已加密)","Passphrase changed":"已更改密碼","Passphrases are not matching":"密碼不相同","Password":"密碼","Path":"路徑","Path not found":"找不到路徑","Path on server":"伺服器上路徑","Pause":"暫停","Pause after startup or hibernation":"啟動或休眠後暫停","Pause options":"暫停選項","Permissions":"權限","Pick location":"選擇位置","Port":"埠","Previous":"Previous","Recreate (delete and repair)":"重建(刪除及修復)","Remote":"遠端","Remove":"移除","Remove option":"移除選項","Repair":"修復","Repeat Passphrase":"重覆密碼","Reporting:":"報告︰","Reset":"重設","Restore":"還原","Restore files":"還原檔案","Restore from":"從...還原檔案","Restore from backup configuration":"從備份設定還原","Restore options":"還原選項","Resume":"繼續","Run again every":"每...重覆執行","Run now":"立即執行","Running task:":"正在執行工作:","S3 Compatible":"S3 相容","Sat":"星期六","Save":"儲存","Save and repair":"儲存並修復","Save immediately":"立即儲存","Schedule":"排程","Search":"搜尋","Search for files":"搜尋檔案","Seconds":"秒","Select files":"選擇檔案","Server":"伺服器","Server and port":"伺服器與連接埠","Server hostname or IP":"伺服器名稱或 IP","Server is currently paused,":"伺服器目前已暫停,","Server is currently paused, do you want to resume now?":"伺服器暫停中,您要現在立即繼續嗎?","Server password":"伺服器密碼","Server paused":"伺服器已暫停","Server state properties":"伺服器狀態","Settings":"設定","Show":"顯示","Show advanced editor":"顯示進階編輯","Show hidden folders":"顯示隱藏的資料夾","Show log":"顯示記錄","Show treeview":"顯示樹狀檢視","Sia server password":"Sia 伺服器密碼","Source Data":"來源資料","Source data":"來源資料","Source folders":"來源資料夾","Source:":"來源:","Standard protocols":"標準通訊協定","Stop after the current file":"現時檔案完成後停止","Stop running backup":"停止正在進行的備份","Stop running task":"停止正在進行的工作","Stopping task:":"停止工作中:","Storage Type":"儲存類型","Storage class":"儲存等級","Stored":"已儲存","Strong":"強","Success":"成功","Sun":"星期日","Symbolic link":"符號連結","System default ({{levelname}})":"系統預設({{levelname}})","System files":"系統檔案","System info":"系統資訊","System properties":"系統內容","TByte":"TByte","TByte/s":"TByte/s","Task is running":"工作執行中","Temporary files":"暫存檔案","Test connection":"測試連線","The dark theme (by Michal)":"深色主題(Michai設計)","The default blue on white theme (by Alex)":"預設的藍白色主題(Alexi設計)","This month":"本月","This week":"本週","Thu":"星期四","To File":"到檔案","Today":"今日","Trust server certificate?":"信任伺服器證書?","Tue":"星期二","Until resumed":"直至手動繼續","Update channel":"更新頻道","Update failed:":"更新失敗:","Use SSL":"使用 SSL","Use weak passphrase":"使用強度為弱的密碼","Useless":"不使用","Username":"使用者","Verify files":"驗證檔案","Verifying answer":"驗證答案中...","Very strong":"十分強","Very weak":"十分弱","Weak passphrase":"弱密碼","Wed":"星期三","Weeks":"星期","Years":"年","Yes":"是","Yesterday":"Yesterday","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您選擇了不加密備份。建議備份所有儲存在遠端伺服器上資料。","You must fill in the server name or address":"您必須填寫伺服器名稱或地址","You must select or fill in the server":"您必須選擇或填寫伺服器","byte":"byte","byte/s":"byte/s","custom":"custom","resume now":"立即繼續","{{number}} Hour":"{{number}} 小時","{{number}} Minutes":"{{number}} 分鐘","{{time}} (took {{duration}})":"{{time}} (花費 {{duration}})"}); + gettextCatalog.setStrings('zh_TW', {"- pick an option -":"選擇一個項目","...loading...":"...載入中...","AWS Access ID":"AWS Access ID","AWS Access Key":"AWS Access Key","AWS IAM Policy":"AWS IAM Policy","About":"關於","About {{appname}}":"關於 {{appname}}","Access Key":"Access Key","Access denied":"拒絕存取","Access to user interface":"進入使用者介面","Account name":"帳號名稱","Add a new backup":"新增備份","Add a path directly":"直接增加資料路徑","Add advanced option":"加入進階選項","Add backup":"備份","Add filter":"加入篩選條件","Add path":"加入路徑","Added":"已加入","Adjust bucket name?":"調整 bucket 名稱?","Advanced Options":"進階選項","Advanced options":"進階選項","Advanced:":"進階:","All Hyper-V Machines":"全部 Hyper-V 主機","All Microsoft SQL Databases":"全部 Microsoft SQL 資料庫","All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information.":"全部的使用報告都是採匿名發送,不包含任何個人資訊。這份報告中包含有關硬體以及作業系統資訊、後端類型、備份時間、來源資料的總容量與相關資訊。當中將不會包含路徑、檔名、帳號、密碼或類似的敏感資訊。","Allow remote access (requires restart)":"允許遠端存取(需要重新啟動)","Allowed days":"允許日","An existing file was found at the new location":"新的位置發現已既有檔案存在","An existing file was found at the new location\nAre you sure you want the database to point to an existing file?":"新的位置發現已既有檔案存在,您要將資料庫指向其中一個既有檔案嗎?","An existing local database for the storage has been found.\nRe-using the database will allow the command-line and server instances to work on the same remote storage.\n\n Do you wish to use the existing database?":"儲存區發現既有的的本機資料庫已存在。\n重新使用資料庫將可以讓您使用命令列和伺服器服務用在同樣的遠端儲存區。\n\n您希望使用既有的資料庫嗎?","Anonymous usage reports":"匿名使用報告","Applications":"Applications","As Command-line":"顯示為 Command-Line","AuthID":"AuthID","Authentication password":"認證密碼","Authentication username":"認證名稱","Autogenerated passphrase":"自動產生密碼","B2 Application Key":"B2 Application Key","B2 Cloud Storage Account ID":"B2 Cloud Storage 帳號 ID","B2 Cloud Storage Application Key":"B2 Cloud Storage Application Key","Back":"返回","Backup complete!":"備份完成。","Backup destination":"備份目的地","Backup location":"備份位置","Backup retention":"保留備份數目","Backup:":"備份:","Beta":"測試版 (Beta)","Broken access":"故障連線","Browse":"瀏覽","Browser default":"瀏覽器預設","Bucket create location":"Bucket 建立位置","Bucket name":"Bucket 名稱","Bucket storage class":"Bucket 儲存等級","Building list of files to restore …":"正在建立還原的檔案清單 ...","Building partial temporary database …":"正在建立部份暫存資料庫 ...","By allowing remote access, the server listens to requests from any machine on your network. If you enable this option, make sure you are always using the computer on a secure firewall protected network.":"允許遠端存取,伺服器間接收來自網路中任何主機的連線。如果啟用了這個選項,請確認已經使用防火牆保護好您網路中的主機。","By default, the tray icon will open the user interface with a token that unlocks the user interface. This ensures that you can access the user interface from the tray icon, while requiring others to enter a password. If you prefer having to type in the password, even when accessing the user interface from the tray icon, enable this option.":"在預設情況下,點選系統列 (Tray) 圖示將會直接打開登入介面,而非直接解鎖進入管理介面。除了您從系統列圖示進入的是登入介面,也可以確保當其它人使用時也需要輸入密碼。如果您喜歡輸入密碼才能進入介面的話,啟用這個選項將是適合您的選擇。","Cache Files":"快取檔案","Canary":"Canary","Cancel":"取消","Cannot move to existing file":"無法搬移已存在檔案","Changelog":"更新記錄","Changelog for {{appname}} {{version}}":"更新記錄:{{appname}} {{version}}","Check failed:":"檢查失敗:","Check for updates now":"現在檢查更新","Checking for updates …":"檢查更新中 ...","Chose a storage type to get started":"選擇儲存區類型,然後開始","Click the AuthID link to create an AuthID":"按下 AuthID 連結來建立一組 AuthID","Click to set throttle options":"點這裡進入頻寬限制設定","Commandline …":"命令列 ...","Compact Phase":"壓縮階段","Compact now":"立即緊密壓縮","Compacting remote data …":"正在緊密壓縮遠端資料 ...","Complete log":"完整記錄","Completing backup …":"正在完成備份 ...","Completing previous backup …":"正在完成上一次備份 ...","Computer":"電腦","Configuration file:":"設定檔:","Configuration:":"設定:","Configure a new backup":"設定一個新備份","Confirm delete":"確認刪除","Confirm encryption passphrase":"確認加密密碼","Confirm passphrase":"確認密碼","Confirmation required":"需要確認","Connect":"連線","Connect now":"立即連線","Connecting to server …":"正在連線到伺服器 ...","Connection lost":"連線失敗","Connection worked!":"連線已建立!","Container name":"容器名稱","Container region":"容器區域","Continue":"繼續","Continue without encryption":"不加密並繼續","Copied!":"已複製","Copy":"複製","Copy Destination URL to Clipboard":"複製目標 URL 至剪貼簿","Copy failed. Please manually copy the URL":"複製失敗。請手動複製 URL","Core options":"核心選項","Counting ({{files}} files found, {{size}})":"正在計算 ({{files}} 個檔案, {{size}})","Crashes only":"只有當機","Create bug report …":"建立問題報告 ...","Create folder?":"建立資料夾?","Created new limited user":"建立新的受限使用者","Creating bug report …":"正在建立問題報告 ...","Creating new user with limited access …":"正在建立有限制存取的新使用者 ...","Creating target folders …":"正在建立目標資料夾 ...","Creating temporary backup …":"正在建立暫存備份 ...","Current action:":"目前動作:","Current file:":"目前檔案:","Current version is {{versionname}} ({{versionnumber}})":"目前版本 {{versionname}} ({{versionnumber}})","Custom S3 endpoint":"自訂 S3 進入點","Custom authentication url":"自訂授權 URL","Custom backup retention":"自訂備份保留規則","Custom location ({{server}})":"自訂位置 ({{server}})","Custom region for creating buckets":"自定區域以建立 Bucket ","Custom region value ({{region}})":"自訂區域 Value ({{region}})","Custom server url ({{server}})":"自訂伺服器 URL ({{server}})","Custom storage class ({{class}})":"自訂儲存等級 ({{class}})","Database …":"資料庫 ...","Days":"日","Default":"預設","Default ({{channelname}})":"預設 ({{channelname}})","Default excludes":"預設排除","Default options":"預設選項","Delete":"刪除","Delete Phase (Old Backup Versions)":"刪除階段 (舊版本備份)","Delete backup":"刪除備份","Delete backups that are older than":"刪除指定條件以前的備份","Delete local database":"刪除本機資料庫","Delete remote files":"刪除遠端檔案","Delete the local database":"刪除本機資料庫","Delete {{filecount}} files ({{filesize}}) from the remote storage?":"刪除遠端儲存區的 {{filecount}} 個檔案 ({{filesize}}) 嗎?","Delete …":"刪除 ...","Deleted":"已刪除","Deleted Versions":"已刪除版本","Deleted files":"已刪除檔案","Deleting remote files …":"正在刪除遠端檔案 ...","Deleting unwanted files …":"正在刪除不需要的檔案 ...","Description (optional)":"說明 (可省略)","Description:":"說明:","Desktop":"桌面","Destination":"目的地","Destination path":"目的路徑","Disabled":"取消","Dismiss":"忽略","Dismiss all":"全部忽略","Display and color theme":"佈景主題設定","Do you really want to delete the backup: \"{{name}}\" ?":"您真的要刪除 \"{{name}}\" 這個備份?","Do you really want to delete the local database for: {{name}}":"您真的要刪除 {{name}} 這個本機資料庫?","Done":"完成","Download":"下載","Downloaded files":"已下載檔案","Downloading files …":"正在下載檔案 ...","Downloading update…":"正在下載更新 ...","Duplicate option {{opt}}":"重複選項 {{opt}}","Duplicati Website":"Duplicati 官方網站","Duplicati forum":"Duplicati 論壇","Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run.":"Duplicati 將於作業系統啟動後執行,但將會保持在暫停狀態。此時 Duplicati 將以最少資源使用率的情況下常駐,不會進行備份作業。","Duration":"時間","Each backup has a local database associated with it, which stores information about the remote backup on the local machine.\n When deleting a backup, you can also delete the local database without affecting the ability to restore the remote files.\n If you are using the local database for backups from the commandline, you should keep the database.":"每個備份都會關聯到一個本機資料庫,裡面儲存有備份目的地的相關資訊。\n 當您刪除備份時,您可以只刪除本機資料庫而不影響恢復備份目的地備份檔的還原能力。\n 如果您使用本機資料庫做命令列方式備份,您將資料庫保留好。","Edit as list":"編輯清單","Edit as text":"編輯文字內容","Edit …":"編輯 ...","Encrypt file":"加密檔案","Encryption":"加密方式","Encryption changed":"加密方式已變更","End":"結束","Enter URL":"輸入 URL","Enter a retention strategy manually. Placeholders are D/W/Y for days/weeks/years and U for unlimited. The syntax is: 7D:1D,4W:1W,36M:1M. This example keeps one backup for each of the next 7 days, one for each of the next 4 weeks, and one for each of the next 36 months. This can also be written as 1W:1D,1M:1W,3Y:1M.":"手動輸入備份保留原則。可用關鍵字 D/W/Y,分別代表 日/週/年。語法如下:7D:1D,4W:1W,36M:1M。上述例子表示,每7日保留1份,每4週保留1份,每36個月保留1份。您也可以寫成 1W:1D,1M:1W,3Y:1M。","Enter backup passphrase, if any":"輸入備份密碼,如果有的話","Enter configuration details":"進入設定細節","Enter encryption passphrase":"輸入加密密碼","Enter expression here":"在這裡輸入運算式","Enter the destination path":"輸入目的地路徑","Error":"錯誤","Error!":"錯誤!","Errors and crashes":"錯誤與當機","Examined":"已檢查","Exclude":"例外","Exclude directories whose names contain":"排除目錄名稱含有","Exclude expression":"排除表示式","Exclude file":"例外檔案","Exclude file extension":"例外副檔名","Exclude files whose names contain":"排除檔案名稱包含有","Exclude filter group":"例外篩選群組","Exclude folder":"例外資料夾","Exclude regular expression":"排除的正規表示式","Existing file found":"檔案已存在","Experimental":"實驗版 (Experimental)","Export":"匯出","Export backup configuration":"匯出備份設定","Export configuration":"匯出設定","Export passwords":"匯出密碼","Export …":"匯出 ...","Exporting …":"正在匯出 ...","External link":"外部連結","FTP (Alternative)":"FTP (替代)","Failed to build temporary database: {{message}}":"建立暫存資料庫失敗:{{message}}","Failed to connect:":"連線失敗:","Failed to connect: {{message}}":"連線失敗:{{message}}","Failed to delete:":"刪除失敗:","Failed to fetch path information: {{message}}":"列取路徑資訊失敗: {{message}}","Failed to find backup:":"尋找備份失敗:","Failed to read backup defaults:":"讀取備份預設值失敗︰","Failed to restore files: {{message}}":"還原檔案失敗:{{message}}","Failed to save:":"儲存失敗:","Fetching path information …":"正在列舉路徑資訊 ...","File":"檔案","Files larger than:":"檔案大小超過:","Filters":"篩選","Finished!":"已完成!","First run setup":"執行初始化設定","Folder":"資料夾","Folder path":"資料夾路徑","Fri":"週五","GByte":"GByte","GByte/s":"GByte/s","GCS Project ID":"GCS Project ID","General":"一般","General backup settings":"一般備份設定","General options":"一般選項","Generate":"產生","Generate IAM access policy":"產生 IAM access policy","Getting file versions …":"正在取得檔案版本 ...","Group email":"群組郵件","Hidden files":"隱藏檔案","Hide":"隱藏","Hide hidden folders":"隱藏目錄","Home":"首頁","Hostnames":"主機名稱","Hours":"小時","How do you want to handle existing files?":"您如何處理既有檔案?","Hyper-V Machine":"Hyper-V 主機","Hyper-V Machine:":"Hyper-V 主機:","Hyper-V Machines":"Hyper-V 主機","ID:":"ID:","If a date was missed, the job will run as soon as possible.":"如果已錯過時間,將儘可能快速進行這個工作。","If at least one newer backup is found, all backups older than this date are deleted.":"如果有更新的備份存在,則刪除比這個日期早的所有備份。","If you do not enter a path, all files will be stored in the login folder.\nAre you sure this is what you want?":"如果沒有輸入路徑,將會儲存所有檔案在登入資料夾。\n確定這是您要的嗎?","If you do not enter an API Key, the tenant name is required":"If you do not enter an API Key, the tenant name is required","Import":"匯入","Import Destination URL":"匯入目的地 URL","Import backup configuration":"匯入備份設定","Import from a file":"從檔案匯入","Import metadata":"匯入 metadata","Importing …":"正在匯入 ...","Include a file?":"包含檔案?","Include expression":"包含表示式","Include regular expression":"包含正則表示式","Incorrect answer, try again":"回應不正確,請重試一次","Individual builds for developers only. Not for use with important data.":"僅針對開發人員的個別組建版本,請不要使用在重要資料上。","Information":"資訊","Invalid characters in path":"路徑有無法使用的字元","Invalid retention time":"保留時間無效","It is possible to connect to some FTP without a password.\nAre you sure your FTP server supports password-less logins?":"可以在無密碼的情況下連接到 FTP。\n您確定您的 FTP 伺服器支援無密碼登錄嗎?","KByte":"KByte","KByte/s":"KByte/s","Keep a specific number of backups":"保留指定份數的備份","Keep all backups":"保留所有備份","Keystone API version":"Keystone API 版本","Language in user interface":"使用者介面語言","Last month":"上個月","Last successful backup:":"上一次成功備份:","Last successful restore: {{time}} (took {{duration || '0 seconds'}})":"上一次成功還原:{{time}} (took {{duration || '0 seconds'}})","Latest":"最新","Libraries":"函式庫","Listing backup dates …":"正在列出備份日期 ...","Listing remote files for purge …":"正在列出要清除的遠端檔案...","Listing remote files …":"正在列出遠端檔案 ...","Live":"即時","Load a configuration from an exported job or a storage provider":"從匯出的備份作業或儲存區來載入組態設定","Load destination from an exported job or a storage provider":"從匯出的備份作業或儲存區來載入備份目的地","Load older data":"載入較舊的資料","Loading …":"載入中 ...","Local database path:":"本機資料庫路徑:","Local repository":"本機 repository","Local storage":"本機儲存區","Location":"位置","Location where buckets are created":"建立 Buckets 的位置","Log data for {{Backup.Backup.Name}}":"{{Backup.Backup.Name}} 的記錄資料","Log data from the server":"伺服器上的記錄","Log out":"登出","MByte":"MByte","MByte/s":"MByte/s","Maintenance":"維護","Manually type path":"手動輸入路徑","Max download speed":"最大下載速度","Max upload speed":"最大上傳速度","Menu":"功能","Microsoft SQL Database:":"Microsoft SQL 資料庫:","Microsoft SQL Databases":"Microsoft SQL 資料庫","Minimum redundancy":"Minimum redundancy","Minimum redundancy is 1.0":"Minimum redundancy is 1.0","Minutes":"分鐘","Missing name":"遺失名稱","Missing passphrase":"遺失密碼","Missing sources":"遺失來源","Modified":"已修改","Mon":"週一","Months":"月","Move existing database":"搬移已存在資料庫","Move failed:":"搬移失敗:","My Documents":"My Documents","My Music":"My Music","My Photos":"My Photos","My Pictures":"My Pictures","Name":"名稱","Never":"從未","New user name is {{user}}.\nUpdated credentials to use the new limited user":"新使用者名稱是 {{user}}.\n更新憑證以使用新的受限使用者帳號","Next":"下一頁","Next scheduled run:":"下一次排程執行:","Next scheduled task:":"下一個排程工作:","Next task:":"下一個工作:","Next time":"下一次","No":"否","No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?":"No certificate was specified previously, please verify with the server administrator that the key is correct: {{key}} \n\nDo you want to approve the reported host key?","No editor found for the "{{backend}}" storage type":"找不到 "{{backend}}" 儲存區類型","No encryption":"不加密","No items selected":"沒有選擇","No items to restore, please select one or more items":"沒有要還原的項目,請至少選擇一個項目","No passphrase entered":"沒有輸入密碼","No scheduled tasks":"沒有排程工作","Non-matching passphrase":"密碼不相符","None / disabled":"無 / 取消","Not using encryption":"未使用加密","Nothing will be deleted. The backup size will grow with each change.":"什麼都不刪除。備份大小將隨著每次異動而持續增長。","OK":"確定","Once there are more backups than the specified number, the oldest backups are deleted.":"當備份數量超過指定數目,最舊的備份將被刪除。","OpenStack AuthURI":"OpenStack AuthURI","OpenStack Object Storage / Swift":"OpenStack Object Storage / Swift","Opened":"已開啟","Operating System":"作業系統","Operation":"作業","Operations:":"作業:","Optional authentication password":"(非必要)認證密碼","Optional authentication username":"(非必要)認證帳號","Options":"選項","Original location":"原始位置","Others":"其它","Over time backups will be deleted automatically. There will remain one backup for each of the last 7 days, each of the last 4 weeks, each of the last 12 months. There will always be at least one remaining backup.":"智慧保留模式,兼具長時間保存與短時間份數考量。保留每7天、每4週、每12個月均有一份備份。","Overwrite":"覆寫","Passphrase":"密碼","Passphrase (if encrypted)":"密碼 (如果已加密)","Passphrase changed":"密碼已變更","Passphrases are not matching":"密碼不相符","Passphrases do not match":"密碼不相符","Password":"密碼","Patching files with local blocks …":"使用本機區塊修復檔案中 ...","Path":"路徑","Path not found":"找不到路徑","Path on server":"伺服器路徑","Path or subfolder in the bucket":"Bucket 裡的路徑或子資料夾","Pause":"暫停","Pause after startup or hibernation":"當啟動或休眠後暫停","Pause options":"暫停選項","Permissions":"權限","Pick location":"選擇位置","Point to your backup files and restore from there":"指向您的備份檔案,將會由此還原","Port":"連接埠","Prevent tray icon automatic log-in":"關閉從系統列 (Tray) 圖示自動登入","Previous":"上一頁","Progress:":"正在處理:","ProjectID is optional if the bucket exist":"ProjectID is optional if the bucket exist","Proprietary":"雲端服務","Purge Phase":"清除階段","Purging files complete!":"檔案清除完成!","Purging files …":"正在清理檔案 ...","Rebuilding local database …":"正在重建本機資料庫 ...","Recreate (delete and repair)":"重新建立(刪除並修復)","Recreate Database Phase":"重建資料庫階段","Recreating database …":"正在重建資料庫 ...","Registering temporary backup …":"正在註冊暫時備份 ...","Relative paths not allowed":"不允許使用相對路徑","Reload":"重新載入","Remote":"遠端","Remote Path":"遠端 Path","Remote Repository":"遠端 Repository","Remote path":"遠端 path","Remote repository":"遠端 repository","Remote volume size":"遠端區塊大小","Remove":"移除","Remove option":"移除選項","Removed files":"檔案已移除","Repair":"修復","Repair Phase":"修復階段","Repairing database …":"正在修復資料庫 ...","Repeat Passphrase":"重複密碼","Reporting:":"報告︰","Reset":"重置","Restore":"還原","Restore complete!":"還原完成!","Restore files":"還原檔案","Restore files …":"還原檔案 ...","Restore from":"還原檔案從 ","Restore from backup configuration":"從備份設定檔還原","Restore options":"還原選項","Restore read/write permissions":"還原讀/寫權限","Restored Files":"已還原檔案","Restored Folders":"已還原資料夾","Restored Symlinks":"已還原符號連結","Restoring files …":"正在還原檔案 ...","Resume":"繼續","Rewritten File Lists":"覆寫檔案清單","Run again every":"重複執行於每","Run now":"立即執行","Running commandline entry":"Running commandline entry","Running task:":"正在執行工作:","Running …":"正在執行 ...","S3 Compatible":"S3 相容","Same as the base install version: {{channelname}}":"與目前已安裝版本相同: {{channelname}}","Sat":"週六","Save":"儲存","Save and repair":"儲存並修復","Save different versions with timestamp in file name":"在檔案名稱中儲存不同版本的時間戳記","Save immediately":"立即儲存","Scanning existing files …":"正在掃描已存在檔案 ...","Scanning for local blocks …":"正在掃描本機區塊 ...","Schedule":"排程","Search":"搜尋","Search for files":"搜尋檔案","Seconds":"秒","Select a log level and see messages as they happen:":"選擇一個記錄等級以查看訊息︰","Select files":"選擇檔案","Server":"伺服器","Server and port":"伺服器與連接埠","Server hostname or IP":"伺服器名稱或 IP","Server is currently paused,":"伺服器目前已暫停,","Server is currently paused, do you want to resume now?":"伺服器目前已暫停,請問您現在要繼續嗎?","Server password":"伺服器密碼","Server paused":"伺服器目前已暫停","Server state properties":"伺服器狀態屬性","Settings":"設定","Show":"顯示","Show advanced editor":"顯示進階編輯器","Show hidden folders":"顯示隱藏資料夾","Show log":"顯示記錄","Show log …":"顯示記錄 ...","Show treeview":"顯示樹狀清單","Sia server password":"Sia 伺服器密碼","Smart backup retention":"智慧管理備份數","Some OpenStack providers allow an API key instead of a password and tenant name":"某些 OpenStack 供應商允許 API Key 而不用密碼與 Tenant 名稱","Source Data":"來源資料","Source Files":"來源檔案","Source data":"來源資料","Source folders":"來源資料夾","Source:":"來源:","Specific builds for developers only. Not for use with important data.":"僅針對開發人員的特定組建版本,請不要使用在重要資料上。","Standard protocols":"標準通訊協定","Start":"開始","Starting backup …":"正在開始備份 ...","Starting restore …":"正在開始還原...","Starting the restore process …":"正在開始還原程序 ...","Stop after the current file":"這個檔案完成後停止","Stop running backup":"停止正在進行的備份","Stop running task":"停止正在進行的工作","Stopping after the current file:":"正在等檔案完成後停止:","Stopping task:":"正在停止工作:","Storage Type":"儲存區類型","Storage class":"儲存區等級","Storage class for creating a bucket":"建立 Bucket 的儲存類型","Stored":"儲存","Strong":"強","Success":"成功","Sun":"週日","Symbolic link":"符號連結","System Files":"系統檔案","System default ({{levelname}})":"系統預設 ({{levelname}})","System files":"系統檔案","System info":"系統資訊","System properties":"系統屬性","TByte":"TByte","TByte/s":"TByte/s","Task is running":"工作正在執行","Temporary Files":"暫存檔案","Temporary files":"暫存檔案","Test Phase":"測試階段","Test connection":"測試連線","Testing permissions …":"正在測試權限 ...","Testing …":"測試中 ...","The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})":"在 '{{fieldname}}' 欄位當中有無效字元: {{character}} (value: {{value}}, index: {{pos}})","The backup is missing, has it been deleted?":"這個備份已遺失,是否要刪除?","The backup was temporary and does not exist anymore, so the log data is lost":"這是已經不存在的臨時備份,因此已無記錄資料。","The bucket name should be all lower-case, convert automatically?":"Bucket 名稱應該全部小寫,要自動轉換嗎?","The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?":"設定應該注意安全,您確定將含有密碼的設定儲存為不加密的檔案嗎?","The dark theme (by Michal)":"深色主題 (by Michal)","The default blue on white theme (by Alex)":"預設白色主題 (by Alex)","The folder {{folder}} does not exist.\nCreate it now?":"資料夾 {{folder}} 不存在,是否立即建立?","The host key has changed, please check with the server administrator if this is correct, otherwise you could be the victim of a MAN-IN-THE-MIDDLE attack.\n\nDo you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?":"主機金鑰已變更,如果是正確的請您與伺服器管理員聯繫,否則您可能已遭受中間人攻擊。\n\n你想要更換原先的主機金鑰 \"{{prev}}\" 到 {{key}} 嗎?","The passwords do not match":"密碼不符","The path does not appear to exist, do you want to add it anyway?":"路徑似乎不存在,無論如何你都要加入嗎?","The path does not end with a '{{dirsep}}' character, which means that you include a file, not a folder.\n\nDo you want to include the specified file?":"這個路徑的尾端沒有 '{{dirsep}}' 字元,這表示您指定的是檔案而非資料夾。\n\n您確認是要指定這個檔案嗎?","The path must be an absolute path, i.e. it must start with a forward slash '/'":"必須是絕對路徑,也就是說必須以斜線開頭 '/'","The region parameter is only applied when creating a new bucket":"區域參數只有在建立新 Bucket 時套用","The region parameter is only used when creating a bucket":"區域參數只使用在在建立新 Bucket 時","The server certificate could not be validated.\nDo you want to approve the SSL certificate with the hash: {{hash}}?":"伺服器無法驗證。\n您要使用這個 SSL 憑證 {{hash}} 嗎?","The storage class affects the availability and price for a stored file":"儲存區類型會影響到可用性以及... 價格","The target folder contains encrypted files, please supply the passphrase":"目的資料夾中包含加密檔案,請提供密碼","The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?":"這個使用者擁有太多權限,您是否要建立另一個新的使用者,只具備指定路徑的權限?","This backup was created on another operating system. Restoring files without specifying a destination folder can cause files to be restored in unexpected places. Are you sure you want to continue without choosing a destination folder?":"這個備份是在另一個作業系統上建立的,在不指定目標資料夾的情況下還原檔案,可能會讓檔案還原到您預期外的地方,請問您是否仍確定繼續而不重新指定資料夾?","This month":"本月","This week":"本週","Throttle settings":"頻寬限制設定","Thu":"週四","Time":"時間","To File":"到檔案","To confirm you want to delete all remote files for \"{{name}}\", please enter the word you see below":"確認要刪除所有的遠端檔案 \"{{name}}\",請輸入下面的單字","To export without a passphrase, uncheck the \"Encrypt file\" box":"若要無密碼匯出,請不要勾選\"加密檔案\"核取方塊","To prevent various DNS based attacks, Duplicati limits the allowed hostnames to the ones listed here. Direct IP access and localhost is always allowed. Multiple hostnames can be supplied with a semicolon separator. If any of the allowed hostnames is an asterisk (*), all hostnames are allowed and this feature is disabled. If the field is empty, only IP address and localhost access is allowed.":"為了避免基於 DNS 的攻擊,Duplicati 可以用主機名稱作為連接的來源限制。\n直接使用 IP 與 localhost 是內建允許的方式。\n若有多個主機名稱,可以用分號 (;) 做為分隔,如果使用萬用字元 (*),則表示所有主機名稱均可以連線至 Duplicaiti,等於關閉此功能;如果內容為空,則只允許使用 IP 與 localhost 進行連線。","Today":"今天","Trust host certificate?":"信任主機憑證?","Trust server certificate?":"信任伺服器憑證?","Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.":"嘗試我們正在開發中的新功能。這是目前最穩定的版本,要在正式環境使用此功能之前,請先測試是否可以正確還原資料。","Tue":"週二","Type passphrase here.":"在此這輸入密碼。","Type to highlight files":"輸入字串,符合的檔名會以粗體字方式標示","Unknown backup size and versions":"未知的備份大小與版本","Until resumed":"手動繼續","Update channel":"更新頻道","Update failed:":"更新失敗:","Updating with existing database":"正在更新既有資料庫 ...","Uploaded files":"已上傳檔案","Uploading verification file …":"正在上傳驗證檔案 ...","Usage statistics":"使用統計","Usage statistics, warnings, errors, and crashes":"使用統計、警告、錯誤與當機","Use SSL":"使用 SSL","Use existing database?":"使用已存在資料庫?","Use weak passphrase":"使用低強度密碼","Useless":"不使用","User data":"使用者資料","User domain name":"使用者網域名稱","User has too many permissions":"使用者有太多權限","User interface settings":"使用者介面設定","Username":"使用者","Vacuuming database …":"正在清理資料庫 ...","Validating …":"驗證中 ...","Verifications":"驗證","Verify files":"驗證檔案","Verifying answer":"驗證答案","Verifying backend data …":"正在驗證後端資料 ...","Verifying files …":"正在驗證檔案 ...","Verifying remote data …":"正在驗證遠端資料 ...","Verifying restored files …":"正在驗證已還原檔案 ...","Verifying …":"驗證中 ...","Version ID":"版本 ID","Very strong":"非常強","Very weak":"非常弱","Visit us on":"造訪我們","WARNING: This will prevent you from restoring the data in the future.":"警告︰ 這將會阻止您日後還原資料。","Waiting for task to begin":"正在等待工作開始","Waiting for upload to finish …":"等待上傳完成中 ...","Warnings, errors and crashes":"警告、錯誤與當機","We recommend that you encrypt all backups stored outside your system":"我們建議,您將放在您自己控管系統以外的備份都進行加密","Weak":"弱","Weak passphrase":"弱密碼","Wed":"週三","Weeks":"週","Where do you want to restore from?":"您要從那裡還原?","Where do you want to restore the files to?":"您要還原檔案到哪裡?","Years":"年","Yes":"是","Yes, I have stored the passphrase safely":"是,我已安全的儲存密碼","Yes, I understand the risk":"是的,我理解這個風險","Yes, I'm brave!":"是的,我敢!","Yes, please break my backup!":"是,請中斷我的備份!","Yesterday":"昨天","You are changing the database path away from an existing database.\nAre you sure this is what you want?":"您正在變更現有資料庫的路徑。\n您確定這是您想要的嗎?","You are currently running {{appname}} {{version}}":"您正在執行 {{appname}} {{version}}","You have changed the encryption mode. This may break stuff. You are encouraged to create a new backup instead":"您已變更加密模式。這可能導致資料損毀。我們建議您建立一個新的備份","You have changed the passphrase, which is not supported. You are encouraged to create a new backup instead.":"您變更加密密碼,這個動作不被支援。我們建議您建立一個新的備份。","You have chosen not to encrypt the backup. Encryption is recommended for all data stored on a remote server.":"您已選擇備份不加密。建議您應將存在遠端伺服器上的資料予以加密。","You have chosen to restore to a new location, but not entered one":"您已經選擇還原到新的位置,但還沒輸入位置資訊","You have generated a strong passphrase. Make sure you have made a safe copy of the passphrase, as the data cannot be recovered if you lose the passphrase.":"您已經產生足夠強度的密碼。請確保您已經另外備份好這組密碼,若您遺失這組密碼,您的資料將無法還原。","You must choose at least one source folder":"您至少要選擇一個來源資料夾","You must enter a domain name to use v3 API":"您必須輸入網域名稱以使用 v3 API","You must enter a name for the backup":"您必須輸入備份名稱","You must enter a passphrase or disable encryption":"您必須輸入密碼或取消加密","You must enter a password to use v3 API":"您必須輸入密碼以使用 v3 API","You must enter a positive number of backups to keep":"您必須輸入正數,備份才能保存","You must enter a tenant (aka project) name to use v3 API":"您必須輸入 tenant (或 project) 名稱以使用 v3 API","You must enter a valid duration for the time to keep backups":"您必須輸入有效的起迄時間來保留備份","You must fill in the password":"您必須輸入密碼","You must fill in the server name or address":"您必須填寫伺服器名稱或位址","You must fill in the username":"您必須填寫使用者名稱","You must fill in {{field}}":"您必須填寫 {{field}}","You must select or fill in the AuthURI":"您必須選擇或填寫 AuthURI","You must select or fill in the server":"您必須選擇或填寫伺服器","You must specify a path":"您必須指定一個路徑","Your files and folders have been restored successfully.":"您的檔案與資料夾已成功還原。","Your passphrase is easy to guess. Consider changing passphrase.":"您的密碼很容易被猜到。請考慮變更密碼。","bucket/folder/subfolder":"bucket/folder/subfolder","byte":"byte","byte/s":"byte/s","custom":"自訂","resume now":"立即繼續","unless you are explicitly specifying --group-id":"除非您明確的指定 --group-id","{{appname}} was primarily developed by {{dev1}} and {{dev2}}. {{appname}} can be downloaded from {{websitename}}. {{appname}} is licensed under the {{licensename}}.":"{{appname}} 主要是由 {{dev1}} 以及 {{dev2}} 所開發。 {{appname}} 可以從 {{websitename}} 下載取得。 {{appname}} 採用 {{licensename}} 授權。","{{files}} files ({{size}}) to go {{speed_txt}}":"{{files}} 個檔案 ({{size}}) 正在傳輸 {{speed_txt}}","{{item.Backup.Metadata.TargetSizeString}} / {{$count}} Version":"{{item.Backup.Metadata.TargetSizeString}} / {{$count}} 個版本","{{number}} Hour":"{{number}} 小時","{{number}} Hours":"{{number}} 小時","{{number}} Minutes":"{{number}} 分鐘","{{time}} (took {{duration}})":"{{time}} (花費 {{duration}})"}); /* jshint +W100 */ }]); \ No newline at end of file diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/AppController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/AppController.js index 8e8e399f9..a9e18fb2a 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/AppController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/AppController.js @@ -1,4 +1,4 @@ -backupApp.controller('AppController', function($scope, $cookies, $location, AppService, BrandingService, ServerStatus, SystemInfo, AppUtils, DialogService, gettextCatalog) { +backupApp.controller('AppController', function($rootScope, $scope, $cookies, $location, AppService, BrandingService, ServerStatus, SystemInfo, AppUtils, DialogService, gettextCatalog) { $scope.brandingService = BrandingService.watch($scope); $scope.state = ServerStatus.watch($scope); $scope.systemInfo = SystemInfo.watch($scope); @@ -29,8 +29,8 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS ServerStatus.resume().then(function() {}, AppUtils.connectionError); }; - $scope.pause = function(duration) { - ServerStatus.pause(duration).then(function() {}, AppUtils.connectionError); + $scope.pause = function(duration, pauseTransfers) { + ServerStatus.pause(duration, pauseTransfers).then(function() {}, AppUtils.connectionError); }; $scope.isLoggedIn = false; @@ -55,7 +55,8 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS function(index, text, cur) { if (index == 0 && cur != null && cur.time != null) { var time = cur.time; - $scope.pause(time == 'infinite' ? '' : time); + var pauseTransfers = cur.pauseTransfers; + $scope.pause(time == 'infinite' ? '' : time, pauseTransfers); } } ); @@ -176,6 +177,9 @@ backupApp.controller('AppController', function($scope, $cookies, $location, AppS var dt = data.data['max-download-speed']; $scope.throttle_active = (ut != null && ut.trim().length != 0) || (dt != null && dt.trim().length != 0); + ServerStatus.state.orderBy = data.data['backup-list-sort-order'] || 'id'; + $rootScope.$broadcast('sortorder_changed', { orderBy: ServerStatus.state.orderBy } ); + var has_asked = AppUtils.parseBoolString(data.data['has-asked-for-password-change']); var autogen_passphrase = AppUtils.parseBoolString(data.data['autogenerated-passphrase']); if (!has_asked && autogen_passphrase) { diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js deleted file mode 100644 index eb286bb16..000000000 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/CaptchaController.js +++ /dev/null @@ -1,23 +0,0 @@ -backupApp.controller('CaptchaController', function($scope, CaptchaService, DialogService, AppService, AppUtils) { - var entry = $scope.entry = CaptchaService.active; - - function refreshChallenge() { - entry.imageurl = null; - - AppService.postJson('/captcha', { 'target': entry.target}).then(function(resp) { - entry.token = resp.data.Token; - entry.expectedAnswer = resp.data.Answer; - entry.noVisualChallenge = resp.data.NoVisualChallenge; - entry.imageurl = AppService.apiurl + '/captcha/' + entry.token; - - }, function(err) { - DialogService.dismissCurrent(); - AppUtils.connectionError(err); - }); - }; - - if (entry.token == null) - refreshChallenge(); - - $scope.reload = refreshChallenge; -}); diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/ConfirmDeleteController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/ConfirmDeleteController.js new file mode 100644 index 000000000..e158532e2 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/ConfirmDeleteController.js @@ -0,0 +1,7 @@ +backupApp.controller('ConfirmDeleteController', function($scope, $location, gettextCatalog) { + $scope.selection = $scope.$parent.state.CurrentItem; + + if ($scope.selection.requiredword == '') + $scope.selection.requiredword = 'delete all files'; + $scope.selection.typedword = ''; +}); diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js index c93d7e30a..ca180d359 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/DeleteController.js @@ -1,4 +1,4 @@ -backupApp.controller('DeleteController', function($scope, $routeParams, $location, gettextCatalog, CaptchaService, DialogService, ServerStatus, SystemInfo, BackupList, AppService, AppUtils) { +backupApp.controller('DeleteController', function($scope, $routeParams, $location, gettextCatalog, DialogService, ServerStatus, SystemInfo, BackupList, AppService, AppUtils) { $scope.BackupID = $routeParams.backupid; $scope.DeleteLocalDatabase = true; $scope.DeleteRemoteFiles = false; @@ -61,16 +61,38 @@ backupApp.controller('DeleteController', function($scope, $routeParams, $locatio $scope.doDelete = function() { if ($scope.DeleteRemoteFiles) { - CaptchaService.Authorize( + const dlg = DialogService.htmlDialog( gettextCatalog.getString('Confirm delete'), - gettextCatalog.getString('To confirm you want to delete all remote files for "{{name}}", please enter the word you see below', {name: $scope.Backup.Backup.Name}), - 'DELETE /backup/' + $scope.BackupID, - function(token, answer) { - AppService.delete('/backup/' + $scope.BackupID + '?delete-local-db=' + $scope.DeleteLocalDatabase + '&delete-remote-files=' + $scope.DeleteRemoteFiles + '&captcha-token=' + token + '&captcha-answer=' + answer).then(function() { - $location.path('/'); - }, AppUtils.connectionError); + 'templates/confirmdelete.html', + [gettextCatalog.getString('Cancel'), gettextCatalog.getString('Delete')], + function(ix) { + if (ix == 1) { + AppService.delete('/backup/' + $scope.BackupID + '?delete-local-db=' + $scope.DeleteLocalDatabase + '&delete-remote-files=' + $scope.DeleteRemoteFiles).then(function() { + $location.path('/'); + }, AppUtils.connectionError); + } + }, + null, + function(index, text, cur) { + // Allow the user to cancel + if (index != 1) + return true; + + // Compare case insensitive + if (cur.requiredword?.toLowerCase() != cur.typedword?.toLowerCase() || cur.typedword == '') + { + alert("Please enter the required phrase to confirm the deletion"); + return false; + } + + return true; } ); + + // Set the dialog data + dlg.backupId = $scope.BackupID; + dlg.backupname = $scope.Backup.Backup.Name; + dlg.requiredword = ('delete ' + $scope.Backup.Backup.Name).toLowerCase(); } else { diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js index 7d56ba783..be6d0ba88 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js @@ -49,6 +49,9 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout $scope.$watch('Options["passphrase"]', computePassPhraseStrength); $scope.$watch('RepeatPasshrase', computePassPhraseStrength); + $scope.parseSizeString = function(v) { + return AppUtils.parseSizeString(v); + } $scope.checkGpgAsymmetric = function() { if (!this.Options) { diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/HomeController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/HomeController.js index f66252270..a8e9c8f71 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/HomeController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/HomeController.js @@ -1,6 +1,21 @@ backupApp.controller('HomeController', function ($scope, $location, ServerStatus, BackupList, AppService, AppUtils, DialogService, gettextCatalog) { $scope.backups = BackupList.watch($scope); + $scope.folderContext = null; + + $scope.orderByFromState = function() + { + return ServerStatus.state.orderBy; + } + + $scope.orderBy = null; + + $scope.openFolderContext = function(path) { + BackupList.getFolderContext(path).then(function(context) { + $scope.folderContext = context; + }); + }; + $scope.doRun = function(id) { AppService.post('/backup/' + id + '/run').then(function() { if (ServerStatus.state.programState == 'Paused') { @@ -56,6 +71,17 @@ backupApp.controller('HomeController', function ($scope, $location, ServerStatus $scope.doCreateBugReport = function(id, name) { AppService.post('/backup/' + id + '/createreport'); }; + + $scope.doApplyOrder = function (orderby) + { + ServerStatus.state.orderBy = orderby; + AppService.patch('/serversettings', { 'backup-list-sort-order': orderby }); + BackupList.reload(); + }; + + $scope.$on('sortorder_changed', function() { + $scope.orderBy = $scope.orderByFromState(); + }); $scope.formatDuration = AppUtils.formatDuration; }); diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/PauseController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/PauseController.js index 58c02482e..58a66250a 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/PauseController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/PauseController.js @@ -2,4 +2,5 @@ backupApp.controller('PauseController', function($scope, $location, gettextCatal $scope.selection = $scope.$parent.state.CurrentItem; $scope.selection.time = 'infinite'; + $scope.selection.pauseTransfers = false; }); diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js index 72c0588b4..88747583b 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/StateController.js @@ -91,11 +91,11 @@ backupApp.controller('StateController', function($scope, $timeout, ServerStatus, function handleClick(ix) { if (ix == 0) { - AppService.post('/task/' + taskId + '/stopaftercurrentfile'); + AppService.post('/task/' + taskId + '/stop'); $scope.StopReqId = taskId; } else if (ix == 1) { - AppService.post('/task/' + taskId + '/stopnow'); + AppService.post('/task/' + taskId + '/abort'); $scope.StopReqId = taskId; } }; @@ -104,8 +104,8 @@ backupApp.controller('StateController', function($scope, $timeout, ServerStatus, { DialogService.dialog( gettextCatalog.getString("Stop running backup"), - gettextCatalog.getString("You can stop the backup after any file uploads currently in progress have finished."), - [gettextCatalog.getString("Stop after current file"), gettextCatalog.getString("Stop now"), gettextCatalog.getString("Cancel")], + gettextCatalog.getString("You can stop the backup after any file uploads currently in progress have finished. If you terminate the backup, the next run will need to recover from a failed backup."), + [gettextCatalog.getString("Stop after the current file"), gettextCatalog.getString("Terminate"), gettextCatalog.getString("Cancel")], handleClick ); } @@ -113,8 +113,8 @@ backupApp.controller('StateController', function($scope, $timeout, ServerStatus, { DialogService.dialog( gettextCatalog.getString("Stop running task"), - gettextCatalog.getString("You can stop the task immediately, or allow the process to continue its current file and then stop."), - [gettextCatalog.getString("Stop after the current file"), gettextCatalog.getString("Stop now"), gettextCatalog.getString("Cancel")], + gettextCatalog.getString("You can stop the task immediately, or allow the process to continue its current file and then stop. If you terminate the task, the backup could be left in an inconsistent state."), + [gettextCatalog.getString("Stop after the current file"), gettextCatalog.getString("Terminate"), gettextCatalog.getString("Cancel")], handleClick ); } diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/SystemSettingsController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/SystemSettingsController.js index 9cfcde376..78cff9d22 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/SystemSettingsController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/SystemSettingsController.js @@ -1,6 +1,10 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $route, $cookies, AppService, DialogService, AppUtils, SystemInfo, gettextCatalog) { - $scope.SystemInfo = SystemInfo.watch($scope); + $scope.SystemInfo = SystemInfo.watch($scope, () => { + // Avoid a race when the system info is loaded after obtaining the settings + if (($scope.timezone ?? '').trim().length == 0) + $scope.timezone = $scope.SystemInfo.ServerTimeZone; + }); $scope.theme = $scope.$parent.$parent.saved_theme; if (($scope.theme || '').trim().length == 0) $scope.theme = 'default'; @@ -33,7 +37,7 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $r $scope.uiLanguage = $cookies.get('ui-locale'); $scope.lang_browser_default = gettextCatalog.getString('Browser default'); - $scope.lang_default = gettextCatalog.getString('Default'); + $scope.lang_default = gettextCatalog.getString('Default'); function setUILanguage() { if (($scope.uiLanguage || '').trim().length == 0) { @@ -64,6 +68,9 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $r $scope.usageReporterLevel = data.data['usage-reporter-level']; $scope.disableTrayIconLogin = AppUtils.parseBoolString(data.data['disable-tray-icon-login']); $scope.remoteHostnames = data.data['allowed-hostnames']; + $scope.timezone = data.data['server-timezone']; + if (($scope.timezone ?? '').trim().length == 0) + $scope.timezone = $scope.SystemInfo.ServerTimeZone; $scope.advancedOptions = AppUtils.serializeAdvancedOptionsToArray(data.data); $scope.servermodulesettings = {}; $scope.autogeneratedPassphrase = data.data['autogenerated-passphrase'] == 'True'; @@ -226,7 +233,8 @@ backupApp.controller('SystemSettingsController', function($rootScope, $scope, $r 'startup-delay': $scope.startupDelayDurationValue + '' + $scope.startupDelayDurationMultiplier, 'update-channel': $scope.updateChannel, 'usage-reporter-level': $scope.usageReporterLevel, - 'disable-tray-icon-login': $scope.disableTrayIconLogin + 'disable-tray-icon-login': $scope.disableTrayIconLogin, + 'server-timezone': $scope.timezone }; if ($scope.changeServerPassword && $scope.rawdata['server-passphrase'] != $scope.remotePassword) { diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/advancedOptionsEditor.js b/Duplicati/Server/webroot/ngax/scripts/directives/advancedOptionsEditor.js index 0adcc2286..fe5432d96 100644 --- a/Duplicati/Server/webroot/ngax/scripts/directives/advancedOptionsEditor.js +++ b/Duplicati/Server/webroot/ngax/scripts/directives/advancedOptionsEditor.js @@ -27,6 +27,7 @@ backupApp.directive('advancedOptionsEditor', function() { 'retry-delay': 'shorttimespan', 'web-timeout': 'shorttimespan', + 'read-write-timeout': 'shorttimespan', 'run-script-timeout': 'shorttimespan' }; diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js b/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js index 6baa53f19..a2a25ed9d 100644 --- a/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js +++ b/Duplicati/Server/webroot/ngax/scripts/directives/sourceFolderPicker.js @@ -54,6 +54,12 @@ backupApp.directive('sourceFolderPicker', function() { n.iconCls = 'x-tree-icon-desktop'; else if (cp == compareablePath('%HOME%')) n.iconCls = 'x-tree-icon-home'; + else if (cp == compareablePath('%MY_MOVIES%')) + n.iconCls = 'x-tree-icon-mymovies'; + else if (cp == compareablePath('%MY_DOWNLOADS%')) + n.iconCls = 'x-tree-icon-mydownloads'; + else if (cp == compareablePath('%MY_PUBLIC%')) + n.iconCls = 'x-tree-icon-mypublic'; else if (n.id.substr(0, 9) == "%HYPERV%\\" && n.id.length >= 10) { n.iconCls = 'x-tree-icon-hypervmachine'; n.tooltip = gettextCatalog.getString("ID:") + " " + n.id.substring(9, n.id.length); diff --git a/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js b/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js index 703e1faeb..99eefdfa2 100644 --- a/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js +++ b/Duplicati/Server/webroot/ngax/scripts/directives/targetFolderPicker.js @@ -45,6 +45,12 @@ backupApp.directive('destinationFolderPicker', function() { n.iconCls = 'x-tree-icon-desktop'; else if (cp == compareablePath('%HOME%')) n.iconCls = 'x-tree-icon-home'; + else if (cp == compareablePath('%MY_MOVIES%')) + n.iconCls = 'x-tree-icon-mymovies'; + else if (cp == compareablePath('%MY_DOWNLOADS%')) + n.iconCls = 'x-tree-icon-mydownloads'; + else if (cp == compareablePath('%MY_PUBLIC%')) + n.iconCls = 'x-tree-icon-mypublic'; else if (cp.substr(cp.length - 1, 1) != dirsep) n.iconCls = 'x-tree-icon-leaf'; diff --git a/Duplicati/Server/webroot/ngax/scripts/services/AppUtils.js b/Duplicati/Server/webroot/ngax/scripts/services/AppUtils.js index 748ba155f..287a1bd0e 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/AppUtils.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/AppUtils.js @@ -60,6 +60,12 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, AppServic res = gettextCatalog.getString('Desktop'); else if (cls == 'x-tree-icon-home') res = gettextCatalog.getString('Home'); + else if (cls == 'x-tree-icon-mymovies') + res = gettextCatalog.getString('My Movies'); + else if (cls == 'x-tree-icon-mydownloads') + res = gettextCatalog.getString('My Downloads'); + else if (cls == 'x-tree-icon-mypublic') + res = gettextCatalog.getString('Public'); else if (cls == 'x-tree-icon-hypervmachine') res = gettextCatalog.getString('Hyper-V Machine'); else if (cls == 'x-tree-icon-hyperv') @@ -501,21 +507,32 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, AppServic var URL_REGEXP_FIELDS = ['source_uri', 'backend-type', '--auth-username', '--auth-password', 'server-name', 'server-port', 'server-path', 'querystring']; var URL_REGEXP = /([^:]+)\:\/\/(?:(?:([^\:]+)(?:\:?:([^@]*))?\@))?(?:([^\/\?\:]*)(?:\:(\d+))?)(?:\/([^\?]*))?(?:\?(.+))?/; + // Same as URL_REGEXP, but also accepts :\\ as a separator between drive letter (server for legacy reasons) and path + var FILE_REGEXP = /(file)\:\/\/(?:(?:([^\:]+)(?:\:?:([^@]*))?\@))?(?:([^\/\?\:]*)(?:\:(\d+))?)(?:(?:\/|\:\\)([^\?]*))?(?:\?(.+))?/; var QUERY_REGEXP = /(?:^|&)([^&=]*)=?([^&]*)/g; this.decode_uri = function(uri, backendlist) { - var i = URL_REGEXP_FIELDS.length + 1; + var i = 0; var res = {}; - var m = URL_REGEXP.exec(uri); + // File URLs contain backslashes on Win which breaks the other regexp + // This is not standard, but for compatibility it is allowed for now + var fileMatch = FILE_REGEXP.exec(uri); + if (fileMatch) { + for (i = 0; i < URL_REGEXP_FIELDS.length; ++i) { + res[URL_REGEXP_FIELDS[i]] = fileMatch[i] || ""; + } + } else { + var m = URL_REGEXP.exec(uri); - // Invalid URI - if (!m) - return res; + // Invalid URI + if (!m) + return res; - while (i--) { - res[URL_REGEXP_FIELDS[i]] = m[i] || ""; + for (i = 0; i < URL_REGEXP_FIELDS.length; ++i) { + res[URL_REGEXP_FIELDS[i]] = m[i] || ""; + } } res.querystring.replace(QUERY_REGEXP, function(str, key, val) { @@ -524,7 +541,7 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, AppServic }); var backends = {}; - for(var i in backendlist) + for(i in backendlist) backends[backendlist[i].Key] = true; var scheme = res['backend-type']; @@ -592,7 +609,7 @@ backupApp.service('AppUtils', function($rootScope, $timeout, $cookies, AppServic var pre = apputils.replace_all(n.prefix || '', '!', dirsep); var suf = apputils.replace_all(n.suffix || '', '!', dirsep); - if (txt.indexOf(pre) != 0 || txt.lastIndexOf(suf) != txt.length - suf.length) + if (txt.indexOf(pre) != 0 || txt.lastIndexOf(suf) != txt.length - suf.length || txt.lastIndexOf(suf) < 0) return null; var type = n.key; diff --git a/Duplicati/Server/webroot/ngax/scripts/services/BackupList.js b/Duplicati/Server/webroot/ngax/scripts/services/BackupList.js index 3c3fac513..8a82f95fc 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/BackupList.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/BackupList.js @@ -1,6 +1,7 @@ -backupApp.service('BackupList', function($rootScope, $timeout, AppService, AppUtils, ServerStatus) { +backupApp.service('BackupList', function($rootScope, $timeout, AppService, AppUtils, ServerStatus, $http) { var list = []; var lookup = {}; + var activeLoad = false; this.list = list; this.lookup = lookup; @@ -34,8 +35,14 @@ backupApp.service('BackupList', function($rootScope, $timeout, AppService, AppUt $rootScope.$broadcast('backuplistchanged'); }; - var reload = function() { - AppService.get('/backups').then(function(data) { + this.reload = function() { + // Wait until we know the sort order + if (ServerStatus.state.orderBy == null || activeLoad) + return; + + // Prevent flickering + activeLoad = true; + AppService.get('/backups?orderby='+ (ServerStatus.state.orderBy || '')).then(function(data) { list.length = 0; for (var prop in lookup) @@ -48,10 +55,22 @@ backupApp.service('BackupList', function($rootScope, $timeout, AppService, AppUt } updateNextRunStamp(); + }).finally(function() { + activeLoad = false; }); }; + this.getFolderContext = function(path) { + return $http.get('/api/folder-context', { + params: { + path: path + } + }).then(function(response) { + return response.data; + }); + }; - $rootScope.$on('serverstatechanged.lastDataUpdateId', reload); + $rootScope.$on('serverstatechanged.lastDataUpdateId', this.reload); $rootScope.$on('serverstatechanged.proposedSchedule', updateNextRunStamp); + $rootScope.$on('sortorder_changed', this.reload); }); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js b/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js deleted file mode 100644 index 2551a9f40..000000000 --- a/Duplicati/Server/webroot/ngax/scripts/services/CaptchaService.js +++ /dev/null @@ -1,55 +0,0 @@ -backupApp.service('CaptchaService', function(DialogService, AppService, AppUtils, gettextCatalog) { - this.active = null; - var self = this; - - this.Authorize = function(title, message, target, callback) { - - var cb = self.active = { - 'message': message, - 'target': target, - 'callback': callback, - 'attempts': 0, - 'hasfailed': false, - 'verifying': false - }; - - self.attemptSolve = function() { - if (cb.attempts >= 3) { - cb.attempts = 0; - cb.token = null; - } - - DialogService.htmlDialog(title, 'templates/captcha.html', [gettextCatalog.getString('Cancel'), gettextCatalog.getString('OK')], function(btn) { - if (btn != 1) { - self.active = null; - return; - } - - cb.attempts += 1; - cb.verifying = true; - - - DialogService.dialog(gettextCatalog.getString('Verifying answer'), gettextCatalog.getString('Verifying …'), [], function() {}, function() { - - AppService.postJson('/captcha/' + encodeURIComponent(cb.token), {'answer': cb.answer, 'target': cb.target}).then(function(resp) { - DialogService.dismissCurrent(); - self.active = null; - cb.callback(cb.token, cb.answer); - }, function(err) { - - DialogService.dismissCurrent(); - cb.verifying = false; - cb.hasfailed = true; - if (err.status == 400) - self.attemptSolve(); - else - AppUtils.connectionError(err); - }); - - }); - }); - }; - - self.attemptSolve(); - }; -}); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBackendConfig.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBackendConfig.js index 8b4f3b69a..032f46af2 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBackendConfig.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBackendConfig.js @@ -131,4 +131,12 @@ backupApp.service('EditUriBackendConfig', function(AppService, AppUtils, SystemI return true; }; + this.recommend_username = function(scope, continuation) { + if ((scope.Username || '').trim().length == 0) + return self.show_warning_dialog(gettextCatalog.getString('Most servers require a username, so you will likely need to enter one.\nAre you sure want to continue without a username?'), continuation); + else + continuation(); + }; + + }); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js index d3720425e..ba61e296a 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js @@ -30,10 +30,14 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.templates['dropbox'] = 'templates/backends/oauth.html'; EditUriBackendConfig.templates['sia'] = 'templates/backends/sia.html'; EditUriBackendConfig.templates['storj'] = 'templates/backends/storj.html'; - EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html'; + EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html'; EditUriBackendConfig.templates['cos'] = 'templates/backends/cos.html'; - EditUriBackendConfig.templates['aliyunoss'] = 'templates/backends/aliyunoss.html'; + EditUriBackendConfig.templates['aliyunoss'] = 'templates/backends/aliyunoss.html'; EditUriBackendConfig.templates['e2'] = 'templates/backends/e2.html'; + EditUriBackendConfig.templates['pcloud'] = 'templates/backends/pcloud.html'; + EditUriBackendConfig.templates['smb'] = 'templates/backends/smb.html'; + EditUriBackendConfig.templates['cifs'] = 'templates/backends/smb.html'; + EditUriBackendConfig.templates['filen'] = 'templates/backends/filen.html'; EditUriBackendConfig.testers['s3'] = function(scope, callback) { @@ -189,6 +193,10 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, scope.oauth_create_token = Math.random().toString(36).substr(2) + Math.random().toString(36).substr(2); scope.oauth_service_link = 'https://duplicati-oauth-handler.appspot.com/'; scope.oauth_start_link = scope.oauth_service_link + '?type=' + scope.Backend.Key + '&token=' + scope.oauth_create_token; + + scope.oauth_service_link_v2 = 'https://oauth-service.duplicati.com/'; + scope.oauth_start_link_v2 = scope.oauth_service_link_v2 + '?type=' + scope.Backend.Key + '&token=' + scope.oauth_create_token; + scope.oauth_in_progress = false; // Handle a global override for OAuth or a local from advanced options @@ -202,22 +210,30 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, if (!oauthurl.endsWith("/")) oauthurl += "/"; scope.oauth_global_server = oauthurl; } - - if (!oauthurl.endsWith("/")) - oauthurl += "/"; - scope.oauth_service_link = oauthurl; - scope.oauth_start_link = scope.oauth_service_link + '?type=' + scope.Backend.Key + '&token=' + scope.oauth_create_token; + + if (oauthurl != null && oauthurl != '') { + if (!oauthurl.endsWith("/")) + oauthurl += "/"; + scope.oauth_service_link = oauthurl; + scope.oauth_start_link = scope.oauth_service_link + '?type=' + scope.Backend.Key + '&token=' + scope.oauth_create_token; + } }, AppUtils.connectionError); scope.oauth_start_token_creation = function () { + scope.oauth_start_token_creation_any(scope.oauth_service_link); + } + + scope.oauth_start_token_creation_v2 = function () { + scope.oauth_start_token_creation_any(scope.oauth_service_link_v2); + } + + scope.oauth_start_token_creation_any = function (servicelink) { scope.oauth_in_progress = true; var w = 450; var h = 600; - var servicelink = scope.oauth_service_link; - // Handle local override for (var n in scope.AdvancedOptions) { if (scope.AdvancedOptions[n].indexOf('--oauth-url=') == 0) { @@ -276,6 +292,18 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.loaders['jottacloud'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.loaders['box'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.loaders['dropbox'] = function() { return this['oauth-base'].apply(this, arguments); }; + EditUriBackendConfig.loaders['pcloud'] = function(scope) { + if (scope.Server == null || scope.Server == '') { + scope.Server = 'api.pcloud.com'; + } + return this['oauth-base'].apply(this, arguments); + }; + EditUriBackendConfig.loaders['smb'] = function(scope) { + if (scope.Transport === undefined || scope.Transport === '') { + scope.Transport = 'directtcp'; + } + }; + EditUriBackendConfig.loaders['cifs'] = EditUriBackendConfig.loaders['smb']; EditUriBackendConfig.loaders['openstack'] = function (scope) { if (scope.openstack_providers == null) { @@ -333,8 +361,8 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, this['oauth-base'].apply(this, arguments); }; - - // Parsers take a decomposed uri input and sets up the scope variables + + // Parsers take a decomposed uri input and sets up the scope variables EditUriBackendConfig.parsers['file'] = function (scope, module, server, port, path, options) { if (scope.Path == null && scope.Server == null) { var ix = scope.uri.indexOf('?'); @@ -419,8 +447,8 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, scope.AuthID = options['--authid']; delete options['--authid']; - - EditUriBackendConfig.mergeServerAndPath(scope); + if (module !== 'pcloud') + EditUriBackendConfig.mergeServerAndPath(scope); }; EditUriBackendConfig.parsers['googledrive'] = function() { return this['oauth-base'].apply(this, arguments); }; @@ -430,7 +458,8 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.parsers['jottacloud'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.parsers['box'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.parsers['dropbox'] = function() { return this['oauth-base'].apply(this, arguments); }; - + EditUriBackendConfig.parsers['pcloud'] = function() { return this['oauth-base'].apply(this, arguments); }; + EditUriBackendConfig.parsers['openstack'] = function (scope, module, server, port, path, options) { scope.openstack_domainname = options['--openstack-domain-name']; scope.openstack_server = scope.openstack_server_custom = options['--openstack-authuri']; @@ -588,6 +617,36 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.mergeServerAndPath(scope); } + EditUriBackendConfig.parsers['smb'] = function (scope, module, server, path, port, options) { + if (options['--transport']) + scope.Transport = options['--transport']; + else + scope.Transport = 'directtcp'; + + if (options['--auth-domain']) + scope.Domain = options['--auth-domain']; + + var nukeopts = ['--transport', '--auth-domain']; + for (var x in nukeopts) + delete options[nukeopts[x]]; + + const slashIndex = path.indexOf('/'); + scope.ShareName = slashIndex >= 0 ? path.substring(0, slashIndex).replace(/[/\\]+$/, '') : path.replace(/[/\\]+$/, ''); + scope.Path = slashIndex >= 0 ? path.substring(slashIndex + 1).replace(/[/\\]+$/, '') : ""; + + }; + + EditUriBackendConfig.parsers['cifs'] = EditUriBackendConfig.parsers['smb']; + + EditUriBackendConfig.parsers['filen'] = function (scope, module, server, path, port, options) { + console.log("filen parser", scope, module, server, path, port, options); + var p1 = server ?? ''; + var p2 = path ?? ''; + var combined = p1; + if (p1.length > 0 && p2.length > 0) + combined += '/' + p2; + scope.Path = combined; + }; // Builders take the scope and produce the uri output EditUriBackendConfig.builders['s3'] = function (scope) { @@ -651,6 +710,20 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, return url; }; + EditUriBackendConfig.builders['pcloud'] = function (scope) { + var opts = { + 'authid': scope.AuthID + } + EditUriBackendConfig.merge_in_advanced_options(scope, opts, false); + + return AppUtils.format('{0}://{1}/{2}{3}', + scope.Backend.Key, + scope.Server, + scope.Path || '', + AppUtils.encodeDictAsUrl(opts) + ); + }; + EditUriBackendConfig.builders['googledrive'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.builders['onedrive'] = function() { return this['oauth-base'].apply(this, arguments); }; EditUriBackendConfig.builders['onedrivev2'] = function() { return this['oauth-base'].apply(this, arguments); }; @@ -916,8 +989,64 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, return url; } + + EditUriBackendConfig.builders['smb'] = function (scope) { + var opts = { + 'transport': scope.Transport, + 'auth-domain': scope.Domain + }; + + EditUriBackendConfig.merge_in_advanced_options(scope, opts, true); + // Remove trailing and leading slashes + if (scope.ShareName) + scope.ShareName = scope.ShareName.replace(/^[/\\]+/, '').replace(/[/\\]+$/, ''); + + if (scope.Path) + scope.Path = scope.Path.replace(/^[/\\]+/, '').replace(/[/\\]+$/, ''); + else + scope.Path = ''; + + return AppUtils.format('{0}://{1}/{2}/{3}/{4}', + scope.Backend.Key, + scope.Server, + scope.ShareName, + scope.Path, + AppUtils.encodeDictAsUrl(opts) + ); + } + + EditUriBackendConfig.builders['cifs'] = EditUriBackendConfig.builders['smb']; + + EditUriBackendConfig.builders['filen'] = function (scope) { + var opts = {}; + EditUriBackendConfig.merge_in_advanced_options(scope, opts, true); + // Remove trailing and leading slashes + if (scope.Path) + scope.Path = scope.Path.replace(/^[/\\]+/, '').replace(/[/\\]+$/, ''); + else + scope.Path = ''; + + return AppUtils.format('{0}://{1}{2}', + scope.Backend.Key, + scope.Path, + AppUtils.encodeDictAsUrl(opts) + ); + } + + EditUriBackendConfig.validaters['smb'] = function (scope, continuation) { + if (EditUriBackendConfig.require_server(scope) + && EditUriBackendConfig.require_username(scope) + && EditUriBackendConfig.require_field(scope, 'ShareName', gettextCatalog.getString('Share name'))) + continuation(); + }; + + EditUriBackendConfig.validaters['smb'] = EditUriBackendConfig.validaters['cifs']; - + EditUriBackendConfig.validaters['filen'] = function (scope, continuation) { + if (EditUriBackendConfig.require_username_and_password(scope)) + EditUriBackendConfig.recommend_path(scope, continuation); + }; + EditUriBackendConfig.validaters['file'] = function (scope, continuation) { if (EditUriBackendConfig.require_path(scope)) continuation(); @@ -947,12 +1076,31 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, if (res) EditUriBackendConfig.recommend_path(scope, continuation); }; + + EditUriBackendConfig.validaters['webdav'] = function (scope, continuation) { + var res = EditUriBackendConfig.require_server(scope); + + if (res) + EditUriBackendConfig.recommend_username(scope, () => { + EditUriBackendConfig.recommend_path(scope, continuation); + }); + }; - EditUriBackendConfig.validaters['webdav'] = EditUriBackendConfig.validaters['ssh']; + EditUriBackendConfig.validaters['webdav'] = EditUriBackendConfig.validaters['webdav']; EditUriBackendConfig.validaters['cloudfiles'] = EditUriBackendConfig.validaters['ssh']; EditUriBackendConfig.validaters['tahoe'] = EditUriBackendConfig.validaters['ssh']; + EditUriBackendConfig.validaters['pcloud'] = function (scope, continuation) { + var res = + EditUriBackendConfig.require_server(scope) && + EditUriBackendConfig.require_field(scope, 'AuthID', gettextCatalog.getString('AuthID')); + if (res) + EditUriBackendConfig.recommend_path(scope, function() { + continuation(); + }); + }; + EditUriBackendConfig.validaters['authid-base'] = function (scope, continuation) { var res = EditUriBackendConfig.require_field(scope, 'AuthID', gettextCatalog.getString('AuthID')); @@ -972,7 +1120,8 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.validaters['onedrive'] = EditUriBackendConfig.validaters['authid-base']; EditUriBackendConfig.validaters['onedrivev2'] = EditUriBackendConfig.validaters['authid-base']; EditUriBackendConfig.validaters['sharepoint'] = EditUriBackendConfig.validaters['authid-base']; - + EditUriBackendConfig.validaters['pcloud'] = EditUriBackendConfig.validaters['pcloud']; + EditUriBackendConfig.validaters['msgroup'] = function (scope, continuation) { EditUriBackendConfig.validaters['authid-base'](scope, function () { @@ -1176,7 +1325,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, if(res && scope['storj_auth_method'] == 'API key' && scope['storj_secret'] != scope['storj_secret_verify']) res = EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('The encryption passphrases do not match')); - var re = new RegExp('^([a-z0-9]+([a-z0-9\-][a-z0-9])*)+(.[a-z0-9]+([a-z0-9\-][a-z0-9])*)*$'); + var re = new RegExp('^([a-z0-9]+([a-z0-9-][a-z0-9])*)+(.[a-z0-9]+([a-z0-9-][a-z0-9])*)*$'); if(res && scope['storj_bucket'] && (!re.test(scope['storj_bucket']) || !(scope['storj_bucket'].length > 2 && scope['storj_bucket'].length < 64))){ res = EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes')); } diff --git a/Duplicati/Server/webroot/ngax/scripts/services/ServerStatus.js b/Duplicati/Server/webroot/ngax/scripts/services/ServerStatus.js index a49552a0d..e55fc2a5d 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/ServerStatus.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/ServerStatus.js @@ -24,7 +24,8 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap updatedVersion: null, updateDownloadProgress: 0, proposedSchedule: [], - schedulerQueueIds: [] + schedulerQueueIds: [], + orderBy: null }; this.state = state; @@ -89,8 +90,14 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap return AppService.post('/serverstate/resume'); }; - this.pause = function (duration) { - return AppService.post('/serverstate/pause' + (duration == null ? '' : '?duration=' + duration)); + this.pause = function (duration, pauseTransfers) { + var query = ''; + if (duration != null && duration != 'infinite' && duration != '') + query += '?duration=' + duration; + if (pauseTransfers === true) + query += (query.length > 0 ? '&' : '?') + 'pauseTransfers=true'; + + return AppService.post('/serverstate/pause' + query); }; function notifyTaskCompleted(taskid) { @@ -342,7 +349,10 @@ backupApp.service('ServerStatus', function ($rootScope, $timeout, AppService, Ap const reconnect_websocket = function () { const websocketProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const w = new WebSocket(`${websocketProtocol}//${window.location.host}/notifications?token=${AppService.access_token}`); + const parts = window.location.pathname.split('/').slice(0, -2); // Remove the last two segments (e.g., index.html and ngax) + const websocketPath = parts.join('/') + '/notifications'; + + const w = new WebSocket(`${websocketProtocol}//${window.location.host}${websocketPath}?token=${AppService.access_token}`); w.addEventListener("message", (event) => { const status = JSON.parse(event.data); handleServerState({data: status}); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js index 12f05143a..e96cc6626 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js @@ -49,7 +49,8 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ 'webdav': null, 'openstack': gettextCatalog.getString('OpenStack Object Storage / Swift'), 's3': gettextCatalog.getString('S3 Compatible'), - 'aftp': gettextCatalog.getString('FTP (Alternative)') + 'aftp': gettextCatalog.getString('FTP (Alternative)'), + 'smb': gettextCatalog.getString('SMB / CIFS'), }, local: {'file': null}, prop: { @@ -76,7 +77,9 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ 'jottacloud': null, 'rclone': null, 'cos': null, - 'aliyunoss': null + 'aliyunoss': null, + 'pcloud': null, + 'filen': null, } }; diff --git a/Duplicati/Server/webroot/ngax/styles/dark.css b/Duplicati/Server/webroot/ngax/styles/dark.css index 5c51db7dd..17956dc1f 100644 --- a/Duplicati/Server/webroot/ngax/styles/dark.css +++ b/Duplicati/Server/webroot/ngax/styles/dark.css @@ -1,4 +1,4 @@ -@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Light-webfont.eot');src:url('../fonts/ClearSans-Light-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Light-webfont.woff') format('woff'),url('../fonts/ClearSans-Light-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Light-webfont.svg#clear_sans_lightregular') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Regular-webfont.eot');src:url('../fonts/ClearSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Regular-webfont.woff') format('woff'),url('../fonts/ClearSans-Regular-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Regular-webfont.svg#clear_sansregular') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Medium-webfont.eot');src:url('../fonts/ClearSans-Medium-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Medium-webfont.woff') format('woff'),url('../fonts/ClearSans-Medium-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Medium-webfont.svg#clear_sans_mediumregular') format('svg');font-weight:500;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Bold-webfont.eot');src:url('../fonts/ClearSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Bold-webfont.woff') format('woff'),url('../fonts/ClearSans-Bold-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Bold-webfont.svg#clear_sansbold') format('svg');font-weight:700;font-style:normal}form.styled div.leftflush input{width:auto;margin-top:10px}form.styled div.leftflush label{width:auto;min-width:190px}form.styled label{display:block;width:190px;float:left;line-height:37px}form.styled input,form.styled select,form.styled textarea{color:#b0b0b0;font-size:16px;font-weight:300;float:left;display:block;border:1px #d8d8d8 solid;border-radius:2px;width:420px}form.styled input:focus,form.styled select:focus,form.styled textarea:focus{border:1px #a5a5a5 solid}form.styled .input{padding-bottom:18px;overflow:hidden}form.styled .input.password input,form.styled .input.select>select+input,form.styled .input.text input{height:35px;line-height:35px;padding:0 12px}form.styled .input.text.text-browse input{width:375px;border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}form.styled .input.text.text-browse a.browse{width:45px;display:block;float:left;height:37px;border-radius:2px;border-top-left-radius:0;border-bottom-left-radius:0;color:#fff;background:#2a89c0;line-height:37px}form.styled .input.text.text-browse a.browse:hover{background:#184d6c}form.styled .input.textarea textarea{height:130px;padding:10px 12px}form.styled .input.select select{--height:38px;width:446px;padding:0 12px;appearance:menulist-button;background:#fff;border-radius:2px;height:var(--height);line-height:var(--height)}form.styled .buttons{overflow:hidden;float:right}form.styled .buttons a,form.styled .buttons input{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}form.styled .buttons input{padding:4px 15px}form.styled .buttons a:hover,form.styled .buttons input:hover{background:#133e57}@media (width <= 480px){form.styled input,form.styled select,form.styled textarea{font-size:15px}}/*! +@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Light-webfont.eot');src:url('../fonts/ClearSans-Light-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Light-webfont.woff') format('woff'),url('../fonts/ClearSans-Light-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Light-webfont.svg#clear_sans_lightregular') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Regular-webfont.eot');src:url('../fonts/ClearSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Regular-webfont.woff') format('woff'),url('../fonts/ClearSans-Regular-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Regular-webfont.svg#clear_sansregular') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Medium-webfont.eot');src:url('../fonts/ClearSans-Medium-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Medium-webfont.woff') format('woff'),url('../fonts/ClearSans-Medium-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Medium-webfont.svg#clear_sans_mediumregular') format('svg');font-weight:500;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Bold-webfont.eot');src:url('../fonts/ClearSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Bold-webfont.woff') format('woff'),url('../fonts/ClearSans-Bold-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Bold-webfont.svg#clear_sansbold') format('svg');font-weight:700;font-style:normal}form.styled div.leftflush input{width:auto;margin-top:10px}form.styled div.leftflush label{width:auto;min-width:190px}form.styled label{display:block;width:190px;float:left;line-height:37px}form.styled input,form.styled select,form.styled textarea{color:#b0b0b0;font-size:16px;font-weight:300;float:left;display:block;border:1px #d8d8d8 solid;border-radius:2px;width:420px}form.styled input:focus,form.styled select:focus,form.styled textarea:focus{border:1px #a5a5a5 solid}form.styled .input{padding-bottom:18px;overflow:hidden}form.styled .input.password input,form.styled .input.select>select+input,form.styled .input.text input{height:35px;line-height:35px;padding:0 12px}form.styled .input.text.text-browse input{width:375px;border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}form.styled .input.text.text-browse a.browse{width:45px;display:block;float:left;height:37px;border-radius:2px;border-top-left-radius:0;border-bottom-left-radius:0;color:#fff;background:#2a89c0;line-height:37px}form.styled .input.text.text-browse a.browse:hover{background:#184d6c}form.styled .input.textarea textarea{height:130px;padding:10px 12px}form.styled .input.select select{--height:38px;width:446px;padding:0 12px;-webkit-appearance:menulist-button;-moz-appearance:menulist-button;appearance:menulist-button;background:#fff;border-radius:2px;height:var(--height);line-height:var(--height)}form.styled .buttons{overflow:hidden;float:right}form.styled .buttons a,form.styled .buttons input{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}form.styled .buttons input{padding:4px 15px}form.styled .buttons a:hover,form.styled .buttons input:hover{background:#133e57}@media (width <= 480px){form.styled input,form.styled select,form.styled textarea{font-size:15px}}/*! * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}*{font-family:'Clear Sans',sans-serif}body,html{margin:0;padding:0;height:100%}h1,h2{font-weight:300;color:#609301}h1{margin:10px 0}h3{font-weight:400}a{text-decoration:none}button{border:none}ul{list-style:none;margin:0;padding:0}hr{border:none;border-bottom:1px #ddd solid}textarea{max-width:94%}.external-link-image{display:inline-block;margin-left:8px;margin-right:8px;height:16px;width:16px;background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.external-link-image{background-image:url('../img/external-link-hover_3x.png')}}a .external-link-image{background:url('../img/external-link.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){a .external-link-image{background-image:url('../img/external-link_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){a .external-link-image{background-image:url('../img/external-link_3x.png')}}.header a:hover .external-link-image{background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_3x.png')}}.button{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.button:hover{background:#216b96}#folder_path_picker,#restore_file_picker,.step3 source-folder-picker{display:block;border:1px solid #d3d3d3;padding:2px;height:100%;overflow:scroll;box-sizing:border-box}.not-clickable{cursor:default!important}.not-clickable div,.not-clickable span,.not-clickable>a{cursor:default!important}.ui-match{font-weight:700;color:#006400}wait-area{min-width:350px;text-align:center;display:block}.prewrapped-text{white-space:pre-wrap}.exceptiontext{background-color:#d3d3d3;color:#000}.backup-result{width:90%;display:grid;grid-template-columns:50% 50%;grid-auto-rows:minmax(50px,auto);margin:0 auto}.backup-result div .horizontal-rule{width:100%;border-bottom:1px solid #d8d8d8;margin:5px 0}.backup-result .box{margin:10px;margin-bottom:0}.backup-result .title{color:#3f6001;font-weight:700;font-size:30px}.backup-result .item{display:block}.backup-result .item .key{color:#609301;font-weight:700}.backup-result .item .value{color:#b0b0b0}.backup-result .item .expanded{padding:0 10px 0 18px;margin-bottom:10px}.backup-result .one{border-right:1px solid #d8d8d8;grid-column:1;grid-row:1}.backup-result .two{grid-column:2;grid-row:1}.backup-result .wide{grid-column:span 2;border-top:1px solid #d8d8d8;padding-top:10px}.backup-result .three{grid-row:2}.backup-result .four{grid-row:3;margin-bottom:10px}.backup-result .four .log-expand-copy{display:flex;margin-bottom:6px}.backup-result .four .log-expand-copy a{margin-left:auto}.backup-result .four textarea{width:100%;max-width:99%;min-height:420px;padding:8px 6px;white-space:pre}.success-color{color:#390}.error-color{color:#c00}.warning-color{color:#fc0}.fatal-color{color:#900}ul.tabs{margin-bottom:10px}ul.tabs>li{display:inline;margin-right:10px;border:1px solid #2a89c0;padding:5px}ul.tabs>li.active{background-color:#2a89c0;color:#fff}ul.tabs>li.active>a{background-color:#2a89c0;color:#fff}ul.tabs>li.active.disabled{border:1px solid #d3d3d3;background-color:#d3d3d3;color:grey;cursor:default}ul.tabs>li.active.disabled>a{background-color:#d3d3d3;color:grey;cursor:default}.licenses>ul{list-style:initial;margin:10px;margin-left:20px}.licenses li{margin-bottom:10px}.licenses a.itemlink{font-weight:700}.logpage ul.entries{list-style:initial;margin:10px;margin-left:20px}.logpage .entries div.entryline.clickable{cursor:pointer}.logpage .entries.livedata li.expanded{height:auto}.logpage .button{text-align:center;margin-right:10px;border:1px solid #2a89c0;padding:5px;background-color:#2a89c0;color:#fff;cursor:pointer}.exportpage .checkbox input{width:auto;margin-top:10px}.exportpage .commandline div{background-color:#d3d3d3;color:#000}.themelink{margin-left:20px}ul.notification{position:fixed;bottom:0;left:0;right:0;margin:auto;width:480px}.notification .title{border:1px solid #2a89c0;background-color:#2a89c0;border-radius:5px 5px 0 0;padding:2px;padding-left:5px;padding-right:5px;font-weight:700;color:#d3d3d3;width:100%;text-align:center;clear:both}.notification .content{background-color:#fff;border:1px solid #2a89c0;border-radius:0 0 5px 5px;padding:2px;padding-left:5px;padding-right:5px;width:100%}.notification .message{width:100%;color:#000}.notification .button{padding:2px 10px;margin-top:6px}.notification .clear{clear:right;height:1px}.notification .error .title{border-color:red;background-color:red}.notification .error .content{border-color:red}.notification .error .button{border-color:red;background-color:red}.notification .warning .title{background-color:orange;border-color:orange}.notification .warning .button{background-color:orange;border-color:orange}.notification .warning .content{border-color:orange}.filepicker{height:200px}.resizable{margin-bottom:6px;max-width:100%}.advanced-toggle{float:right;margin-right:25px;line-height:37px}.advancedoptions li{clear:both;margin-bottom:10px;padding:10px 0;border-top:1px #d3d3d3 solid}.advancedentry .multiple{display:inline}.advancedentry .shortname{font-weight:700}.advancedentry input[type=text]{width:300px}.advancedentry select{width:300px}.advancedentry input[type=checkbox]{margin-top:13px;width:auto}.advancedentry .delete-item{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.advancedentry .longdescription{--margin-block:10px;margin-top:var(--margin-block);margin-left:190px;clear:both;font-style:italic;white-space:pre-wrap}.advancedentry .longdescription .longdescription__item{margin-block:0 var(--margin-block)}.advancedentry .longdescription .longdescription__default{margin-block:var(--margin-block) 0}.settings div.sublabel{clear:both;padding:0 31px;font-style:italic}.logo img.mainlogo{height:64px;width:64px;float:left;padding-right:8px;padding-top:2px}.logo div.logotext{float:left}.logo a{float:left;display:block;line-height:normal}.logo div.build-suffix{clear:both;display:inline;float:left;font-size:16px;line-height:16px}.logo div.powered-by{font-size:16px;margin:0;line-height:16px;float:left;padding:0;margin-left:5px}.note p{margin-block:0.5rem}.note p:first-child{margin-top:0}.note p:last-child{margin-bottom:0}.fixed-width-font{font-family:monospace}.warning{margin:10px;font-style:italic;color:#f49b42}div.captcha .details{padding-top:10px;margin-left:auto;margin-right:auto;width:180px}div.captcha .code{background:#d3d3d3;color:#000;font-family:monospace;font-size:xx-large;padding:10px}div.captcha .answer{margin-top:16px}.centered-text{text-align:center}body{color:#b0b0b0}body .container{min-height:100%;position:relative}body .container .header{line-height:70px;background:#ededed;overflow:hidden;height:70px;position:fixed;top:0;left:0;right:0;z-index:100}body .container .header a{color:#2a89c0}body .container .header a.active,body .container .header a:hover{color:#707070}body .container .header button{width:26px;height:26px;background-size:26px;cursor:pointer}body .container .header .logo{font-size:30px;font-weight:700;float:left;padding-left:40px}body .container .header .statepadding{padding-right:90px;margin-left:320px}body .container .header .state{float:left;color:#3f6001;width:595px;padding:13px 15px;margin:10px 20px;border:1px #3f6001 solid;font-weight:300;font-size:18px;overflow:hidden;line-height:normal;display:inline-block;background-color:#fff;text-overflow:ellipsis;position:relative;height:25px}body .container .header .state strong{display:inline;margin-right:10px}body .container .header .state span{display:inline}body .container .header .state .button{position:static;margin-top:70px}body .container .header .state .content{position:relative;z-index:10;margin-right:40px;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}body .container .header .state .buttons{position:absolute;right:0;top:0;bottom:0;width:26px;margin:13px 15px}body .container .header .state .buttons button{display:block}body .container .header .state .buttons .stop{background:url('../img/progress-stop.png');background-size:100%;z-index:10;position:relative}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_3x.png')}}body .container .header .state .buttons .resume{background:url('../img/progress-resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_3x.png')}}body .container .header .state .progress-bar{position:absolute;top:0;bottom:0;left:0;background:rgba(96,147,1,.25);z-index:5}body .container .header .state .task-name{overflow:hidden;text-overflow:ellipsis;cursor:help}body .container .header .state .task-state-info{display:flex}body .container .header .action-icons{display:inline-block;line-height:normal;margin:10px 0;padding:13px 0;float:left}body .container .header .action-icons-small{display:none;float:right;margin-top:21px;line-height:normal}body .container .header .action-icons-small>button,body .container .header .action-icons>button{display:inline-block}body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background:url('../img/pause.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_3x.png')}}body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background:url('../img/resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_3x.png')}}body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background:url('../img/throttle.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_3x.png')}}body .container .header .action-icons-small>.throttle.inactive,body .container .header .action-icons>.throttle.inactive{opacity:.5}body .container .header .about-header{float:right;padding-right:20px;overflow:hidden}body .container .header .about-header ul{overflow:hidden;list-style:none}body .container .header .about-header ul li{float:right;padding-right:20px}body .container .body{width:100%;overflow:hidden;min-height:500px;padding-top:120px;padding-bottom:70px}body .container .body a{color:#2a89c0}body .container .body .mainmenu{width:260px;padding-left:40px;float:left;position:fixed}body .container .body .mainmenu>ul>li{position:relative}body .container .body .mainmenu>ul>li>a{font-size:22px;font-weight:300;padding:5px 10px 5px 55px;display:block}body .container .body .mainmenu>ul>li>a:hover{color:#fff}body .container .body .mainmenu>ul>li>a.active{color:#fff}body .container .body .mainmenu>ul>li>a.add{background:url('../img/mainmenu/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore{background:url('../img/mainmenu/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume{background:url('../img/mainmenu/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings{background:url('../img/mainmenu/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout{background:url('../img/mainmenu/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home{background:url('../img/mainmenu/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about{background:url('../img/mainmenu/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_3x.png')}}body .container .body .mainmenu>ul>li>a.home.active{background:#5bacdb url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.add.active{background:#5bacdb url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore.active{background:#5bacdb url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume.active{background:#5bacdb url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings.active{background:#5bacdb url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.about.active{background:#5bacdb url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul>li>a.add:hover{background:#2a89c0 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore:hover{background:#2a89c0 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume:hover{background:#2a89c0 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings:hover{background:#2a89c0 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout:hover{background:#2a89c0 url('../img/mainmenu/over/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home:hover{background:#2a89c0 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about:hover{background:#2a89c0 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul li.hr-top{padding-top:25px;margin-top:25px;border-top:1px #ededed solid}body .container .body div.contextmenu_container{position:relative}body .container .body .contextmenu{display:none;position:absolute;background:#fff;border:1px #ededed solid;box-shadow:0 4px 8px #000;z-index:200;padding:5px}body .container .body .contextmenu li a{color:#2a89c0;font-size:15px;font-weight:400;display:block;min-width:200px;padding:4px 10px;white-space:nowrap;padding-left:45px;overflow:hidden;text-overflow:ellipsis}body .container .body .contextmenu li a:hover{background:#2a89c0;color:#fff}body .container .body .contextmenu.open{display:block}body .container .body .content{float:left;padding-left:350px;padding-bottom:50px;max-width:70%}body .container .body .content ul.tabs>li{display:inline-block}body .container .body .content .tasks .tasklist .task{border-top:1px solid #eee;padding-top:20px;margin-bottom:25px}body .container .body .content .tasks .tasklist .task:last-child{border-bottom:1px solid #eee;padding-bottom:20px}body .container .body .content .tasks .tasklist .task:first-child{padding-top:0;border-top:0 none}body .container .body .content .tasks .tasklist .progress-small{text-align:center;height:18px;background:#a4d105}body .container .body .content .tasks .tasklist .progress-small-bg{border:1px #65b1dd solid;width:200px}body .container .body .content .tasks .tasklist a{font-size:30px;font-weight:300;display:inline-block}body .container .body .content .tasks .tasklist a.action-link{font-size:14px;background:0 0;padding-left:0}body .container .body .content .tasks .tasklist dl{padding-left:55px;overflow:hidden;font-size:14px}body .container .body .content .tasks .tasklist dl dd,body .container .body .content .tasks .tasklist dl dt{display:block;float:left}body .container .body .content .tasks .tasklist dl dt{clear:both;font-weight:500;margin-bottom:5px}body .container .body .content .tasks .tasklist dl dd{margin-left:10px}body .container .body .content .tasks .tasklist dl.taskmenu p{display:inline;margin-right:10px;color:#2a89c0;cursor:pointer}body .container .body .content .tasks .tasklist dl.taskmenu dt{float:left;margin-right:10px;margin-bottom:0;padding:5px 8px;color:#b0b0b0;cursor:pointer;clear:none}body .container .body .content .tasks .tasklist dl.taskmenu dd{clear:both;float:none;padding-bottom:8px;border-bottom:1px #ddd solid;margin-bottom:5px}body .container .body .content div.add,body .container .body .content div.restore{--legends-width:700px;--legends-padding-left:calc(calc(700px - var(--legends-width)) / 2);--circle-width:43px;--step-width:calc(var(--legends-width) / var(--legends-steps))}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{margin-left:calc(calc(calc(var(--step-width) - var(--circle-width))/ 2) + var(--legends-padding-left))}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{padding-left:calc(var(--step-width) - var(--circle-width));padding-right:0}body .container .body .content div.add .steps button:first-child,body .container .body .content div.add .steps div:first-child,body .container .body .content div.restore .steps button:first-child,body .container .body .content div.restore .steps div:first-child{padding-left:unset}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{padding-left:var(--legends-padding-left)}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{width:var(--step-width)}body .container .body .content div.add{--legends-steps:5}body .container .body .content div.restore{--legends-steps:2}body .container .body .content div.restore.restore-direct{--legends-steps:4}body .container .body .content div.restore.restore-direct .steps-legend{padding-left:20px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{width:100%;overflow:hidden}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{float:left;background:url('../img/steps/line-out.png') no-repeat top left;background-size:485px 24px;color:#c7e5f6}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_3x.png')}}body .container .body .content div.add .steps button span,body .container .body .content div.add .steps div span,body .container .body .content div.restore .steps button span,body .container .body .content div.restore .steps div span{--size:35px;display:block;border-width:4px;border-style:solid;border-color:#c7e5f6;background:#fff;border-radius:50%;width:var(--size);height:var(--size);text-align:center;font-size:22px;line-height:var(--size);cursor:pointer}body .container .body .content div.add .steps button.active,body .container .body .content div.add .steps div.active,body .container .body .content div.restore .steps button.active,body .container .body .content div.restore .steps div.active{color:#2a89c0}body .container .body .content div.add .steps button.active span,body .container .body .content div.add .steps div.active span,body .container .body .content div.restore .steps button.active span,body .container .body .content div.restore .steps div.active span{border-color:#2a89c0;background:#2a89c0;color:#fff}body .container .body .content div.add .steps button.active h2,body .container .body .content div.add .steps div.active h2,body .container .body .content div.restore .steps button.active h2,body .container .body .content div.restore .steps div.active h2{color:#2a89c0}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{overflow:hidden;padding-bottom:50px;list-style:none;margin:0}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{color:#c7e5f6;font-size:18px;text-align:center;float:left;padding-top:10px;cursor:pointer}body .container .body .content div.add .steps-legend li.active,body .container .body .content div.restore .steps-legend li.active{color:#2a89c0}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes{padding-left:40px}body .container .body .content div.add .steps-boxes .step,body .container .body .content div.restore .steps-boxes .step{display:none}body .container .body .content div.add .steps-boxes .step.active,body .container .body .content div.restore .steps-boxes .step.active{display:block}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:left;margin-left:20px;color:#b0b0b0}body .container .body .content div.add .steps-boxes .box.browser .checklinks a i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a i{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive{color:#e3e3e3;cursor:default}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive i{border-color:#e3e3e3}body .container .body .content div.add .steps-boxes .box.browser .checklinks a:first-child,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a:first-child{margin-left:0}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton{padding-top:10px;max-width:100%}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton input#sourcePath,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton input#sourcePath{width:100%;box-sizing:border-box;height:37px}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton a.button,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton a.button{top:10px}body .container .body .content div.add .steps-boxes .box.filters .input.link a,body .container .body .content div.restore .steps-boxes .box.filters .input.link a{color:#b0b0b0}body .container .body .content div.add .steps-boxes .box.filters .input.link a i,body .container .body .content div.restore .steps-boxes .box.filters .input.link a i{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist{overflow:hidden;padding-bottom:15px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li{overflow:hidden;clear:both;padding-bottom:25px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li select,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li select{width:200px;margin-right:5px;height:36px;line-height:36px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li input,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li input{width:calc(100% - 280px);padding:5px}body .container .body .content div.add .steps-boxes .step1 li.strength.score-0,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-0{color:red}body .container .body .content div.add .steps-boxes .step1 li.strength.score-1,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-1{color:#f70}body .container .body .content div.add .steps-boxes .step1 li.strength.score-2,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-2{color:#aa0}body .container .body .content div.add .steps-boxes .step1 li.strength.score-3,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-3{color:#070}body .container .body .content div.add .steps-boxes .step1 li.strength.score-4,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-4{color:#427e27}body .container .body .content div.add .steps-boxes .step1 li.strength.score-x,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-x{color:red}body .container .body .content div.add .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.add .steps-boxes .step5 div.input.maxSize input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.maxSize input.number{width:60px}body .container .body .content div.add .steps-boxes .step5 .hint,body .container .body .content div.add .steps-boxes .step5 .retention-options,body .container .body .content div.restore .steps-boxes .step5 .hint,body .container .body .content div.restore .steps-boxes .step5 .retention-options{clear:both;margin-left:190px;margin-top:50px;font-style:italic}body .container .body .content div.add .steps-boxes .step5 .retention-options input,body .container .body .content div.restore .steps-boxes .step5 .retention-options input{margin-bottom:10px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions{padding-top:15px;clear:both}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li{border-top:none}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li.advancedentry,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li.advancedentry{border-bottom:1px solid #d3d3d3}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child{padding-top:0}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child select{max-width:400px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions label,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions label{line-height:normal}body .container .body .content div.add .steps-boxes .step5 .advancedoptions input,body .container .body .content div.add .steps-boxes .step5 .advancedoptions select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions input,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions select{width:auto;max-width:100%;box-sizing:border-box}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle{color:#b0b0b0;line-height:normal;margin-top:16px;clear:both;float:left}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle i.fa,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle i.fa{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .step5 textarea,body .container .body .content div.restore .steps-boxes .step5 textarea{box-sizing:border-box;clear:both;margin-top:15px;width:100%}body .container .body .content div.add form,body .container .body .content div.restore form{padding-bottom:50px;overflow:hidden}body .container .body .content div.add form .input.password .tools,body .container .body .content div.restore form .input.password .tools{clear:both;padding-left:190px;padding-top:10px}body .container .body .content div.add form .input.password .tools ul,body .container .body .content div.restore form .input.password .tools ul{overflow:hidden}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{float:left;padding-right:7px}body .container .body .content div.add form .input.password .tools ul li.strength.useless,body .container .body .content div.restore form .input.password .tools ul li.strength.useless{color:red}body .container .body .content div.add form .input.password .tools ul li.strength.average,body .container .body .content div.restore form .input.password .tools ul li.strength.average{color:#ff0}body .container .body .content div.add form .input.password .tools ul li.strength.good,body .container .body .content div.restore form .input.password .tools ul li.strength.good{color:#2a89c0}body .container .body .content div.add form .input.multiple input,body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple input,body .container .body .content div.restore form .input.multiple select{width:auto;margin-right:5px}body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple select{--padding-block:5px;padding:var(--padding-block) 12px;line-height:calc(var(--height) - calc(var(--padding-block) * 2))}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{overflow:hidden;position:relative;max-width:446px}body .container .body .content div.add form .input.overlayButton input,body .container .body .content div.restore form .input.overlayButton input{width:347px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{position:absolute;top:0;right:0;padding:7px 12px 8px}body .container .body .content div.add form .input.checkbox.multiple strong,body .container .body .content div.restore form .input.checkbox.multiple strong{display:block;padding-bottom:5px}body .container .body .content div.add form .input.checkbox.multiple label,body .container .body .content div.restore form .input.checkbox.multiple label{display:inline-block;float:none;width:auto;padding-right:10px}body .container .body .content div.add form .input.checkbox.multiple input,body .container .body .content div.restore form .input.checkbox.multiple input{width:auto;display:inline-block;float:none}body .container .body .content div.add form .buttons,body .container .body .content div.restore form .buttons{float:none;width:635px;padding-top:30px}body .container .body .content .commandline .input.select,body .container .body .content div.add .step2 .input.select,body .container .body .content div.restore .step1 .input.select{display:grid;grid-auto-flow:column;justify-content:flex-start;grid-template-areas:"label server" ". custom"}body .container .body .content .commandline .input.select label,body .container .body .content div.add .step2 .input.select label,body .container .body .content div.restore .step1 .input.select label{grid-area:label}body .container .body .content .commandline .input.select select,body .container .body .content div.add .step2 .input.select select,body .container .body .content div.restore .step1 .input.select select{grid-area:server}body .container .body .content .commandline .input.select input,body .container .body .content div.add .step2 .input.select input,body .container .body .content div.restore .step1 .input.select input{grid-area:custom;margin-top:10px}body .container .body .content .commandline .input.text #generic_server,body .container .body .content div.add .step2 .input.text #generic_server,body .container .body .content div.restore .step1 .input.text #generic_server{width:335px}body .container .body .content .commandline .input.text #generic_port,body .container .body .content div.add .step2 .input.text #generic_port,body .container .body .content div.restore .step1 .input.text #generic_port{width:50px;margin-left:10px}body .container .body .content div.headerthreedotmenu{margin:20px 0}body .container .body .content div.headerthreedotmenu h2{display:inline}body .container .body .content div.headerthreedotmenu .contextmenu_container{float:right}body .container .body .content div.headerthreedotmenu .contextmenu{left:auto;right:0;top:auto}body .container .body .content div.headerthreedotmenu .threedotmenubutton{padding:5px}body .container .body .content .expandable{margin:20px 0}body .container .body .content .expandable h2{display:inline}body .container .body .content .expandable img{padding:0 6px}body .container .body .content div.settings .input.checkbox input.checkbox,body .container .body .content div.settings .input.mixed.multiple input.checkbox{width:auto}body .container .body .content div.settings .input.checkbox select,body .container .body .content div.settings .input.mixed.multiple select{width:auto;margin-right:5px}body .container .body .content div.settings .input.checkbox label,body .container .body .content div.settings .input.mixed.multiple label{line-height:normal;padding:0 15px;width:auto}body .container .body .content .logpage ul.tabs{padding:15px 0}body .container .body .content .logpage ul.entries li{padding:10px 0;border-bottom:1px solid #d8d8d8}body .container .body .content .logpage ul.backuplog{list-style:none}body .container .body .content .about-general .about-general__block{margin-block:1rem}body .container .body .content .about-general .about-general__block:first-child{margin-top:10px}body .container .body .content .about-general .about-general__block:last-child{margin-bottom:0}body .container .body .content .prewrapped-text{white-space:pre-wrap;overflow-x:auto}body .container .footer{background:#ededed;min-height:70px;line-height:70px;overflow:hidden;position:absolute;bottom:0;width:100%}body .container .footer a{color:#2a89c0}body .container .footer .about-footer{float:left;overflow:hidden;padding-right:20px;display:none}body .container .footer .about-footer span{display:block;float:left;padding-left:20px}body .container .footer .about-footer ul{float:left}body .container .footer .about-footer li{float:left;padding-left:20px}body .container .footer .social{float:right}body .container .footer .social ul{overflow:hidden;float:right;padding-left:20px;padding-right:10px}body .container .footer .social ul li{float:right;margin-right:10px;padding-top:5px}body .container .footer .social ul li img{opacity:.6}body .container .footer .social ul li img:hover{opacity:1}body .container .footer .themelink{float:right;padding-right:20px}body #modal-menu{max-width:400px}body #modal-menu a{color:#2a89c0;font-size:20px;line-height:40px}.remodal{padding:30px;box-shadow:0 2px 7px #000;background:#fff;display:none}.remodal form .buttons{float:none}.remodal-wrapper .remodal{display:block}span.info{font-size:10px;font-weight:500;display:inline-block;background:#2a89c0;border-radius:50%;width:15px;height:15px;vertical-align:super;color:#fff;line-height:15px;margin-left:5px;text-align:center}.hidden{display:none}.clear{clear:both}.nofloat{float:none!important}div.blocker,div.connection-lost,div.modal-dialog{position:fixed;inset:0;margin:auto}div.blocker{z-index:5000;background-color:#000;opacity:.65}#connection-lost-blocker{z-index:5100}#connection-lost-dialog{z-index:5200}div.connection-lost,div.modal-dialog{z-index:5001;display:-webkit-box;display:-moz-box;display:flexbox;display:flex;-webkit-box-pack:center;-moz-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-moz-box-align:center;-ms-flex-align:center;align-items:center}div.connection-lost div.info,div.modal-dialog div.info{min-width:310px;max-width:650px;margin:5px}div.connection-lost div.title,div.modal-dialog div.title{border:1px solid #65b1dd;background-color:#65b1dd;border-radius:5px 5px 0 0;padding:10px 20px;font-weight:700;color:#d3d3d3;text-align:center}div.connection-lost div.content,div.modal-dialog div.content{background-color:#fff;border:1px solid #fff;padding:20px}div.connection-lost div.content p:first-child,div.modal-dialog div.content p:first-child{margin-top:0}div.connection-lost div.content p:last-child,div.modal-dialog div.content p:last-child{margin-bottom:0}div.connection-lost .buttons,div.modal-dialog .buttons{border-radius:0 0 5px 5px;padding-top:10px;overflow:auto}div.connection-lost form,div.modal-dialog form{margin-top:15px}div.connection-lost form textarea,div.modal-dialog form textarea{height:130px;width:420px;padding:10px 12px;border:1px #d8d8d8 solid;border-radius:2px;color:#b0b0b0;font-size:16px;font-weight:300}div.connection-lost form input,div.modal-dialog form input{height:35px;line-height:35px;padding:0 12px}div.modal-dialog .content.buttons ul{float:right}div.modal-dialog .content.buttons .tooltipped{position:relative}div.modal-dialog .content.buttons .tooltipped::after{position:absolute;z-index:1000000;display:none;padding:5px 8px;font:normal normal 11px/1.5 Helvetica,arial,nimbussansl,liberationsans,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol";color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#000;border-radius:3px;-webkit-font-smoothing:subpixel-antialiased}div.modal-dialog .content.buttons .tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:#000;pointer-events:none;content:"";border:5px solid transparent}div.modal-dialog .content.buttons .tooltipped:active::after,div.modal-dialog .content.buttons .tooltipped:active::before,div.modal-dialog .content.buttons .tooltipped:focus::after,div.modal-dialog .content.buttons .tooltipped:focus::before,div.modal-dialog .content.buttons .tooltipped:hover::after,div.modal-dialog .content.buttons .tooltipped:hover::before{display:inline-block;text-decoration:none}div.modal-dialog .content.buttons .tooltipped-w::after{right:100%;bottom:50%;margin-right:5px;transform:translateY(50%)}div.modal-dialog .content.buttons .tooltipped-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:#000}.importpage form.styled input{margin-top:11px;margin-bottom:11px}.addwizard form.styled ul,.restorewizard form.styled ul{margin:20px;margin-left:0}.addwizard form.styled input[type=radio],.restorewizard form.styled input[type=radio]{width:20px;margin-left:5px;margin-right:5px}.addwizard form.styled label,.restorewizard form.styled label{width:auto;line-height:normal}.addwizard form.styled div.subtext,.restorewizard form.styled div.subtext{clear:both;margin-left:30px;padding-top:5px;color:#d6d6d6}.pauseoptions form.styled li{line-height:normal;padding:0}.pauseoptions form.styled li input{height:auto;margin-top:8px;margin-right:8px;width:auto}.progress-bar-striped{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress-striped .progress-bar{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress{position:relative;min-height:25px}.progress>span{vertical-align:middle;display:block;width:100%;height:100%;text-align:center;z-index:100;padding-top:2px}.progress.active .progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress .progress-bar{float:left;width:0;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;box-shadow:inset 0 -1px 0 #000;transition:width .6s ease;height:100%;position:absolute;top:0}.progress .progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.tree-view{list-style-type:none;margin-left:10px;padding-bottom:5px}.tree-view ul{margin-left:16px}.tree-view span.nodeLabel{cursor:pointer}.tree-view span.nodeLabel.selected{border:1px solid #aaa;background-color:#ddd;padding:1px 3px}.tree-view li .node{padding-bottom:5px}.tree-view li div.selected{border-color:#add8e6;background-color:#add8e6}.tree-view li>ul{display:none}.tree-view li>ul.expanded{display:block}.tree-view li a.nav{cursor:pointer;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:-80px 0;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.nav.leaf{background:0 0}.tree-view li a.nav.expanded{background-position:-80px -16px}.tree-view li a.type{cursor:auto;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 -16px;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.type.invisible{background-position:0 -32px}.tree-view li a.type.loading{cursor:progress;background-image:url("../img/loader-16.gif");background-repeat:no-repeat;background-position:0 0;background-size:16px 16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type.loading{background-image:url('../img/loader-32.gif')}}.tree-view li a.type.x-tree-icon-drive{background-position:-16px -16px}.tree-view li a.type.x-tree-icon-leaf{background-position:-32px -16px}.tree-view li a.type.x-tree-icon-symlink{background-position:-48px -16px}.tree-view li a.type.x-tree-icon-userdata{background-position:-16px -48px}.tree-view li a.type.x-tree-icon-locked{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-broken{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-computer{background-position:0 -48px}.tree-view li a.type.x-tree-icon-hyperv{background-position:-96px -16px}.tree-view li a.type.x-tree-icon-hypervmachine{background-position:-96px 0}.tree-view li a.type.x-tree-icon-mssql{background-position:-96px -32px}.tree-view li a.type.x-tree-icon-mssqldb{background-position:-80px -32px}.tree-view li a.type.x-tree-icon-mydocuments{background-position:-32px -48px}.tree-view li a.type.x-tree-icon-mymusic{background-position:-48px -48px}.tree-view li a.type.x-tree-icon-mypictures{background-position:-64px -48px}.tree-view li a.type.x-tree-icon-desktop{background-position:-80px -48px}.tree-view li a.type.x-tree-icon-home{background-position:-96px -48px}.tree-view li a.type.x-tree-icon-drive.invisible{background-position:-16px -32px}.tree-view li a.type.x-tree-icon-leaf.invisible{background-position:-32px -32px}.tree-view li a.type.x-tree-icon-symlink.invisible{cursor:auto;background-position:-48px -32px}.tree-view li a.type.x-tree-icon-locked.invisible{background-position:-64px -32px}.tree-view li a.check{height:16px;width:16px;display:inline-block;cursor:pointer;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 0;vertical-align:middle;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.partial{background-position:-32px 0}.tree-view li a.include{background-position:-16px 0}.tree-view li a.exclude{background-position:-48px 0}.tree-view li a.root{background:0 0;display:none}.throttlesettings div.multiple select{width:auto;margin-right:5px}.throttlesettings div.multiple input{width:100px}.throttlesettings div.multiple input.checkbox{width:auto}.throttlesettings div.multiple label{line-height:35px;padding:0 15px;width:auto;min-width:150px}.throttlesettings .disabled{color:#f0f0f0}.throttlesettings .disabled input,.throttlesettings .disabled select{color:#f0f0f0}@media (width <= 1150px){body .container .header{height:140px}body .container .header .statepadding{padding-right:90px;margin-left:0}body .container .header .state{width:100%;margin:10px 40px;clear:left;float:left}body .container .header .action-icons{display:none}body .container .header .action-icons-small{display:inline-block}body .container .header .menubutton{display:block;font-size:18px;padding-right:50px;margin-top:5px;margin-right:15px;background:url('../img/menu.png') no-repeat right top;background-size:39px 39px;position:relative;height:40px;line-height:40px;color:#b0b0b0;float:right;top:10px;padding-left:20px;text-transform:uppercase;text-align:right}body .container .header .menubutton.active{background-image:url('../img/menu_active.png');background-size:39px 39px;color:#2a89c0}body .container .body{position:relative;padding-top:140px}body .container .body .mainmenu{display:none;position:fixed;background:none repeat scroll 0 0 #fff;box-shadow:0 4px 8px #000;left:10px;padding:20px;top:60px}body .container .body .mainmenu.mobile-open{display:block;left:auto;right:0;top:0;z-index:1000}body .container .body .contextmenu{left:0;top:auto}body .container .body .content{float:none;padding:20px;margin:0 auto 30px}body .container .body .content .state{width:auto}body .container .mobileOpen{display:block!important}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton{background-image:url('../img/menu_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton{background-image:url('../img/menu_3x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_3x.png')}}@media (width <= 768px){body .container .body .content .tasks .tasklist a{font-size:20px;background-size:24px;background-position:0 4px;padding-left:35px}body .container .body .content .tasks .tasklist dl{padding-left:35px}body .container .header .logo{padding-left:10px}body .container .header .statepadding{padding-right:50px}body .container .header .state{margin-left:10px}body .container .header .menubutton{margin-right:5px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps,body .container .body .content div.settings .steps{display:none}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend,body .container .body .content div.settings .steps-legend{list-style:decimal;padding-left:20px;border-bottom:1px solid #eee;margin-bottom:30px;padding-bottom:20px}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li,body .container .body .content div.settings .steps-legend li{float:none;font-weight:500;width:auto!important;padding-right:0!important}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes,body .container .body .content div.settings .steps-boxes{padding-left:0}body .container .body .content div.add form.styled .input input,body .container .body .content div.add form.styled .input select,body .container .body .content div.add form.styled .input textarea,body .container .body .content div.restore form.styled .input input,body .container .body .content div.restore form.styled .input select,body .container .body .content div.restore form.styled .input textarea,body .container .body .content div.settings form.styled .input input,body .container .body .content div.settings form.styled .input select,body .container .body .content div.settings form.styled .input textarea{max-width:100%;box-sizing:border-box}body .container .body .content div.add form.styled .input.select select,body .container .body .content div.restore form.styled .input.select select,body .container .body .content div.settings form.styled .input.select select{width:420px}body .container .body .content div.add form.styled .buttons,body .container .body .content div.restore form.styled .buttons,body .container .body .content div.settings form.styled .buttons{max-width:100%;width:auto}body .container .body .content div.add form.styled .tools,body .container .body .content div.restore form.styled .tools,body .container .body .content div.settings form.styled .tools{padding-left:0!important}body .container .body .content div.add form.styled .input.checkbox.multiple,body .container .body .content div.restore form.styled .input.checkbox.multiple,body .container .body .content div.settings form.styled .input.checkbox.multiple{padding-bottom:5px}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.add form.styled .input.checkbox.multiple label,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple label,body .container .body .content div.settings form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple label{display:block!important;float:left!important;line-height:normal}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple input{clear:both}body .container .body .content div.add form.styled .input.text.multiple input,body .container .body .content div.restore form.styled .input.text.multiple input,body .container .body .content div.settings form.styled .input.text.multiple input{max-width:48%!important}}@media (width <= 640px){body h2{font-size:20px;text-align:center}body .container .body{padding-bottom:10px}body .container .body .content{margin:0 auto}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{padding-top:8px;padding-bottom:30px;margin-bottom:10px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{padding:7px 10px;right:1px;top:9px}body .container .body .content div.add form .input.checkbox.multiple div,body .container .body .content div.restore form .input.checkbox.multiple div{display:block}body .container .body .content div.add form .input.select.multiple input#exclude-larger-than-number,body .container .body .content div.restore form .input.select.multiple input#exclude-larger-than-number{width:75px}body .container .body .content div.add form .input.select.multiple select#exclude-larger-than-multiplier,body .container .body .content div.restore form .input.select.multiple select#exclude-larger-than-multiplier{width:140px}body .container .body .content div.add form .filters .input.textarea,body .container .body .content div.restore form .filters .input.textarea{padding-bottom:10px}body .container .body .content div.add form .filters h3,body .container .body .content div.restore form .filters h3{margin:5px 0}body .container .body .content div.add form .input.text.select.multiple.repeat label,body .container .body .content div.restore form .input.text.select.multiple.repeat label{float:none}body .container .body .content div.add form .input.text.select.multiple.repeat input#repeatRunNumber,body .container .body .content div.restore form .input.text.select.multiple.repeat input#repeatRunNumber{width:70px}body .container .body .content div.add form .input.text.select.multiple.repeat select#repeatRunMultiplier,body .container .body .content div.restore form .input.text.select.multiple.repeat select#repeatRunMultiplier{width:100px}body .container .body .content div.add form .input.multiple.text.select.maxSize input,body .container .body .content div.restore form .input.multiple.text.select.maxSize input{width:70px}body .container .body .content div.add form .input.multiple.text.select.maxSize select,body .container .body .content div.restore form .input.multiple.text.select.maxSize select{width:100px}body .container .body .content div.add form .input.multiple.text.select.keepBackups select,body .container .body .content div.restore form .input.multiple.text.select.keepBackups select{width:85px;padding:4px 6px}body .container .body .content div.add form .input.multiple.text.select.keepBackups input,body .container .body .content div.restore form .input.multiple.text.select.keepBackups input{width:60px}body .container .footer{position:static;padding:15px;line-height:normal;text-align:left;box-sizing:border-box}body .container .footer *{float:none!important;text-align:center;box-sizing:border-box}body .container .footer .about-footer{padding-right:0;display:block}body .container .footer .about-footer span{padding-left:0;padding-bottom:5px}body .container .footer .about-footer li{padding-left:0;float:none;display:inline-block;height:32px;width:32px;background-size:28px!important;border-bottom:none}body .container .footer .about-footer li:first-child{padding-bottom:0}body .container .footer .about-footer li:last-child{padding-bottom:20px}body .container .footer .about-footer,body .container .footer .social,body .container .footer li{padding:8px 0;border-bottom:1px #ddd solid}body .container .footer .social li{display:inline-block;border:none}body .container .footer .themelink{padding:8px 0}}@media (width <= 580px){.advancedentry .longdescription{margin-left:0}}@media (width <= 492px){ul.notification{width:auto}}@media (width <= 480px){body{font-size:15px}body .container .header .logo{padding-left:5px}body .container .header .menubutton{margin-right:5px}body .container .header .state{margin-left:5px}body .container .header .statepadding{padding-right:40px}body .container .header .menubutton{padding-left:10px}body .container .body .mainmenu{width:280px;box-sizing:border-box}body .container .body .mainmenu ul li a{font-size:22px}body .container .body .content{padding:15px}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{font-size:14px}body .container .body .content div.add form .buttons a,body .container .body .content div.restore form .buttons a{float:none;text-align:center;margin-bottom:5px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:none;margin-bottom:8px;display:block}}@media (width <= 400px){body{font-size:15px}body .container .header .menubutton{margin-right:0;padding-left:0;padding-right:40px}body .container .header .menubutton span{display:none}}@media (width <= 325px){body{font-size:15px}body .container .header .logo div{display:none}}@media (width <= 200px){body{font-size:15px}body .container .header .menubutton{position:static;margin-top:0}body .container .header .action-icons-small{clear:right;margin-top:0}}body{background-color:#1a1a1a!important;color:#b0b0b0}body .footer{background-color:#333!important}body .header{background-color:#333!important}body #mainmenu{background:#1a1a1a}body .header a.active,body .header a.hover{color:#f0f0f0}body .container .header .state{color:#81c601;border:1px #81c601 solid}body .state{background-color:#1a1a1a!important}body form.styled .buttons a,body form.styled .buttons input{background:#4a5879}body form.styled .buttons a:hover,body form.styled .buttons input:hover{background:#6089b5}body .button{background:#4a5879}body .button:hover{background:#6089b5}body .container .body .mainmenu>ul>li>a.active{color:#000}body .container .body .content div.add .steps .step,body .container .body .content div.restore .steps .step{color:#2780b3}body #folder_path_picker,body #restore_file_picker,body .step3 source-folder-picker{background-color:#fff}.addwizard form.styled div.subtext,.restorewizard{color:#8a8a8a}body form.styled .input.select select,body form.styled input,body form.styled select,body form.styled textarea{color:#b0b0b0;background-color:#1a1a1a} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0)}100%{transform:rotate(359deg)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scale(-1,1)}.fa-flip-vertical{transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}*{font-family:'Clear Sans',sans-serif}body,html{margin:0;padding:0;height:100%}h1,h2{font-weight:300;color:#609301}h1{margin:10px 0}h3{font-weight:400}a{text-decoration:none}button{border:none}ul{list-style:none;margin:0;padding:0}hr{border:none;border-bottom:1px #ddd solid}textarea{max-width:94%}.external-link-image{display:inline-block;margin-left:8px;margin-right:8px;height:16px;width:16px;background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.external-link-image{background-image:url('../img/external-link-hover_3x.png')}}a .external-link-image{background:url('../img/external-link.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){a .external-link-image{background-image:url('../img/external-link_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){a .external-link-image{background-image:url('../img/external-link_3x.png')}}.header a:hover .external-link-image{background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_3x.png')}}.button{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.button:hover{background:#216b96}#folder_path_picker,#restore_file_picker,.step3 source-folder-picker{display:block;border:1px solid #d3d3d3;padding:2px;height:100%;overflow:scroll;box-sizing:border-box}.not-clickable{cursor:default!important}.not-clickable div,.not-clickable span,.not-clickable>a{cursor:default!important}.ui-match{font-weight:700;color:#006400}wait-area{min-width:350px;text-align:center;display:block}.prewrapped-text{white-space:pre-wrap}.exceptiontext{background-color:#d3d3d3;color:#000}.backup-result{width:90%;display:grid;grid-template-columns:50% 50%;grid-auto-rows:minmax(50px,auto);margin:0 auto}.backup-result div .horizontal-rule{width:100%;border-bottom:1px solid #d8d8d8;margin:5px 0}.backup-result .box{margin:10px;margin-bottom:0}.backup-result .title{color:#3f6001;font-weight:700;font-size:30px}.backup-result .item{display:block}.backup-result .item .key{color:#609301;font-weight:700}.backup-result .item .value{color:#b0b0b0}.backup-result .item .expanded{padding:0 10px 0 18px;margin-bottom:10px}.backup-result .one{border-right:1px solid #d8d8d8;grid-column:1;grid-row:1}.backup-result .two{grid-column:2;grid-row:1}.backup-result .wide{grid-column:span 2;border-top:1px solid #d8d8d8;padding-top:10px}.backup-result .three{grid-row:2}.backup-result .four{grid-row:3;margin-bottom:10px}.backup-result .four .log-expand-copy{display:flex;margin-bottom:6px}.backup-result .four .log-expand-copy a{margin-left:auto}.backup-result .four textarea{width:100%;max-width:99%;min-height:420px;padding:8px 6px;white-space:pre}.success-color{color:#390}.error-color{color:#c00}.warning-color{color:#fc0}.fatal-color{color:#900}ul.tabs{margin-bottom:10px}ul.tabs>li{display:inline;margin-right:10px;border:1px solid #2a89c0;padding:5px}ul.tabs>li.active{background-color:#2a89c0;color:#fff}ul.tabs>li.active>a{background-color:#2a89c0;color:#fff}ul.tabs>li.active.disabled{border:1px solid #d3d3d3;background-color:#d3d3d3;color:grey;cursor:default}ul.tabs>li.active.disabled>a{background-color:#d3d3d3;color:grey;cursor:default}.licenses>ul{list-style:initial;margin:10px;margin-left:20px}.licenses li{margin-bottom:10px}.licenses a.itemlink{font-weight:700}.logpage ul.entries{list-style:initial;margin:10px;margin-left:20px}.logpage .entries div.entryline.clickable{cursor:pointer}.logpage .entries.livedata li.expanded{height:auto}.logpage .button{text-align:center;margin-right:10px;border:1px solid #2a89c0;padding:5px;background-color:#2a89c0;color:#fff;cursor:pointer}.exportpage .checkbox input{width:auto;margin-top:10px}.exportpage .commandline div{background-color:#d3d3d3;color:#000}.themelink{margin-left:20px}ul.notification{position:fixed;bottom:0;left:0;right:0;margin:auto;width:480px}.notification .title{border:1px solid #2a89c0;background-color:#2a89c0;border-radius:5px 5px 0 0;padding:2px;padding-left:5px;padding-right:5px;font-weight:700;color:#d3d3d3;width:100%;text-align:center;clear:both}.notification .content{background-color:#fff;border:1px solid #2a89c0;border-radius:0 0 5px 5px;padding:2px;padding-left:5px;padding-right:5px;width:100%}.notification .message{width:100%;color:#000}.notification .button{padding:2px 10px;margin-top:6px}.notification .clear{clear:right;height:1px}.notification .error .title{border-color:red;background-color:red}.notification .error .content{border-color:red}.notification .error .button{border-color:red;background-color:red}.notification .warning .title{background-color:orange;border-color:orange}.notification .warning .button{background-color:orange;border-color:orange}.notification .warning .content{border-color:orange}.filepicker{height:200px}.resizable{margin-bottom:6px;max-width:100%}.advanced-toggle{float:right;margin-right:25px;line-height:37px}.advancedoptions li{clear:both;margin-bottom:10px;padding:10px 0;border-top:1px #d3d3d3 solid}.advancedentry .multiple{display:inline}.advancedentry .shortname{font-weight:700}.advancedentry input[type=text]{width:300px}.advancedentry select{width:300px}.advancedentry input[type=checkbox]{margin-top:13px;width:auto}.advancedentry .delete-item{display:block;background:#2a89c0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.advancedentry .longdescription{--margin-block:10px;margin-top:var(--margin-block);margin-left:190px;clear:both;font-style:italic;white-space:pre-wrap}.advancedentry .longdescription .longdescription__item{margin-block:0 var(--margin-block)}.advancedentry .longdescription .longdescription__default{margin-block:var(--margin-block) 0}.settings div.sublabel{clear:both;padding:0 31px;font-style:italic}.logo img.mainlogo{height:64px;width:64px;float:left;padding-right:8px;padding-top:2px}.logo div.logotext{float:left}.logo a{float:left;display:block;line-height:normal}.logo div.build-suffix{clear:both;display:inline;float:left;font-size:16px;line-height:16px}.logo div.powered-by{font-size:16px;margin:0;line-height:16px;float:left;padding:0;margin-left:5px}.note p{margin-block:0.5rem}.note p:first-child{margin-top:0}.note p:last-child{margin-bottom:0}.fixed-width-font{font-family:monospace}.warning{margin:10px;font-style:italic;color:#f49b42}div.captcha .details{padding-top:10px;margin-left:auto;margin-right:auto;width:180px}div.captcha .code{background:#d3d3d3;color:#000;font-family:monospace;font-size:xx-large;padding:10px}div.captcha .answer{margin-top:16px}.pauseoptions div.sublabel{clear:both;padding:0 31px;font-style:italic}.pauseoptions .input.checkbox{margin-top:10px}.pauseoptions .input.checkbox input.checkbox{width:auto;height:auto}.pauseoptions .input.checkbox label{line-height:normal;padding:0 15px;width:auto}.centered-text{text-align:center}body{color:#b0b0b0}body .container{min-height:100%;position:relative}body .container .header{line-height:70px;background:#ededed;overflow:hidden;height:70px;position:fixed;top:0;left:0;right:0;z-index:100}body .container .header a{color:#2a89c0}body .container .header a.active,body .container .header a:hover{color:#707070}body .container .header button{width:26px;height:26px;background-size:26px;cursor:pointer}body .container .header .logo{font-size:30px;font-weight:700;float:left;padding-left:40px}body .container .header .statepadding{padding-right:90px;margin-left:320px}body .container .header .state{float:left;color:#3f6001;width:595px;padding:13px 15px;margin:10px 20px;border:1px #3f6001 solid;font-weight:300;font-size:18px;overflow:hidden;line-height:normal;display:inline-block;background-color:#fff;text-overflow:ellipsis;position:relative;height:25px}body .container .header .state strong{display:inline;margin-right:10px}body .container .header .state span{display:inline}body .container .header .state .button{position:static;margin-top:70px}body .container .header .state .content{position:relative;z-index:10;margin-right:40px;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}body .container .header .state .buttons{position:absolute;right:0;top:0;bottom:0;width:26px;margin:13px 15px}body .container .header .state .buttons button{display:block}body .container .header .state .buttons .stop{background:url('../img/progress-stop.png');background-size:100%;z-index:10;position:relative}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_3x.png')}}body .container .header .state .buttons .resume{background:url('../img/progress-resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_3x.png')}}body .container .header .state .progress-bar{position:absolute;top:0;bottom:0;left:0;background:rgba(96,147,1,.25);z-index:5}body .container .header .state .task-name{overflow:hidden;text-overflow:ellipsis;cursor:help}body .container .header .state .task-state-info{display:flex}body .container .header .action-icons{display:inline-block;line-height:normal;margin:10px 0;padding:13px 0;float:left}body .container .header .action-icons-small{display:none;float:right;margin-top:21px;line-height:normal}body .container .header .action-icons-small>button,body .container .header .action-icons>button{display:inline-block}body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background:url('../img/pause.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_3x.png')}}body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background:url('../img/resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_3x.png')}}body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background:url('../img/throttle.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_3x.png')}}body .container .header .action-icons-small>.throttle.inactive,body .container .header .action-icons>.throttle.inactive{opacity:.5}body .container .header .about-header{float:right;padding-right:20px;overflow:hidden}body .container .header .about-header ul{overflow:hidden;list-style:none}body .container .header .about-header ul li{float:right;padding-right:20px}body .container .body{width:100%;overflow:hidden;min-height:500px;padding-top:120px;padding-bottom:70px}body .container .body a{color:#2a89c0}body .container .body .mainmenu{width:260px;padding-left:40px;float:left;position:fixed}body .container .body .mainmenu>ul>li{position:relative}body .container .body .mainmenu>ul>li>a{font-size:22px;font-weight:300;padding:5px 10px 5px 55px;display:block}body .container .body .mainmenu>ul>li>a:hover{color:#fff}body .container .body .mainmenu>ul>li>a.active{color:#fff}body .container .body .mainmenu>ul>li>a.add{background:url('../img/mainmenu/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore{background:url('../img/mainmenu/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume{background:url('../img/mainmenu/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings{background:url('../img/mainmenu/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout{background:url('../img/mainmenu/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home{background:url('../img/mainmenu/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about{background:url('../img/mainmenu/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_3x.png')}}body .container .body .mainmenu>ul>li>a.home.active{background:#5bacdb url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.add.active{background:#5bacdb url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore.active{background:#5bacdb url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume.active{background:#5bacdb url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings.active{background:#5bacdb url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.about.active{background:#5bacdb url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul>li>a.add:hover{background:#2a89c0 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore:hover{background:#2a89c0 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume:hover{background:#2a89c0 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings:hover{background:#2a89c0 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout:hover{background:#2a89c0 url('../img/mainmenu/over/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home:hover{background:#2a89c0 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about:hover{background:#2a89c0 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul li.hr-top{padding-top:25px;margin-top:25px;border-top:1px #ededed solid}body .container .body div.contextmenu_container{position:relative}body .container .body .contextmenu{display:none;position:absolute;background:#fff;border:1px #ededed solid;box-shadow:0 4px 8px #000;z-index:200;padding:5px}body .container .body .contextmenu li a{color:#2a89c0;font-size:15px;font-weight:400;display:block;min-width:200px;padding:4px 10px;white-space:nowrap;padding-left:45px;overflow:hidden;text-overflow:ellipsis}body .container .body .contextmenu li a:hover{background:#2a89c0;color:#fff}body .container .body .contextmenu.open{display:block}body .container .body .content{float:left;padding-left:350px;padding-bottom:50px;max-width:70%}body .container .body .content ul.tabs>li{display:inline-block}body .container .body .content .tasks .orderbox{float:right}body .container .body .content .tasks .tasklist .task{border-top:1px solid #eee;padding-top:20px;margin-bottom:25px}body .container .body .content .tasks .tasklist .task:last-child{border-bottom:1px solid #eee;padding-bottom:20px}body .container .body .content .tasks .tasklist .task:first-child{padding-top:0;border-top:0 none}body .container .body .content .tasks .tasklist .progress-small{text-align:center;height:18px;background:#a4d105}body .container .body .content .tasks .tasklist .progress-small-bg{border:1px #65b1dd solid;width:200px}body .container .body .content .tasks .tasklist a{font-size:30px;font-weight:300;display:inline-block}body .container .body .content .tasks .tasklist a.action-link{font-size:14px;background:0 0;padding-left:0}body .container .body .content .tasks .tasklist dl{padding-left:55px;overflow:hidden;font-size:14px}body .container .body .content .tasks .tasklist dl dd,body .container .body .content .tasks .tasklist dl dt{display:block;float:left}body .container .body .content .tasks .tasklist dl dt{clear:both;font-weight:500;margin-bottom:5px}body .container .body .content .tasks .tasklist dl dd{margin-left:10px}body .container .body .content .tasks .tasklist dl.taskmenu p{display:inline;margin-right:10px;color:#2a89c0;cursor:pointer}body .container .body .content .tasks .tasklist dl.taskmenu dt{float:left;margin-right:10px;margin-bottom:0;padding:5px 8px;color:#b0b0b0;cursor:pointer;clear:none}body .container .body .content .tasks .tasklist dl.taskmenu dd{clear:both;float:none;padding-bottom:8px;border-bottom:1px #ddd solid;margin-bottom:5px}body .container .body .content div.add,body .container .body .content div.restore{--legends-width:700px;--legends-padding-left:calc(calc(700px - var(--legends-width)) / 2);--circle-width:43px;--step-width:calc(var(--legends-width) / var(--legends-steps))}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{margin-left:calc(calc(calc(var(--step-width) - var(--circle-width))/ 2) + var(--legends-padding-left))}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{padding-left:calc(var(--step-width) - var(--circle-width));padding-right:0}body .container .body .content div.add .steps button:first-child,body .container .body .content div.add .steps div:first-child,body .container .body .content div.restore .steps button:first-child,body .container .body .content div.restore .steps div:first-child{padding-left:unset}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{padding-left:var(--legends-padding-left)}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{width:var(--step-width)}body .container .body .content div.add{--legends-steps:5}body .container .body .content div.restore{--legends-steps:2}body .container .body .content div.restore.restore-direct{--legends-steps:4}body .container .body .content div.restore.restore-direct .steps-legend{padding-left:20px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{width:100%;overflow:hidden}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{float:left;background:url('../img/steps/line-out.png') no-repeat top left;background-size:485px 24px;color:#c7e5f6}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_3x.png')}}body .container .body .content div.add .steps button span,body .container .body .content div.add .steps div span,body .container .body .content div.restore .steps button span,body .container .body .content div.restore .steps div span{--size:35px;display:block;border-width:4px;border-style:solid;border-color:#c7e5f6;background:#fff;border-radius:50%;width:var(--size);height:var(--size);text-align:center;font-size:22px;line-height:var(--size);cursor:pointer}body .container .body .content div.add .steps button.active,body .container .body .content div.add .steps div.active,body .container .body .content div.restore .steps button.active,body .container .body .content div.restore .steps div.active{color:#2a89c0}body .container .body .content div.add .steps button.active span,body .container .body .content div.add .steps div.active span,body .container .body .content div.restore .steps button.active span,body .container .body .content div.restore .steps div.active span{border-color:#2a89c0;background:#2a89c0;color:#fff}body .container .body .content div.add .steps button.active h2,body .container .body .content div.add .steps div.active h2,body .container .body .content div.restore .steps button.active h2,body .container .body .content div.restore .steps div.active h2{color:#2a89c0}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{overflow:hidden;padding-bottom:50px;list-style:none;margin:0}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{color:#c7e5f6;font-size:18px;text-align:center;float:left;padding-top:10px;cursor:pointer}body .container .body .content div.add .steps-legend li.active,body .container .body .content div.restore .steps-legend li.active{color:#2a89c0}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes{padding-left:40px}body .container .body .content div.add .steps-boxes .step,body .container .body .content div.restore .steps-boxes .step{display:none}body .container .body .content div.add .steps-boxes .step.active,body .container .body .content div.restore .steps-boxes .step.active{display:block}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:left;margin-left:20px;color:#b0b0b0}body .container .body .content div.add .steps-boxes .box.browser .checklinks a i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a i{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive{color:#e3e3e3;cursor:default}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive i{border-color:#e3e3e3}body .container .body .content div.add .steps-boxes .box.browser .checklinks a:first-child,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a:first-child{margin-left:0}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton{padding-top:10px;max-width:100%}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton input#sourcePath,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton input#sourcePath{width:100%;box-sizing:border-box;height:37px}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton a.button,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton a.button{top:10px}body .container .body .content div.add .steps-boxes .box.filters .input.link a,body .container .body .content div.restore .steps-boxes .box.filters .input.link a{color:#b0b0b0}body .container .body .content div.add .steps-boxes .box.filters .input.link a i,body .container .body .content div.restore .steps-boxes .box.filters .input.link a i{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist{overflow:hidden;padding-bottom:15px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li{overflow:hidden;clear:both;padding-bottom:25px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li select,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li select{width:200px;margin-right:5px;height:36px;line-height:36px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li input,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li input{width:calc(100% - 280px);padding:5px}body .container .body .content div.add .steps-boxes .step1 li.strength.score-0,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-0{color:red}body .container .body .content div.add .steps-boxes .step1 li.strength.score-1,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-1{color:#f70}body .container .body .content div.add .steps-boxes .step1 li.strength.score-2,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-2{color:#aa0}body .container .body .content div.add .steps-boxes .step1 li.strength.score-3,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-3{color:#070}body .container .body .content div.add .steps-boxes .step1 li.strength.score-4,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-4{color:#427e27}body .container .body .content div.add .steps-boxes .step1 li.strength.score-x,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-x{color:red}body .container .body .content div.add .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.add .steps-boxes .step5 div.input.maxSize input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.maxSize input.number{width:60px}body .container .body .content div.add .steps-boxes .step5 .hint,body .container .body .content div.add .steps-boxes .step5 .retention-options,body .container .body .content div.restore .steps-boxes .step5 .hint,body .container .body .content div.restore .steps-boxes .step5 .retention-options{clear:both;margin-left:190px;margin-top:50px;font-style:italic}body .container .body .content div.add .steps-boxes .step5 .retention-options input,body .container .body .content div.restore .steps-boxes .step5 .retention-options input{margin-bottom:10px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions{padding-top:15px;clear:both}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li{border-top:none}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li.advancedentry,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li.advancedentry{border-bottom:1px solid #d3d3d3}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child{padding-top:0}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child select{max-width:400px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions label,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions label{line-height:normal}body .container .body .content div.add .steps-boxes .step5 .advancedoptions input,body .container .body .content div.add .steps-boxes .step5 .advancedoptions select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions input,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions select{width:auto;max-width:100%;box-sizing:border-box}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle{color:#b0b0b0;line-height:normal;margin-top:16px;clear:both;float:left}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle i.fa,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle i.fa{border:2px solid;border-color:#b0b0b0;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .step5 textarea,body .container .body .content div.restore .steps-boxes .step5 textarea{box-sizing:border-box;clear:both;margin-top:15px;width:100%}body .container .body .content div.add form,body .container .body .content div.restore form{padding-bottom:50px;overflow:hidden}body .container .body .content div.add form .input.password .tools,body .container .body .content div.restore form .input.password .tools{clear:both;padding-left:190px;padding-top:10px}body .container .body .content div.add form .input.password .tools ul,body .container .body .content div.restore form .input.password .tools ul{overflow:hidden}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{float:left;padding-right:7px}body .container .body .content div.add form .input.password .tools ul li.strength.useless,body .container .body .content div.restore form .input.password .tools ul li.strength.useless{color:red}body .container .body .content div.add form .input.password .tools ul li.strength.average,body .container .body .content div.restore form .input.password .tools ul li.strength.average{color:#ff0}body .container .body .content div.add form .input.password .tools ul li.strength.good,body .container .body .content div.restore form .input.password .tools ul li.strength.good{color:#2a89c0}body .container .body .content div.add form .input.multiple input,body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple input,body .container .body .content div.restore form .input.multiple select{width:auto;margin-right:5px}body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple select{--padding-block:5px;padding:var(--padding-block) 12px;line-height:calc(var(--height) - calc(var(--padding-block) * 2))}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{overflow:hidden;position:relative;max-width:446px}body .container .body .content div.add form .input.overlayButton input,body .container .body .content div.restore form .input.overlayButton input{width:347px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{position:absolute;top:0;right:0;padding:7px 12px 8px}body .container .body .content div.add form .input.checkbox.multiple strong,body .container .body .content div.restore form .input.checkbox.multiple strong{display:block;padding-bottom:5px}body .container .body .content div.add form .input.checkbox.multiple label,body .container .body .content div.restore form .input.checkbox.multiple label{display:inline-block;float:none;width:auto;padding-right:10px}body .container .body .content div.add form .input.checkbox.multiple input,body .container .body .content div.restore form .input.checkbox.multiple input{width:auto;display:inline-block;float:none}body .container .body .content div.add form .buttons,body .container .body .content div.restore form .buttons{float:none;width:635px;padding-top:30px}body .container .body .content .commandline .input.select,body .container .body .content div.add .step2 .input.select,body .container .body .content div.restore .step1 .input.select{display:grid;grid-auto-flow:column;justify-content:flex-start;grid-template-areas:"label server" ". custom"}body .container .body .content .commandline .input.select label,body .container .body .content div.add .step2 .input.select label,body .container .body .content div.restore .step1 .input.select label{grid-area:label}body .container .body .content .commandline .input.select select,body .container .body .content div.add .step2 .input.select select,body .container .body .content div.restore .step1 .input.select select{grid-area:server}body .container .body .content .commandline .input.select input,body .container .body .content div.add .step2 .input.select input,body .container .body .content div.restore .step1 .input.select input{grid-area:custom;margin-top:10px}body .container .body .content .commandline .input.text #generic_server,body .container .body .content div.add .step2 .input.text #generic_server,body .container .body .content div.restore .step1 .input.text #generic_server{width:335px}body .container .body .content .commandline .input.text #generic_port,body .container .body .content div.add .step2 .input.text #generic_port,body .container .body .content div.restore .step1 .input.text #generic_port{width:50px;margin-left:10px}body .container .body .content div.headerthreedotmenu{margin:20px 0}body .container .body .content div.headerthreedotmenu h2{display:inline}body .container .body .content div.headerthreedotmenu .contextmenu_container{float:right}body .container .body .content div.headerthreedotmenu .contextmenu{left:auto;right:0;top:auto}body .container .body .content div.headerthreedotmenu .threedotmenubutton{padding:5px}body .container .body .content .expandable{margin:20px 0}body .container .body .content .expandable h2{display:inline}body .container .body .content .expandable img{padding:0 6px}body .container .body .content div.settings .input.checkbox input.checkbox,body .container .body .content div.settings .input.mixed.multiple input.checkbox{width:auto}body .container .body .content div.settings .input.checkbox select,body .container .body .content div.settings .input.mixed.multiple select{width:auto;margin-right:5px}body .container .body .content div.settings .input.checkbox label,body .container .body .content div.settings .input.mixed.multiple label{line-height:normal;padding:0 15px;width:auto}body .container .body .content .logpage ul.tabs{padding:15px 0}body .container .body .content .logpage ul.entries li{padding:10px 0;border-bottom:1px solid #d8d8d8}body .container .body .content .logpage ul.backuplog{list-style:none}body .container .body .content .about-general .about-general__block{margin-block:1rem}body .container .body .content .about-general .about-general__block:first-child{margin-top:10px}body .container .body .content .about-general .about-general__block:last-child{margin-bottom:0}body .container .body .content .prewrapped-text{white-space:pre-wrap;overflow-x:auto}body .container .footer{background:#ededed;min-height:70px;line-height:70px;overflow:hidden;position:absolute;bottom:0;width:100%}body .container .footer a{color:#2a89c0}body .container .footer .about-footer{float:left;overflow:hidden;padding-right:20px;display:none}body .container .footer .about-footer span{display:block;float:left;padding-left:20px}body .container .footer .about-footer ul{float:left}body .container .footer .about-footer li{float:left;padding-left:20px}body .container .footer .social{float:right}body .container .footer .social ul{overflow:hidden;float:right;padding-left:20px;padding-right:10px}body .container .footer .social ul li{float:right;margin-right:10px;padding-top:5px}body .container .footer .social ul li img{opacity:.6}body .container .footer .social ul li img:hover{opacity:1}body .container .footer .themelink{float:right;padding-right:20px}body #modal-menu{max-width:400px}body #modal-menu a{color:#2a89c0;font-size:20px;line-height:40px}.remodal{padding:30px;box-shadow:0 2px 7px #000;background:#fff;display:none}.remodal form .buttons{float:none}.remodal-wrapper .remodal{display:block}span.info{font-size:10px;font-weight:500;display:inline-block;background:#2a89c0;border-radius:50%;width:15px;height:15px;vertical-align:super;color:#fff;line-height:15px;margin-left:5px;text-align:center}.hidden{display:none}.clear{clear:both}.nofloat{float:none!important}div.blocker,div.connection-lost,div.modal-dialog{position:fixed;inset:0;margin:auto}div.blocker{z-index:5000;background-color:#000;opacity:.65}#connection-lost-blocker{z-index:5100}#connection-lost-dialog{z-index:5200}div.connection-lost,div.modal-dialog{z-index:5001;display:flexbox;display:flex;justify-content:center;align-items:center}div.connection-lost div.info,div.modal-dialog div.info{min-width:310px;max-width:650px;margin:5px}div.connection-lost div.title,div.modal-dialog div.title{border:1px solid #65b1dd;background-color:#65b1dd;border-radius:5px 5px 0 0;padding:10px 20px;font-weight:700;color:#d3d3d3;text-align:center}div.connection-lost div.content,div.modal-dialog div.content{background-color:#fff;border:1px solid #fff;padding:20px}div.connection-lost div.content p:first-child,div.modal-dialog div.content p:first-child{margin-top:0}div.connection-lost div.content p:last-child,div.modal-dialog div.content p:last-child{margin-bottom:0}div.connection-lost .buttons,div.modal-dialog .buttons{border-radius:0 0 5px 5px;padding-top:10px;overflow:auto}div.connection-lost form,div.modal-dialog form{margin-top:15px}div.connection-lost form textarea,div.modal-dialog form textarea{height:130px;width:420px;padding:10px 12px;border:1px #d8d8d8 solid;border-radius:2px;color:#b0b0b0;font-size:16px;font-weight:300}div.connection-lost form input,div.modal-dialog form input{height:35px;line-height:35px;padding:0 12px}div.modal-dialog .content.buttons ul{float:right}div.modal-dialog .content.buttons .tooltipped{position:relative}div.modal-dialog .content.buttons .tooltipped::after{position:absolute;z-index:1000000;display:none;padding:5px 8px;font:normal normal 11px/1.5 Helvetica,arial,nimbussansl,liberationsans,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol";color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#000;border-radius:3px;-webkit-font-smoothing:subpixel-antialiased}div.modal-dialog .content.buttons .tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:#000;pointer-events:none;content:"";border:5px solid transparent}div.modal-dialog .content.buttons .tooltipped:active::after,div.modal-dialog .content.buttons .tooltipped:active::before,div.modal-dialog .content.buttons .tooltipped:focus::after,div.modal-dialog .content.buttons .tooltipped:focus::before,div.modal-dialog .content.buttons .tooltipped:hover::after,div.modal-dialog .content.buttons .tooltipped:hover::before{display:inline-block;text-decoration:none}div.modal-dialog .content.buttons .tooltipped-w::after{right:100%;bottom:50%;margin-right:5px;transform:translateY(50%)}div.modal-dialog .content.buttons .tooltipped-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:#000}.importpage form.styled input{margin-top:11px;margin-bottom:11px}.addwizard form.styled ul,.restorewizard form.styled ul{margin:20px;margin-left:0}.addwizard form.styled input[type=radio],.restorewizard form.styled input[type=radio]{width:20px;margin-left:5px;margin-right:5px}.addwizard form.styled label,.restorewizard form.styled label{width:auto;line-height:normal}.addwizard form.styled div.subtext,.restorewizard form.styled div.subtext{clear:both;margin-left:30px;padding-top:5px;color:#d6d6d6}.pauseoptions form.styled li{line-height:normal;padding:0}.pauseoptions form.styled li input{height:auto;margin-top:8px;margin-right:8px;width:auto}.confirmdelete div.overview{width:420px}.confirmdelete div.word{font-weight:700;padding:12px;border:1px solid #000;margin-top:12px;margin-bottom:12px;background-color:#e0e0e0;width:420px}.confirmdelete span.backupname{font-weight:700}.confirmdelete input.typedword{float:none}.progress-bar-striped{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress-striped .progress-bar{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress{position:relative;min-height:25px}.progress>span{vertical-align:middle;display:block;width:100%;height:100%;text-align:center;z-index:100;padding-top:2px}.progress.active .progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress .progress-bar{float:left;width:0;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;box-shadow:inset 0 -1px 0 #000;transition:width .6s ease;height:100%;position:absolute;top:0}.progress .progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.tree-view{list-style-type:none;margin-left:10px;padding-bottom:5px}.tree-view ul{margin-left:16px}.tree-view span.nodeLabel{cursor:pointer}.tree-view span.nodeLabel.selected{border:1px solid #aaa;background-color:#ddd;padding:1px 3px}.tree-view li .node{padding-bottom:5px}.tree-view li div.selected{border-color:#add8e6;background-color:#add8e6}.tree-view li>ul{display:none}.tree-view li>ul.expanded{display:block}.tree-view li a.nav{cursor:pointer;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:-80px 0;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.nav.leaf{background:0 0}.tree-view li a.nav.expanded{background-position:-80px -16px}.tree-view li a.type{cursor:auto;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 -16px;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.type.invisible{background-position:0 -32px}.tree-view li a.type.loading{cursor:progress;background-image:url("../img/loader-16.gif");background-repeat:no-repeat;background-position:0 0;background-size:16px 16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type.loading{background-image:url('../img/loader-32.gif')}}.tree-view li a.type.x-tree-icon-drive{background-position:-16px -16px}.tree-view li a.type.x-tree-icon-leaf{background-position:-32px -16px}.tree-view li a.type.x-tree-icon-symlink{background-position:-48px -16px}.tree-view li a.type.x-tree-icon-userdata{background-position:-16px -48px}.tree-view li a.type.x-tree-icon-locked{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-broken{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-computer{background-position:0 -48px}.tree-view li a.type.x-tree-icon-hyperv{background-position:-96px -16px}.tree-view li a.type.x-tree-icon-hypervmachine{background-position:-96px 0}.tree-view li a.type.x-tree-icon-mssql{background-position:-96px -32px}.tree-view li a.type.x-tree-icon-mssqldb{background-position:-80px -32px}.tree-view li a.type.x-tree-icon-mydocuments{background-position:-32px -48px}.tree-view li a.type.x-tree-icon-mymusic{background-position:-48px -48px}.tree-view li a.type.x-tree-icon-mypictures{background-position:-64px -48px}.tree-view li a.type.x-tree-icon-desktop{background-position:-80px -48px}.tree-view li a.type.x-tree-icon-home{background-position:-96px -48px}.tree-view li a.type.x-tree-icon-drive.invisible{background-position:-16px -32px}.tree-view li a.type.x-tree-icon-leaf.invisible{background-position:-32px -32px}.tree-view li a.type.x-tree-icon-symlink.invisible{cursor:auto;background-position:-48px -32px}.tree-view li a.type.x-tree-icon-locked.invisible{background-position:-64px -32px}.tree-view li a.check{height:16px;width:16px;display:inline-block;cursor:pointer;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 0;vertical-align:middle;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.partial{background-position:-32px 0}.tree-view li a.include{background-position:-16px 0}.tree-view li a.exclude{background-position:-48px 0}.tree-view li a.root{background:0 0;display:none}.throttlesettings div.multiple select{width:auto;margin-right:5px}.throttlesettings div.multiple input{width:100px}.throttlesettings div.multiple input.checkbox{width:auto}.throttlesettings div.multiple label{line-height:35px;padding:0 15px;width:auto;min-width:150px}.throttlesettings .disabled{color:#f0f0f0}.throttlesettings .disabled input,.throttlesettings .disabled select{color:#f0f0f0}@media (width <= 1150px){body .container .header{height:140px}body .container .header .statepadding{padding-right:90px;margin-left:0}body .container .header .state{width:100%;margin:10px 40px;clear:left;float:left}body .container .header .action-icons{display:none}body .container .header .action-icons-small{display:inline-block}body .container .header .menubutton{display:block;font-size:18px;padding-right:50px;margin-top:5px;margin-right:15px;background:url('../img/menu.png') no-repeat right top;background-size:39px 39px;position:relative;height:40px;line-height:40px;color:#b0b0b0;float:right;top:10px;padding-left:20px;text-transform:uppercase;text-align:right}body .container .header .menubutton.active{background-image:url('../img/menu_active.png');background-size:39px 39px;color:#2a89c0}body .container .body{position:relative;padding-top:140px}body .container .body .mainmenu{display:none;position:fixed;background:none repeat scroll 0 0 #fff;box-shadow:0 4px 8px #000;left:10px;padding:20px;top:60px}body .container .body .mainmenu.mobile-open{display:block;left:auto;right:0;top:0;z-index:1000}body .container .body .contextmenu{left:0;top:auto}body .container .body .content{float:none;padding:20px;margin:0 auto 30px}body .container .body .content .state{width:auto}body .container .mobileOpen{display:block!important}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton{background-image:url('../img/menu_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton{background-image:url('../img/menu_3x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_3x.png')}}@media (width <= 768px){body .container .body .content .tasks .tasklist a{font-size:20px;background-size:24px;background-position:0 4px;padding-left:35px}body .container .body .content .tasks .tasklist dl{padding-left:35px}body .container .header .logo{padding-left:10px}body .container .header .statepadding{padding-right:50px}body .container .header .state{margin-left:10px}body .container .header .menubutton{margin-right:5px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps,body .container .body .content div.settings .steps{display:none}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend,body .container .body .content div.settings .steps-legend{list-style:decimal;padding-left:20px;border-bottom:1px solid #eee;margin-bottom:30px;padding-bottom:20px}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li,body .container .body .content div.settings .steps-legend li{float:none;font-weight:500;width:auto!important;padding-right:0!important}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes,body .container .body .content div.settings .steps-boxes{padding-left:0}body .container .body .content div.add form.styled .input input,body .container .body .content div.add form.styled .input select,body .container .body .content div.add form.styled .input textarea,body .container .body .content div.restore form.styled .input input,body .container .body .content div.restore form.styled .input select,body .container .body .content div.restore form.styled .input textarea,body .container .body .content div.settings form.styled .input input,body .container .body .content div.settings form.styled .input select,body .container .body .content div.settings form.styled .input textarea{max-width:100%;box-sizing:border-box}body .container .body .content div.add form.styled .input.select select,body .container .body .content div.restore form.styled .input.select select,body .container .body .content div.settings form.styled .input.select select{width:420px}body .container .body .content div.add form.styled .buttons,body .container .body .content div.restore form.styled .buttons,body .container .body .content div.settings form.styled .buttons{max-width:100%;width:auto}body .container .body .content div.add form.styled .tools,body .container .body .content div.restore form.styled .tools,body .container .body .content div.settings form.styled .tools{padding-left:0!important}body .container .body .content div.add form.styled .input.checkbox.multiple,body .container .body .content div.restore form.styled .input.checkbox.multiple,body .container .body .content div.settings form.styled .input.checkbox.multiple{padding-bottom:5px}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.add form.styled .input.checkbox.multiple label,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple label,body .container .body .content div.settings form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple label{display:block!important;float:left!important;line-height:normal}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple input{clear:both}body .container .body .content div.add form.styled .input.text.multiple input,body .container .body .content div.restore form.styled .input.text.multiple input,body .container .body .content div.settings form.styled .input.text.multiple input{max-width:48%!important}}@media (width <= 640px){body h2{font-size:20px;text-align:center}body .container .body{padding-bottom:10px}body .container .body .content{margin:0 auto}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{padding-top:8px;padding-bottom:30px;margin-bottom:10px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{padding:7px 10px;right:1px;top:9px}body .container .body .content div.add form .input.checkbox.multiple div,body .container .body .content div.restore form .input.checkbox.multiple div{display:block}body .container .body .content div.add form .input.select.multiple input#exclude-larger-than-number,body .container .body .content div.restore form .input.select.multiple input#exclude-larger-than-number{width:75px}body .container .body .content div.add form .input.select.multiple select#exclude-larger-than-multiplier,body .container .body .content div.restore form .input.select.multiple select#exclude-larger-than-multiplier{width:140px}body .container .body .content div.add form .filters .input.textarea,body .container .body .content div.restore form .filters .input.textarea{padding-bottom:10px}body .container .body .content div.add form .filters h3,body .container .body .content div.restore form .filters h3{margin:5px 0}body .container .body .content div.add form .input.text.select.multiple.repeat label,body .container .body .content div.restore form .input.text.select.multiple.repeat label{float:none}body .container .body .content div.add form .input.text.select.multiple.repeat input#repeatRunNumber,body .container .body .content div.restore form .input.text.select.multiple.repeat input#repeatRunNumber{width:70px}body .container .body .content div.add form .input.text.select.multiple.repeat select#repeatRunMultiplier,body .container .body .content div.restore form .input.text.select.multiple.repeat select#repeatRunMultiplier{width:100px}body .container .body .content div.add form .input.multiple.text.select.maxSize input,body .container .body .content div.restore form .input.multiple.text.select.maxSize input{width:70px}body .container .body .content div.add form .input.multiple.text.select.maxSize select,body .container .body .content div.restore form .input.multiple.text.select.maxSize select{width:100px}body .container .body .content div.add form .input.multiple.text.select.keepBackups select,body .container .body .content div.restore form .input.multiple.text.select.keepBackups select{width:85px;padding:4px 6px}body .container .body .content div.add form .input.multiple.text.select.keepBackups input,body .container .body .content div.restore form .input.multiple.text.select.keepBackups input{width:60px}body .container .footer{position:static;padding:15px;line-height:normal;text-align:left;box-sizing:border-box}body .container .footer *{float:none!important;text-align:center;box-sizing:border-box}body .container .footer .about-footer{padding-right:0;display:block}body .container .footer .about-footer span{padding-left:0;padding-bottom:5px}body .container .footer .about-footer li{padding-left:0;float:none;display:inline-block;height:32px;width:32px;background-size:28px!important;border-bottom:none}body .container .footer .about-footer li:first-child{padding-bottom:0}body .container .footer .about-footer li:last-child{padding-bottom:20px}body .container .footer .about-footer,body .container .footer .social,body .container .footer li{padding:8px 0;border-bottom:1px #ddd solid}body .container .footer .social li{display:inline-block;border:none}body .container .footer .themelink{padding:8px 0}}@media (width <= 580px){.advancedentry .longdescription{margin-left:0}}@media (width <= 492px){ul.notification{width:auto}}@media (width <= 480px){body{font-size:15px}body .container .header .logo{padding-left:5px}body .container .header .menubutton{margin-right:5px}body .container .header .state{margin-left:5px}body .container .header .statepadding{padding-right:40px}body .container .header .menubutton{padding-left:10px}body .container .body .mainmenu{width:280px;box-sizing:border-box}body .container .body .mainmenu ul li a{font-size:22px}body .container .body .content{padding:15px}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{font-size:14px}body .container .body .content div.add form .buttons a,body .container .body .content div.restore form .buttons a{float:none;text-align:center;margin-bottom:5px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:none;margin-bottom:8px;display:block}}@media (width <= 400px){body{font-size:15px}body .container .header .menubutton{margin-right:0;padding-left:0;padding-right:40px}body .container .header .menubutton span{display:none}}@media (width <= 325px){body{font-size:15px}body .container .header .logo div{display:none}}@media (width <= 200px){body{font-size:15px}body .container .header .menubutton{position:static;margin-top:0}body .container .header .action-icons-small{clear:right;margin-top:0}}body{background-color:#1a1a1a!important;color:#b0b0b0}body .footer{background-color:#333!important}body .header{background-color:#333!important}body #mainmenu{background:#1a1a1a}body .header a.active,body .header a.hover{color:#f0f0f0}body .container .header .state{color:#81c601;border:1px #81c601 solid}body .state{background-color:#1a1a1a!important}body form.styled .buttons a,body form.styled .buttons input{background:#4a5879}body form.styled .buttons a:hover,body form.styled .buttons input:hover{background:#6089b5}body .button{background:#4a5879}body .button:hover{background:#6089b5}body .container .body .mainmenu>ul>li>a.active{color:#000}body .container .body .content div.add .steps .step,body .container .body .content div.restore .steps .step{color:#2780b3}body #folder_path_picker,body #restore_file_picker,body .step3 source-folder-picker{background-color:#fff}.addwizard form.styled div.subtext,.restorewizard{color:#8a8a8a}body form.styled .input.select select,body form.styled input,body form.styled select,body form.styled textarea{color:#b0b0b0;background-color:#1a1a1a} \ No newline at end of file diff --git a/Duplicati/Server/webroot/ngax/styles/default.css b/Duplicati/Server/webroot/ngax/styles/default.css index 7c22b5b56..4cd5333cf 100644 --- a/Duplicati/Server/webroot/ngax/styles/default.css +++ b/Duplicati/Server/webroot/ngax/styles/default.css @@ -1,4 +1,4 @@ -@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Light-webfont.eot');src:url('../fonts/ClearSans-Light-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Light-webfont.woff') format('woff'),url('../fonts/ClearSans-Light-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Light-webfont.svg#clear_sans_lightregular') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Regular-webfont.eot');src:url('../fonts/ClearSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Regular-webfont.woff') format('woff'),url('../fonts/ClearSans-Regular-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Regular-webfont.svg#clear_sansregular') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Medium-webfont.eot');src:url('../fonts/ClearSans-Medium-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Medium-webfont.woff') format('woff'),url('../fonts/ClearSans-Medium-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Medium-webfont.svg#clear_sans_mediumregular') format('svg');font-weight:500;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Bold-webfont.eot');src:url('../fonts/ClearSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Bold-webfont.woff') format('woff'),url('../fonts/ClearSans-Bold-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Bold-webfont.svg#clear_sansbold') format('svg');font-weight:700;font-style:normal}form.styled div.leftflush input{width:auto;margin-top:10px}form.styled div.leftflush label{width:auto;min-width:190px}form.styled label{display:block;width:190px;float:left;line-height:37px}form.styled input,form.styled select,form.styled textarea{color:#505050;font-size:16px;font-weight:300;float:left;display:block;border:1px #d8d8d8 solid;border-radius:2px;width:420px}form.styled input:focus,form.styled select:focus,form.styled textarea:focus{border:1px #a5a5a5 solid}form.styled .input{padding-bottom:18px;overflow:hidden}form.styled .input.password input,form.styled .input.select>select+input,form.styled .input.text input{height:35px;line-height:35px;padding:0 12px}form.styled .input.text.text-browse input{width:375px;border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}form.styled .input.text.text-browse a.browse{width:45px;display:block;float:left;height:37px;border-radius:2px;border-top-left-radius:0;border-bottom-left-radius:0;color:#fff;background:#277db0;line-height:37px}form.styled .input.text.text-browse a.browse:hover{background:#14425d}form.styled .input.textarea textarea{height:130px;padding:10px 12px}form.styled .input.select select{--height:38px;width:446px;padding:0 12px;appearance:menulist-button;background:#fff;border-radius:2px;height:var(--height);line-height:var(--height)}form.styled .buttons{overflow:hidden;float:right}form.styled .buttons a,form.styled .buttons input{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}form.styled .buttons input{padding:4px 15px}form.styled .buttons a:hover,form.styled .buttons input:hover{background:#103348}@media (width <= 480px){form.styled input,form.styled select,form.styled textarea{font-size:15px}}/*! +@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Light-webfont.eot');src:url('../fonts/ClearSans-Light-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Light-webfont.woff') format('woff'),url('../fonts/ClearSans-Light-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Light-webfont.svg#clear_sans_lightregular') format('svg');font-weight:300;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Regular-webfont.eot');src:url('../fonts/ClearSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Regular-webfont.woff') format('woff'),url('../fonts/ClearSans-Regular-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Regular-webfont.svg#clear_sansregular') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Medium-webfont.eot');src:url('../fonts/ClearSans-Medium-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Medium-webfont.woff') format('woff'),url('../fonts/ClearSans-Medium-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Medium-webfont.svg#clear_sans_mediumregular') format('svg');font-weight:500;font-style:normal}@font-face{font-family:'Clear Sans';src:url('../fonts/ClearSans-Bold-webfont.eot');src:url('../fonts/ClearSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),url('../fonts/ClearSans-Bold-webfont.woff') format('woff'),url('../fonts/ClearSans-Bold-webfont.ttf') format('truetype'),url('../fonts/ClearSans-Bold-webfont.svg#clear_sansbold') format('svg');font-weight:700;font-style:normal}form.styled div.leftflush input{width:auto;margin-top:10px}form.styled div.leftflush label{width:auto;min-width:190px}form.styled label{display:block;width:190px;float:left;line-height:37px}form.styled input,form.styled select,form.styled textarea{color:#505050;font-size:16px;font-weight:300;float:left;display:block;border:1px #d8d8d8 solid;border-radius:2px;width:420px}form.styled input:focus,form.styled select:focus,form.styled textarea:focus{border:1px #a5a5a5 solid}form.styled .input{padding-bottom:18px;overflow:hidden}form.styled .input.password input,form.styled .input.select>select+input,form.styled .input.text input{height:35px;line-height:35px;padding:0 12px}form.styled .input.text.text-browse input{width:375px;border-top-right-radius:0;border-bottom-right-radius:0;border-right:0}form.styled .input.text.text-browse a.browse{width:45px;display:block;float:left;height:37px;border-radius:2px;border-top-left-radius:0;border-bottom-left-radius:0;color:#fff;background:#277db0;line-height:37px}form.styled .input.text.text-browse a.browse:hover{background:#14425d}form.styled .input.textarea textarea{height:130px;padding:10px 12px}form.styled .input.select select{--height:38px;width:446px;padding:0 12px;-webkit-appearance:menulist-button;-moz-appearance:menulist-button;appearance:menulist-button;background:#fff;border-radius:2px;height:var(--height);line-height:var(--height)}form.styled .buttons{overflow:hidden;float:right}form.styled .buttons a,form.styled .buttons input{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}form.styled .buttons input{padding:4px 15px}form.styled .buttons a:hover,form.styled .buttons input:hover{background:#103348}@media (width <= 480px){form.styled input,form.styled select,form.styled textarea{font-size:15px}}/*! * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}*{font-family:'Clear Sans',sans-serif}body,html{margin:0;padding:0;height:100%}h1,h2{font-weight:300;color:#568301}h1{margin:10px 0}h3{font-weight:400}a{text-decoration:none}button{border:none}ul{list-style:none;margin:0;padding:0}hr{border:none;border-bottom:1px #ddd solid}textarea{max-width:94%}.external-link-image{display:inline-block;margin-left:8px;margin-right:8px;height:16px;width:16px;background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.external-link-image{background-image:url('../img/external-link-hover_3x.png')}}a .external-link-image{background:url('../img/external-link.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){a .external-link-image{background-image:url('../img/external-link_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){a .external-link-image{background-image:url('../img/external-link_3x.png')}}.header a:hover .external-link-image{background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_3x.png')}}.button{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.button:hover{background:#1e5f86}#folder_path_picker,#restore_file_picker,.step3 source-folder-picker{display:block;border:1px solid #d3d3d3;padding:2px;height:100%;overflow:scroll;box-sizing:border-box}.not-clickable{cursor:default!important}.not-clickable div,.not-clickable span,.not-clickable>a{cursor:default!important}.ui-match{font-weight:700;color:#006400}wait-area{min-width:350px;text-align:center;display:block}.prewrapped-text{white-space:pre-wrap}.exceptiontext{background-color:#d3d3d3;color:#000}.backup-result{width:90%;display:grid;grid-template-columns:50% 50%;grid-auto-rows:minmax(50px,auto);margin:0 auto}.backup-result div .horizontal-rule{width:100%;border-bottom:1px solid #d8d8d8;margin:5px 0}.backup-result .box{margin:10px;margin-bottom:0}.backup-result .title{color:#355001;font-weight:700;font-size:30px}.backup-result .item{display:block}.backup-result .item .key{color:#568301;font-weight:700}.backup-result .item .value{color:#505050}.backup-result .item .expanded{padding:0 10px 0 18px;margin-bottom:10px}.backup-result .one{border-right:1px solid #d8d8d8;grid-column:1;grid-row:1}.backup-result .two{grid-column:2;grid-row:1}.backup-result .wide{grid-column:span 2;border-top:1px solid #d8d8d8;padding-top:10px}.backup-result .three{grid-row:2}.backup-result .four{grid-row:3;margin-bottom:10px}.backup-result .four .log-expand-copy{display:flex;margin-bottom:6px}.backup-result .four .log-expand-copy a{margin-left:auto}.backup-result .four textarea{width:100%;max-width:99%;min-height:420px;padding:8px 6px;white-space:pre}.success-color{color:#390}.error-color{color:#c00}.warning-color{color:#fc0}.fatal-color{color:#900}ul.tabs{margin-bottom:10px}ul.tabs>li{display:inline;margin-right:10px;border:1px solid #277db0;padding:5px}ul.tabs>li.active{background-color:#277db0;color:#fff}ul.tabs>li.active>a{background-color:#277db0;color:#fff}ul.tabs>li.active.disabled{border:1px solid #d3d3d3;background-color:#d3d3d3;color:grey;cursor:default}ul.tabs>li.active.disabled>a{background-color:#d3d3d3;color:grey;cursor:default}.licenses>ul{list-style:initial;margin:10px;margin-left:20px}.licenses li{margin-bottom:10px}.licenses a.itemlink{font-weight:700}.logpage ul.entries{list-style:initial;margin:10px;margin-left:20px}.logpage .entries div.entryline.clickable{cursor:pointer}.logpage .entries.livedata li.expanded{height:auto}.logpage .button{text-align:center;margin-right:10px;border:1px solid #277db0;padding:5px;background-color:#277db0;color:#fff;cursor:pointer}.exportpage .checkbox input{width:auto;margin-top:10px}.exportpage .commandline div{background-color:#d3d3d3;color:#000}.themelink{margin-left:20px}ul.notification{position:fixed;bottom:0;left:0;right:0;margin:auto;width:480px}.notification .title{border:1px solid #277db0;background-color:#277db0;border-radius:5px 5px 0 0;padding:2px;padding-left:5px;padding-right:5px;font-weight:700;color:#d3d3d3;width:100%;text-align:center;clear:both}.notification .content{background-color:#fff;border:1px solid #277db0;border-radius:0 0 5px 5px;padding:2px;padding-left:5px;padding-right:5px;width:100%}.notification .message{width:100%;color:#000}.notification .button{padding:2px 10px;margin-top:6px}.notification .clear{clear:right;height:1px}.notification .error .title{border-color:red;background-color:red}.notification .error .content{border-color:red}.notification .error .button{border-color:red;background-color:red}.notification .warning .title{background-color:orange;border-color:orange}.notification .warning .button{background-color:orange;border-color:orange}.notification .warning .content{border-color:orange}.filepicker{height:200px}.resizable{margin-bottom:6px;max-width:100%}.advanced-toggle{float:right;margin-right:25px;line-height:37px}.advancedoptions li{clear:both;margin-bottom:10px;padding:10px 0;border-top:1px #d3d3d3 solid}.advancedentry .multiple{display:inline}.advancedentry .shortname{font-weight:700}.advancedentry input[type=text]{width:300px}.advancedentry select{width:300px}.advancedentry input[type=checkbox]{margin-top:13px;width:auto}.advancedentry .delete-item{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.advancedentry .longdescription{--margin-block:10px;margin-top:var(--margin-block);margin-left:190px;clear:both;font-style:italic;white-space:pre-wrap}.advancedentry .longdescription .longdescription__item{margin-block:0 var(--margin-block)}.advancedentry .longdescription .longdescription__default{margin-block:var(--margin-block) 0}.settings div.sublabel{clear:both;padding:0 31px;font-style:italic}.logo img.mainlogo{height:64px;width:64px;float:left;padding-right:8px;padding-top:2px}.logo div.logotext{float:left}.logo a{float:left;display:block;line-height:normal}.logo div.build-suffix{clear:both;display:inline;float:left;font-size:16px;line-height:16px}.logo div.powered-by{font-size:16px;margin:0;line-height:16px;float:left;padding:0;margin-left:5px}.note p{margin-block:0.5rem}.note p:first-child{margin-top:0}.note p:last-child{margin-bottom:0}.fixed-width-font{font-family:monospace}.warning{margin:10px;font-style:italic;color:#f49b42}div.captcha .details{padding-top:10px;margin-left:auto;margin-right:auto;width:180px}div.captcha .code{background:#d3d3d3;color:#000;font-family:monospace;font-size:xx-large;padding:10px}div.captcha .answer{margin-top:16px}.centered-text{text-align:center}body{color:#505050}body .container{min-height:100%;position:relative}body .container .header{line-height:70px;background:#ededed;overflow:hidden;height:70px;position:fixed;top:0;left:0;right:0;z-index:100}body .container .header a{color:#277db0}body .container .header a.active,body .container .header a:hover{color:#101010}body .container .header button{width:26px;height:26px;background-size:26px;cursor:pointer}body .container .header .logo{font-size:30px;font-weight:700;float:left;padding-left:40px}body .container .header .statepadding{padding-right:90px;margin-left:320px}body .container .header .state{float:left;color:#355001;width:595px;padding:13px 15px;margin:10px 20px;border:1px #355001 solid;font-weight:300;font-size:18px;overflow:hidden;line-height:normal;display:inline-block;background-color:#fff;text-overflow:ellipsis;position:relative;height:25px}body .container .header .state strong{display:inline;margin-right:10px}body .container .header .state span{display:inline}body .container .header .state .button{position:static;margin-top:70px}body .container .header .state .content{position:relative;z-index:10;margin-right:40px;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}body .container .header .state .buttons{position:absolute;right:0;top:0;bottom:0;width:26px;margin:13px 15px}body .container .header .state .buttons button{display:block}body .container .header .state .buttons .stop{background:url('../img/progress-stop.png');background-size:100%;z-index:10;position:relative}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_3x.png')}}body .container .header .state .buttons .resume{background:url('../img/progress-resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_3x.png')}}body .container .header .state .progress-bar{position:absolute;top:0;bottom:0;left:0;background:rgba(86,131,1,.25);z-index:5}body .container .header .state .task-name{overflow:hidden;text-overflow:ellipsis;cursor:help}body .container .header .state .task-state-info{display:flex}body .container .header .action-icons{display:inline-block;line-height:normal;margin:10px 0;padding:13px 0;float:left}body .container .header .action-icons-small{display:none;float:right;margin-top:21px;line-height:normal}body .container .header .action-icons-small>button,body .container .header .action-icons>button{display:inline-block}body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background:url('../img/pause.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_3x.png')}}body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background:url('../img/resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_3x.png')}}body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background:url('../img/throttle.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_3x.png')}}body .container .header .action-icons-small>.throttle.inactive,body .container .header .action-icons>.throttle.inactive{opacity:.5}body .container .header .about-header{float:right;padding-right:20px;overflow:hidden}body .container .header .about-header ul{overflow:hidden;list-style:none}body .container .header .about-header ul li{float:right;padding-right:20px}body .container .body{width:100%;overflow:hidden;min-height:500px;padding-top:120px;padding-bottom:70px}body .container .body a{color:#277db0}body .container .body .mainmenu{width:260px;padding-left:40px;float:left;position:fixed}body .container .body .mainmenu>ul>li{position:relative}body .container .body .mainmenu>ul>li>a{font-size:22px;font-weight:300;padding:5px 10px 5px 55px;display:block}body .container .body .mainmenu>ul>li>a:hover{color:#fff}body .container .body .mainmenu>ul>li>a.active{color:#fff}body .container .body .mainmenu>ul>li>a.add{background:url('../img/mainmenu/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore{background:url('../img/mainmenu/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume{background:url('../img/mainmenu/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings{background:url('../img/mainmenu/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout{background:url('../img/mainmenu/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home{background:url('../img/mainmenu/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about{background:url('../img/mainmenu/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_3x.png')}}body .container .body .mainmenu>ul>li>a.home.active{background:#4ca4d7 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.add.active{background:#4ca4d7 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore.active{background:#4ca4d7 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume.active{background:#4ca4d7 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings.active{background:#4ca4d7 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.about.active{background:#4ca4d7 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul>li>a.add:hover{background:#277db0 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore:hover{background:#277db0 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume:hover{background:#277db0 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings:hover{background:#277db0 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout:hover{background:#277db0 url('../img/mainmenu/over/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home:hover{background:#277db0 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about:hover{background:#277db0 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul li.hr-top{padding-top:25px;margin-top:25px;border-top:1px #ededed solid}body .container .body div.contextmenu_container{position:relative}body .container .body .contextmenu{display:none;position:absolute;background:#fff;border:1px #ededed solid;box-shadow:0 4px 8px #000;z-index:200;padding:5px}body .container .body .contextmenu li a{color:#277db0;font-size:15px;font-weight:400;display:block;min-width:200px;padding:4px 10px;white-space:nowrap;padding-left:45px;overflow:hidden;text-overflow:ellipsis}body .container .body .contextmenu li a:hover{background:#277db0;color:#fff}body .container .body .contextmenu.open{display:block}body .container .body .content{float:left;padding-left:350px;padding-bottom:50px;max-width:70%}body .container .body .content ul.tabs>li{display:inline-block}body .container .body .content .tasks .tasklist .task{border-top:1px solid #eee;padding-top:20px;margin-bottom:25px}body .container .body .content .tasks .tasklist .task:last-child{border-bottom:1px solid #eee;padding-bottom:20px}body .container .body .content .tasks .tasklist .task:first-child{padding-top:0;border-top:0 none}body .container .body .content .tasks .tasklist .progress-small{text-align:center;height:18px;background:#a4d105}body .container .body .content .tasks .tasklist .progress-small-bg{border:1px #65b1dd solid;width:200px}body .container .body .content .tasks .tasklist a{font-size:30px;font-weight:300;display:inline-block}body .container .body .content .tasks .tasklist a.action-link{font-size:14px;background:0 0;padding-left:0}body .container .body .content .tasks .tasklist dl{padding-left:55px;overflow:hidden;font-size:14px}body .container .body .content .tasks .tasklist dl dd,body .container .body .content .tasks .tasklist dl dt{display:block;float:left}body .container .body .content .tasks .tasklist dl dt{clear:both;font-weight:500;margin-bottom:5px}body .container .body .content .tasks .tasklist dl dd{margin-left:10px}body .container .body .content .tasks .tasklist dl.taskmenu p{display:inline;margin-right:10px;color:#277db0;cursor:pointer}body .container .body .content .tasks .tasklist dl.taskmenu dt{float:left;margin-right:10px;margin-bottom:0;padding:5px 8px;color:#505050;cursor:pointer;clear:none}body .container .body .content .tasks .tasklist dl.taskmenu dd{clear:both;float:none;padding-bottom:8px;border-bottom:1px #ddd solid;margin-bottom:5px}body .container .body .content div.add,body .container .body .content div.restore{--legends-width:700px;--legends-padding-left:calc(calc(700px - var(--legends-width)) / 2);--circle-width:43px;--step-width:calc(var(--legends-width) / var(--legends-steps))}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{margin-left:calc(calc(calc(var(--step-width) - var(--circle-width))/ 2) + var(--legends-padding-left))}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{padding-left:calc(var(--step-width) - var(--circle-width));padding-right:0}body .container .body .content div.add .steps button:first-child,body .container .body .content div.add .steps div:first-child,body .container .body .content div.restore .steps button:first-child,body .container .body .content div.restore .steps div:first-child{padding-left:unset}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{padding-left:var(--legends-padding-left)}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{width:var(--step-width)}body .container .body .content div.add{--legends-steps:5}body .container .body .content div.restore{--legends-steps:2}body .container .body .content div.restore.restore-direct{--legends-steps:4}body .container .body .content div.restore.restore-direct .steps-legend{padding-left:20px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{width:100%;overflow:hidden}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{float:left;background:url('../img/steps/line-out.png') no-repeat top left;background-size:485px 24px;color:#c7e5f6}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_3x.png')}}body .container .body .content div.add .steps button span,body .container .body .content div.add .steps div span,body .container .body .content div.restore .steps button span,body .container .body .content div.restore .steps div span{--size:35px;display:block;border-width:4px;border-style:solid;border-color:#c7e5f6;background:#fff;border-radius:50%;width:var(--size);height:var(--size);text-align:center;font-size:22px;line-height:var(--size);cursor:pointer}body .container .body .content div.add .steps button.active,body .container .body .content div.add .steps div.active,body .container .body .content div.restore .steps button.active,body .container .body .content div.restore .steps div.active{color:#277db0}body .container .body .content div.add .steps button.active span,body .container .body .content div.add .steps div.active span,body .container .body .content div.restore .steps button.active span,body .container .body .content div.restore .steps div.active span{border-color:#277db0;background:#277db0;color:#fff}body .container .body .content div.add .steps button.active h2,body .container .body .content div.add .steps div.active h2,body .container .body .content div.restore .steps button.active h2,body .container .body .content div.restore .steps div.active h2{color:#277db0}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{overflow:hidden;padding-bottom:50px;list-style:none;margin:0}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{color:#c7e5f6;font-size:18px;text-align:center;float:left;padding-top:10px;cursor:pointer}body .container .body .content div.add .steps-legend li.active,body .container .body .content div.restore .steps-legend li.active{color:#277db0}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes{padding-left:40px}body .container .body .content div.add .steps-boxes .step,body .container .body .content div.restore .steps-boxes .step{display:none}body .container .body .content div.add .steps-boxes .step.active,body .container .body .content div.restore .steps-boxes .step.active{display:block}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:left;margin-left:20px;color:#505050}body .container .body .content div.add .steps-boxes .box.browser .checklinks a i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a i{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive{color:#838383;cursor:default}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive i{border-color:#838383}body .container .body .content div.add .steps-boxes .box.browser .checklinks a:first-child,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a:first-child{margin-left:0}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton{padding-top:10px;max-width:100%}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton input#sourcePath,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton input#sourcePath{width:100%;box-sizing:border-box;height:37px}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton a.button,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton a.button{top:10px}body .container .body .content div.add .steps-boxes .box.filters .input.link a,body .container .body .content div.restore .steps-boxes .box.filters .input.link a{color:#505050}body .container .body .content div.add .steps-boxes .box.filters .input.link a i,body .container .body .content div.restore .steps-boxes .box.filters .input.link a i{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist{overflow:hidden;padding-bottom:15px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li{overflow:hidden;clear:both;padding-bottom:25px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li select,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li select{width:200px;margin-right:5px;height:36px;line-height:36px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li input,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li input{width:calc(100% - 280px);padding:5px}body .container .body .content div.add .steps-boxes .step1 li.strength.score-0,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-0{color:red}body .container .body .content div.add .steps-boxes .step1 li.strength.score-1,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-1{color:#f70}body .container .body .content div.add .steps-boxes .step1 li.strength.score-2,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-2{color:#aa0}body .container .body .content div.add .steps-boxes .step1 li.strength.score-3,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-3{color:#070}body .container .body .content div.add .steps-boxes .step1 li.strength.score-4,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-4{color:#427e27}body .container .body .content div.add .steps-boxes .step1 li.strength.score-x,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-x{color:red}body .container .body .content div.add .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.add .steps-boxes .step5 div.input.maxSize input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.maxSize input.number{width:60px}body .container .body .content div.add .steps-boxes .step5 .hint,body .container .body .content div.add .steps-boxes .step5 .retention-options,body .container .body .content div.restore .steps-boxes .step5 .hint,body .container .body .content div.restore .steps-boxes .step5 .retention-options{clear:both;margin-left:190px;margin-top:50px;font-style:italic}body .container .body .content div.add .steps-boxes .step5 .retention-options input,body .container .body .content div.restore .steps-boxes .step5 .retention-options input{margin-bottom:10px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions{padding-top:15px;clear:both}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li{border-top:none}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li.advancedentry,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li.advancedentry{border-bottom:1px solid #d3d3d3}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child{padding-top:0}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child select{max-width:400px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions label,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions label{line-height:normal}body .container .body .content div.add .steps-boxes .step5 .advancedoptions input,body .container .body .content div.add .steps-boxes .step5 .advancedoptions select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions input,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions select{width:auto;max-width:100%;box-sizing:border-box}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle{color:#505050;line-height:normal;margin-top:16px;clear:both;float:left}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle i.fa,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle i.fa{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .step5 textarea,body .container .body .content div.restore .steps-boxes .step5 textarea{box-sizing:border-box;clear:both;margin-top:15px;width:100%}body .container .body .content div.add form,body .container .body .content div.restore form{padding-bottom:50px;overflow:hidden}body .container .body .content div.add form .input.password .tools,body .container .body .content div.restore form .input.password .tools{clear:both;padding-left:190px;padding-top:10px}body .container .body .content div.add form .input.password .tools ul,body .container .body .content div.restore form .input.password .tools ul{overflow:hidden}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{float:left;padding-right:7px}body .container .body .content div.add form .input.password .tools ul li.strength.useless,body .container .body .content div.restore form .input.password .tools ul li.strength.useless{color:red}body .container .body .content div.add form .input.password .tools ul li.strength.average,body .container .body .content div.restore form .input.password .tools ul li.strength.average{color:#ff0}body .container .body .content div.add form .input.password .tools ul li.strength.good,body .container .body .content div.restore form .input.password .tools ul li.strength.good{color:#277db0}body .container .body .content div.add form .input.multiple input,body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple input,body .container .body .content div.restore form .input.multiple select{width:auto;margin-right:5px}body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple select{--padding-block:5px;padding:var(--padding-block) 12px;line-height:calc(var(--height) - calc(var(--padding-block) * 2))}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{overflow:hidden;position:relative;max-width:446px}body .container .body .content div.add form .input.overlayButton input,body .container .body .content div.restore form .input.overlayButton input{width:347px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{position:absolute;top:0;right:0;padding:7px 12px 8px}body .container .body .content div.add form .input.checkbox.multiple strong,body .container .body .content div.restore form .input.checkbox.multiple strong{display:block;padding-bottom:5px}body .container .body .content div.add form .input.checkbox.multiple label,body .container .body .content div.restore form .input.checkbox.multiple label{display:inline-block;float:none;width:auto;padding-right:10px}body .container .body .content div.add form .input.checkbox.multiple input,body .container .body .content div.restore form .input.checkbox.multiple input{width:auto;display:inline-block;float:none}body .container .body .content div.add form .buttons,body .container .body .content div.restore form .buttons{float:none;width:635px;padding-top:30px}body .container .body .content .commandline .input.select,body .container .body .content div.add .step2 .input.select,body .container .body .content div.restore .step1 .input.select{display:grid;grid-auto-flow:column;justify-content:flex-start;grid-template-areas:"label server" ". custom"}body .container .body .content .commandline .input.select label,body .container .body .content div.add .step2 .input.select label,body .container .body .content div.restore .step1 .input.select label{grid-area:label}body .container .body .content .commandline .input.select select,body .container .body .content div.add .step2 .input.select select,body .container .body .content div.restore .step1 .input.select select{grid-area:server}body .container .body .content .commandline .input.select input,body .container .body .content div.add .step2 .input.select input,body .container .body .content div.restore .step1 .input.select input{grid-area:custom;margin-top:10px}body .container .body .content .commandline .input.text #generic_server,body .container .body .content div.add .step2 .input.text #generic_server,body .container .body .content div.restore .step1 .input.text #generic_server{width:335px}body .container .body .content .commandline .input.text #generic_port,body .container .body .content div.add .step2 .input.text #generic_port,body .container .body .content div.restore .step1 .input.text #generic_port{width:50px;margin-left:10px}body .container .body .content div.headerthreedotmenu{margin:20px 0}body .container .body .content div.headerthreedotmenu h2{display:inline}body .container .body .content div.headerthreedotmenu .contextmenu_container{float:right}body .container .body .content div.headerthreedotmenu .contextmenu{left:auto;right:0;top:auto}body .container .body .content div.headerthreedotmenu .threedotmenubutton{padding:5px}body .container .body .content .expandable{margin:20px 0}body .container .body .content .expandable h2{display:inline}body .container .body .content .expandable img{padding:0 6px}body .container .body .content div.settings .input.checkbox input.checkbox,body .container .body .content div.settings .input.mixed.multiple input.checkbox{width:auto}body .container .body .content div.settings .input.checkbox select,body .container .body .content div.settings .input.mixed.multiple select{width:auto;margin-right:5px}body .container .body .content div.settings .input.checkbox label,body .container .body .content div.settings .input.mixed.multiple label{line-height:normal;padding:0 15px;width:auto}body .container .body .content .logpage ul.tabs{padding:15px 0}body .container .body .content .logpage ul.entries li{padding:10px 0;border-bottom:1px solid #d8d8d8}body .container .body .content .logpage ul.backuplog{list-style:none}body .container .body .content .about-general .about-general__block{margin-block:1rem}body .container .body .content .about-general .about-general__block:first-child{margin-top:10px}body .container .body .content .about-general .about-general__block:last-child{margin-bottom:0}body .container .body .content .prewrapped-text{white-space:pre-wrap;overflow-x:auto}body .container .footer{background:#ededed;min-height:70px;line-height:70px;overflow:hidden;position:absolute;bottom:0;width:100%}body .container .footer a{color:#277db0}body .container .footer .about-footer{float:left;overflow:hidden;padding-right:20px;display:none}body .container .footer .about-footer span{display:block;float:left;padding-left:20px}body .container .footer .about-footer ul{float:left}body .container .footer .about-footer li{float:left;padding-left:20px}body .container .footer .social{float:right}body .container .footer .social ul{overflow:hidden;float:right;padding-left:20px;padding-right:10px}body .container .footer .social ul li{float:right;margin-right:10px;padding-top:5px}body .container .footer .social ul li img{opacity:.6}body .container .footer .social ul li img:hover{opacity:1}body .container .footer .themelink{float:right;padding-right:20px}body #modal-menu{max-width:400px}body #modal-menu a{color:#277db0;font-size:20px;line-height:40px}.remodal{padding:30px;box-shadow:0 2px 7px #000;background:#fff;display:none}.remodal form .buttons{float:none}.remodal-wrapper .remodal{display:block}span.info{font-size:10px;font-weight:500;display:inline-block;background:#277db0;border-radius:50%;width:15px;height:15px;vertical-align:super;color:#fff;line-height:15px;margin-left:5px;text-align:center}.hidden{display:none}.clear{clear:both}.nofloat{float:none!important}div.blocker,div.connection-lost,div.modal-dialog{position:fixed;inset:0;margin:auto}div.blocker{z-index:5000;background-color:#000;opacity:.65}#connection-lost-blocker{z-index:5100}#connection-lost-dialog{z-index:5200}div.connection-lost,div.modal-dialog{z-index:5001;display:-webkit-box;display:-moz-box;display:flexbox;display:flex;-webkit-box-pack:center;-moz-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-moz-box-align:center;-ms-flex-align:center;align-items:center}div.connection-lost div.info,div.modal-dialog div.info{min-width:310px;max-width:650px;margin:5px}div.connection-lost div.title,div.modal-dialog div.title{border:1px solid #65b1dd;background-color:#65b1dd;border-radius:5px 5px 0 0;padding:10px 20px;font-weight:700;color:#d3d3d3;text-align:center}div.connection-lost div.content,div.modal-dialog div.content{background-color:#fff;border:1px solid #fff;padding:20px}div.connection-lost div.content p:first-child,div.modal-dialog div.content p:first-child{margin-top:0}div.connection-lost div.content p:last-child,div.modal-dialog div.content p:last-child{margin-bottom:0}div.connection-lost .buttons,div.modal-dialog .buttons{border-radius:0 0 5px 5px;padding-top:10px;overflow:auto}div.connection-lost form,div.modal-dialog form{margin-top:15px}div.connection-lost form textarea,div.modal-dialog form textarea{height:130px;width:420px;padding:10px 12px;border:1px #d8d8d8 solid;border-radius:2px;color:#505050;font-size:16px;font-weight:300}div.connection-lost form input,div.modal-dialog form input{height:35px;line-height:35px;padding:0 12px}div.modal-dialog .content.buttons ul{float:right}div.modal-dialog .content.buttons .tooltipped{position:relative}div.modal-dialog .content.buttons .tooltipped::after{position:absolute;z-index:1000000;display:none;padding:5px 8px;font:normal normal 11px/1.5 Helvetica,arial,nimbussansl,liberationsans,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol";color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#000;border-radius:3px;-webkit-font-smoothing:subpixel-antialiased}div.modal-dialog .content.buttons .tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:#000;pointer-events:none;content:"";border:5px solid transparent}div.modal-dialog .content.buttons .tooltipped:active::after,div.modal-dialog .content.buttons .tooltipped:active::before,div.modal-dialog .content.buttons .tooltipped:focus::after,div.modal-dialog .content.buttons .tooltipped:focus::before,div.modal-dialog .content.buttons .tooltipped:hover::after,div.modal-dialog .content.buttons .tooltipped:hover::before{display:inline-block;text-decoration:none}div.modal-dialog .content.buttons .tooltipped-w::after{right:100%;bottom:50%;margin-right:5px;transform:translateY(50%)}div.modal-dialog .content.buttons .tooltipped-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:#000}.importpage form.styled input{margin-top:11px;margin-bottom:11px}.addwizard form.styled ul,.restorewizard form.styled ul{margin:20px;margin-left:0}.addwizard form.styled input[type=radio],.restorewizard form.styled input[type=radio]{width:20px;margin-left:5px;margin-right:5px}.addwizard form.styled label,.restorewizard form.styled label{width:auto;line-height:normal}.addwizard form.styled div.subtext,.restorewizard form.styled div.subtext{clear:both;margin-left:30px;padding-top:5px;color:#767676}.pauseoptions form.styled li{line-height:normal;padding:0}.pauseoptions form.styled li input{height:auto;margin-top:8px;margin-right:8px;width:auto}.progress-bar-striped{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress-striped .progress-bar{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress{position:relative;min-height:25px}.progress>span{vertical-align:middle;display:block;width:100%;height:100%;text-align:center;z-index:100;padding-top:2px}.progress.active .progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress .progress-bar{float:left;width:0;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;box-shadow:inset 0 -1px 0 #000;transition:width .6s ease;height:100%;position:absolute;top:0}.progress .progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.tree-view{list-style-type:none;margin-left:10px;padding-bottom:5px}.tree-view ul{margin-left:16px}.tree-view span.nodeLabel{cursor:pointer}.tree-view span.nodeLabel.selected{border:1px solid #aaa;background-color:#ddd;padding:1px 3px}.tree-view li .node{padding-bottom:5px}.tree-view li div.selected{border-color:#add8e6;background-color:#add8e6}.tree-view li>ul{display:none}.tree-view li>ul.expanded{display:block}.tree-view li a.nav{cursor:pointer;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:-80px 0;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.nav.leaf{background:0 0}.tree-view li a.nav.expanded{background-position:-80px -16px}.tree-view li a.type{cursor:auto;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 -16px;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.type.invisible{background-position:0 -32px}.tree-view li a.type.loading{cursor:progress;background-image:url("../img/loader-16.gif");background-repeat:no-repeat;background-position:0 0;background-size:16px 16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type.loading{background-image:url('../img/loader-32.gif')}}.tree-view li a.type.x-tree-icon-drive{background-position:-16px -16px}.tree-view li a.type.x-tree-icon-leaf{background-position:-32px -16px}.tree-view li a.type.x-tree-icon-symlink{background-position:-48px -16px}.tree-view li a.type.x-tree-icon-userdata{background-position:-16px -48px}.tree-view li a.type.x-tree-icon-locked{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-broken{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-computer{background-position:0 -48px}.tree-view li a.type.x-tree-icon-hyperv{background-position:-96px -16px}.tree-view li a.type.x-tree-icon-hypervmachine{background-position:-96px 0}.tree-view li a.type.x-tree-icon-mssql{background-position:-96px -32px}.tree-view li a.type.x-tree-icon-mssqldb{background-position:-80px -32px}.tree-view li a.type.x-tree-icon-mydocuments{background-position:-32px -48px}.tree-view li a.type.x-tree-icon-mymusic{background-position:-48px -48px}.tree-view li a.type.x-tree-icon-mypictures{background-position:-64px -48px}.tree-view li a.type.x-tree-icon-desktop{background-position:-80px -48px}.tree-view li a.type.x-tree-icon-home{background-position:-96px -48px}.tree-view li a.type.x-tree-icon-drive.invisible{background-position:-16px -32px}.tree-view li a.type.x-tree-icon-leaf.invisible{background-position:-32px -32px}.tree-view li a.type.x-tree-icon-symlink.invisible{cursor:auto;background-position:-48px -32px}.tree-view li a.type.x-tree-icon-locked.invisible{background-position:-64px -32px}.tree-view li a.check{height:16px;width:16px;display:inline-block;cursor:pointer;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 0;vertical-align:middle;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.partial{background-position:-32px 0}.tree-view li a.include{background-position:-16px 0}.tree-view li a.exclude{background-position:-48px 0}.tree-view li a.root{background:0 0;display:none}.throttlesettings div.multiple select{width:auto;margin-right:5px}.throttlesettings div.multiple input{width:100px}.throttlesettings div.multiple input.checkbox{width:auto}.throttlesettings div.multiple label{line-height:35px;padding:0 15px;width:auto;min-width:150px}.throttlesettings .disabled{color:#909090}.throttlesettings .disabled input,.throttlesettings .disabled select{color:#909090}@media (width <= 1150px){body .container .header{height:140px}body .container .header .statepadding{padding-right:90px;margin-left:0}body .container .header .state{width:100%;margin:10px 40px;clear:left;float:left}body .container .header .action-icons{display:none}body .container .header .action-icons-small{display:inline-block}body .container .header .menubutton{display:block;font-size:18px;padding-right:50px;margin-top:5px;margin-right:15px;background:url('../img/menu.png') no-repeat right top;background-size:39px 39px;position:relative;height:40px;line-height:40px;color:#505050;float:right;top:10px;padding-left:20px;text-transform:uppercase;text-align:right}body .container .header .menubutton.active{background-image:url('../img/menu_active.png');background-size:39px 39px;color:#277db0}body .container .body{position:relative;padding-top:140px}body .container .body .mainmenu{display:none;position:fixed;background:none repeat scroll 0 0 #fff;box-shadow:0 4px 8px #000;left:10px;padding:20px;top:60px}body .container .body .mainmenu.mobile-open{display:block;left:auto;right:0;top:0;z-index:1000}body .container .body .contextmenu{left:0;top:auto}body .container .body .content{float:none;padding:20px;margin:0 auto 30px}body .container .body .content .state{width:auto}body .container .mobileOpen{display:block!important}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton{background-image:url('../img/menu_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton{background-image:url('../img/menu_3x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_3x.png')}}@media (width <= 768px){body .container .body .content .tasks .tasklist a{font-size:20px;background-size:24px;background-position:0 4px;padding-left:35px}body .container .body .content .tasks .tasklist dl{padding-left:35px}body .container .header .logo{padding-left:10px}body .container .header .statepadding{padding-right:50px}body .container .header .state{margin-left:10px}body .container .header .menubutton{margin-right:5px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps,body .container .body .content div.settings .steps{display:none}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend,body .container .body .content div.settings .steps-legend{list-style:decimal;padding-left:20px;border-bottom:1px solid #eee;margin-bottom:30px;padding-bottom:20px}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li,body .container .body .content div.settings .steps-legend li{float:none;font-weight:500;width:auto!important;padding-right:0!important}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes,body .container .body .content div.settings .steps-boxes{padding-left:0}body .container .body .content div.add form.styled .input input,body .container .body .content div.add form.styled .input select,body .container .body .content div.add form.styled .input textarea,body .container .body .content div.restore form.styled .input input,body .container .body .content div.restore form.styled .input select,body .container .body .content div.restore form.styled .input textarea,body .container .body .content div.settings form.styled .input input,body .container .body .content div.settings form.styled .input select,body .container .body .content div.settings form.styled .input textarea{max-width:100%;box-sizing:border-box}body .container .body .content div.add form.styled .input.select select,body .container .body .content div.restore form.styled .input.select select,body .container .body .content div.settings form.styled .input.select select{width:420px}body .container .body .content div.add form.styled .buttons,body .container .body .content div.restore form.styled .buttons,body .container .body .content div.settings form.styled .buttons{max-width:100%;width:auto}body .container .body .content div.add form.styled .tools,body .container .body .content div.restore form.styled .tools,body .container .body .content div.settings form.styled .tools{padding-left:0!important}body .container .body .content div.add form.styled .input.checkbox.multiple,body .container .body .content div.restore form.styled .input.checkbox.multiple,body .container .body .content div.settings form.styled .input.checkbox.multiple{padding-bottom:5px}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.add form.styled .input.checkbox.multiple label,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple label,body .container .body .content div.settings form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple label{display:block!important;float:left!important;line-height:normal}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple input{clear:both}body .container .body .content div.add form.styled .input.text.multiple input,body .container .body .content div.restore form.styled .input.text.multiple input,body .container .body .content div.settings form.styled .input.text.multiple input{max-width:48%!important}}@media (width <= 640px){body h2{font-size:20px;text-align:center}body .container .body{padding-bottom:10px}body .container .body .content{margin:0 auto}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{padding-top:8px;padding-bottom:30px;margin-bottom:10px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{padding:7px 10px;right:1px;top:9px}body .container .body .content div.add form .input.checkbox.multiple div,body .container .body .content div.restore form .input.checkbox.multiple div{display:block}body .container .body .content div.add form .input.select.multiple input#exclude-larger-than-number,body .container .body .content div.restore form .input.select.multiple input#exclude-larger-than-number{width:75px}body .container .body .content div.add form .input.select.multiple select#exclude-larger-than-multiplier,body .container .body .content div.restore form .input.select.multiple select#exclude-larger-than-multiplier{width:140px}body .container .body .content div.add form .filters .input.textarea,body .container .body .content div.restore form .filters .input.textarea{padding-bottom:10px}body .container .body .content div.add form .filters h3,body .container .body .content div.restore form .filters h3{margin:5px 0}body .container .body .content div.add form .input.text.select.multiple.repeat label,body .container .body .content div.restore form .input.text.select.multiple.repeat label{float:none}body .container .body .content div.add form .input.text.select.multiple.repeat input#repeatRunNumber,body .container .body .content div.restore form .input.text.select.multiple.repeat input#repeatRunNumber{width:70px}body .container .body .content div.add form .input.text.select.multiple.repeat select#repeatRunMultiplier,body .container .body .content div.restore form .input.text.select.multiple.repeat select#repeatRunMultiplier{width:100px}body .container .body .content div.add form .input.multiple.text.select.maxSize input,body .container .body .content div.restore form .input.multiple.text.select.maxSize input{width:70px}body .container .body .content div.add form .input.multiple.text.select.maxSize select,body .container .body .content div.restore form .input.multiple.text.select.maxSize select{width:100px}body .container .body .content div.add form .input.multiple.text.select.keepBackups select,body .container .body .content div.restore form .input.multiple.text.select.keepBackups select{width:85px;padding:4px 6px}body .container .body .content div.add form .input.multiple.text.select.keepBackups input,body .container .body .content div.restore form .input.multiple.text.select.keepBackups input{width:60px}body .container .footer{position:static;padding:15px;line-height:normal;text-align:left;box-sizing:border-box}body .container .footer *{float:none!important;text-align:center;box-sizing:border-box}body .container .footer .about-footer{padding-right:0;display:block}body .container .footer .about-footer span{padding-left:0;padding-bottom:5px}body .container .footer .about-footer li{padding-left:0;float:none;display:inline-block;height:32px;width:32px;background-size:28px!important;border-bottom:none}body .container .footer .about-footer li:first-child{padding-bottom:0}body .container .footer .about-footer li:last-child{padding-bottom:20px}body .container .footer .about-footer,body .container .footer .social,body .container .footer li{padding:8px 0;border-bottom:1px #ddd solid}body .container .footer .social li{display:inline-block;border:none}body .container .footer .themelink{padding:8px 0}}@media (width <= 580px){.advancedentry .longdescription{margin-left:0}}@media (width <= 492px){ul.notification{width:auto}}@media (width <= 480px){body{font-size:15px}body .container .header .logo{padding-left:5px}body .container .header .menubutton{margin-right:5px}body .container .header .state{margin-left:5px}body .container .header .statepadding{padding-right:40px}body .container .header .menubutton{padding-left:10px}body .container .body .mainmenu{width:280px;box-sizing:border-box}body .container .body .mainmenu ul li a{font-size:22px}body .container .body .content{padding:15px}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{font-size:14px}body .container .body .content div.add form .buttons a,body .container .body .content div.restore form .buttons a{float:none;text-align:center;margin-bottom:5px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:none;margin-bottom:8px;display:block}}@media (width <= 400px){body{font-size:15px}body .container .header .menubutton{margin-right:0;padding-left:0;padding-right:40px}body .container .header .menubutton span{display:none}}@media (width <= 325px){body{font-size:15px}body .container .header .logo div{display:none}}@media (width <= 200px){body{font-size:15px}body .container .header .menubutton{position:static;margin-top:0}body .container .header .action-icons-small{clear:right;margin-top:0}} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0)}100%{transform:rotate(359deg)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scale(-1,1)}.fa-flip-vertical{transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-y-combinator:before,.fa-yc:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-television:before,.fa-tv:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}*{font-family:'Clear Sans',sans-serif}body,html{margin:0;padding:0;height:100%}h1,h2{font-weight:300;color:#568301}h1{margin:10px 0}h3{font-weight:400}a{text-decoration:none}button{border:none}ul{list-style:none;margin:0;padding:0}hr{border:none;border-bottom:1px #ddd solid}textarea{max-width:94%}.external-link-image{display:inline-block;margin-left:8px;margin-right:8px;height:16px;width:16px;background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.external-link-image{background-image:url('../img/external-link-hover_3x.png')}}a .external-link-image{background:url('../img/external-link.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){a .external-link-image{background-image:url('../img/external-link_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){a .external-link-image{background-image:url('../img/external-link_3x.png')}}.header a:hover .external-link-image{background:url('../img/external-link-hover.png');background-size:16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.header a:hover .external-link-image{background-image:url('../img/external-link-hover_3x.png')}}.button{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.button:hover{background:#1e5f86}#folder_path_picker,#restore_file_picker,.step3 source-folder-picker{display:block;border:1px solid #d3d3d3;padding:2px;height:100%;overflow:scroll;box-sizing:border-box}.not-clickable{cursor:default!important}.not-clickable div,.not-clickable span,.not-clickable>a{cursor:default!important}.ui-match{font-weight:700;color:#006400}wait-area{min-width:350px;text-align:center;display:block}.prewrapped-text{white-space:pre-wrap}.exceptiontext{background-color:#d3d3d3;color:#000}.backup-result{width:90%;display:grid;grid-template-columns:50% 50%;grid-auto-rows:minmax(50px,auto);margin:0 auto}.backup-result div .horizontal-rule{width:100%;border-bottom:1px solid #d8d8d8;margin:5px 0}.backup-result .box{margin:10px;margin-bottom:0}.backup-result .title{color:#355001;font-weight:700;font-size:30px}.backup-result .item{display:block}.backup-result .item .key{color:#568301;font-weight:700}.backup-result .item .value{color:#505050}.backup-result .item .expanded{padding:0 10px 0 18px;margin-bottom:10px}.backup-result .one{border-right:1px solid #d8d8d8;grid-column:1;grid-row:1}.backup-result .two{grid-column:2;grid-row:1}.backup-result .wide{grid-column:span 2;border-top:1px solid #d8d8d8;padding-top:10px}.backup-result .three{grid-row:2}.backup-result .four{grid-row:3;margin-bottom:10px}.backup-result .four .log-expand-copy{display:flex;margin-bottom:6px}.backup-result .four .log-expand-copy a{margin-left:auto}.backup-result .four textarea{width:100%;max-width:99%;min-height:420px;padding:8px 6px;white-space:pre}.success-color{color:#390}.error-color{color:#c00}.warning-color{color:#fc0}.fatal-color{color:#900}ul.tabs{margin-bottom:10px}ul.tabs>li{display:inline;margin-right:10px;border:1px solid #277db0;padding:5px}ul.tabs>li.active{background-color:#277db0;color:#fff}ul.tabs>li.active>a{background-color:#277db0;color:#fff}ul.tabs>li.active.disabled{border:1px solid #d3d3d3;background-color:#d3d3d3;color:grey;cursor:default}ul.tabs>li.active.disabled>a{background-color:#d3d3d3;color:grey;cursor:default}.licenses>ul{list-style:initial;margin:10px;margin-left:20px}.licenses li{margin-bottom:10px}.licenses a.itemlink{font-weight:700}.logpage ul.entries{list-style:initial;margin:10px;margin-left:20px}.logpage .entries div.entryline.clickable{cursor:pointer}.logpage .entries.livedata li.expanded{height:auto}.logpage .button{text-align:center;margin-right:10px;border:1px solid #277db0;padding:5px;background-color:#277db0;color:#fff;cursor:pointer}.exportpage .checkbox input{width:auto;margin-top:10px}.exportpage .commandline div{background-color:#d3d3d3;color:#000}.themelink{margin-left:20px}ul.notification{position:fixed;bottom:0;left:0;right:0;margin:auto;width:480px}.notification .title{border:1px solid #277db0;background-color:#277db0;border-radius:5px 5px 0 0;padding:2px;padding-left:5px;padding-right:5px;font-weight:700;color:#d3d3d3;width:100%;text-align:center;clear:both}.notification .content{background-color:#fff;border:1px solid #277db0;border-radius:0 0 5px 5px;padding:2px;padding-left:5px;padding-right:5px;width:100%}.notification .message{width:100%;color:#000}.notification .button{padding:2px 10px;margin-top:6px}.notification .clear{clear:right;height:1px}.notification .error .title{border-color:red;background-color:red}.notification .error .content{border-color:red}.notification .error .button{border-color:red;background-color:red}.notification .warning .title{background-color:orange;border-color:orange}.notification .warning .button{background-color:orange;border-color:orange}.notification .warning .content{border-color:orange}.filepicker{height:200px}.resizable{margin-bottom:6px;max-width:100%}.advanced-toggle{float:right;margin-right:25px;line-height:37px}.advancedoptions li{clear:both;margin-bottom:10px;padding:10px 0;border-top:1px #d3d3d3 solid}.advancedentry .multiple{display:inline}.advancedentry .shortname{font-weight:700}.advancedentry input[type=text]{width:300px}.advancedentry select{width:300px}.advancedentry input[type=checkbox]{margin-top:13px;width:auto}.advancedentry .delete-item{display:block;background:#277db0;color:#fff!important;padding:5px 15px;float:right;margin-left:10px;cursor:pointer;width:auto;border:none;font-family:'Clear Sans',sans-serif;font-size:16px;font-weight:300;border-radius:0}.advancedentry .longdescription{--margin-block:10px;margin-top:var(--margin-block);margin-left:190px;clear:both;font-style:italic;white-space:pre-wrap}.advancedentry .longdescription .longdescription__item{margin-block:0 var(--margin-block)}.advancedentry .longdescription .longdescription__default{margin-block:var(--margin-block) 0}.settings div.sublabel{clear:both;padding:0 31px;font-style:italic}.logo img.mainlogo{height:64px;width:64px;float:left;padding-right:8px;padding-top:2px}.logo div.logotext{float:left}.logo a{float:left;display:block;line-height:normal}.logo div.build-suffix{clear:both;display:inline;float:left;font-size:16px;line-height:16px}.logo div.powered-by{font-size:16px;margin:0;line-height:16px;float:left;padding:0;margin-left:5px}.note p{margin-block:0.5rem}.note p:first-child{margin-top:0}.note p:last-child{margin-bottom:0}.fixed-width-font{font-family:monospace}.warning{margin:10px;font-style:italic;color:#f49b42}div.captcha .details{padding-top:10px;margin-left:auto;margin-right:auto;width:180px}div.captcha .code{background:#d3d3d3;color:#000;font-family:monospace;font-size:xx-large;padding:10px}div.captcha .answer{margin-top:16px}.pauseoptions div.sublabel{clear:both;padding:0 31px;font-style:italic}.pauseoptions .input.checkbox{margin-top:10px}.pauseoptions .input.checkbox input.checkbox{width:auto;height:auto}.pauseoptions .input.checkbox label{line-height:normal;padding:0 15px;width:auto}.centered-text{text-align:center}body{color:#505050}body .container{min-height:100%;position:relative}body .container .header{line-height:70px;background:#ededed;overflow:hidden;height:70px;position:fixed;top:0;left:0;right:0;z-index:100}body .container .header a{color:#277db0}body .container .header a.active,body .container .header a:hover{color:#101010}body .container .header button{width:26px;height:26px;background-size:26px;cursor:pointer}body .container .header .logo{font-size:30px;font-weight:700;float:left;padding-left:40px}body .container .header .statepadding{padding-right:90px;margin-left:320px}body .container .header .state{float:left;color:#355001;width:595px;padding:13px 15px;margin:10px 20px;border:1px #355001 solid;font-weight:300;font-size:18px;overflow:hidden;line-height:normal;display:inline-block;background-color:#fff;text-overflow:ellipsis;position:relative;height:25px}body .container .header .state strong{display:inline;margin-right:10px}body .container .header .state span{display:inline}body .container .header .state .button{position:static;margin-top:70px}body .container .header .state .content{position:relative;z-index:10;margin-right:40px;display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}body .container .header .state .buttons{position:absolute;right:0;top:0;bottom:0;width:26px;margin:13px 15px}body .container .header .state .buttons button{display:block}body .container .header .state .buttons .stop{background:url('../img/progress-stop.png');background-size:100%;z-index:10;position:relative}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .stop{background-image:url('../img/progress-stop_3x.png')}}body .container .header .state .buttons .resume{background:url('../img/progress-resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .state .buttons .resume{background-image:url('../img/progress-resume_3x.png')}}body .container .header .state .progress-bar{position:absolute;top:0;bottom:0;left:0;background:rgba(86,131,1,.25);z-index:5}body .container .header .state .task-name{overflow:hidden;text-overflow:ellipsis;cursor:help}body .container .header .state .task-state-info{display:flex}body .container .header .action-icons{display:inline-block;line-height:normal;margin:10px 0;padding:13px 0;float:left}body .container .header .action-icons-small{display:none;float:right;margin-top:21px;line-height:normal}body .container .header .action-icons-small>button,body .container .header .action-icons>button{display:inline-block}body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background:url('../img/pause.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause,body .container .header .action-icons>.pause{background-image:url('../img/pause_3x.png')}}body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background:url('../img/resume.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.pause.active,body .container .header .action-icons>.pause.active{background-image:url('../img/resume_3x.png')}}body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background:url('../img/throttle.png');background-size:100%}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .header .action-icons-small>.throttle,body .container .header .action-icons>.throttle{background-image:url('../img/throttle_3x.png')}}body .container .header .action-icons-small>.throttle.inactive,body .container .header .action-icons>.throttle.inactive{opacity:.5}body .container .header .about-header{float:right;padding-right:20px;overflow:hidden}body .container .header .about-header ul{overflow:hidden;list-style:none}body .container .header .about-header ul li{float:right;padding-right:20px}body .container .body{width:100%;overflow:hidden;min-height:500px;padding-top:120px;padding-bottom:70px}body .container .body a{color:#277db0}body .container .body .mainmenu{width:260px;padding-left:40px;float:left;position:fixed}body .container .body .mainmenu>ul>li{position:relative}body .container .body .mainmenu>ul>li>a{font-size:22px;font-weight:300;padding:5px 10px 5px 55px;display:block}body .container .body .mainmenu>ul>li>a:hover{color:#fff}body .container .body .mainmenu>ul>li>a.active{color:#fff}body .container .body .mainmenu>ul>li>a.add{background:url('../img/mainmenu/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add{background-image:url('../img/mainmenu/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore{background:url('../img/mainmenu/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore{background-image:url('../img/mainmenu/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume{background:url('../img/mainmenu/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume{background-image:url('../img/mainmenu/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings{background:url('../img/mainmenu/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings{background-image:url('../img/mainmenu/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout{background:url('../img/mainmenu/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout{background-image:url('../img/mainmenu/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home{background:url('../img/mainmenu/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home{background-image:url('../img/mainmenu/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about{background:url('../img/mainmenu/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about{background-image:url('../img/mainmenu/about_3x.png')}}body .container .body .mainmenu>ul>li>a.home.active{background:#4ca4d7 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home.active{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.add.active{background:#4ca4d7 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add.active{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore.active{background:#4ca4d7 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore.active{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume.active{background:#4ca4d7 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume.active{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings.active{background:#4ca4d7 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings.active{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.about.active{background:#4ca4d7 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about.active{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul>li>a.add:hover{background:#277db0 url('../img/mainmenu/over/add.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.add:hover{background-image:url('../img/mainmenu/over/add_3x.png')}}body .container .body .mainmenu>ul>li>a.restore:hover{background:#277db0 url('../img/mainmenu/over/restore.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.restore:hover{background-image:url('../img/mainmenu/over/restore_3x.png')}}body .container .body .mainmenu>ul>li>a.resume:hover{background:#277db0 url('../img/mainmenu/over/resume.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.resume:hover{background-image:url('../img/mainmenu/over/resume_3x.png')}}body .container .body .mainmenu>ul>li>a.settings:hover{background:#277db0 url('../img/mainmenu/over/settings.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.settings:hover{background-image:url('../img/mainmenu/over/settings_3x.png')}}body .container .body .mainmenu>ul>li>a.logout:hover{background:#277db0 url('../img/mainmenu/over/logout.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.logout:hover{background-image:url('../img/mainmenu/over/logout_3x.png')}}body .container .body .mainmenu>ul>li>a.home:hover{background:#277db0 url('../img/mainmenu/over/home.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.home:hover{background-image:url('../img/mainmenu/over/home_3x.png')}}body .container .body .mainmenu>ul>li>a.about:hover{background:#277db0 url('../img/mainmenu/over/about.png') no-repeat 8px 7px;background-size:27px 26px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .mainmenu>ul>li>a.about:hover{background-image:url('../img/mainmenu/over/about_3x.png')}}body .container .body .mainmenu>ul li.hr-top{padding-top:25px;margin-top:25px;border-top:1px #ededed solid}body .container .body div.contextmenu_container{position:relative}body .container .body .contextmenu{display:none;position:absolute;background:#fff;border:1px #ededed solid;box-shadow:0 4px 8px #000;z-index:200;padding:5px}body .container .body .contextmenu li a{color:#277db0;font-size:15px;font-weight:400;display:block;min-width:200px;padding:4px 10px;white-space:nowrap;padding-left:45px;overflow:hidden;text-overflow:ellipsis}body .container .body .contextmenu li a:hover{background:#277db0;color:#fff}body .container .body .contextmenu.open{display:block}body .container .body .content{float:left;padding-left:350px;padding-bottom:50px;max-width:70%}body .container .body .content ul.tabs>li{display:inline-block}body .container .body .content .tasks .orderbox{float:right}body .container .body .content .tasks .tasklist .task{border-top:1px solid #eee;padding-top:20px;margin-bottom:25px}body .container .body .content .tasks .tasklist .task:last-child{border-bottom:1px solid #eee;padding-bottom:20px}body .container .body .content .tasks .tasklist .task:first-child{padding-top:0;border-top:0 none}body .container .body .content .tasks .tasklist .progress-small{text-align:center;height:18px;background:#a4d105}body .container .body .content .tasks .tasklist .progress-small-bg{border:1px #65b1dd solid;width:200px}body .container .body .content .tasks .tasklist a{font-size:30px;font-weight:300;display:inline-block}body .container .body .content .tasks .tasklist a.action-link{font-size:14px;background:0 0;padding-left:0}body .container .body .content .tasks .tasklist dl{padding-left:55px;overflow:hidden;font-size:14px}body .container .body .content .tasks .tasklist dl dd,body .container .body .content .tasks .tasklist dl dt{display:block;float:left}body .container .body .content .tasks .tasklist dl dt{clear:both;font-weight:500;margin-bottom:5px}body .container .body .content .tasks .tasklist dl dd{margin-left:10px}body .container .body .content .tasks .tasklist dl.taskmenu p{display:inline;margin-right:10px;color:#277db0;cursor:pointer}body .container .body .content .tasks .tasklist dl.taskmenu dt{float:left;margin-right:10px;margin-bottom:0;padding:5px 8px;color:#505050;cursor:pointer;clear:none}body .container .body .content .tasks .tasklist dl.taskmenu dd{clear:both;float:none;padding-bottom:8px;border-bottom:1px #ddd solid;margin-bottom:5px}body .container .body .content div.add,body .container .body .content div.restore{--legends-width:700px;--legends-padding-left:calc(calc(700px - var(--legends-width)) / 2);--circle-width:43px;--step-width:calc(var(--legends-width) / var(--legends-steps))}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{margin-left:calc(calc(calc(var(--step-width) - var(--circle-width))/ 2) + var(--legends-padding-left))}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{padding-left:calc(var(--step-width) - var(--circle-width));padding-right:0}body .container .body .content div.add .steps button:first-child,body .container .body .content div.add .steps div:first-child,body .container .body .content div.restore .steps button:first-child,body .container .body .content div.restore .steps div:first-child{padding-left:unset}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{padding-left:var(--legends-padding-left)}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{width:var(--step-width)}body .container .body .content div.add{--legends-steps:5}body .container .body .content div.restore{--legends-steps:2}body .container .body .content div.restore.restore-direct{--legends-steps:4}body .container .body .content div.restore.restore-direct .steps-legend{padding-left:20px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps{width:100%;overflow:hidden}body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{float:left;background:url('../img/steps/line-out.png') no-repeat top left;background-size:485px 24px;color:#c7e5f6}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){body .container .body .content div.add .steps button,body .container .body .content div.add .steps div,body .container .body .content div.restore .steps button,body .container .body .content div.restore .steps div{background-image:url('../img/steps/line-out_3x.png')}}body .container .body .content div.add .steps button span,body .container .body .content div.add .steps div span,body .container .body .content div.restore .steps button span,body .container .body .content div.restore .steps div span{--size:35px;display:block;border-width:4px;border-style:solid;border-color:#c7e5f6;background:#fff;border-radius:50%;width:var(--size);height:var(--size);text-align:center;font-size:22px;line-height:var(--size);cursor:pointer}body .container .body .content div.add .steps button.active,body .container .body .content div.add .steps div.active,body .container .body .content div.restore .steps button.active,body .container .body .content div.restore .steps div.active{color:#277db0}body .container .body .content div.add .steps button.active span,body .container .body .content div.add .steps div.active span,body .container .body .content div.restore .steps button.active span,body .container .body .content div.restore .steps div.active span{border-color:#277db0;background:#277db0;color:#fff}body .container .body .content div.add .steps button.active h2,body .container .body .content div.add .steps div.active h2,body .container .body .content div.restore .steps button.active h2,body .container .body .content div.restore .steps div.active h2{color:#277db0}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend{overflow:hidden;padding-bottom:50px;list-style:none;margin:0}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li{color:#c7e5f6;font-size:18px;text-align:center;float:left;padding-top:10px;cursor:pointer}body .container .body .content div.add .steps-legend li.active,body .container .body .content div.restore .steps-legend li.active{color:#277db0}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes{padding-left:40px}body .container .body .content div.add .steps-boxes .step,body .container .body .content div.restore .steps-boxes .step{display:none}body .container .body .content div.add .steps-boxes .step.active,body .container .body .content div.restore .steps-boxes .step.active{display:block}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:left;margin-left:20px;color:#505050}body .container .body .content div.add .steps-boxes .box.browser .checklinks a i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a i{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive{color:#838383;cursor:default}body .container .body .content div.add .steps-boxes .box.browser .checklinks a.inactive i,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a.inactive i{border-color:#838383}body .container .body .content div.add .steps-boxes .box.browser .checklinks a:first-child,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a:first-child{margin-left:0}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton{padding-top:10px;max-width:100%}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton input#sourcePath,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton input#sourcePath{width:100%;box-sizing:border-box;height:37px}body .container .body .content div.add .steps-boxes .box.browser .input.overlayButton a.button,body .container .body .content div.restore .steps-boxes .box.browser .input.overlayButton a.button{top:10px}body .container .body .content div.add .steps-boxes .box.filters .input.link a,body .container .body .content div.restore .steps-boxes .box.filters .input.link a{color:#505050}body .container .body .content div.add .steps-boxes .box.filters .input.link a i,body .container .body .content div.restore .steps-boxes .box.filters .input.link a i{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist{overflow:hidden;padding-bottom:15px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li{overflow:hidden;clear:both;padding-bottom:25px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li select,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li select{width:200px;margin-right:5px;height:36px;line-height:36px}body .container .body .content div.add .steps-boxes .box.filters ul#simplefilterlist li input,body .container .body .content div.restore .steps-boxes .box.filters ul#simplefilterlist li input{width:calc(100% - 280px);padding:5px}body .container .body .content div.add .steps-boxes .step1 li.strength.score-0,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-0{color:red}body .container .body .content div.add .steps-boxes .step1 li.strength.score-1,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-1{color:#f70}body .container .body .content div.add .steps-boxes .step1 li.strength.score-2,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-2{color:#aa0}body .container .body .content div.add .steps-boxes .step1 li.strength.score-3,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-3{color:#070}body .container .body .content div.add .steps-boxes .step1 li.strength.score-4,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-4{color:#427e27}body .container .body .content div.add .steps-boxes .step1 li.strength.score-x,body .container .body .content div.restore .steps-boxes .step1 li.strength.score-x{color:red}body .container .body .content div.add .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.add .steps-boxes .step5 div.input.maxSize input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.keepBackups input.number,body .container .body .content div.restore .steps-boxes .step5 div.input.maxSize input.number{width:60px}body .container .body .content div.add .steps-boxes .step5 .hint,body .container .body .content div.add .steps-boxes .step5 .retention-options,body .container .body .content div.restore .steps-boxes .step5 .hint,body .container .body .content div.restore .steps-boxes .step5 .retention-options{clear:both;margin-left:190px;margin-top:50px;font-style:italic}body .container .body .content div.add .steps-boxes .step5 .retention-options input,body .container .body .content div.restore .steps-boxes .step5 .retention-options input{margin-bottom:10px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions{padding-top:15px;clear:both}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li{border-top:none}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li.advancedentry,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li.advancedentry{border-bottom:1px solid #d3d3d3}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child{padding-top:0}body .container .body .content div.add .steps-boxes .step5 .advancedoptions li:last-child select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions li:last-child select{max-width:400px}body .container .body .content div.add .steps-boxes .step5 .advancedoptions label,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions label{line-height:normal}body .container .body .content div.add .steps-boxes .step5 .advancedoptions input,body .container .body .content div.add .steps-boxes .step5 .advancedoptions select,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions input,body .container .body .content div.restore .steps-boxes .step5 .advancedoptions select{width:auto;max-width:100%;box-sizing:border-box}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle{color:#505050;line-height:normal;margin-top:16px;clear:both;float:left}body .container .body .content div.add .steps-boxes .step5 .advanced-toggle i.fa,body .container .body .content div.restore .steps-boxes .step5 .advanced-toggle i.fa{border:2px solid;border-color:#505050;border-radius:2px;font-size:18px;height:18px;vertical-align:top;width:18px}body .container .body .content div.add .steps-boxes .step5 textarea,body .container .body .content div.restore .steps-boxes .step5 textarea{box-sizing:border-box;clear:both;margin-top:15px;width:100%}body .container .body .content div.add form,body .container .body .content div.restore form{padding-bottom:50px;overflow:hidden}body .container .body .content div.add form .input.password .tools,body .container .body .content div.restore form .input.password .tools{clear:both;padding-left:190px;padding-top:10px}body .container .body .content div.add form .input.password .tools ul,body .container .body .content div.restore form .input.password .tools ul{overflow:hidden}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{float:left;padding-right:7px}body .container .body .content div.add form .input.password .tools ul li.strength.useless,body .container .body .content div.restore form .input.password .tools ul li.strength.useless{color:red}body .container .body .content div.add form .input.password .tools ul li.strength.average,body .container .body .content div.restore form .input.password .tools ul li.strength.average{color:#ff0}body .container .body .content div.add form .input.password .tools ul li.strength.good,body .container .body .content div.restore form .input.password .tools ul li.strength.good{color:#277db0}body .container .body .content div.add form .input.multiple input,body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple input,body .container .body .content div.restore form .input.multiple select{width:auto;margin-right:5px}body .container .body .content div.add form .input.multiple select,body .container .body .content div.restore form .input.multiple select{--padding-block:5px;padding:var(--padding-block) 12px;line-height:calc(var(--height) - calc(var(--padding-block) * 2))}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{overflow:hidden;position:relative;max-width:446px}body .container .body .content div.add form .input.overlayButton input,body .container .body .content div.restore form .input.overlayButton input{width:347px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{position:absolute;top:0;right:0;padding:7px 12px 8px}body .container .body .content div.add form .input.checkbox.multiple strong,body .container .body .content div.restore form .input.checkbox.multiple strong{display:block;padding-bottom:5px}body .container .body .content div.add form .input.checkbox.multiple label,body .container .body .content div.restore form .input.checkbox.multiple label{display:inline-block;float:none;width:auto;padding-right:10px}body .container .body .content div.add form .input.checkbox.multiple input,body .container .body .content div.restore form .input.checkbox.multiple input{width:auto;display:inline-block;float:none}body .container .body .content div.add form .buttons,body .container .body .content div.restore form .buttons{float:none;width:635px;padding-top:30px}body .container .body .content .commandline .input.select,body .container .body .content div.add .step2 .input.select,body .container .body .content div.restore .step1 .input.select{display:grid;grid-auto-flow:column;justify-content:flex-start;grid-template-areas:"label server" ". custom"}body .container .body .content .commandline .input.select label,body .container .body .content div.add .step2 .input.select label,body .container .body .content div.restore .step1 .input.select label{grid-area:label}body .container .body .content .commandline .input.select select,body .container .body .content div.add .step2 .input.select select,body .container .body .content div.restore .step1 .input.select select{grid-area:server}body .container .body .content .commandline .input.select input,body .container .body .content div.add .step2 .input.select input,body .container .body .content div.restore .step1 .input.select input{grid-area:custom;margin-top:10px}body .container .body .content .commandline .input.text #generic_server,body .container .body .content div.add .step2 .input.text #generic_server,body .container .body .content div.restore .step1 .input.text #generic_server{width:335px}body .container .body .content .commandline .input.text #generic_port,body .container .body .content div.add .step2 .input.text #generic_port,body .container .body .content div.restore .step1 .input.text #generic_port{width:50px;margin-left:10px}body .container .body .content div.headerthreedotmenu{margin:20px 0}body .container .body .content div.headerthreedotmenu h2{display:inline}body .container .body .content div.headerthreedotmenu .contextmenu_container{float:right}body .container .body .content div.headerthreedotmenu .contextmenu{left:auto;right:0;top:auto}body .container .body .content div.headerthreedotmenu .threedotmenubutton{padding:5px}body .container .body .content .expandable{margin:20px 0}body .container .body .content .expandable h2{display:inline}body .container .body .content .expandable img{padding:0 6px}body .container .body .content div.settings .input.checkbox input.checkbox,body .container .body .content div.settings .input.mixed.multiple input.checkbox{width:auto}body .container .body .content div.settings .input.checkbox select,body .container .body .content div.settings .input.mixed.multiple select{width:auto;margin-right:5px}body .container .body .content div.settings .input.checkbox label,body .container .body .content div.settings .input.mixed.multiple label{line-height:normal;padding:0 15px;width:auto}body .container .body .content .logpage ul.tabs{padding:15px 0}body .container .body .content .logpage ul.entries li{padding:10px 0;border-bottom:1px solid #d8d8d8}body .container .body .content .logpage ul.backuplog{list-style:none}body .container .body .content .about-general .about-general__block{margin-block:1rem}body .container .body .content .about-general .about-general__block:first-child{margin-top:10px}body .container .body .content .about-general .about-general__block:last-child{margin-bottom:0}body .container .body .content .prewrapped-text{white-space:pre-wrap;overflow-x:auto}body .container .footer{background:#ededed;min-height:70px;line-height:70px;overflow:hidden;position:absolute;bottom:0;width:100%}body .container .footer a{color:#277db0}body .container .footer .about-footer{float:left;overflow:hidden;padding-right:20px;display:none}body .container .footer .about-footer span{display:block;float:left;padding-left:20px}body .container .footer .about-footer ul{float:left}body .container .footer .about-footer li{float:left;padding-left:20px}body .container .footer .social{float:right}body .container .footer .social ul{overflow:hidden;float:right;padding-left:20px;padding-right:10px}body .container .footer .social ul li{float:right;margin-right:10px;padding-top:5px}body .container .footer .social ul li img{opacity:.6}body .container .footer .social ul li img:hover{opacity:1}body .container .footer .themelink{float:right;padding-right:20px}body #modal-menu{max-width:400px}body #modal-menu a{color:#277db0;font-size:20px;line-height:40px}.remodal{padding:30px;box-shadow:0 2px 7px #000;background:#fff;display:none}.remodal form .buttons{float:none}.remodal-wrapper .remodal{display:block}span.info{font-size:10px;font-weight:500;display:inline-block;background:#277db0;border-radius:50%;width:15px;height:15px;vertical-align:super;color:#fff;line-height:15px;margin-left:5px;text-align:center}.hidden{display:none}.clear{clear:both}.nofloat{float:none!important}div.blocker,div.connection-lost,div.modal-dialog{position:fixed;inset:0;margin:auto}div.blocker{z-index:5000;background-color:#000;opacity:.65}#connection-lost-blocker{z-index:5100}#connection-lost-dialog{z-index:5200}div.connection-lost,div.modal-dialog{z-index:5001;display:flexbox;display:flex;justify-content:center;align-items:center}div.connection-lost div.info,div.modal-dialog div.info{min-width:310px;max-width:650px;margin:5px}div.connection-lost div.title,div.modal-dialog div.title{border:1px solid #65b1dd;background-color:#65b1dd;border-radius:5px 5px 0 0;padding:10px 20px;font-weight:700;color:#d3d3d3;text-align:center}div.connection-lost div.content,div.modal-dialog div.content{background-color:#fff;border:1px solid #fff;padding:20px}div.connection-lost div.content p:first-child,div.modal-dialog div.content p:first-child{margin-top:0}div.connection-lost div.content p:last-child,div.modal-dialog div.content p:last-child{margin-bottom:0}div.connection-lost .buttons,div.modal-dialog .buttons{border-radius:0 0 5px 5px;padding-top:10px;overflow:auto}div.connection-lost form,div.modal-dialog form{margin-top:15px}div.connection-lost form textarea,div.modal-dialog form textarea{height:130px;width:420px;padding:10px 12px;border:1px #d8d8d8 solid;border-radius:2px;color:#505050;font-size:16px;font-weight:300}div.connection-lost form input,div.modal-dialog form input{height:35px;line-height:35px;padding:0 12px}div.modal-dialog .content.buttons ul{float:right}div.modal-dialog .content.buttons .tooltipped{position:relative}div.modal-dialog .content.buttons .tooltipped::after{position:absolute;z-index:1000000;display:none;padding:5px 8px;font:normal normal 11px/1.5 Helvetica,arial,nimbussansl,liberationsans,freesans,clean,sans-serif,"Segoe UI Emoji","Segoe UI Symbol";color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:#000;border-radius:3px;-webkit-font-smoothing:subpixel-antialiased}div.modal-dialog .content.buttons .tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:#000;pointer-events:none;content:"";border:5px solid transparent}div.modal-dialog .content.buttons .tooltipped:active::after,div.modal-dialog .content.buttons .tooltipped:active::before,div.modal-dialog .content.buttons .tooltipped:focus::after,div.modal-dialog .content.buttons .tooltipped:focus::before,div.modal-dialog .content.buttons .tooltipped:hover::after,div.modal-dialog .content.buttons .tooltipped:hover::before{display:inline-block;text-decoration:none}div.modal-dialog .content.buttons .tooltipped-w::after{right:100%;bottom:50%;margin-right:5px;transform:translateY(50%)}div.modal-dialog .content.buttons .tooltipped-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:#000}.importpage form.styled input{margin-top:11px;margin-bottom:11px}.addwizard form.styled ul,.restorewizard form.styled ul{margin:20px;margin-left:0}.addwizard form.styled input[type=radio],.restorewizard form.styled input[type=radio]{width:20px;margin-left:5px;margin-right:5px}.addwizard form.styled label,.restorewizard form.styled label{width:auto;line-height:normal}.addwizard form.styled div.subtext,.restorewizard form.styled div.subtext{clear:both;margin-left:30px;padding-top:5px;color:#767676}.pauseoptions form.styled li{line-height:normal;padding:0}.pauseoptions form.styled li input{height:auto;margin-top:8px;margin-right:8px;width:auto}.confirmdelete div.overview{width:420px}.confirmdelete div.word{font-weight:700;padding:12px;border:1px solid #000;margin-top:12px;margin-bottom:12px;background-color:#e0e0e0;width:420px}.confirmdelete span.backupname{font-weight:700}.confirmdelete input.typedword{float:none}.progress-bar-striped{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress-striped .progress-bar{background-image:linear-gradient(45deg,#ff1 25%,transparent 25%,transparent 50%,#ff1 50%,#ff1 75%,transparent 75%,transparent);background-size:40px 40px}.progress{position:relative;min-height:25px}.progress>span{vertical-align:middle;display:block;width:100%;height:100%;text-align:center;z-index:100;padding-top:2px}.progress.active .progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress .progress-bar{float:left;width:0;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;box-shadow:inset 0 -1px 0 #000;transition:width .6s ease;height:100%;position:absolute;top:0}.progress .progress-bar.active{animation:progress-bar-stripes 2s linear infinite}.tree-view{list-style-type:none;margin-left:10px;padding-bottom:5px}.tree-view ul{margin-left:16px}.tree-view span.nodeLabel{cursor:pointer}.tree-view span.nodeLabel.selected{border:1px solid #aaa;background-color:#ddd;padding:1px 3px}.tree-view li .node{padding-bottom:5px}.tree-view li div.selected{border-color:#add8e6;background-color:#add8e6}.tree-view li>ul{display:none}.tree-view li>ul.expanded{display:block}.tree-view li a.nav{cursor:pointer;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:-80px 0;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.nav{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.nav.leaf{background:0 0}.tree-view li a.nav.expanded{background-position:-80px -16px}.tree-view li a.type{cursor:auto;display:inline-block;width:16px;height:16px;vertical-align:middle;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 -16px;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.type{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.type.invisible{background-position:0 -32px}.tree-view li a.type.loading{cursor:progress;background-image:url("../img/loader-16.gif");background-repeat:no-repeat;background-position:0 0;background-size:16px 16px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.type.loading{background-image:url('../img/loader-32.gif')}}.tree-view li a.type.x-tree-icon-drive{background-position:-16px -16px}.tree-view li a.type.x-tree-icon-leaf{background-position:-32px -16px}.tree-view li a.type.x-tree-icon-symlink{background-position:-48px -16px}.tree-view li a.type.x-tree-icon-userdata{background-position:-16px -48px}.tree-view li a.type.x-tree-icon-locked{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-broken{background-position:-64px -16px}.tree-view li a.type.x-tree-icon-computer{background-position:0 -48px}.tree-view li a.type.x-tree-icon-hyperv{background-position:-96px -16px}.tree-view li a.type.x-tree-icon-hypervmachine{background-position:-96px 0}.tree-view li a.type.x-tree-icon-mssql{background-position:-96px -32px}.tree-view li a.type.x-tree-icon-mssqldb{background-position:-80px -32px}.tree-view li a.type.x-tree-icon-mydocuments{background-position:-32px -48px}.tree-view li a.type.x-tree-icon-mymusic{background-position:-48px -48px}.tree-view li a.type.x-tree-icon-mypictures{background-position:-64px -48px}.tree-view li a.type.x-tree-icon-desktop{background-position:-80px -48px}.tree-view li a.type.x-tree-icon-home{background-position:-96px -48px}.tree-view li a.type.x-tree-icon-drive.invisible{background-position:-16px -32px}.tree-view li a.type.x-tree-icon-leaf.invisible{background-position:-32px -32px}.tree-view li a.type.x-tree-icon-symlink.invisible{cursor:auto;background-position:-48px -32px}.tree-view li a.type.x-tree-icon-locked.invisible{background-position:-64px -32px}.tree-view li a.check{height:16px;width:16px;display:inline-block;cursor:pointer;background-image:url("../img/treeicons.png");background-repeat:no-repeat;background-position:0 0;vertical-align:middle;background-size:112px 64px}@media only screen and (min-device-pixel-ratio:1.25),only screen and (resolution >= 192dpi),only screen and (resolution >= 1.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_2x.png')}}@media only screen and (min-device-pixel-ratio:2.25),only screen and (resolution >= 288dpi),only screen and (resolution >= 2.25dppx){.tree-view li a.check{background-image:url('../img/treeicons_3x.png')}}.tree-view li a.partial{background-position:-32px 0}.tree-view li a.include{background-position:-16px 0}.tree-view li a.exclude{background-position:-48px 0}.tree-view li a.root{background:0 0;display:none}.throttlesettings div.multiple select{width:auto;margin-right:5px}.throttlesettings div.multiple input{width:100px}.throttlesettings div.multiple input.checkbox{width:auto}.throttlesettings div.multiple label{line-height:35px;padding:0 15px;width:auto;min-width:150px}.throttlesettings .disabled{color:#909090}.throttlesettings .disabled input,.throttlesettings .disabled select{color:#909090}@media (width <= 1150px){body .container .header{height:140px}body .container .header .statepadding{padding-right:90px;margin-left:0}body .container .header .state{width:100%;margin:10px 40px;clear:left;float:left}body .container .header .action-icons{display:none}body .container .header .action-icons-small{display:inline-block}body .container .header .menubutton{display:block;font-size:18px;padding-right:50px;margin-top:5px;margin-right:15px;background:url('../img/menu.png') no-repeat right top;background-size:39px 39px;position:relative;height:40px;line-height:40px;color:#505050;float:right;top:10px;padding-left:20px;text-transform:uppercase;text-align:right}body .container .header .menubutton.active{background-image:url('../img/menu_active.png');background-size:39px 39px;color:#277db0}body .container .body{position:relative;padding-top:140px}body .container .body .mainmenu{display:none;position:fixed;background:none repeat scroll 0 0 #fff;box-shadow:0 4px 8px #000;left:10px;padding:20px;top:60px}body .container .body .mainmenu.mobile-open{display:block;left:auto;right:0;top:0;z-index:1000}body .container .body .contextmenu{left:0;top:auto}body .container .body .content{float:none;padding:20px;margin:0 auto 30px}body .container .body .content .state{width:auto}body .container .mobileOpen{display:block!important}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton{background-image:url('../img/menu_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton{background-image:url('../img/menu_3x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:1.25),(width <= 1150px) and (resolution >= 192dpi),(width <= 1150px) and (resolution >= 1.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_2x.png')}}@media (width <= 1150px) and (min-device-pixel-ratio:2.25),(width <= 1150px) and (resolution >= 288dpi),(width <= 1150px) and (resolution >= 2.25dppx){body .container .header .menubutton.active{background-image:url('../img/menu_active_3x.png')}}@media (width <= 768px){body .container .body .content .tasks .tasklist a{font-size:20px;background-size:24px;background-position:0 4px;padding-left:35px}body .container .body .content .tasks .tasklist dl{padding-left:35px}body .container .header .logo{padding-left:10px}body .container .header .statepadding{padding-right:50px}body .container .header .state{margin-left:10px}body .container .header .menubutton{margin-right:5px}body .container .body .content div.add .steps,body .container .body .content div.restore .steps,body .container .body .content div.settings .steps{display:none}body .container .body .content div.add .steps-legend,body .container .body .content div.restore .steps-legend,body .container .body .content div.settings .steps-legend{list-style:decimal;padding-left:20px;border-bottom:1px solid #eee;margin-bottom:30px;padding-bottom:20px}body .container .body .content div.add .steps-legend li,body .container .body .content div.restore .steps-legend li,body .container .body .content div.settings .steps-legend li{float:none;font-weight:500;width:auto!important;padding-right:0!important}body .container .body .content div.add .steps-boxes,body .container .body .content div.restore .steps-boxes,body .container .body .content div.settings .steps-boxes{padding-left:0}body .container .body .content div.add form.styled .input input,body .container .body .content div.add form.styled .input select,body .container .body .content div.add form.styled .input textarea,body .container .body .content div.restore form.styled .input input,body .container .body .content div.restore form.styled .input select,body .container .body .content div.restore form.styled .input textarea,body .container .body .content div.settings form.styled .input input,body .container .body .content div.settings form.styled .input select,body .container .body .content div.settings form.styled .input textarea{max-width:100%;box-sizing:border-box}body .container .body .content div.add form.styled .input.select select,body .container .body .content div.restore form.styled .input.select select,body .container .body .content div.settings form.styled .input.select select{width:420px}body .container .body .content div.add form.styled .buttons,body .container .body .content div.restore form.styled .buttons,body .container .body .content div.settings form.styled .buttons{max-width:100%;width:auto}body .container .body .content div.add form.styled .tools,body .container .body .content div.restore form.styled .tools,body .container .body .content div.settings form.styled .tools{padding-left:0!important}body .container .body .content div.add form.styled .input.checkbox.multiple,body .container .body .content div.restore form.styled .input.checkbox.multiple,body .container .body .content div.settings form.styled .input.checkbox.multiple{padding-bottom:5px}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.add form.styled .input.checkbox.multiple label,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple label,body .container .body .content div.settings form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple label{display:block!important;float:left!important;line-height:normal}body .container .body .content div.add form.styled .input.checkbox.multiple input,body .container .body .content div.restore form.styled .input.checkbox.multiple input,body .container .body .content div.settings form.styled .input.checkbox.multiple input{clear:both}body .container .body .content div.add form.styled .input.text.multiple input,body .container .body .content div.restore form.styled .input.text.multiple input,body .container .body .content div.settings form.styled .input.text.multiple input{max-width:48%!important}}@media (width <= 640px){body h2{font-size:20px;text-align:center}body .container .body{padding-bottom:10px}body .container .body .content{margin:0 auto}body .container .body .content div.add form .input.overlayButton,body .container .body .content div.restore form .input.overlayButton{padding-top:8px;padding-bottom:30px;margin-bottom:10px}body .container .body .content div.add form .input.overlayButton a.button,body .container .body .content div.restore form .input.overlayButton a.button{padding:7px 10px;right:1px;top:9px}body .container .body .content div.add form .input.checkbox.multiple div,body .container .body .content div.restore form .input.checkbox.multiple div{display:block}body .container .body .content div.add form .input.select.multiple input#exclude-larger-than-number,body .container .body .content div.restore form .input.select.multiple input#exclude-larger-than-number{width:75px}body .container .body .content div.add form .input.select.multiple select#exclude-larger-than-multiplier,body .container .body .content div.restore form .input.select.multiple select#exclude-larger-than-multiplier{width:140px}body .container .body .content div.add form .filters .input.textarea,body .container .body .content div.restore form .filters .input.textarea{padding-bottom:10px}body .container .body .content div.add form .filters h3,body .container .body .content div.restore form .filters h3{margin:5px 0}body .container .body .content div.add form .input.text.select.multiple.repeat label,body .container .body .content div.restore form .input.text.select.multiple.repeat label{float:none}body .container .body .content div.add form .input.text.select.multiple.repeat input#repeatRunNumber,body .container .body .content div.restore form .input.text.select.multiple.repeat input#repeatRunNumber{width:70px}body .container .body .content div.add form .input.text.select.multiple.repeat select#repeatRunMultiplier,body .container .body .content div.restore form .input.text.select.multiple.repeat select#repeatRunMultiplier{width:100px}body .container .body .content div.add form .input.multiple.text.select.maxSize input,body .container .body .content div.restore form .input.multiple.text.select.maxSize input{width:70px}body .container .body .content div.add form .input.multiple.text.select.maxSize select,body .container .body .content div.restore form .input.multiple.text.select.maxSize select{width:100px}body .container .body .content div.add form .input.multiple.text.select.keepBackups select,body .container .body .content div.restore form .input.multiple.text.select.keepBackups select{width:85px;padding:4px 6px}body .container .body .content div.add form .input.multiple.text.select.keepBackups input,body .container .body .content div.restore form .input.multiple.text.select.keepBackups input{width:60px}body .container .footer{position:static;padding:15px;line-height:normal;text-align:left;box-sizing:border-box}body .container .footer *{float:none!important;text-align:center;box-sizing:border-box}body .container .footer .about-footer{padding-right:0;display:block}body .container .footer .about-footer span{padding-left:0;padding-bottom:5px}body .container .footer .about-footer li{padding-left:0;float:none;display:inline-block;height:32px;width:32px;background-size:28px!important;border-bottom:none}body .container .footer .about-footer li:first-child{padding-bottom:0}body .container .footer .about-footer li:last-child{padding-bottom:20px}body .container .footer .about-footer,body .container .footer .social,body .container .footer li{padding:8px 0;border-bottom:1px #ddd solid}body .container .footer .social li{display:inline-block;border:none}body .container .footer .themelink{padding:8px 0}}@media (width <= 580px){.advancedentry .longdescription{margin-left:0}}@media (width <= 492px){ul.notification{width:auto}}@media (width <= 480px){body{font-size:15px}body .container .header .logo{padding-left:5px}body .container .header .menubutton{margin-right:5px}body .container .header .state{margin-left:5px}body .container .header .statepadding{padding-right:40px}body .container .header .menubutton{padding-left:10px}body .container .body .mainmenu{width:280px;box-sizing:border-box}body .container .body .mainmenu ul li a{font-size:22px}body .container .body .content{padding:15px}body .container .body .content div.add form .input.password .tools ul li,body .container .body .content div.restore form .input.password .tools ul li{font-size:14px}body .container .body .content div.add form .buttons a,body .container .body .content div.restore form .buttons a{float:none;text-align:center;margin-bottom:5px}body .container .body .content div.add .steps-boxes .box.browser .checklinks a,body .container .body .content div.restore .steps-boxes .box.browser .checklinks a{float:none;margin-bottom:8px;display:block}}@media (width <= 400px){body{font-size:15px}body .container .header .menubutton{margin-right:0;padding-left:0;padding-right:40px}body .container .header .menubutton span{display:none}}@media (width <= 325px){body{font-size:15px}body .container .header .logo div{display:none}}@media (width <= 200px){body{font-size:15px}body .container .header .menubutton{position:static;margin-top:0}body .container .header .action-icons-small{clear:right;margin-top:0}} \ No newline at end of file diff --git a/Duplicati/Server/webroot/ngax/templates/addoredit.html b/Duplicati/Server/webroot/ngax/templates/addoredit.html index a755c8a3a..5434d4e8f 100644 --- a/Duplicati/Server/webroot/ngax/templates/addoredit.html +++ b/Duplicati/Server/webroot/ngax/templates/addoredit.html @@ -140,7 +140,7 @@
@@ -310,9 +310,11 @@
- The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information. +

+ The chosen size is outside the recommended range. This may cause performance issues, excessively large temporary files or other problems. +

+ The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information.
-
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/file.html b/Duplicati/Server/webroot/ngax/templates/backends/file.html index d1ebc39d6..e542340c1 100644 --- a/Duplicati/Server/webroot/ngax/templates/backends/file.html +++ b/Duplicati/Server/webroot/ngax/templates/backends/file.html @@ -12,8 +12,8 @@
- Hide hidden folders - Show hidden folders + Hide hidden items + Show hidden items Manually type path diff --git a/Duplicati/Server/webroot/ngax/templates/backends/filen.html b/Duplicati/Server/webroot/ngax/templates/backends/filen.html new file mode 100644 index 000000000..5ec521a01 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/templates/backends/filen.html @@ -0,0 +1,13 @@ +
+ + +
+ +
+ + +
+
+ + +
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/pcloud.html b/Duplicati/Server/webroot/ngax/templates/backends/pcloud.html new file mode 100644 index 000000000..a3c7ff619 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/templates/backends/pcloud.html @@ -0,0 +1,25 @@ +
+ + +
+
+ + +
+ +
+ + +
diff --git a/Duplicati/Server/webroot/ngax/templates/backends/smb.html b/Duplicati/Server/webroot/ngax/templates/backends/smb.html new file mode 100644 index 000000000..edac8d943 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/templates/backends/smb.html @@ -0,0 +1,44 @@ + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
diff --git a/Duplicati/Server/webroot/ngax/templates/captcha.html b/Duplicati/Server/webroot/ngax/templates/captcha.html deleted file mode 100644 index a8a36eb03..000000000 --- a/Duplicati/Server/webroot/ngax/templates/captcha.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
-
{{entry.message}}
-
- - {{entry.expectedAnswer}} - - -
- -
Checking …
-
Incorrect answer, try again
- -
-
Loading …
-
diff --git a/Duplicati/Server/webroot/ngax/templates/confirmdelete.html b/Duplicati/Server/webroot/ngax/templates/confirmdelete.html new file mode 100644 index 000000000..72dbab856 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/templates/confirmdelete.html @@ -0,0 +1,15 @@ +
+
+
+ To confirm you want to delete all remote files for + "{{selection.backupname}}", please enter + this phrase: +
+ +
{{selection.requiredword}}
+ +
+ +
+
+
diff --git a/Duplicati/Server/webroot/ngax/templates/home.html b/Duplicati/Server/webroot/ngax/templates/home.html index 505bba3dd..0c1a513a9 100644 --- a/Duplicati/Server/webroot/ngax/templates/home.html +++ b/Duplicati/Server/webroot/ngax/templates/home.html @@ -1,5 +1,34 @@
-
+ + +
+ +
+ + +
diff --git a/Duplicati/Server/webroot/ngax/templates/pause.html b/Duplicati/Server/webroot/ngax/templates/pause.html index 7ff6abdf7..29c3dce31 100644 --- a/Duplicati/Server/webroot/ngax/templates/pause.html +++ b/Duplicati/Server/webroot/ngax/templates/pause.html @@ -26,17 +26,17 @@ -
  • - - -
  • +
  • + + +
  • -
  • +
  • -
  • +
  • @@ -46,6 +46,13 @@ + +
    + + +
    If you pause transfers they could time out and cause retries or failures.
    +
    +
    diff --git a/Duplicati/Server/webroot/ngax/templates/restore.html b/Duplicati/Server/webroot/ngax/templates/restore.html index 62b255932..d09f1b1a8 100644 --- a/Duplicati/Server/webroot/ngax/templates/restore.html +++ b/Duplicati/Server/webroot/ngax/templates/restore.html @@ -95,8 +95,8 @@
    - Hide hidden folders - Show hidden folders + Hide hidden items + Show hidden items Manually type path diff --git a/Duplicati/Server/webroot/ngax/templates/settings.html b/Duplicati/Server/webroot/ngax/templates/settings.html index 5744fd7dc..d3a116aab 100644 --- a/Duplicati/Server/webroot/ngax/templates/settings.html +++ b/Duplicati/Server/webroot/ngax/templates/settings.html @@ -36,7 +36,7 @@
    -
    +
    @@ -57,15 +57,15 @@
    - +
    - +
    - +
    @@ -113,6 +113,16 @@
    +
    + + +
    + +
    + +
    +

    Update channel

    @@ -128,7 +138,7 @@
    -
    Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments.
    +
    Try out the new features that we are working on. Test Backup & Restore before using this in production environments.
    diff --git a/Duplicati/Server/webroot/ngclient/package-lock.json b/Duplicati/Server/webroot/ngclient/package-lock.json index d3a4c45bc..34f830ffb 100644 --- a/Duplicati/Server/webroot/ngclient/package-lock.json +++ b/Duplicati/Server/webroot/ngclient/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@duplicati/ngclient": "^0.0.11" + "@duplicati/ngclient": "^0.0.97" } }, "node_modules/@duplicati/ngclient": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@duplicati/ngclient/-/ngclient-0.0.11.tgz", - "integrity": "sha512-9VXT6iZSE/T2kPzFgcpm81bpOw+51FqOjHRKyLNRjOBzmIktg0ccJ9K1x9YCUKQJrcvwo5DyAaMtbnD/L/XfcA==", + "version": "0.0.97", + "resolved": "https://registry.npmjs.org/@duplicati/ngclient/-/ngclient-0.0.97.tgz", + "integrity": "sha512-4c0RCVfBk3WwqDeK5mYc6NwhEwnM1UQLtfD5n7FoMs0i6NTFkRBkKX35bgxFZK2ujLONDylt/hkR8FxZNiMxAA==", "license": "MIT" } } diff --git a/Duplicati/Server/webroot/ngclient/package.json b/Duplicati/Server/webroot/ngclient/package.json index 13ead96b1..c64d2fd23 100644 --- a/Duplicati/Server/webroot/ngclient/package.json +++ b/Duplicati/Server/webroot/ngclient/package.json @@ -4,6 +4,6 @@ "description": "Modern Angular Client for Duplicati", "license": "MIT", "dependencies": { - "@duplicati/ngclient": "^0.0.11" + "@duplicati/ngclient": "^0.0.97" } } diff --git a/Duplicati/Server/webroot/signin.html b/Duplicati/Server/webroot/signin.html index 60fd16636..9a6b79579 100644 --- a/Duplicati/Server/webroot/signin.html +++ b/Duplicati/Server/webroot/signin.html @@ -29,6 +29,12 @@

    + Need help locating the signin token?
    diff --git a/Duplicati/Server/webroot/signin/signin.css b/Duplicati/Server/webroot/signin/signin.css index 5f1190d30..3151103b2 100644 --- a/Duplicati/Server/webroot/signin/signin.css +++ b/Duplicati/Server/webroot/signin/signin.css @@ -76,3 +76,10 @@ input { #signin fieldset input[type="submit"]:hover { background: #65b1dd; } + +#signin a.helplink { + display: inline-block; + margin-top: 16px; + margin-left: 16px; + color: #777; +} \ No newline at end of file diff --git a/Duplicati/Server/webroot/theme.html b/Duplicati/Server/webroot/theme.html index f3dc3e60a..08bbb54bd 100644 --- a/Duplicati/Server/webroot/theme.html +++ b/Duplicati/Server/webroot/theme.html @@ -49,8 +49,8 @@ return cookies; } - function updateToCurrentTheme() { - var theme = parseCookies()["default-theme"]; + function updateToCurrentClient() { + var theme = parseCookies()["default-client"]; var themes = ["ngax", "ngclient"]; for (var k in themes) { @@ -68,7 +68,7 @@ } // Lazy hack for DOM timing issues... - window.setTimeout(updateToCurrentTheme, 1000); + window.setTimeout(updateToCurrentClient, 1000); @@ -99,7 +99,7 @@ diff --git a/Duplicati/Service/Duplicati.Service.csproj b/Duplicati/Service/Duplicati.Service.csproj index 66fd2ccba..8d22f181b 100644 --- a/Duplicati/Service/Duplicati.Service.csproj +++ b/Duplicati/Service/Duplicati.Service.csproj @@ -3,7 +3,7 @@ net8.0 Duplicati.Service.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/Service/Program.cs b/Duplicati/Service/Program.cs index 9a098a2f9..6e105e0aa 100644 --- a/Duplicati/Service/Program.cs +++ b/Duplicati/Service/Program.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/Service/Runner.cs b/Duplicati/Service/Runner.cs index 939dd80f9..a406fe1a7 100644 --- a/Duplicati/Service/Runner.cs +++ b/Duplicati/Service/Runner.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -62,9 +62,10 @@ namespace Duplicati.Service { var path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); var exec = System.IO.Path.Combine(path, PackageHelper.GetExecutableName(m_executable)); - var cmdargs = "--ping-pong-keepalive=true"; - if (m_cmdargs != null && m_cmdargs.Length > 0) - cmdargs = Duplicati.Library.Utility.Utility.WrapAsCommandLine(new string[] { cmdargs }.Concat(m_cmdargs)); + // Preserve order, but ensure the ping-pong-keepalive is set + var cmdargs = (m_cmdargs ?? []) + .Concat(["--ping-pong-keepalive=true"]) + .ToArray(); var firstRun = true; var startAttempts = 0; @@ -84,7 +85,7 @@ namespace Duplicati.Service if (!firstRun) m_reportMessage(string.Format("Attempting to restart server process: {0}", exec), true); - m_reportMessage(string.Format("Starting process {0} with cmd args {1}", exec, cmdargs), false); + m_reportMessage(string.Format("Starting process {0} with cmd args {1}", exec, string.Join(Environment.NewLine, cmdargs)), false); var pr = new System.Diagnostics.ProcessStartInfo(exec, cmdargs) { @@ -116,6 +117,11 @@ namespace Duplicati.Service PingProcess(); } } + + if (m_process.ExitCode != 0) + m_reportMessage(string.Format("Process has exited with code {0}", m_process.ExitCode), true); + else if (!m_terminate) + m_reportMessage("Process has exited without an error code", true); } catch (Exception ex) { @@ -130,11 +136,11 @@ namespace Duplicati.Service m_terminate = true; } } - - // Throttle restarts - if (!m_terminate) - System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10)); } + + // Throttle restarts + if (!m_terminate) + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10)); } } finally diff --git a/Duplicati/Tools/Duplicati.Tools.csproj b/Duplicati/Tools/Duplicati.Tools.csproj index ecb48b4cc..ee68d0b16 100644 --- a/Duplicati/Tools/Duplicati.Tools.csproj +++ b/Duplicati/Tools/Duplicati.Tools.csproj @@ -3,9 +3,9 @@ net8.0 Library - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license - + utility-scripts\DuplicatiVerify.ps1 @@ -24,12 +24,12 @@ utility-scripts\pause-resume.py - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Duplicati/Tools/Unused.cs b/Duplicati/Tools/Unused.cs index a82c29d6f..ebb34bb59 100644 --- a/Duplicati/Tools/Unused.cs +++ b/Duplicati/Tools/Unused.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/AESStringEncryptionTests.cs b/Duplicati/UnitTest/AESStringEncryptionTests.cs index 4fa80d415..b659ded32 100644 --- a/Duplicati/UnitTest/AESStringEncryptionTests.cs +++ b/Duplicati/UnitTest/AESStringEncryptionTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/BackendToolTests.cs b/Duplicati/UnitTest/BackendToolTests.cs index 2b3c4b631..402cf4e67 100755 --- a/Duplicati/UnitTest/BackendToolTests.cs +++ b/Duplicati/UnitTest/BackendToolTests.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; diff --git a/Duplicati/UnitTest/BasicSetupHelper.cs b/Duplicati/UnitTest/BasicSetupHelper.cs index cb6c44b90..0fc14097b 100644 --- a/Duplicati/UnitTest/BasicSetupHelper.cs +++ b/Duplicati/UnitTest/BasicSetupHelper.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -25,9 +25,7 @@ using System.Diagnostics; using System.IO; using System.Collections.Generic; using System.IO.Compression; -using Duplicati.Library.Common; using Duplicati.Library.Common.IO; -using Duplicati.Library.Utility; using System.Timers; namespace Duplicati.UnitTest @@ -89,7 +87,7 @@ namespace Duplicati.UnitTest [OneTimeSetUp] public void BasicHelperOneTimeSetUp() - { + { TestContext.Progress.WriteLine("One Time Setup {0}", TestContext.CurrentContext.Test.Name); if (DEBUG_OUTPUT) { @@ -103,16 +101,18 @@ namespace Duplicati.UnitTest private static int count = 1; - private static Timer SetupTimer() { + private static Timer SetupTimer() + { var timer = new Timer(); - + timer.Interval = 2000; - timer.Elapsed += delegate(object obj, ElapsedEventArgs e){ - var nogc = GC.GetTotalMemory(false)/1000/1000; - var yesgc = GC.GetTotalMemory(true)/1000/1000; - + timer.Elapsed += delegate (object obj, ElapsedEventArgs e) + { + var nogc = GC.GetTotalMemory(false) / 1000 / 1000; + var yesgc = GC.GetTotalMemory(true) / 1000 / 1000; + var me = Process.GetCurrentProcess(); - var process = me.WorkingSet64/1000/1000; + var process = me.WorkingSet64 / 1000 / 1000; TestContext.Progress.WriteLine("Memory: {0}MB -> {1}MB ({2}MB)", nogc, yesgc, process); }; timer.AutoReset = true; @@ -125,19 +125,20 @@ namespace Duplicati.UnitTest { memoryTimer.Enabled = true; var me = Process.GetCurrentProcess(); - TestContext.Progress.WriteLine("Setup {0} {1}MB {2}MB", TestContext.CurrentContext.Test.Name, GC.GetTotalMemory(true)/1000/1000, me.WorkingSet64/1000/1000); + TestContext.Progress.WriteLine("Setup {0} {1}MB {2}MB", TestContext.CurrentContext.Test.Name, GC.GetTotalMemory(true) / 1000 / 1000, me.WorkingSet64 / 1000 / 1000); systemIO.DirectoryCreate(this.DATAFOLDER); systemIO.DirectoryCreate(this.TARGETFOLDER); - systemIO.DirectoryCreate(this.RESTOREFOLDER); + systemIO.DirectoryCreate(this.RESTOREFOLDER); } [TearDown] public void BasicHelperTearDown() { memoryTimer.Enabled = false; - if( TestContext.CurrentContext.Test.MethodName != null){ + if (TestContext.CurrentContext.Test.MethodName != null) + { var me = Process.GetCurrentProcess(); - TestContext.Progress.WriteLine("TearDown {0} {1}MB {2}MB", TestContext.CurrentContext.Test.MethodName, GC.GetTotalMemory(true)/1000/1000, me.WorkingSet64/1000/1000); + TestContext.Progress.WriteLine("TearDown {0} {1}MB {2}MB", TestContext.CurrentContext.Test.MethodName, GC.GetTotalMemory(true) / 1000 / 1000, me.WorkingSet64 / 1000 / 1000); } if (systemIO.DirectoryExists(this.DATAFOLDER)) { diff --git a/Duplicati/UnitTest/BorderTests.cs b/Duplicati/UnitTest/BorderTests.cs index 6e9015b37..e03f8b10f 100644 --- a/Duplicati/UnitTest/BorderTests.cs +++ b/Duplicati/UnitTest/BorderTests.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in +// +// The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -30,17 +30,6 @@ namespace Duplicati.UnitTest { public class BorderTests : BasicSetupHelper { - private readonly string recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); - - [TearDown] - public void TearDown() - { - if (File.Exists(this.recreatedDatabaseFile)) - { - File.Delete(this.recreatedDatabaseFile); - } - } - [Test] [Category("Border")] public void Run10kNoProgress() @@ -65,6 +54,7 @@ namespace Duplicati.UnitTest RunCommands(1024 * 10, modifyOptions: opts => { opts["blocksize"] = "10mb"; + opts["dblock-size"] = "25mb"; }); } @@ -281,6 +271,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "c" + k.Key), data.Take(k.Value).ToArray()); } + System.Threading.Tasks.Task.Delay(1000).Wait(); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { @@ -309,15 +300,25 @@ namespace Duplicati.UnitTest Assert.AreEqual((filenames.Count * 3) + 1, r.Files.Count()); } - testopts["dbpath"] = this.recreatedDatabaseFile; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + { + var r = c.Test(long.MaxValue); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.IsFalse(r.Verifications.Any(p => p.Value.Any())); + } + + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { IRepairResults repairResults = c.Repair(); Assert.AreEqual(0, repairResults.Errors.Count()); - - // TODO: This sometimes results in a "No block hash found for file: C:\projects\duplicati\testdata\backup-data\a-0" warning. - // Because of this, we don't check for warnings here. + Assert.AreEqual(0, repairResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) @@ -358,6 +359,14 @@ namespace Duplicati.UnitTest Assert.AreEqual((filenames.Count * 3) + 1, r.Files.Count()); } + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + { + var r = c.Test(long.MaxValue); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.IsFalse(r.Verifications.Any(p => p.Value.Any())); + } + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) { var r = c.Restore(null); diff --git a/Duplicati/UnitTest/CommandLineOperationsTests.cs b/Duplicati/UnitTest/CommandLineOperationsTests.cs index 1b7dc1355..2b22812ae 100644 --- a/Duplicati/UnitTest/CommandLineOperationsTests.cs +++ b/Duplicati/UnitTest/CommandLineOperationsTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -32,7 +32,7 @@ namespace Duplicati.UnitTest { public class CommandLineOperationsTests : BasicSetupHelper { - private static readonly string S3_URL = $"https://testfiles.duplicati.com/"; + public const string S3_URL = $"https://testfiles.duplicati.com/"; /// /// The log tag @@ -79,7 +79,7 @@ namespace Duplicati.UnitTest private void DownloadS3FileIfNewer(string destinationFilePath, string url, int retries = 5) => DownloadS3FileIfNewerAsync(destinationFilePath, url, retries).Await(); - private async Task DownloadS3FileIfNewerAsync(string destinationFilePath, string url, int retries = 5) + public static async Task DownloadS3FileIfNewerAsync(string destinationFilePath, string url, int retries = 5) { do { @@ -92,7 +92,6 @@ namespace Duplicati.UnitTest request.Headers.IfModifiedSince = systemIO.FileGetLastWriteTimeUtc(destinationFilePath); using var response = await httpClient.SendAsync(request); - using var tmpFile = new TempFile(); if (response.StatusCode == System.Net.HttpStatusCode.NotModified) { @@ -101,6 +100,7 @@ namespace Duplicati.UnitTest } else { + using var tmpFile = new TempFile(); Console.WriteLine($"Downloading file from {url} to: {tmpFile}"); var contentStream = await response.Content.ReadAsStreamAsync(); var fileInfo = new FileInfo(tmpFile); @@ -126,6 +126,13 @@ namespace Duplicati.UnitTest throw; await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); + try + { + System.Net.Dns.GetHostEntry(new System.Uri(url).Host); + } + catch (Exception) + { + } Console.WriteLine($"Retrying download, {retries} retries left."); } } while (retries > 0); diff --git a/Duplicati/UnitTest/CompactDisruptionTests.cs b/Duplicati/UnitTest/CompactDisruptionTests.cs new file mode 100644 index 000000000..8a8f9328c --- /dev/null +++ b/Duplicati/UnitTest/CompactDisruptionTests.cs @@ -0,0 +1,606 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Interface; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Duplicati.UnitTest +{ + public class CompactDisruptionTests : BasicSetupHelper + { + private string VerificationToString(IEnumerable>>> verifications) + { + StringBuilder res = new StringBuilder(); + foreach (var file in verifications) + { + if (file.Value.Any()) + { + res.AppendLine($"{file.Key}: {file.Value.Count()} errors"); + foreach (var c in file.Value) + { + res.AppendLine($"\t{c.Key}: {c.Value}"); + } + } + } + return res.ToString(); + } + + /// + /// Each backup run needs a unique timestamp to avoid the same backup being created. + /// During testing we often need to run backups in quick succession, so we need to + /// sleep until the next second to make sure the next backup timestamp is not the same. + /// + /// The timestamp of the previous backup + private void SleepUntilNextSecond(DateTime prevTimestamp) + { + // Sleep until the next second to make sure the next backup is not the same + var nextSecond = prevTimestamp.AddMilliseconds(-prevTimestamp.Millisecond).AddSeconds(2); + var remainder = Math.Max(2000, (nextSecond - DateTime.Now).TotalMilliseconds); + if (remainder > 0) + { + System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(remainder)); + } + } + + /// + /// Each backup run needs a unique timestamp to avoid the same backup being created. + /// During testing we often need to run backups in quick succession, so we need to + /// sleep until the next second to make sure the next backup timestamp is not the same. + /// + /// The results of the previous backup + private void SleepUntilNextSecond(IBackupResults backupResults) + => SleepUntilNextSecond(backupResults.BeginTime); + + [Test] + [Category("Disruption"), Category("Bug")] + public void InterruptedCompact5184() + { + // Reproduction steps from issue #4129 with smaller sizes + var testopts = TestOptions; + testopts["backup-test-samples"] = "0"; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "20KB"; + // Reduce blocksize, because with default blocksize too few blocks fit in a volume + testopts["blocksize"] = "1KB"; + testopts["keep-versions"] = "1"; + testopts["no-auto-compact"] = "true"; + testopts["threshold"] = "5"; + + // This size seems to be appropriate to cause significant compacting + // in combination with dblock-size + const long filesize = 44971; + + string target = "file://" + TARGETFOLDER; + string file1 = Path.Combine(DATAFOLDER, "f1"); + string file2 = Path.Combine(DATAFOLDER, "f2"); + string file3 = Path.Combine(DATAFOLDER, "f3"); + string file4 = Path.Combine(DATAFOLDER, "f4"); + string file5 = Path.Combine(DATAFOLDER, "f5"); + string file6 = Path.Combine(DATAFOLDER, "f6"); + // Add files 1,2 + TestUtils.WriteTestFile(file1, filesize); + TestUtils.WriteTestFile(file2, filesize); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 3,4 + TestUtils.WriteTestFile(file3, filesize); + TestUtils.WriteTestFile(file4, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 5,6 + TestUtils.WriteTestFile(file5, filesize); + TestUtils.WriteTestFile(file6, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Delete 1,3,5 + File.Delete(file1); + File.Delete(file3); + File.Delete(file5); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + // Fail the compact after the first dblock put is completed + bool firstPutCompleted = false; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (firstPutCompleted && action.IsGetOperation) + { + return true; + } + if (action == DeterministicErrorBackend.BackendAction.PutAfter) + { + firstPutCompleted = true; + } + return false; + }; + // Expect error from backend + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + DeterministicErrorBackend.ErrorGenerator = null; + testopts["full-remote-verification"] = "true"; + testopts["full-block-verification"] = "true"; + target = "file://" + TARGETFOLDER; + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Remove the local database + File.Delete(DBFILE); + + // Repair to recreate the local database + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + + // Re-do the full verification + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + } + + [Test] + [Category("Disruption"), Category("Bug")] + public void InterruptedCompactPlusNormalCompact5184() + { + // Reproduction steps from issue #4129 with smaller sizes + var testopts = TestOptions; + testopts["backup-test-samples"] = "0"; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "20KB"; + // Reduce blocksize, because with default blocksize too few blocks fit in a volume + testopts["blocksize"] = "1KB"; + testopts["keep-versions"] = "1"; + testopts["no-auto-compact"] = "true"; + testopts["threshold"] = "5"; + + // This size seems to be appropriate to cause significant compacting + // in combination with dblock-size + const long filesize = 44971; + + string target = "file://" + TARGETFOLDER; + string file1 = Path.Combine(DATAFOLDER, "f1"); + string file2 = Path.Combine(DATAFOLDER, "f2"); + string file3 = Path.Combine(DATAFOLDER, "f3"); + string file4 = Path.Combine(DATAFOLDER, "f4"); + string file5 = Path.Combine(DATAFOLDER, "f5"); + string file6 = Path.Combine(DATAFOLDER, "f6"); + // Add files 1,2 + TestUtils.WriteTestFile(file1, filesize); + TestUtils.WriteTestFile(file2, filesize); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 3,4 + TestUtils.WriteTestFile(file3, filesize); + TestUtils.WriteTestFile(file4, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 5,6 + TestUtils.WriteTestFile(file5, filesize); + TestUtils.WriteTestFile(file6, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Delete 1,3,5 + File.Delete(file1); + File.Delete(file3); + File.Delete(file5); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + // Fail the compact after the first dblock put is completed + bool firstPutCompleted = false; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (firstPutCompleted && action.IsGetOperation) + { + return true; + } + if (action == DeterministicErrorBackend.BackendAction.PutAfter) + { + firstPutCompleted = true; + } + return false; + }; + // Expect error from backend + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + DeterministicErrorBackend.ErrorGenerator = null; + testopts["full-remote-verification"] = "true"; + testopts["full-block-verification"] = "true"; + + target = "file://" + TARGETFOLDER; + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Make sure we can compact after the interrupted compact + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + + // Make sure there are no errors after success compacting + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Remove the local database + File.Delete(DBFILE); + + // Repair to recreate the local database + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + + // Re-do the full verification + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + } + + [Test] + [Category("Disruption"), Category("Bug")] + public void DoubleInterruptedCompact5184() + { + // Reproduction steps from issue #4129 with smaller sizes + var testopts = TestOptions; + testopts["backup-test-samples"] = "0"; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "20KB"; + // Reduce blocksize, because with default blocksize too few blocks fit in a volume + testopts["blocksize"] = "1KB"; + testopts["keep-versions"] = "1"; + testopts["no-auto-compact"] = "true"; + testopts["threshold"] = "5"; + + // This size seems to be appropriate to cause significant compacting + // in combination with dblock-size + const long filesize = 44971; + + string target = "file://" + TARGETFOLDER; + string file1 = Path.Combine(DATAFOLDER, "f1"); + string file2 = Path.Combine(DATAFOLDER, "f2"); + string file3 = Path.Combine(DATAFOLDER, "f3"); + string file4 = Path.Combine(DATAFOLDER, "f4"); + string file5 = Path.Combine(DATAFOLDER, "f5"); + string file6 = Path.Combine(DATAFOLDER, "f6"); + // Add files 1,2 + TestUtils.WriteTestFile(file1, filesize); + TestUtils.WriteTestFile(file2, filesize); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 3,4 + TestUtils.WriteTestFile(file3, filesize); + TestUtils.WriteTestFile(file4, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 5,6 + TestUtils.WriteTestFile(file5, filesize); + TestUtils.WriteTestFile(file6, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Delete 1,3,5 + File.Delete(file1); + File.Delete(file3); + File.Delete(file5); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + // Fail the compact after the first dblock put is completed + bool firstPutCompleted = false; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (firstPutCompleted && action.IsGetOperation) + { + return true; + } + if (action == DeterministicErrorBackend.BackendAction.PutAfter) + { + firstPutCompleted = true; + } + return false; + }; + // Expect error from backend + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + + // Expect error from backend, again + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + + DeterministicErrorBackend.ErrorGenerator = null; + testopts["full-remote-verification"] = "true"; + testopts["full-block-verification"] = "true"; + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Make sure we can compact after two interrupted compacts + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + + // Make sure there are no errors after success compacting + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Remove the local database + File.Delete(DBFILE); + + // Repair to recreate the local database + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + + // Re-do the full verification + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + } + + [Test] + [Category("Disruption"), Category("Bug")] + public void RestoreAfterDoubleInterruptedCompact5184() + { + // Reproduction steps from issue #4129 with smaller sizes + var testopts = TestOptions; + testopts["backup-test-samples"] = "0"; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "20KB"; + // Reduce blocksize, because with default blocksize too few blocks fit in a volume + testopts["blocksize"] = "1KB"; + testopts["keep-versions"] = "1"; + testopts["no-auto-compact"] = "true"; + testopts["threshold"] = "5"; + + // This size seems to be appropriate to cause significant compacting + // in combination with dblock-size + const long filesize = 44971; + + string target = "file://" + TARGETFOLDER; + string file1 = Path.Combine(DATAFOLDER, "f1"); + string file2 = Path.Combine(DATAFOLDER, "f2"); + string file3 = Path.Combine(DATAFOLDER, "f3"); + string file4 = Path.Combine(DATAFOLDER, "f4"); + string file5 = Path.Combine(DATAFOLDER, "f5"); + string file6 = Path.Combine(DATAFOLDER, "f6"); + // Add files 1,2 + TestUtils.WriteTestFile(file1, filesize); + TestUtils.WriteTestFile(file2, filesize); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 3,4 + TestUtils.WriteTestFile(file3, filesize); + TestUtils.WriteTestFile(file4, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Add files 5,6 + TestUtils.WriteTestFile(file5, filesize); + TestUtils.WriteTestFile(file6, filesize); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + SleepUntilNextSecond(backupResults); + + // Delete 1,3,5 + File.Delete(file1); + File.Delete(file3); + File.Delete(file5); + backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + // Fail the compact after the first dblock put is completed + bool firstPutCompleted = false; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (firstPutCompleted && action.IsGetOperation) + { + return true; + } + if (action == DeterministicErrorBackend.BackendAction.PutAfter) + { + firstPutCompleted = true; + } + return false; + }; + // Expect error from backend + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + + // Expect error from backend, again + Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + }); + + DeterministicErrorBackend.ErrorGenerator = null; + target = "file://" + TARGETFOLDER; + testopts["full-remote-verification"] = "true"; + testopts["full-block-verification"] = "true"; + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Remove the local database + File.Delete(DBFILE); + + // Repair to recreate the local database + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + + // Re-do the full verification + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + + // Compact without issues on the repaired database + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ICompactResults compactResults = c.Compact(); + Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); + } + + // Make sure there are no errors after success compacting + using (var c = new Library.Main.Controller(target, testopts, null)) + { + ITestResults testResults = c.Test(long.MaxValue); + // Expect no verification errors + Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), + "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); + } + } + } +} diff --git a/Duplicati/UnitTest/CompressionTests.cs b/Duplicati/UnitTest/CompressionTests.cs index 93d60c031..c7bb355f9 100644 --- a/Duplicati/UnitTest/CompressionTests.cs +++ b/Duplicati/UnitTest/CompressionTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Duplicati.Library.Logging; using NUnit.Framework; namespace Duplicati.UnitTest @@ -32,7 +33,6 @@ namespace Duplicati.UnitTest { [TestCase("zip-io")] [TestCase("zip-sc")] - [TestCase("7z")] public void TestCompressionHints(string module) { const int TESTSIZE = 1024 * 1024; @@ -77,7 +77,6 @@ namespace Duplicati.UnitTest /// [TestCase("zip-sc")] [TestCase("zip-io")] - [TestCase("7z")] public void TestCompressionReversibility(string module) { const int TESTSIZE = 1024 * 1024; @@ -136,5 +135,63 @@ namespace Duplicati.UnitTest } } } + + /// + /// Despite archives being able to store multiple files with the same name, this should not leak into the application + /// + /// The compression module + [TestCase("zip-sc")] + [TestCase("zip-io")] + public void TestMaskingDuplicates(string module) + { + using (var stream = new MemoryStream()) + { + var opts = new Dictionary(); + opts["zip-compression-level"] = "9"; + opts["unittest-mode"] = "true"; + + if (module == "zip-sc") + { + module = "zip"; + opts["zip-compression-library"] = "SharpCompress"; + } + else if (module == "zip-io") + { + module = "zip"; + opts["zip-compression-library"] = "BuiltIn"; + } + + // Build archive with duplicate entries + using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, stream, Library.Interface.ArchiveMode.Write, opts)) + { + // Add two identical files to the archive + using (var fs = z0.CreateFile("sample", Library.Interface.CompressionHint.Compressible, DateTime.Now)) + fs.Write(new byte[1024]); + + try + { + using (var fs = z0.CreateFile("sample", Library.Interface.CompressionHint.Compressible, DateTime.Now)) + fs.Write(new byte[1024]); + } + catch + { + Assert.Inconclusive("Unable to create faulty archive for testing"); + } + } + + // Check only a single file is reported + var logMessages = new List(); + using (var rootscope = Library.Logging.Log.StartIsolatingScope(true)) + using (var scope = Library.Logging.Log.StartScope(logMessages.Add)) + using (var z0 = Library.DynamicLoader.CompressionLoader.GetModule(module, stream, Library.Interface.ArchiveMode.Read, opts)) + { + // Get files list + var files = z0.ListFiles(null); + Assert.AreEqual(1, files.Length); + Assert.That(logMessages.Any(x => x.Level == LogMessageType.Warning && x.Message.Contains("duplicate", StringComparison.OrdinalIgnoreCase))); + } + } + } + } } diff --git a/Duplicati/UnitTest/ControllerTests.cs b/Duplicati/UnitTest/ControllerTests.cs index 22b776983..1a4108be1 100644 --- a/Duplicati/UnitTest/ControllerTests.cs +++ b/Duplicati/UnitTest/ControllerTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/DeleteHandlerTests.cs b/Duplicati/UnitTest/DeleteHandlerTests.cs index 394036244..2a60ee65e 100644 --- a/Duplicati/UnitTest/DeleteHandlerTests.cs +++ b/Duplicati/UnitTest/DeleteHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/DeterministicErrorBackend.cs b/Duplicati/UnitTest/DeterministicErrorBackend.cs new file mode 100644 index 000000000..a545d1b50 --- /dev/null +++ b/Duplicati/UnitTest/DeterministicErrorBackend.cs @@ -0,0 +1,169 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Interface; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.UnitTest +{ + public class DeterministicErrorBackend : IBackend, IStreamingBackend + { + public sealed record BackendAction(string Code) + { + public static readonly BackendAction GetBefore = new BackendAction("get_0"); + public static readonly BackendAction GetAfter = new BackendAction("get_1"); + public static readonly BackendAction PutBefore = new BackendAction("put_0"); + public static readonly BackendAction PutAfter = new BackendAction("put_1"); + public static readonly BackendAction DeleteBefore = new BackendAction("delete_0"); + public static readonly BackendAction DeleteAfter = new BackendAction("delete_1"); + + public bool IsGetOperation => this == GetBefore || this == GetAfter; + public bool IsPutOperation => this == PutBefore || this == PutAfter; + public bool IsDeleteOperation => this == DeleteBefore || this == DeleteAfter; + } + + public class DeterministicErrorBackendException(string message) : Exception(message) { }; + + static DeterministicErrorBackend() { WrappedBackend = "file"; } + + // return true to throw exception, parameters: (action, remotename) + public static Func ErrorGenerator = null; + + public static string WrappedBackend { get; set; } + + private IStreamingBackend m_backend; + public DeterministicErrorBackend() + { + } + + // ReSharper disable once UnusedMember.Global + public DeterministicErrorBackend(string url, Dictionary options) + { + var u = new Library.Utility.Uri(url).SetScheme(WrappedBackend).ToString(); + m_backend = (IStreamingBackend)Library.DynamicLoader.BackendLoader.GetBackend(u, options); + } + + private void ThrowError(BackendAction action, string remotename) + { + if (ErrorGenerator != null && ErrorGenerator(action, remotename)) + { + throw new DeterministicErrorBackendException("Backend error"); + } + } + #region IStreamingBackend implementation + public async Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) + { + ThrowError(BackendAction.PutBefore, remotename); + await m_backend.PutAsync(remotename, stream, cancelToken).ConfigureAwait(false); + ThrowError(BackendAction.PutAfter, remotename); + } + public async Task GetAsync(string remotename, Stream stream, CancellationToken cancellationToken) + { + ThrowError(BackendAction.GetBefore, remotename); + await m_backend.GetAsync(remotename, stream, cancellationToken).ConfigureAwait(false); + ThrowError(BackendAction.GetAfter, remotename); + } + #endregion + + #region IBackend implementation + public IAsyncEnumerable ListAsync(CancellationToken cancelToken) + { + return m_backend.ListAsync(cancelToken); + } + public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) + { + ThrowError(BackendAction.PutBefore, remotename); + await m_backend.PutAsync(remotename, filename, cancelToken).ConfigureAwait(false); + ThrowError(BackendAction.PutAfter, remotename); + } + public async Task GetAsync(string remotename, string filename, CancellationToken cancelToken) + { + ThrowError(BackendAction.GetBefore, remotename); + await m_backend.GetAsync(remotename, filename, cancelToken).ConfigureAwait(false); + ThrowError(BackendAction.GetAfter, remotename); + } + public async Task DeleteAsync(string remotename, CancellationToken cancelToken) + { + ThrowError(BackendAction.DeleteBefore, remotename); + await m_backend.DeleteAsync(remotename, cancelToken).ConfigureAwait(false); + ThrowError(BackendAction.DeleteAfter, remotename); + } + public Task TestAsync(CancellationToken cancelToken) + { + return m_backend.TestAsync(cancelToken); + } + public Task CreateFolderAsync(CancellationToken cancelToken) + { + return m_backend.CreateFolderAsync(cancelToken); + } + public Task GetDNSNamesAsync(CancellationToken cancelToken) + { + return m_backend.GetDNSNamesAsync(cancelToken); + } + public string DisplayName + { + get + { + return "Deterministic Error Backend"; + } + } + public string ProtocolKey + { + get + { + return "deterror"; + } + } + public IList SupportedCommands + { + get + { + if (m_backend == null) + try { return Duplicati.Library.DynamicLoader.BackendLoader.GetSupportedCommands(WrappedBackend + "://").ToList(); } + catch { } + + return m_backend.SupportedCommands; + } + } + public string Description + { + get + { + return "A testing backend that randomly fails"; + } + } + #endregion + #region IDisposable implementation + public void Dispose() + { + if (m_backend != null) + try { m_backend.Dispose(); } + finally { m_backend = null; } + } + #endregion + } +} + diff --git a/Duplicati/UnitTest/DisruptionTests.cs b/Duplicati/UnitTest/DisruptionTests.cs index bc7c4ae13..9618951cf 100644 --- a/Duplicati/UnitTest/DisruptionTests.cs +++ b/Duplicati/UnitTest/DisruptionTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -51,32 +51,39 @@ namespace Duplicati.UnitTest } } - private async Task RunPartialBackup(Controller controller) + private async Task<(IBackupResults Result, List ModifiedFiles)> RunPartialBackup(Controller controller) { this.ModifySourceFiles(); + var stopped = new TaskCompletionSource(); + var modifiedFiles = new List(); + controller.OnOperationStarted = r => + { + var pv = r as ITaskControlProvider; + if (pv == null) + throw new Exception("Task control provider not found"); +#if DEBUG + pv.TaskControl.TestMethodCallback = (path) => + { + modifiedFiles.Add(path); + if (modifiedFiles.Count == 3) + { + Thread.Sleep(500); + controller.Stop(); + stopped.TrySetResult(true); + } + }; +#endif + }; + // ReSharper disable once AccessToDisposedClosure Task backupTask = Task.Run(() => controller.Backup(new[] { this.DATAFOLDER })); - IBackupResults taskControl = null; - // Wait for the controller to have the task associated - while (!backupTask.IsCompleted) - { - taskControl = controller.GetType().GetField("m_currentTask", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(controller) as IBackupResults; - if (taskControl != null) - break; - Thread.Sleep(100); - } - - // Wait for the first file to be processed - while (!backupTask.IsCompleted && taskControl.ExaminedFiles == 0) - Thread.Sleep(100); - - if (backupTask.IsCompleted) + var t = await Task.WhenAny(backupTask, stopped.Task).ConfigureAwait(false); + if (t != stopped.Task) throw new Exception("Backup task completed before we could stop it"); - controller.Stop(true); - return await backupTask.ConfigureAwait(false); + return (await backupTask.ConfigureAwait(false), modifiedFiles); } [SetUp] @@ -89,13 +96,18 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task FilesetFiles() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif // Choose a dblock size that is small enough so that more than one volume is needed. Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb", // This allows us to inspect the dlist files without needing the BackendManager (which is inaccessible here) to decrypt them. - ["no-encryption"] = "true" + ["no-encryption"] = "true", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1", }; // Run a full backup. @@ -112,7 +124,7 @@ namespace Duplicati.UnitTest // Run a partial backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } @@ -143,9 +155,7 @@ namespace Duplicati.UnitTest Dictionary backupTypeMap; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IPurgeFilesResults purgeResults = c.PurgeFiles(new Library.Utility.FilterExpression($"*{this.fileSizes[0]}*")); - Assert.AreEqual(0, purgeResults.Errors.Count()); - Assert.AreEqual(0, purgeResults.Warnings.Count()); + TestUtils.AssertResults(c.PurgeFiles(new Library.Utility.FilterExpression($"{this.DATAFOLDER}/*{this.fileSizes[0]}*"))); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(2, filesets.Count); @@ -169,9 +179,7 @@ namespace Duplicati.UnitTest // Run a repair and verify that the fileset file exists in the new dlist files. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); - Assert.AreEqual(0, repairResults.Errors.Count()); - Assert.AreEqual(0, repairResults.Warnings.Count()); + TestUtils.AssertResults(c.Repair()); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(2, filesets.Count); @@ -191,17 +199,24 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task KeepTimeRetention() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + // Choose a dblock size that is small enough so that more than one volume is needed. - Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb" }; + Dictionary options = new Dictionary(this.TestOptions) + { + ["dblock-size"] = "10mb", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1", + }; // First, run two complete backups followed by a partial backup. We will then set the keep-time // option so that the threshold lies between the first and second backups. DateTime firstBackupTime; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); firstBackupTime = c.List().Filesets.First().Time; } @@ -212,9 +227,7 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); secondBackupTime = c.List().Filesets.First().Time; } @@ -222,7 +235,7 @@ namespace Duplicati.UnitTest DateTime thirdBackupTime; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); thirdBackupTime = c.List().Filesets.First().Time; @@ -233,9 +246,7 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { options["keep-time"] = $"{(int)((DateTime.Now - firstBackupTime).TotalSeconds - (secondBackupTime - firstBackupTime).TotalSeconds / 2)}s"; - IDeleteResults deleteResults = c.Delete(); - Assert.AreEqual(0, deleteResults.Errors.Count()); - Assert.AreEqual(0, deleteResults.Warnings.Count()); + TestUtils.AssertResults(c.Delete()); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(2, filesets.Count); @@ -249,7 +260,7 @@ namespace Duplicati.UnitTest // even when all the "recent" backups are partial. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); DateTime fourthBackupTime = c.List().Filesets.First().Time; @@ -257,9 +268,7 @@ namespace Duplicati.UnitTest // Set the keep-time option so that the threshold lies after the most recent full backup // and run the delete operation. options["keep-time"] = "1s"; - IDeleteResults deleteResults = c.Delete(); - Assert.AreEqual(0, deleteResults.Errors.Count()); - Assert.AreEqual(0, deleteResults.Warnings.Count()); + TestUtils.AssertResults(c.Delete()); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(3, filesets.Count); @@ -276,23 +285,30 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task KeepVersionsRetention() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + // Choose a dblock size that is small enough so that more than one volume is needed. - Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb" }; + Dictionary options = new Dictionary(this.TestOptions) + { + ["dblock-size"] = "10mb", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1", + }; // Run a full backup. DateTime firstBackupTime; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); firstBackupTime = c.List().Filesets.First().Time; } // Run a partial backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } @@ -300,7 +316,7 @@ namespace Duplicati.UnitTest // Run a partial backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } @@ -310,9 +326,7 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); fourthBackupTime = c.List().Filesets.First().Time; } @@ -320,11 +334,14 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { options["keep-versions"] = "2"; - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); DateTime fifthBackupTime = c.List().Filesets.First().Time; + // Since we stopped the operation, files were not deleted automatically + c.Delete(); + // Partial backups that are followed by a full backup can be deleted. List filesets = c.List().Filesets.ToList(); Assert.AreEqual(3, filesets.Count); @@ -340,9 +357,7 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); DateTime sixthBackupTime = c.List().Filesets.First().Time; // Since the last backup was full, we can now expect to have just the 2 most recent full backups. @@ -359,25 +374,27 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task ListWithoutLocalDb() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + // Choose a dblock size that is small enough so that more than one volume is needed. Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb", - ["no-local-db"] = "true" + ["no-local-db"] = "true", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1", }; // Run a full backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - } + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); // Run a partial backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); @@ -392,6 +409,10 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task RetentionPolicyRetention() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + Dictionary options = new Dictionary(this.TestOptions) { // Choose a dblock size that is small enough so that more than one volume is needed. @@ -399,24 +420,22 @@ namespace Duplicati.UnitTest // This test assumes that we can perform 3 backups within 1 minute. ["retention-policy"] = "1m:59s,U:1m", - ["no-backend-verification"] = "true" + ["no-backend-verification"] = "true", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1" }; DateTime firstBackupTime; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); firstBackupTime = c.List().Filesets.First().Time; List filesets = c.List().Filesets.ToList(); Assert.AreEqual(1, filesets.Count); this.ModifySourceFiles(); - backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); DateTime secondBackupTime = c.List().Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, the only backup in the first time frame @@ -435,11 +454,14 @@ namespace Duplicati.UnitTest DateTime thirdBackupTime; using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); thirdBackupTime = c.List().Filesets.First().Time; + // Since we stopped the backup, files were not deleted automatically + c.Delete(); + // Since the most recent backup is not considered in the retention logic, there are no backups in the first time // frame. The original 2 backups have now spilled over to the U:1m specification. Since we keep the first // backup in the interval, we should be left with the first backup, as well as the third partial one. @@ -455,9 +477,7 @@ namespace Duplicati.UnitTest using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); fourthBackupTime = c.List().Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, the third backup is the only backup @@ -473,9 +493,7 @@ namespace Duplicati.UnitTest Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup); this.ModifySourceFiles(); - backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); DateTime fifthBackupTime = c.List().Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, we now have two backups in the @@ -525,15 +543,22 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task StopAfterCurrentFile() { - // Choose a dblock size that is small enough so that more than one volume is needed. - Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb" }; +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + + Dictionary options = new Dictionary(this.TestOptions) + { + // Choose a dblock size that is small enough so that more than one volume is needed. + ["dblock-size"] = "10mb", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1" + }; // Run a complete backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); Assert.AreEqual(1, c.List().Filesets.Count()); Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup); @@ -542,9 +567,13 @@ namespace Duplicati.UnitTest // Run a partial backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, var modified) = await this.RunPartialBackup(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); + if (backupResults.ModifiedFiles == 0) + throw new Exception($"No files were modified, likely the stop was issued too early, list is: {string.Join(", ", modified)}"); + Assert.That(backupResults.ModifiedFiles, Is.GreaterThan(0), "No files were added, likely the stop was issued too early"); + Assert.That(backupResults.ModifiedFiles, Is.LessThan(fileSizes.Length), "All files were added, likely the stop was issued too late"); // If we interrupt the backup, the most recent Fileset should be marked as partial. Assert.AreEqual(2, c.List().Filesets.Count()); @@ -556,14 +585,14 @@ namespace Duplicati.UnitTest Dictionary restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IListResults lastResults = c.List("*"); - string[] partialVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); + var lastResults = c.List("*"); + var partialVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.GreaterOrEqual(partialVersionFiles.Length, 1); c.Restore(partialVersionFiles); foreach (string filepath in partialVersionFiles) { - string filename = Path.GetFileName(filepath); + var filename = Path.GetFileName(filepath); TestUtils.AssertFilesAreEqual(filepath, Path.Combine(this.RESTOREFOLDER, filename ?? String.Empty), false, filename); } } @@ -572,9 +601,7 @@ namespace Duplicati.UnitTest File.Delete(this.DBFILE); using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); - Assert.AreEqual(0, repairResults.Errors.Count()); - Assert.AreEqual(0, repairResults.Warnings.Count()); + TestUtils.AssertResults(c.Repair()); Assert.AreEqual(2, c.List().Filesets.Count()); Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup); @@ -584,9 +611,7 @@ namespace Duplicati.UnitTest // Run a complete backup. Listing the Filesets should include both full and partial backups. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); Assert.AreEqual(3, c.List().Filesets.Count()); Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 2).IsFullBackup); @@ -598,13 +623,11 @@ namespace Duplicati.UnitTest restoreOptions["overwrite"] = "true"; using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IListResults lastResults = c.List("*"); - string[] fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); + var lastResults = c.List("*"); + var fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length); - IRestoreResults restoreResults = c.Restore(fullVersionFiles); - Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); + TestUtils.AssertResults(c.Restore(fullVersionFiles)); foreach (string filepath in fullVersionFiles) { @@ -618,42 +641,69 @@ namespace Duplicati.UnitTest [Category("Disruption")] public async Task StopNow() { +#if !DEBUG + Assert.Ignore("This test requires DEBUG to be defined"); +#endif + // Choose a dblock size that is small enough so that more than one volume is needed. - Dictionary options = new Dictionary(this.TestOptions) { ["dblock-size"] = "10mb", ["disable-synthetic-filelist"] = "true" }; + Dictionary options = new Dictionary(this.TestOptions) + { + ["dblock-size"] = "10mb", + ["disable-synthetic-filelist"] = "true", + ["disable-file-scanner"] = "true", + ["concurrency-fileprocessors"] = "1", + }; // Run a complete backup. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(1, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup); } - // Interrupt a backup with "stop now". + // Interrupt a backup with "abort". this.ModifySourceFiles(); using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - // ReSharper disable once AccessToDisposedClosure - Task backupTask = Task.Run(() => c.Backup(new[] { this.DATAFOLDER })); + var startedTcs = new TaskCompletionSource(); + c.OnOperationStarted += results => + { + ((BackupResults)results).OperationProgressUpdater.PhaseChanged += (phase, previousPhase) => + { + if (phase == OperationPhase.Backup_ProcessingFiles) + startedTcs.SetResult(true); + }; + }; + + Task backupTask = Task.Run(() => c.Backup([DATAFOLDER])); - // Block for a small amount of time to allow the ITaskControl to be associated - // with the Controller. Otherwise, the call to Stop will simply be a no-op. - Thread.Sleep(1000); - - c.Stop(false); - await backupTask.ConfigureAwait(false); + Task completedTask = await Task.WhenAny(startedTcs.Task, Task.Delay(5000)); + if (completedTask == startedTcs.Task) + { + c.Abort(); + try + { + await backupTask.ConfigureAwait(false); + Assert.Fail("Expected OperationCanceledException but none was thrown."); + } + catch (OperationCanceledException) + { + // Success, exception thrown as expected + } + } + else + { + Assert.Fail("Operation did not reach ready state within timeout."); + } } - + // The next backup should proceed without issues. using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); List filesets = c.List().Filesets.ToList(); Assert.AreEqual(2, filesets.Count); @@ -665,14 +715,11 @@ namespace Duplicati.UnitTest Dictionary restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IListResults lastResults = c.List("*"); - string[] fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); + var lastResults = c.List("*"); + var fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length); - IRestoreResults restoreResults = c.Restore(fullVersionFiles); - Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); - + TestUtils.AssertResults(c.Restore(fullVersionFiles)); foreach (string filepath in fullVersionFiles) { string filename = Path.GetFileName(filepath); @@ -680,5 +727,552 @@ namespace Duplicati.UnitTest } } } + + [Test] + [Category("Disruption")] + public void TestUploadExceptionWithNoRetries() + { + var testopts = TestOptions; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "10mb"; + + // Make a base backup + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + + // Make a new backup that fails uploading a dblock file + ModifySourceFiles(); + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + + var hasFailed = false; + var secondUploadStarted = false; + var secondUploadCompleted = false; + + // Fail the compact after the first dblock put is completed + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (action.IsGetOperation) + { + return true; + } + + if (!hasFailed && action == DeterministicErrorBackend.BackendAction.PutBefore) + { + // We only fail one upload, but there are no retries + hasFailed = true; + + // Make sure we can start a second upload + Thread.Sleep(1000); + return true; + } + + if (action == DeterministicErrorBackend.BackendAction.PutBefore) + secondUploadStarted = true; + if (action == DeterministicErrorBackend.BackendAction.PutAfter) + secondUploadCompleted = true; + + return false; + }; + + using (var c = new Controller(failtarget, testopts, null)) + Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + + Assert.That(secondUploadStarted, Is.True, "Second upload was not started"); + Assert.That(secondUploadCompleted, Is.True, "Second upload was not started"); + + // Create a regular backup + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + + // Verify that all is in order + using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(c.Test(long.MaxValue)); + + // Test that we can recreate + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; + + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 3 versions + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(3, listResults.Filesets.Count()); + } + } + + [Test] + [Category("Disruption")] + [TestCase(true, false)] + [TestCase(false, false)] + [TestCase(true, true)] + [TestCase(false, true)] + public void TestUploadExceptionOnFirstDlistWithRepair(bool before, bool failOnLastDblock) + { + var testopts = TestOptions; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "10mb"; + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + var operation = before + ? DeterministicErrorBackend.BackendAction.PutBefore + : DeterministicErrorBackend.BackendAction.PutAfter; + + // We expect to upload 7 volumes, and fail on the last one + var count = 7; + var failed = false; + + // Fail when uploading the dlist file + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (failOnLastDblock) + { + if (action == operation && remotename.Contains(".dblock.")) + { + if (Interlocked.Decrement(ref count) == 0) + { + // Allow others to progress + Thread.Sleep(2000); + failed = true; + return true; + } + } + } + else + { + if (action == operation && remotename.Contains(".dlist.")) + { + failed = true; + return true; + } + } + + return false; + }; + + using (var c = new Controller(failtarget, testopts, null)) + Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + + Assert.That(failed, Is.True, "Failed to fail the upload"); + + // Make spacing for the next backup + Thread.Sleep(3000); + + // Issue repair + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Verify that all is in order + using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(c.Test(long.MaxValue)); + + // Make a new backup, should continue as normal + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var backupResults = c.Backup(new string[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + Assert.AreEqual(2, c.List().Filesets.Count()); + } + + // Test that we can recreate + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; + + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 2 versions + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(2, listResults.Filesets.Count()); + } + } + + [Test] + [Category("Disruption")] + [TestCase(true, false)] + [TestCase(false, false)] + [TestCase(true, true)] + [TestCase(false, true)] + public void TestUploadExceptionOnFirstDlist(bool before, bool failOnLastDblock) + { + var testopts = TestOptions; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "10mb"; + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + var operation = before + ? DeterministicErrorBackend.BackendAction.PutBefore + : DeterministicErrorBackend.BackendAction.PutAfter; + + // We expect to upload 7 volumes, and fail on the last one + var count = 7; + var failed = false; + + // Fail when uploading the dlist file + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (failOnLastDblock) + { + if (action == operation && remotename.Contains(".dblock.")) + { + if (Interlocked.Decrement(ref count) == 0) + { + // Allow others to progress + Thread.Sleep(2000); + failed = true; + return true; + } + } + } + else + { + if (action == operation && remotename.Contains(".dlist.")) + { + failed = true; + return true; + } + } + + return false; + }; + + using (var c = new Controller(failtarget, testopts, null)) + Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + + Assert.That(failed, Is.True, "Failed to fail the upload"); + + // Make spacing for the next backup + Thread.Sleep(3000); + + // Make a new backup, should continue as normal + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + Assert.AreEqual(2, c.List().Filesets.Count()); + } + + // Verify that all is in order + using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(c.Test(long.MaxValue)); + + // Test that we can recreate + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; + + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 2 versions + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(2, listResults.Filesets.Count()); + } + } + + [Test] + [Category("Disruption")] + [TestCase(true)] + [TestCase(false)] + public void TestRepairWithNoDlist(bool before) + { + var testopts = TestOptions; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "10mb"; + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + var operation = before + ? DeterministicErrorBackend.BackendAction.PutBefore + : DeterministicErrorBackend.BackendAction.PutAfter; + + // Fail when uploading the first dlist file + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (action == operation && remotename.Contains(".dlist.")) + return true; + + return false; + }; + + using (var c = new Controller(failtarget, testopts, null)) + Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + + // Test that we can repair + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 1 version + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(1, listResults.Filesets.Count()); + } + } + + + [Test] + [Category("Disruption")] + [TestCase(true, true, 3, false)] + [TestCase(false, false, 3, false)] + [TestCase(true, false, 3, false)] + [TestCase(false, true, 3, false)] + [TestCase(true, true, 3, true)] + [TestCase(false, true, 3, true)] + public void TestMultiExceptionOnDlist(bool before, bool modifyInBetween, int runs, bool withBase) + { + var testopts = TestOptions; + testopts["number-of-retries"] = "0"; + testopts["dblock-size"] = "10mb"; + var expectedFilesets = withBase ? 3 : 2; + + if (withBase) + { + // Make a regular backup + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + } + + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + var operation = before + ? DeterministicErrorBackend.BackendAction.PutBefore + : DeterministicErrorBackend.BackendAction.PutAfter; + + // Fail when uploading the first dlist file + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (action == operation && remotename.Contains(".dlist.")) + return true; + + return false; + }; + + for (var i = 0; i < runs; i++) + { + if (modifyInBetween) + ModifySourceFiles(); + + using (var c = new Controller(failtarget, testopts, null)) + try + { + c.Backup(new string[] { DATAFOLDER }); + } + catch (DeterministicErrorBackend.DeterministicErrorBackendException) + { + } + + // Prevent clashes in timestamps + Thread.Sleep(3000); + } + + if (modifyInBetween) + ModifySourceFiles(); + + // Make a new backup, should continue as normal + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var backupResults = c.Backup(new string[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + Assert.AreEqual(expectedFilesets, c.List().Filesets.Count()); + } + + // Verify that all is in order + using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(c.Test(long.MaxValue)); + + // Test that we can recreate + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; + + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 2 versions + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(expectedFilesets, listResults.Filesets.Count()); + } + } + + private class LogSink : IMessageSink + { + public List StartedFiles { get; } = new List(); + public List CompletedFiles { get; } = new List(); + public void BackendEvent(BackendActionType action, BackendEventType type, string path, long size) + { + if (action == BackendActionType.Put && type == BackendEventType.Started) + { + this.StartedFiles.Add(path); + } + if (action == BackendActionType.Put && type == BackendEventType.Completed) + { + this.CompletedFiles.Add(path); + } + } + + public void SetBackendProgress(IBackendProgress progress) + { + } + + public void SetOperationProgress(IOperationProgress progress) + { + } + + public void WriteMessage(LogEntry entry) + { + } + } + + [Test] + [Category("Disruption")] + public void TestDblockUploadWithOperationCancellation() + { + TestUploadWithOperationCancellation(".dblock."); + } + + + [Test] + [Category("Disruption")] + public void TestDindexUploadWithOperationCancellation() + { + TestUploadWithOperationCancellation(".dindex."); + } + + [Test] + [Category("Disruption")] + public void TestDlistUploadWithOperationCancellation() + { + TestUploadWithOperationCancellation(".dlist."); + } + + private void TestUploadWithOperationCancellation(string filefragment) + { + var testopts = TestOptions; + testopts["number-of-retries"] = "1"; + testopts["dblock-size"] = "10mb"; + + // Make a base backup + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + + // Make a new backup that fails uploading a dblock file + ModifySourceFiles(); + + // Deterministic error backend + Library.DynamicLoader.BackendLoader.AddBackend(new DeterministicErrorBackend()); + var failtarget = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + var uploadCount = 0; + var failstep = DeterministicErrorBackend.BackendAction.PutBefore; + + // Fail the upload before + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + if (action == failstep && remotename.Contains(filefragment)) + { + // Fail once with a cancellation + if (Interlocked.Increment(ref uploadCount) == 1) + throw new OperationCanceledException(); + } + + return false; + }; + + using (var c = new Controller(failtarget, testopts, null)) + { + var sink = new LogSink(); + c.AppendSink(sink); + var res = c.Backup(new string[] { DATAFOLDER }); + if (uploadCount == 0) + Assert.Fail("Upload count was not incremented"); + + Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, Directory.GetFiles(TARGETFOLDER, "*.dlist.*", SearchOption.TopDirectoryOnly).Count()); + Assert.AreEqual(7 * 2 + 1, sink.CompletedFiles.Count); // 7 completed (dblock + dindex) + 1 dlist + Assert.AreEqual(7 * 2 + 2, sink.StartedFiles.Count); // 1 retry + Assert.AreEqual(sink.CompletedFiles.Count(x => x.Contains(".dblock.")), sink.CompletedFiles.Count(x => x.Contains(".dindex."))); + Assert.AreEqual(1, res.BackendStatistics.RetryAttempts); + } + + uploadCount = 0; + failstep = DeterministicErrorBackend.BackendAction.PutAfter; + + ModifySourceFiles(); + using (var c = new Controller(failtarget, testopts, null)) + { + var sink = new LogSink(); + c.AppendSink(sink); + var res = c.Backup(new string[] { DATAFOLDER }); + if (uploadCount == 0) + Assert.Fail("Upload count was not incremented"); + + Assert.AreEqual(3, c.List().Filesets.Count()); + Assert.AreEqual(3, Directory.GetFiles(TARGETFOLDER, "*.dlist.*", SearchOption.TopDirectoryOnly).Count()); + Assert.AreEqual(7 * 2 + 1, sink.CompletedFiles.Count); // 7 completed (dblock + dindex) + 1 dlist + Assert.AreEqual(7 * 2 + 2, sink.StartedFiles.Count); // 1 retry + Assert.AreEqual(sink.CompletedFiles.Count(x => x.Contains(".dblock.")), sink.CompletedFiles.Count(x => x.Contains(".dindex."))); + Assert.AreEqual(1, res.BackendStatistics.RetryAttempts); + } + + ModifySourceFiles(); + // Create a regular backup + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + + // Verify that all is in order + using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(c.Test(long.MaxValue)); + + // Test that we can recreate + var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); + if (File.Exists(recreatedDatabaseFile)) + File.Delete(recreatedDatabaseFile); + + testopts["dbpath"] = recreatedDatabaseFile; + + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + // Check that we have 3 versions + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) + { + var listResults = c.List(); + TestUtils.AssertResults(listResults); + Assert.AreEqual(4, listResults.Filesets.Count()); + } + } } } \ No newline at end of file diff --git a/Duplicati/UnitTest/Duplicati.UnitTest.csproj b/Duplicati/UnitTest/Duplicati.UnitTest.csproj index d8a930cdf..9925d51ee 100644 --- a/Duplicati/UnitTest/Duplicati.UnitTest.csproj +++ b/Duplicati/UnitTest/Duplicati.UnitTest.csproj @@ -6,9 +6,9 @@ false false Duplicati.UnitTest - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license - + @@ -35,14 +35,15 @@ + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Duplicati/UnitTest/EncryptedFieldHelperTests.cs b/Duplicati/UnitTest/EncryptedFieldHelperTests.cs index ba289a1d4..4fa1ea4b1 100644 --- a/Duplicati/UnitTest/EncryptedFieldHelperTests.cs +++ b/Duplicati/UnitTest/EncryptedFieldHelperTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -177,13 +177,12 @@ namespace Duplicati.UnitTest Assert.IsNotEmpty(decrypted); Assert.AreEqual(decrypted, sampleTargerURL); - try { - // So far, this tests does not ensure it is using the default key, so lets check that - // by using the default key and checking if it still works, it should throw + // So far, this tests does not ensure it is using the any key, so lets check that + // by using the differnt key and checking if it still works, it should throw // a SettingsKeymismatchException - var secondtest = EncryptedFieldHelper.Decrypt(encrypted); + var secondtest = EncryptedFieldHelper.Decrypt(encrypted, EncryptedFieldHelper.KeyInstance.CreateKey("another good and long password")); } catch (Exception ex) diff --git a/Duplicati/UnitTest/FilterTest.cs b/Duplicati/UnitTest/FilterTest.cs index a92f40b6e..a510fe37f 100644 --- a/Duplicati/UnitTest/FilterTest.cs +++ b/Duplicati/UnitTest/FilterTest.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs index a69200865..8fd859395 100644 --- a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs +++ b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -22,9 +22,8 @@ using NUnit.Framework; using System.Linq; using System.IO; -using System; using System.Collections.Generic; -using System.Net; +using Duplicati.Library.Utility; namespace Duplicati.UnitTest { @@ -39,10 +38,10 @@ namespace Duplicati.UnitTest get { var rx = new System.Text.RegularExpressions.Regex("r(?\\d+)"); - return + return from n in Directory.EnumerateDirectories(SOURCE_FOLDERS) let m = rx.Match(n) - where m.Success + where m.Success orderby int.Parse(m.Groups["number"].Value) select n; } @@ -56,7 +55,7 @@ namespace Duplicati.UnitTest } } - protected string TestTarget + protected string TestTarget { get { @@ -70,44 +69,7 @@ namespace Duplicati.UnitTest public void OneTimeSetUp() { this.OneTimeTearDown(); - var url = $"https://testfiles.duplicati.com/{this.zipFilename}"; - var destinationFilePath = this.zipFilepath; - var webRequest = (HttpWebRequest)WebRequest.Create(url); - - using (var wr = (HttpWebResponse)webRequest.GetResponse()) - using (WebClient client = new WebClient()) - { - Console.WriteLine("downloading test file to: {0}, length: {1}", destinationFilePath, wr.ContentLength); - var maxAttempts = 5; - while (maxAttempts-- > 0) { - try { - DateTime beginTime = DateTime.Now; - client.DownloadFile(url, destinationFilePath); - long length = new System.IO.FileInfo(destinationFilePath).Length; - Console.WriteLine("downloaded test file: {0}: length {1}, duration {2}", destinationFilePath, length, (DateTime.Now - beginTime).TotalSeconds); - if (length == wr.ContentLength) { - maxAttempts = -1; - } else { - Console.WriteLine("invalid downloaded length {0}, should be {1}...", length, wr.ContentLength); - System.Threading.Thread.Sleep(120000); - } - } - catch (WebException ex) - { - if (ex.Response == null){ - Console.WriteLine("ex.Response is null !"); - } else { - Console.WriteLine("exception {0}", ex); - throw; - } - } - } - - if (maxAttempts == 0) { - throw new Exception(string.Format("Unable to download test file from {0}", url)); - } - } - + CommandLineOperationsTests.DownloadS3FileIfNewerAsync(this.zipFilepath, $"{CommandLineOperationsTests.S3_URL}{this.zipFilename}").Await(); System.IO.Compression.ZipFile.ExtractToDirectory(this.zipFilepath, BASEFOLDER); } @@ -118,16 +80,11 @@ namespace Duplicati.UnitTest { Directory.Delete(SOURCE_FOLDERS, true); } - if (File.Exists(this.zipFilepath)) - { - File.Delete(this.zipFilepath); - } } [Test] [Category("SVNData")] [TestCase("zip")] - [TestCase("7z")] public void TestWithSVNShort(string compression) { var opts = TestOptions; @@ -138,7 +95,6 @@ namespace Duplicati.UnitTest [Test] [Category("SVNDataLong")] [TestCase("zip")] - [TestCase("7z")] public void TestWithSVNLong(string compression) { var opts = TestOptions; diff --git a/Duplicati/UnitTest/HasherTests.cs b/Duplicati/UnitTest/HasherTests.cs index a71cac051..35588b6a7 100644 --- a/Duplicati/UnitTest/HasherTests.cs +++ b/Duplicati/UnitTest/HasherTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/IOTests.cs b/Duplicati/UnitTest/IOTests.cs index 0a2fc4749..0be6d3de7 100644 --- a/Duplicati/UnitTest/IOTests.cs +++ b/Duplicati/UnitTest/IOTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,8 +23,6 @@ using System.Text; using NUnit.Framework; using Duplicati.Library.Common.IO; -using Duplicati.Library.Utility; -using Duplicati.Library.Common; using System.Collections.Generic; using System; @@ -62,13 +60,13 @@ namespace Duplicati.UnitTest var filePath = root + filename; var filePathWithExtendedDevicePathPrefix = SystemIOWindows.AddExtendedDevicePathPrefix(filePath); - var filePathWithExtendedDevicePathPrefixRoot = SystemIO.IO_WIN.GetPathRoot(filePathWithExtendedDevicePathPrefix); + var filePathWithExtendedDevicePathPrefixRoot = SystemIO.IO_OS.GetPathRoot(filePathWithExtendedDevicePathPrefix); //Prefixed with extended device path prefix remains prefixed Assert.AreEqual(SystemIOWindows.AddExtendedDevicePathPrefix(root), filePathWithExtendedDevicePathPrefixRoot); //Without extended device path prefix, no prefix. - var filePathRoot = SystemIO.IO_WIN.GetPathRoot(filePath); + var filePathRoot = SystemIO.IO_OS.GetPathRoot(filePath); Assert.AreEqual(root, filePathRoot); } @@ -282,7 +280,7 @@ namespace Duplicati.UnitTest }; foreach (var path in testCasesWherePathGetFullGivesSameResultsAsDotNet) { - var actual = SystemIO.IO_WIN.PathGetFullPath(path); + var actual = SystemIO.IO_OS.PathGetFullPath(path); var expected = System.IO.Path.GetFullPath(path); Assert.AreEqual(expected, actual, $"Path: {path}"); } @@ -313,7 +311,7 @@ namespace Duplicati.UnitTest foreach (var keyValuePair in testCasesWherePathGetFullGivesDifferentResultsThanDotNet) { var path = keyValuePair.Key; - var actual = SystemIO.IO_WIN.PathGetFullPath(path); + var actual = SystemIO.IO_OS.PathGetFullPath(path); var expected = keyValuePair.Value; Assert.AreEqual(expected, actual, $"Path: {path}"); } diff --git a/Duplicati/UnitTest/ImportExportTests.cs b/Duplicati/UnitTest/ImportExportTests.cs index c03f277cb..26062445c 100644 --- a/Duplicati/UnitTest/ImportExportTests.cs +++ b/Duplicati/UnitTest/ImportExportTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.IO; using Duplicati.Library.RestAPI; +using Duplicati.Library.SQLiteHelper; using Duplicati.Server; using Duplicati.Server.Database; using Duplicati.Server.Serializable; @@ -126,46 +127,57 @@ namespace Duplicati.UnitTest [Category("ImportExport")] public void RoundTrip() { - Dictionary metadata = new Dictionary { { "SourceFilesCount", "1" } }; - Dictionary advancedOptions = new Dictionary { { "server-datafolder", this.serverDatafolder } }; + var metadata = new Dictionary { { "SourceFilesCount", "1" } }; + var advancedOptions = new Dictionary { { "server-datafolder", this.serverDatafolder } }; + + // Mock the setup enough to get the import/export working var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(new EventPollNotify()); FIXMEGlobal.Provider = new DefaultServiceProviderFactory().CreateServiceProvider(serviceCollection); + FIXMEGlobal.DataFolder = this.serverDatafolder; - using (Program.DataConnection = Program.GetDatabaseConnection(advancedOptions, true)) + var dbpath = Path.Combine(this.serverDatafolder, Library.AutoUpdater.DataFolderManager.SERVER_DATABASE_FILENAME); + if (File.Exists(dbpath)) + File.Delete(dbpath); + + var con = SQLiteLoader.LoadConnection(); + SQLiteLoader.OpenDatabase(con, dbpath, null); + DatabaseUpgrader.UpgradeDatabase(con, dbpath, typeof(Duplicati.Library.RestAPI.Database.DatabaseConnectionSchemaMarker)); + + using (var connection = new Connection(con, true, null)) { // Unencrypted file, don't import metadata. string unencryptedWithoutMetadata = Path.Combine(this.serverDatafolder, Path.GetRandomFileName()); - File.WriteAllBytes(unencryptedWithoutMetadata, BackupImportExportHandler.ExportToJSON(Program.DataConnection, this.CreateBackup("unencrypted without metadata", "user", "password", metadata), null)); - BackupImportExportHandler.ImportBackup(Program.DataConnection, unencryptedWithoutMetadata, false, () => null); - Assert.AreEqual(1, Program.DataConnection.Backups.Length); - Assert.AreEqual(0, Program.DataConnection.Backups[0].Metadata.Count); + File.WriteAllBytes(unencryptedWithoutMetadata, BackupImportExportHandler.ExportToJSON(connection, this.CreateBackup("unencrypted without metadata", "user", "password", metadata), null)); + BackupImportExportHandler.ImportBackup(connection, unencryptedWithoutMetadata, false, () => null); + Assert.AreEqual(1, connection.Backups.Length); + Assert.AreEqual(0, connection.Backups[0].Metadata.Count); // Unencrypted file, import metadata. string unencryptedWithMetadata = Path.Combine(this.serverDatafolder, Path.GetRandomFileName()); - File.WriteAllBytes(unencryptedWithMetadata, BackupImportExportHandler.ExportToJSON(Program.DataConnection, this.CreateBackup("unencrypted with metadata", "user", "password", metadata), null)); - BackupImportExportHandler.ImportBackup(Program.DataConnection, unencryptedWithMetadata, true, () => null); - Assert.AreEqual(2, Program.DataConnection.Backups.Length); - Assert.AreEqual(metadata.Count, Program.DataConnection.Backups[1].Metadata.Count); + File.WriteAllBytes(unencryptedWithMetadata, BackupImportExportHandler.ExportToJSON(connection, this.CreateBackup("unencrypted with metadata", "user", "password", metadata), null)); + BackupImportExportHandler.ImportBackup(connection, unencryptedWithMetadata, true, () => null); + Assert.AreEqual(2, connection.Backups.Length); + Assert.AreEqual(metadata.Count, connection.Backups[1].Metadata.Count); // Encrypted file, don't import metadata. string encryptedWithoutMetadata = Path.Combine(this.serverDatafolder, Path.GetRandomFileName()); string passphrase = "abcde"; - File.WriteAllBytes(encryptedWithoutMetadata, BackupImportExportHandler.ExportToJSON(Program.DataConnection, this.CreateBackup("encrypted without metadata", "user", "password", metadata), passphrase)); - BackupImportExportHandler.ImportBackup(Program.DataConnection, encryptedWithoutMetadata, false, () => passphrase); - Assert.AreEqual(3, Program.DataConnection.Backups.Length); - Assert.AreEqual(0, Program.DataConnection.Backups[2].Metadata.Count); + File.WriteAllBytes(encryptedWithoutMetadata, BackupImportExportHandler.ExportToJSON(connection, this.CreateBackup("encrypted without metadata", "user", "password", metadata), passphrase)); + BackupImportExportHandler.ImportBackup(connection, encryptedWithoutMetadata, false, () => passphrase); + Assert.AreEqual(3, connection.Backups.Length); + Assert.AreEqual(0, connection.Backups[2].Metadata.Count); // Encrypted file, import metadata. string encryptedWithMetadata = Path.Combine(this.serverDatafolder, Path.GetRandomFileName()); - File.WriteAllBytes(encryptedWithMetadata, BackupImportExportHandler.ExportToJSON(Program.DataConnection, this.CreateBackup("encrypted with metadata", "user", "password", metadata), passphrase)); - BackupImportExportHandler.ImportBackup(Program.DataConnection, encryptedWithMetadata, true, () => passphrase); - Assert.AreEqual(4, Program.DataConnection.Backups.Length); - Assert.AreEqual(metadata.Count, Program.DataConnection.Backups[3].Metadata.Count); + File.WriteAllBytes(encryptedWithMetadata, BackupImportExportHandler.ExportToJSON(connection, this.CreateBackup("encrypted with metadata", "user", "password", metadata), passphrase)); + BackupImportExportHandler.ImportBackup(connection, encryptedWithMetadata, true, () => passphrase); + Assert.AreEqual(4, connection.Backups.Length); + Assert.AreEqual(metadata.Count, connection.Backups[3].Metadata.Count); // Encrypted file, incorrect passphrase. - Assert.Throws(Is.InstanceOf(), () => BackupImportExportHandler.ImportBackup(Program.DataConnection, encryptedWithMetadata, true, () => passphrase + " ")); + Assert.Throws(Is.InstanceOf(), () => BackupImportExportHandler.ImportBackup(connection, encryptedWithMetadata, true, () => passphrase + " ")); } } } diff --git a/Duplicati/UnitTest/Issue1410.cs b/Duplicati/UnitTest/Issue1410.cs index a57e6390c..c47e48aee 100644 --- a/Duplicati/UnitTest/Issue1410.cs +++ b/Duplicati/UnitTest/Issue1410.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/UnitTest/Issue1723.cs b/Duplicati/UnitTest/Issue1723.cs index db7b747bd..ed75df07c 100644 --- a/Duplicati/UnitTest/Issue1723.cs +++ b/Duplicati/UnitTest/Issue1723.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/UnitTest/Issue4312.cs b/Duplicati/UnitTest/Issue4312.cs new file mode 100644 index 000000000..cbfa60e63 --- /dev/null +++ b/Duplicati/UnitTest/Issue4312.cs @@ -0,0 +1,137 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using NUnit.Framework; + +namespace Duplicati.UnitTest; + +public class Issue4312 : BasicSetupHelper +{ + [Test] + [Category("Targeted")] + public void ChangeTimestampShouldCreateExtraBackup() + { + var testopts = TestOptions; + // Make sure we detect changes from metadata + testopts["upload-unchanged-backups"] = "false"; + testopts["blocksize"] = "50kb"; + + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(1, r.AddedFiles); + var pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) + throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + + System.Threading.Thread.Sleep(3000); + + r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(0, r.AddedFiles); + Assert.AreEqual(0, r.ModifiedFiles); + pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) + throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + + System.Threading.Thread.Sleep(3000); + + // Force a change in the file metadata + File.SetLastWriteTimeUtc(Path.Combine(DATAFOLDER, "a"), DateTime.Now); + + r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(0, r.AddedFiles); + Assert.AreEqual(1, r.ModifiedFiles); + pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 6 || pr.BackupListCount != 2) + throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + } + } + + [Test] + [Category("Targeted")] + public void BackupFromRecreatedDatabaseShouldUpdateMetadata() + { + var testopts = TestOptions; + // Make sure we detect changes from metadata + testopts["upload-unchanged-backups"] = "false"; + testopts["blocksize"] = "50kb"; + + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(1, r.AddedFiles); + var pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) + throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + + // Remove the database + File.Delete(testopts["dbpath"]); + + // Get a second between the two backups + Thread.Sleep(2000); + + // Recreate + var rr = c.Repair(); + Assert.AreEqual(0, rr.Errors.Count()); + Assert.AreEqual(0, rr.Warnings.Count()); + + // Because the timestamps are restored with lower precision + // this will trigger a rescan of the files + r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(0, r.AddedFiles); + Assert.AreEqual(0, r.ModifiedFiles); + Assert.AreEqual(1, r.OpenedFiles); + pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) + throw new Exception(string.Format("Looks like the metadata scan failed: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + + // Make a backup again, and ensure that no files are opened + r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.AreEqual(0, r.AddedFiles); + Assert.AreEqual(0, r.ModifiedFiles); + Assert.AreEqual(0, r.OpenedFiles); + pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; + if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) + throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); + } + + } +} diff --git a/Duplicati/UnitTest/Issue5097.cs b/Duplicati/UnitTest/Issue5097.cs new file mode 100644 index 000000000..a02f83238 --- /dev/null +++ b/Duplicati/UnitTest/Issue5097.cs @@ -0,0 +1,100 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using Duplicati.Library.Interface; +using NUnit.Framework; + +namespace Duplicati.UnitTest +{ + public class Issue5097 : BasicSetupHelper + { + [Test] + [Category("Targeted")] + public void RunCommands() + { + var testopts = TestOptions; + testopts["upload-unchanged-backups"] = "true"; + testopts["keep-versions"] = "2"; + testopts["number-of-retries"] = "0"; + testopts["blocksize"] = "50kb"; + + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + } + + var firstdlistfile = Directory.GetFiles(TARGETFOLDER, "*dlist*").FirstOrDefault(); + + File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + } + + var seconddlistfile = Directory.GetFiles(TARGETFOLDER, "*dlist*").FirstOrDefault(x => x != firstdlistfile); + + // Corrupt the second dlist file + using (var fs = File.OpenWrite(seconddlistfile)) + { + fs.Seek(fs.Length / 2, SeekOrigin.End); + fs.WriteByte(42); + } + + // Delete the local database + File.Delete(DBFILE); + + // Repair the database, expect an error + Assert.Catch(() => + { + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Repair(); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + } + }); + + // Make a backup, this should fail + File.WriteAllBytes(Path.Combine(DATAFOLDER, "c"), data); + var uix = Assert.Catch(() => + { + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(1, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + } + }); + + Assert.That(uix.Message, Does.Contain("database was attempted repaired")); + } + } +} diff --git a/Duplicati/UnitTest/Issue5196.cs b/Duplicati/UnitTest/Issue5196.cs index 238ffec91..aa2eb106b 100644 --- a/Duplicati/UnitTest/Issue5196.cs +++ b/Duplicati/UnitTest/Issue5196.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/Issue5845.cs b/Duplicati/UnitTest/Issue5845.cs new file mode 100644 index 000000000..9eef94cd9 --- /dev/null +++ b/Duplicati/UnitTest/Issue5845.cs @@ -0,0 +1,278 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using NUnit.Framework; +using System.Linq; +using Duplicati.Library.Interface; +using Duplicati.Library.DynamicLoader; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; + +namespace Duplicati.UnitTest +{ + public class Issue5845 : BasicSetupHelper + { + private class ListSortingBackend : IStreamingBackend + { + public const string Key = "test-sorter-5845"; + public string DisplayName => "Test sorter"; + public string ProtocolKey => Key; + public string Description => "Test sorter"; + public IList SupportedCommands => backend.SupportedCommands; + private readonly IStreamingBackend backend; + + public static string RealKey { get; set; } + public static Func, IEnumerable> Sorter { get; set; } + + public ListSortingBackend() + { + backend = (IStreamingBackend)BackendLoader.Backends.First(x => x.ProtocolKey == RealKey); + } + + public ListSortingBackend(string url, Dictionary opts) + { + backend = (IStreamingBackend)BackendLoader.GetBackend(RealKey + url.Substring(ProtocolKey.Length), opts); + } + + public Task CreateFolderAsync(CancellationToken cancellationToken) + => backend.CreateFolderAsync(cancellationToken); + + public Task DeleteAsync(string remotename, CancellationToken cancellationToken) + => backend.DeleteAsync(remotename, cancellationToken); + public void Dispose() + => backend.Dispose(); + public Task GetAsync(string remotename, Stream stream, CancellationToken cancelToken) + => backend.GetAsync(remotename, stream, cancelToken); + public Task GetAsync(string remotename, string filename, CancellationToken cancellationToken) + => backend.GetAsync(remotename, filename, cancellationToken); + public Task GetDNSNamesAsync(CancellationToken cancelToken) + => GetDNSNamesAsync(cancelToken); + public IAsyncEnumerable ListAsync(CancellationToken cancellationToken) + => Sorter(backend.ListAsync(cancellationToken).ToBlockingEnumerable()).ToAsyncEnumerable(); + + public Task PutAsync(string remotename, Stream stream, CancellationToken cancelToken) + => backend.PutAsync(remotename, stream, cancelToken); + + public Task PutAsync(string remotename, string filename, CancellationToken cancellationToken) + => backend.PutAsync(remotename, filename, cancellationToken); + + public Task TestAsync(CancellationToken cancellationToken) + => backend.TestAsync(cancellationToken); + } + + [Test] + [Category("Targeted")] + public void TestDuplicatedBlocksInOrphanIndex() + { + var testopts = TestOptions; + testopts.Add("no-encryption", "true"); + + var opts = new Library.Main.Options(testopts); + + // Make a backup + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + var blockfilename = Directory.EnumerateFiles(TARGETFOLDER, $"*.dblock.{opts.CompressionModule}").First(); + var indexfilename = Directory.EnumerateFiles(TARGETFOLDER, $"*.dindex.{opts.CompressionModule}").First(); + + (string indexfile, string blockfile) CreateDuplicatedFiles() + { + // Create a copy of the block file + var newblockfilename = Path.Combine(TARGETFOLDER, Library.Main.Volumes.VolumeBase.GenerateFilename(Library.Main.RemoteVolumeType.Blocks, opts.Prefix, Library.Main.Volumes.VolumeWriterBase.GenerateGuid(), DateTime.Now, opts.CompressionModule, opts.EncryptionModule)); + if (File.Exists(newblockfilename)) + File.Delete(newblockfilename); + File.Copy(blockfilename, newblockfilename); + + // Create a duplicated index file + var newindexfilename = Path.Combine(TARGETFOLDER, Library.Main.Volumes.VolumeBase.GenerateFilename(Library.Main.RemoteVolumeType.Index, opts.Prefix, Library.Main.Volumes.VolumeWriterBase.GenerateGuid(), DateTime.Now, opts.CompressionModule, opts.EncryptionModule)); + if (File.Exists(newindexfilename)) + File.Delete(newindexfilename); + + using (var writer = new Library.Main.Volumes.IndexVolumeWriter(opts)) + using (var fs = File.OpenRead(indexfilename)) + using (var cmp = CompressionLoader.GetModule(opts.CompressionModule, fs, ArchiveMode.Read, testopts)) + using (var reader = new Library.Main.Volumes.IndexVolumeReader(cmp, opts, opts.BlockhashSize)) + { + foreach (var v in reader.Volumes) + { + writer.StartVolume(v.Filename == Path.GetFileName(blockfilename) ? Path.GetFileName(newblockfilename) : v.Filename); + foreach (var b in v.Blocks) + writer.AddBlock(b.Key, b.Value); + writer.FinishVolume(v.Hash, v.Length); + } + + foreach (var bl in reader.BlockLists) + writer.WriteBlocklist(bl.Hash, bl.Data); + + writer.Close(); + File.Copy(writer.LocalFilename, newindexfilename); + } + + return (newindexfilename, newblockfilename); + } + + var set1 = CreateDuplicatedFiles(); + var set2 = CreateDuplicatedFiles(); + + File.Delete(set1.blockfile); + File.Delete(set2.blockfile); + + // Make sure the index files are returned in this order + var requiredOrder = new[] { + Path.GetFileName(set1.indexfile), + Path.GetFileName(set2.indexfile), + Path.GetFileName(indexfilename), + }; + + ListSortingBackend.RealKey = "file"; + ListSortingBackend.Sorter = files => + { + var lst = files.ToList(); + var entries = requiredOrder.Select(x => lst.First(y => y.Name == x)).ToList(); + return lst.Except(entries).Concat(entries).ToList(); + }; + BackendLoader.AddBackend(new ListSortingBackend()); + + // Delete the local database, and recreate + File.Delete(DBFILE); + using (var c = new Library.Main.Controller(ListSortingBackend.Key + "://" + TARGETFOLDER, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + Assert.AreEqual(0, repairResults.Errors.Count()); + Assert.AreEqual(3, repairResults.Warnings.Count()); + } + + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IListResults listResults = c.List(); + Assert.AreEqual(0, listResults.Errors.Count()); + Assert.AreEqual(0, listResults.Warnings.Count()); + Assert.AreEqual(listResults.Filesets.Count(), 1); + } + } + + [Test] + [Category("Targeted")] + public void TestReplicatedBlocksInOrphanIndex() + { + var replicas = 4; + var goodExtras = 1; + var testopts = TestOptions; + testopts.Add("no-encryption", "true"); + + var opts = new Library.Main.Options(testopts); + + // Make a backup + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + var blockfilename = Directory.EnumerateFiles(TARGETFOLDER, $"*.dblock.{opts.CompressionModule}").First(); + var indexfilename = Directory.EnumerateFiles(TARGETFOLDER, $"*.dindex.{opts.CompressionModule}").First(); + + (string indexfile, string blockfile) CreateDuplicatedFiles() + { + // Create a copy of the block file + var newblockfilename = Path.Combine(TARGETFOLDER, Library.Main.Volumes.VolumeBase.GenerateFilename(Library.Main.RemoteVolumeType.Blocks, opts.Prefix, Library.Main.Volumes.VolumeWriterBase.GenerateGuid(), DateTime.Now, opts.CompressionModule, opts.EncryptionModule)); + if (File.Exists(newblockfilename)) + File.Delete(newblockfilename); + File.Copy(blockfilename, newblockfilename); + + // Create a duplicated index file + var newindexfilename = Path.Combine(TARGETFOLDER, Library.Main.Volumes.VolumeBase.GenerateFilename(Library.Main.RemoteVolumeType.Index, opts.Prefix, Library.Main.Volumes.VolumeWriterBase.GenerateGuid(), DateTime.Now, opts.CompressionModule, opts.EncryptionModule)); + if (File.Exists(newindexfilename)) + File.Delete(newindexfilename); + + using (var writer = new Library.Main.Volumes.IndexVolumeWriter(opts)) + using (var fs = File.OpenRead(indexfilename)) + using (var cmp = CompressionLoader.GetModule(opts.CompressionModule, fs, ArchiveMode.Read, testopts)) + using (var reader = new Library.Main.Volumes.IndexVolumeReader(cmp, opts, opts.BlockhashSize)) + { + foreach (var v in reader.Volumes) + { + writer.StartVolume(v.Filename == Path.GetFileName(blockfilename) ? Path.GetFileName(newblockfilename) : v.Filename); + foreach (var b in v.Blocks) + writer.AddBlock(b.Key, b.Value); + writer.FinishVolume(v.Hash, v.Length); + } + + foreach (var bl in reader.BlockLists) + writer.WriteBlocklist(bl.Hash, bl.Data); + + writer.Close(); + File.Copy(writer.LocalFilename, newindexfilename); + } + + return (newindexfilename, newblockfilename); + } + + var sets = Enumerable.Range(0, replicas).Select(x => CreateDuplicatedFiles()).ToList(); + sets.Skip(goodExtras).ToList().ForEach(x => File.Delete(x.blockfile)); + + // Make sure the index files that point to non-existing blocks are returned first + var mustBeLastOrder = sets.Take(goodExtras).Select(x => Path.GetFileName(x.indexfile)) + .Append(Path.GetFileName(indexfilename)) + .ToList(); + + ListSortingBackend.RealKey = "file"; + ListSortingBackend.Sorter = files => + { + var lst = files.ToList(); + var entries = mustBeLastOrder.Select(x => lst.First(y => y.Name == x)).ToList(); + return lst.Except(entries).Concat(entries).ToList(); + }; + BackendLoader.AddBackend(new ListSortingBackend()); + + // Delete the local database, and recreate + File.Delete(DBFILE); + using (var c = new Library.Main.Controller(ListSortingBackend.Key + "://" + TARGETFOLDER, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + Assert.AreEqual(0, repairResults.Errors.Count()); + Assert.AreEqual(1 + replicas - goodExtras, repairResults.Warnings.Count()); + } + + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IListResults listResults = c.List(); + Assert.AreEqual(0, listResults.Errors.Count()); + Assert.AreEqual(0, listResults.Warnings.Count()); + Assert.AreEqual(1, listResults.Filesets.Count()); + } + } + } +} + diff --git a/Duplicati/UnitTest/IssueTests.cs b/Duplicati/UnitTest/IssueTests.cs new file mode 100644 index 000000000..b20ccac24 --- /dev/null +++ b/Duplicati/UnitTest/IssueTests.cs @@ -0,0 +1,886 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.DynamicLoader; +using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Cryptography; +using System.Security.Principal; +using System.Threading; + +namespace Duplicati.UnitTest +{ + public class IssueTests : BasicSetupHelper + { + + [Test] + [Category("Targeted")] + public void Issue5023ReferencedFileMissing([Values] bool compactBeforeRecreate) + { + // Reproduction for part of issue #5023 + // Error during repair: "Remote file referenced as x by y, but not found in list, registering a missing remote file" + // Can be caused by interrupted index upload followed by compact before the repair + + var testopts = new Dictionary(TestOptions) + { + ["number-of-retries"] = "0", + ["dblock-size"] = "20KB", + ["threshold"] = "1", + ["keep-versions"] = "1", + ["no-auto-compact"] = "true", + ["no-encryption"] = "true" + }; + + const long filesize = 1024; + + // First backup OK to create valid database + string target = "file://" + TARGETFOLDER; + string file1 = Path.Combine(DATAFOLDER, "f1"); + TestUtils.WriteTestFile(file1, filesize); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + // Sleep to ensure timestamps are different + Thread.Sleep(1000); + // Fail after index file put + target = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + string interruptedName = ""; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + // Fail dindex upload + if (action == DeterministicErrorBackend.BackendAction.PutAfter && remotename.Contains("dindex")) + { + interruptedName = remotename; + return true; + } + return false; + }; + BackendLoader.AddBackend(new DeterministicErrorBackend()); + string file2 = Path.Combine(DATAFOLDER, "f2"); + TestUtils.WriteTestFile(file2, filesize); + var uploadEx = Assert.Catch(() => + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + } + }); + + while (uploadEx is AggregateException && uploadEx.InnerException is not null) + uploadEx = uploadEx.InnerException; + + Assert.That(uploadEx, Is.TypeOf()); + + Console.WriteLine("Interrupted after upload of {0}", interruptedName); + // Sleep to ensure timestamps are different + Thread.Sleep(1000); + // Complete upload + target = "file://" + TARGETFOLDER; + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + // At this point there are two index files for the last dblock + Console.WriteLine("Target folder contents (expect extra index file):"); + Console.WriteLine(string.Join("\n", from v in Directory.EnumerateFiles(TARGETFOLDER) select Path.GetFileName(v))); + + // Note: If there is a recreate here (between the above extra file creation and the following compact), + // the bug does not occur as the extra index file will be recorded in the database and properly deleted by compact + + if (compactBeforeRecreate) + { + // Make room for a backup + Thread.Sleep(2000); + + // Delete f2 and compact + File.Delete(file2); + using (var c = new Controller(target, testopts, null)) + { + var backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + ICompactResults compactResults = c.Compact(); + TestUtils.AssertResults(compactResults); + Assert.Greater(compactResults.DeletedFileCount, 0); + } + // At this point there are two index files for the last dblock + Console.WriteLine("Target folder contents (expect extra index file):"); + Console.WriteLine(string.Join("\n", from v in Directory.EnumerateFiles(TARGETFOLDER) select Path.GetFileName(v))); + } + + // Database recreate should work (fails after compact) + File.Delete(DBFILE); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + } + + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue5825RestoreNoOverwrite([Values] bool legacy, [Values] bool local_blocks) + { + // Reproduction of Issue #5825 + // The logic in the previous version was to create a timestamped version of the file being restored to, if it already exists. + // It appears the new restore flow is not correctly doing the same. + // See forum thread: https://forum.duplicati.com/t/save-different-versions-with-timestamp-in-file-name-broken/19805 + + var testopts = new Dictionary(TestOptions) + { + ["restore-legacy"] = legacy.ToString().ToLower(), + ["restore-with-local-blocks"] = local_blocks.ToString().ToLower() + }; + int test_filesize = 1024; + + // TODO check if the original files are untocuched + + var original_dir = Path.Combine(DATAFOLDER, "some_original_dir"); + Directory.CreateDirectory(original_dir); + string f0 = Path.Combine(original_dir, "some_file"); + TestUtils.WriteTestFile(f0, test_filesize); + + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + // Attempt to restore the file + testopts["restore-path"] = RESTOREFOLDER; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); + } + + // Verify that the files are equal + string f1 = Path.Combine(RESTOREFOLDER, "some_file"); + Assert.That(File.ReadAllBytes(f0), Is.EqualTo(File.ReadAllBytes(f1)), "Restored file should be equal to original file"); + + // Modify the restored file + TestUtils.WriteTestFile(f1, test_filesize); + + // Restore the file again, with overwrite. + testopts["overwrite"] = "true"; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); + } + + // Verify that the files are equal + Assert.That(File.ReadAllBytes(f0), Is.EqualTo(File.ReadAllBytes(f1)), "Restored file should be equal to original file"); + + // Save the timestamp of the file + var timestamp = File.GetLastWriteTime(f1); + + // Touch the file + File.SetLastWriteTime(f1, DateTime.Now); + + // Check that the timestamp has changed, but the file is still equal + Assert.That(File.GetLastWriteTime(f1), Is.Not.EqualTo(timestamp), "Timestamp should have changed"); + Assert.That(File.ReadAllBytes(f0), Is.EqualTo(File.ReadAllBytes(f1)), "Restored file should be equal to original file"); + + // Restore the file again, with overwrite, should restore the timestamp of the file + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "File should not have been restored, only the metadata."); + } + + // Verify that the files are equal and the timestamp is restored + Assert.That(File.ReadAllBytes(f0), Is.EqualTo(File.ReadAllBytes(f1)), "Restored file should be equal to original file"); + Assert.That(File.GetLastWriteTime(f1), Is.EqualTo(timestamp), "Timestamp should be restored"); + + // Modify the restored file + TestUtils.WriteTestFile(f1, test_filesize); + var f1_original = File.ReadAllBytes(f1); + + // Restore the file again, without overwrite. + testopts["overwrite"] = "false"; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); + } + + // Verify that f1 is still the same + Assert.That(File.ReadAllBytes(f1), Is.EqualTo(f1_original), "The first restored file should remain untouched"); + + // Verify that there exists a new file with a timestamp + var files = Directory.GetFiles(RESTOREFOLDER, "*", SearchOption.TopDirectoryOnly); + Assert.That(files.Length, Is.EqualTo(2), "There should be two files in the folder"); + var f2 = files.FirstOrDefault(v => v != f1); + + // Modify the new restored file as well + TestUtils.WriteTestFile(f2, test_filesize); + var f2_original = File.ReadAllBytes(f2); + + // Restore the file again, without overwrite. + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); + } + + // Verify that f1 and f2 are still the same + Assert.That(File.ReadAllBytes(f1), Is.EqualTo(f1_original), "The first restored file should remain untouched"); + Assert.That(File.ReadAllBytes(f2), Is.EqualTo(f2_original), "The second restored file should remain untouched"); + + // Verify that there exists a new file with a timestamp + files = Directory.GetFiles(RESTOREFOLDER, "*", SearchOption.TopDirectoryOnly); + Assert.That(files.Length, Is.EqualTo(3), "There should be three files in the folder"); + + // Verify that the files are equal + var f3 = files.FirstOrDefault(v => v != f1 && v != f2); + Assert.That(File.ReadAllBytes(f0), Is.EqualTo(File.ReadAllBytes(f3)), "Restored file should be equal to original file"); + + // Modify the second file to match the original file - should not restore any files + File.WriteAllBytes(f2, File.ReadAllBytes(f0)); + + // Restore the file again, without overwrite. + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f0]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "File should not have been restored"); + } + + // Verify that f1 is still untouched + Assert.That(File.ReadAllBytes(f1), Is.EqualTo(f1_original), "The first restored file should remain untouched"); + } + + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue5826RestoreMissingFolder([Values] bool compressRestorePaths, [Values] bool restoreLegacy) + { + // Reproduction of Issue #5826 + // With the new restore engine, it looks like a file will not be restored if the folder is not selected for restore. + // See forum issue for details: https://forum.duplicati.com/t/could-not-find-a-part-of-the-path/19775 + + var testopts = new Dictionary(TestOptions) + { + ["dont-compress-restore-paths"] = (!compressRestorePaths).ToString().ToLower(), + ["restore-legacy"] = restoreLegacy.ToString().ToLower() + }; + + var original_dir = Path.Combine(DATAFOLDER, "some_original_dir"); + Directory.CreateDirectory(original_dir); + string f = Path.Combine(original_dir, "some_file"); + TestUtils.WriteTestFile(f, 1024 * 20); + + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + // Attempt to restore a file without selecting its parent folder + testopts["restore-path"] = RESTOREFOLDER; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); + Assert.That(restoreResults.Warnings.Count(), Is.EqualTo(compressRestorePaths ? 0 : 1), "Warning should be generated for missing folder"); + } + } + + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue5886RestoreModifiedMiddleBlock() + { + var blocksize = 1024; + var n_blocks = 5; + var testopts = new Dictionary(TestOptions) + { + ["blocksize"] = $"{blocksize}b", + ["overwrite"] = "true" + }; + + var original_dir = Path.Combine(DATAFOLDER, "some_original_dir"); + Directory.CreateDirectory(original_dir); + string f = Path.Combine(original_dir, "some_file"); + TestUtils.WriteTestFile(f, n_blocks * blocksize); + + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + var original_contents = File.ReadAllBytes(f); + var new_block = new byte[blocksize]; + new Random(5888).NextBytes(new_block); + var new_contents = new byte[n_blocks * blocksize]; + + for (int i = 0; i < n_blocks; i++) + { + for (int j = i; j < n_blocks; j++) + { + // Modify the blocks + for (int k = 0; k < n_blocks; k++) + { + if (k == i || k == j) + { + Array.Copy(new_block, 0, new_contents, k * blocksize, blocksize); + } + else + { + Array.Copy(original_contents, k * blocksize, new_contents, k * blocksize, blocksize); + } + } + + // Write the modified file + File.WriteAllBytes(f, new_contents); + var restored_contents = File.ReadAllBytes(f); + Assert.That(restored_contents, Is.Not.EqualTo(original_contents), "Restored file should not be equal to original file"); + + // Attempt to restore the file + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); + } + + // Verify that the files are equal + restored_contents = File.ReadAllBytes(f); + Assert.That(restored_contents, Is.EqualTo(original_contents), "Restored file should be equal to original file"); + } + } + } + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue5957RestoreModifiedBlockUseLocalBlocks([Values] bool use_local, [Values] bool overwrite) + { + var blocksize = 1024; + var testopts = new Dictionary(TestOptions) + { + ["blocksize"] = $"{blocksize}b", + ["restore-with-local-blocks"] = use_local.ToString().ToLower(), + ["overwrite"] = overwrite.ToString().ToLower() + }; + + var original_dir = Path.Combine(DATAFOLDER, "some_original_dir"); + Directory.CreateDirectory(original_dir); + string f = Path.Combine(original_dir, "some_file"); + TestUtils.WriteTestFile(f, 3 * blocksize); + + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + var original_contents = File.ReadAllBytes(f); + + using (var stream = File.Open(f, FileMode.Open, FileAccess.ReadWrite)) + { + stream.Seek(0, SeekOrigin.Begin); + byte[] buffer = new byte[1]; + stream.Read(buffer, 0, 1); + buffer[0] = (byte)~buffer[0]; + stream.Seek(0, SeekOrigin.Begin); + stream.Write(buffer, 0, 1); + } + + var restored_contents = File.ReadAllBytes(f); + Assert.That(restored_contents, Is.Not.EqualTo(original_contents), "Restored file should not be equal to original file"); + + // Attempt to restore the file + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([f]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); + } + + // Verify that the files are equal + // List files in folder + if (overwrite) + { + restored_contents = File.ReadAllBytes(f); + } + else + { + var files = Directory.GetFiles(original_dir, "*", SearchOption.TopDirectoryOnly); + Assert.That(files.Length, Is.EqualTo(2), "There should be two files in the folder"); + var f2 = files.FirstOrDefault(v => v != f); + restored_contents = File.ReadAllBytes(f2); + } + Assert.That(restored_contents, Is.EqualTo(original_contents), "Restored file should be equal to original file"); + } + + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue6068FolderMetadata([Values] bool restoreLegacy) + { + // Reproduction of Issue #6068 + // The folder metadata is not restored + + var testopts = new Dictionary(TestOptions); + + var original_dir_name = "some_original_dir"; + var original_dir = Path.Combine(DATAFOLDER, original_dir_name); + var restored_dir = Path.Combine(RESTOREFOLDER, original_dir_name); + Directory.CreateDirectory(original_dir); + + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + // Sleep to ensure timestamps are different + System.Threading.Tasks.Task.Delay(1000).Wait(); + + // Attempt to restore to another folder + testopts["restore-path"] = RESTOREFOLDER; + testopts["restore-legacy"] = restoreLegacy.ToString().ToLower(); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([]); + Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "No files should have been restored."); + } + + // Assert that the original folders date modified and date created are the same as the restored folder + var original_dir_info = new DirectoryInfo(original_dir); + var restored_dir_info = new DirectoryInfo(restored_dir); + Assert.That(original_dir_info.CreationTime, Is.EqualTo(restored_dir_info.CreationTime), "Creation time should be equal"); + Assert.That(original_dir_info.LastWriteTime, Is.EqualTo(restored_dir_info.LastWriteTime), "Last write time should be equal"); + } + + + [Test] + [Category("Restore"), Category("Bug")] + public void Issue6068FileAndFolderAttributesAndPermissions([Values] bool restorePermissions, [Values] bool restoreLegacy, [Values] bool skip_metadata) + { + // This test is to verify that permissions are restored correctly + // if the restore-permissions option is set, and that the + // attributes are restored regardless. Some discussion is in the + // related PR #6079 + + // Set the options according to the test parameters + var testopts = new Dictionary(TestOptions) + { + ["restore-permissions"] = restorePermissions.ToString().ToLower(), + ["restore-legacy"] = restoreLegacy.ToString().ToLower(), + ["skip-metadata"] = skip_metadata.ToString().ToLower() + }; + + // The attributes to test + var attrs = new FileAttributes[] { + FileAttributes.ReadOnly, + FileAttributes.Hidden, + FileAttributes.System, + // TODO The Archive attribute has been disabled for now, as the CI doesn't behave as expected, but it works locally. + //FileAttributes.Archive + }; + + // Create the generated directories and files + var original_dir = Path.Combine(DATAFOLDER, "some_original_dir"); + + var dirs = attrs + .Select(x => Path.Combine(original_dir, $"dir_{x}")) + .Prepend(original_dir) + .ToArray(); + + foreach (var dir in dirs) + Directory.CreateDirectory(dir); + + foreach (var attr in attrs) + foreach (var dir in dirs) + { + var file = Path.Combine(dir, $"file_{attr}"); + TestUtils.WriteTestFile(file, 1024); + File.SetAttributes(file, attr); + } + + foreach (var (attr, dir) in attrs.Zip(dirs.Skip(1))) + _ = new DirectoryInfo(dir) + { + Attributes = attr + }; + + // Create OS specific files and directories + var os_special_dir = Path.Combine(original_dir, "os_special_dir"); + Directory.CreateDirectory(os_special_dir); + var default_dir_attrs = new DirectoryInfo(os_special_dir).Attributes; + var os_special_file = Path.Combine(os_special_dir, "os_special_file"); + TestUtils.WriteTestFile(os_special_file, 1024); + var default_file_attrs = File.GetAttributes(os_special_file); + + if (OperatingSystem.IsWindows()) + { + // Set only the current user to have access to both the file and the directory + var dir_info = new DirectoryInfo(os_special_dir); + var rules_dir = dir_info.GetAccessControl(); + rules_dir.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + rules_dir.AddAccessRule(new FileSystemAccessRule(Environment.UserName, FileSystemRights.FullControl, AccessControlType.Allow)); + rules_dir.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.ReadAndExecute, AccessControlType.Allow)); + dir_info.SetAccessControl(rules_dir); + + var file_info = new FileInfo(os_special_file); + var rules_file = file_info.GetAccessControl(); + rules_file.AddAccessRule(new FileSystemAccessRule(Environment.UserName, FileSystemRights.FullControl, AccessControlType.Allow)); + rules_file.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.ReadAndExecute, AccessControlType.Allow)); + file_info.SetAccessControl(rules_file); + } + else // Mac and Linux + { + // Set the dir to 700 and the file to 600 + _ = new DirectoryInfo(os_special_dir) + { + UnixFileMode = UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite + }; + _ = new FileInfo(os_special_file) + { + UnixFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite + }; + } + + try + { + // Backup the files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + IBackupResults backupResults = c.Backup([DATAFOLDER]); + TestUtils.AssertResults(backupResults); + } + + // Restore the files + testopts["restore-path"] = RESTOREFOLDER; + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var restoreResults = c.Restore([]); + } + + // Check the folder attributes + foreach (var dir in dirs.Skip(1)) + { + var restored_dir = dir.Replace(DATAFOLDER, RESTOREFOLDER); + var original_attrs = new DirectoryInfo(dir).Attributes; + var restored_attrs = new DirectoryInfo(restored_dir).Attributes; + if (skip_metadata && original_attrs != default_dir_attrs) + Assert.That(original_attrs, Is.Not.EqualTo(restored_attrs), $"Directory attributes should not be equal for directory: {dir}"); + else + Assert.That(original_attrs, Is.EqualTo(restored_attrs), $"Directory attributes should be equal for directory: {dir}"); + } + + // Check the file attributes + foreach (var attr in attrs) + foreach (var dir in dirs) + { + var filename = $"file_{attr}"; + var original_file = Path.Combine(dir, filename); + var restored_file = original_file.Replace(DATAFOLDER, RESTOREFOLDER); + // TODO we ignore the Archive attribute, as the CI sets this sporadically + var original_attrs = File.GetAttributes(original_file); + var restored_attrs = File.GetAttributes(restored_file); + if (skip_metadata && original_attrs != default_file_attrs) + Assert.That(original_attrs & ~FileAttributes.Archive, Is.Not.EqualTo(restored_attrs & ~FileAttributes.Archive), $"File attributes should not be equal for original file: {original_file}"); + else + Assert.That(original_attrs & ~FileAttributes.Archive, Is.EqualTo(restored_attrs & ~FileAttributes.Archive), $"File attributes should be equal for original file: {original_file}"); + } + + // Check the OS specific file and directory + if (OperatingSystem.IsWindows()) + { + var original_dir_info = new DirectoryInfo(os_special_dir); + var restored_dir_info = new DirectoryInfo(os_special_dir.Replace(DATAFOLDER, RESTOREFOLDER)); + var original_dir_rules = original_dir_info.GetAccessControl().GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + var restored_dir_rules = restored_dir_info.GetAccessControl().GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + + // Disable only on Windows warning, as the if ensures this. +#pragma warning disable CA1416 + static bool cmp_elem(FileSystemAccessRule a, FileSystemAccessRule b) => + a.IdentityReference.Value == b.IdentityReference.Value && + a.FileSystemRights == b.FileSystemRights && + a.AccessControlType == b.AccessControlType; +#pragma warning restore CA1416 + + static bool cmp_coll(AuthorizationRuleCollection a, AuthorizationRuleCollection b) => + a.Count == b.Count && + a.Cast().Zip(b.Cast(), cmp_elem).All(x => x); + + if (skip_metadata && original_dir_info.Attributes != default_dir_attrs) + Assert.That(original_dir_info.Attributes, Is.Not.EqualTo(restored_dir_info.Attributes), "Directory attributes should not be equal"); + else + Assert.That(original_dir_info.Attributes, Is.EqualTo(restored_dir_info.Attributes), "Directory attributes should be equal"); + + var dir_permissions_equal = cmp_coll(original_dir_rules, restored_dir_rules); + if (restorePermissions && !skip_metadata) + Assert.That(dir_permissions_equal, Is.True, "Directory permissions should be equal"); + else + Assert.That(dir_permissions_equal, Is.False, "Directory permissions should not be equal"); + + var original_file_info = new FileInfo(os_special_file); + var restored_file_info = new FileInfo(os_special_file.Replace(DATAFOLDER, RESTOREFOLDER)); + var original_file_rules = original_file_info.GetAccessControl().GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + var restored_file_rules = restored_file_info.GetAccessControl().GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + + if (skip_metadata && original_file_info.Attributes != default_file_attrs) + Assert.That(original_file_info.Attributes, Is.Not.EqualTo(restored_file_info.Attributes), "File attributes should not be equal"); + else + Assert.That(original_file_info.Attributes, Is.EqualTo(restored_file_info.Attributes), "File attributes should be equal"); + + var file_permissions_equal = cmp_coll(original_file_rules, restored_file_rules); + if (restorePermissions && !skip_metadata) + Assert.That(file_permissions_equal, Is.True, "File permissions should be equal"); + else + Assert.That(file_permissions_equal, Is.False, "File permissions should not be equal"); + } + else + { + var original_dir_info = new DirectoryInfo(os_special_dir); + var restored_dir_info = new DirectoryInfo(os_special_dir.Replace(DATAFOLDER, RESTOREFOLDER)); + + if (skip_metadata && original_dir_info.Attributes != default_dir_attrs) + Assert.That(original_dir_info.Attributes, Is.Not.EqualTo(restored_dir_info.Attributes), "Directory attributes should not be equal"); + else + Assert.That(original_dir_info.Attributes, Is.EqualTo(restored_dir_info.Attributes), "Directory attributes should be equal"); + + if (restorePermissions && !skip_metadata) + Assert.That(original_dir_info.UnixFileMode, Is.EqualTo(restored_dir_info.UnixFileMode), "Directory permissions should be equal"); + else + Assert.That(original_dir_info.UnixFileMode, Is.Not.EqualTo(restored_dir_info.UnixFileMode), "Directory permissions should not be equal"); + + var original_file_info = new FileInfo(os_special_file); + var restored_file_info = new FileInfo(os_special_file.Replace(DATAFOLDER, RESTOREFOLDER)); + + if (skip_metadata && original_file_info.Attributes != default_file_attrs) + Assert.That(original_file_info.Attributes, Is.Not.EqualTo(restored_file_info.Attributes), "File attributes should not be equal"); + else + Assert.That(original_file_info.Attributes, Is.EqualTo(restored_file_info.Attributes), "File attributes should be equal"); + + if (restorePermissions && !skip_metadata) + Assert.That(original_file_info.UnixFileMode, Is.EqualTo(restored_file_info.UnixFileMode), "File permissions should be equal"); + else + Assert.That(original_file_info.UnixFileMode, Is.Not.EqualTo(restored_file_info.UnixFileMode), "File permissions should not be equal"); + } + } + finally + { + // Revert the attributes so the cleanup can proceed + foreach (var dir in dirs) + { + _ = new DirectoryInfo(dir) + { + Attributes = FileAttributes.Normal | FileAttributes.Directory + }; + var restored_dir = dir.Replace(DATAFOLDER, RESTOREFOLDER); + if (Directory.Exists(restored_dir)) + _ = new DirectoryInfo(restored_dir) + { + Attributes = FileAttributes.Normal | FileAttributes.Directory + }; + } + + foreach (var attr in attrs) + foreach (var dir in dirs) + { + var file = Path.Combine(dir, $"file_{attr}"); + File.SetAttributes(file, FileAttributes.Normal); + var restored_file = file.Replace(DATAFOLDER, RESTOREFOLDER); + if (File.Exists(restored_file)) + File.SetAttributes(restored_file, FileAttributes.Normal); + } + + // Special dirs can be ignored, as they should only block other + // users from accessing the files + } + } + + + [Test] + [Category("Disruption"), Category("Bug")] + public void TestSystematicErrors5023() + { + // Attempt to recreate other bugs from #5023, but not successful + var testopts = new Dictionary(TestOptions) + { + ["number-of-retries"] = "0", + ["dblock-size"] = "20KB", + ["threshold"] = "1", + ["keep-versions"] = "5", + ["no-encryption"] = "true", + ["disable-synthetic-filelist"] = "true" + }; + //testopts["rebuild-missing-dblock-files"] = "true"; + string target = "file://" + TARGETFOLDER; + string targetError = new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER; + int maxFiles = 10; + List files = new List(); + bool failed = false; + long accessCounter = 0; + long errorIdx = 0; + DeterministicErrorBackend.ErrorGenerator = (DeterministicErrorBackend.BackendAction action, string remotename) => + { + ++accessCounter; + if (accessCounter >= errorIdx) + { + return true; + } + return false; + }; + for (int i = 0; i < maxFiles; ++i) + { + string f = Path.Combine(DATAFOLDER, "f" + i); + TestUtils.WriteTestFile(f, 1024 * 20); + files.Add(f); + } + // Initial backup + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + while (errorIdx < (maxFiles + 2)) + { + if (errorIdx % 10 == 0) + { + TestContext.WriteLine("Error index {0}", errorIdx); + } + accessCounter = 0; + try + { + using (var c = new Library.Main.Controller(targetError, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + } + catch (AssertionException) { throw; } + catch { } + Thread.Sleep(1000); + try + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + } + catch (UserInformationException e) + { + TestContext.WriteLine("Error at index {0}: {1}", errorIdx, e.Message); + if (e.HelpID == "MissingRemoteFiles" || e.HelpID == "ExtraRemoteFiles") + { + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + } + failed = true; + } + Thread.Sleep(1000); + foreach (string f in files) + { + TestUtils.WriteTestFile(f, 1024 * 20); + } + ++errorIdx; + } + TestContext.WriteLine("Ran {0} iterations", errorIdx); + Assert.IsFalse(failed); + } + + [Test, Sequential] + [Category("Targeted"), Category("Bug"), Category("Non-critical")] + [TestCase(false, true), TestCase(true, true), TestCase(true, false)] + public void Issue5038MissingListBlocklist(bool sameVersion, bool blockFirst) + { + // Backup containing the blocklist of a file BEFORE the file causes a dindex with missing blocklist entry + // This is not critical, because it only requires extra block volume downloads + var testopts = new Dictionary(TestOptions) + { + ["no-encryption"] = "true" + }; + + string filename = Path.Combine(DATAFOLDER, "file"); + // Start with z to process blockfile after file (at least on some systems) + string blockfile = Path.Combine(DATAFOLDER, blockFirst ? "block" : "zblock"); + string target = "file://" + TARGETFOLDER; + + byte[] block1 = new byte[10 * 1024]; + for (int i = 0; i < block1.Length; ++i) + { + block1[i] = 1; + } + byte[] block2 = new byte[10 * 1024]; + for (int i = 0; i < block1.Length; ++i) + { + block1[i] = 2; + } + + HashAlgorithm blockhasher = Library.Utility.HashFactory.CreateHasher(new Options(testopts).BlockHashAlgorithm); + + var hash1 = blockhasher.ComputeHash(block1, 0, block1.Length); + var hash2 = blockhasher.ComputeHash(block2, 0, block2.Length); + + byte[] blockfileContent = hash1.Concat(hash2).ToArray(); + TestUtils.WriteFile(blockfile, blockfileContent); + if (!sameVersion) + { + // Backup blockfile first + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + } + + byte[] combined = block1.Concat(block2).ToArray(); + TestUtils.WriteFile(filename, combined); + // Backup file that would produce blockfile + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + // Recreate database downloads block volume + File.Delete(DBFILE); + using (var c = new Library.Main.Controller(target, testopts, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + Assert.IsNull(repairResults.Messages.FirstOrDefault(v => v.Contains("ProcessingRequiredBlocklistVolumes")), + "Blocklist download pass was required"); + } + } + } +} diff --git a/Duplicati/UnitTest/JSONSignatureTest.cs b/Duplicati/UnitTest/JSONSignatureTest.cs index 81b4b2aad..7521a0415 100644 --- a/Duplicati/UnitTest/JSONSignatureTest.cs +++ b/Duplicati/UnitTest/JSONSignatureTest.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.IO; using System.Linq; using System.Security.Cryptography; diff --git a/Duplicati/UnitTest/ProblematicPathTests.cs b/Duplicati/UnitTest/ProblematicPathTests.cs index c96ecfd23..0403f0da7 100644 --- a/Duplicati/UnitTest/ProblematicPathTests.cs +++ b/Duplicati/UnitTest/ProblematicPathTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/PurgeTesting.cs b/Duplicati/UnitTest/PurgeTesting.cs index cd0eb7e70..bbbc54c5c 100644 --- a/Duplicati/UnitTest/PurgeTesting.cs +++ b/Duplicati/UnitTest/PurgeTesting.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/UnitTest/RandomErrorBackend.cs b/Duplicati/UnitTest/RandomErrorBackend.cs index 5ede95c8a..e520c937b 100644 --- a/Duplicati/UnitTest/RandomErrorBackend.cs +++ b/Duplicati/UnitTest/RandomErrorBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -72,9 +72,9 @@ namespace Duplicati.UnitTest #endregion #region IBackend implementation - public IEnumerable List() + public IAsyncEnumerable ListAsync(CancellationToken cancellationToken) { - return m_backend.List(); + return m_backend.ListAsync(cancellationToken); } public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) { diff --git a/Duplicati/UnitTest/RecoveryToolTests.cs b/Duplicati/UnitTest/RecoveryToolTests.cs index 53889b1d5..8c1da1241 100644 --- a/Duplicati/UnitTest/RecoveryToolTests.cs +++ b/Duplicati/UnitTest/RecoveryToolTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -260,5 +260,40 @@ namespace Duplicati.UnitTest TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, restoreFolder, false, "Verifying restore using RecoveryTool."); } + + [Test] + [Category("RecoveryTool")] + [TestCase(false)] + [TestCase(true)] + public void Recompress(bool noEncryption) + { + // Files to create in MB. + int[] fileSizes = { 10, 20, 30 }; + foreach (int size in fileSizes) + { + byte[] data = new byte[size * 1024 * 1024]; + Random rng = new Random(); + rng.NextBytes(data); + File.WriteAllBytes(Path.Combine(this.DATAFOLDER, size + "MB"), data); + } + // Run a backup. + Dictionary options = new Dictionary(this.TestOptions) + { + ["no-encryption"] = noEncryption.ToString() + }; + string backendURL = "file://" + this.TARGETFOLDER; + using (Controller c = new Controller(backendURL, options, null)) + { + IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + // Recompress. + string downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles"); + Directory.CreateDirectory(downloadFolder); + int status = CommandLine.RecoveryTool.Program.Main(new[] { "recompress", "zip", $"{backendURL}", this.RESTOREFOLDER, $"--passphrase={options["passphrase"]}" }); + Assert.AreEqual(0, status); + } + } } \ No newline at end of file diff --git a/Duplicati/UnitTest/RepairHandlerTests.cs b/Duplicati/UnitTest/RepairHandlerTests.cs index 70ac010f1..4f07d119b 100644 --- a/Duplicati/UnitTest/RepairHandlerTests.cs +++ b/Duplicati/UnitTest/RepairHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,6 +24,7 @@ using System.Data; using System.IO; using System.IO.Compression; using System.Linq; +using System.Threading; using Duplicati.Library.Interface; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; @@ -43,6 +44,11 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(this.DATAFOLDER, "file"), new byte[] { 0 }); } + public void ModifyFile() + { + File.WriteAllBytes(Path.Combine(this.DATAFOLDER, "file"), new byte[] { 1 }); + } + [Test] [Category("RepairHandler")] public void RepairMissingBlocklistHashes() @@ -384,5 +390,227 @@ namespace Duplicati.UnitTest Assert.AreEqual(5, backupResults.Warnings.Count()); } } + + [Test] + [Category("RepairHandler")] + [TestCase("true")] + [TestCase("false")] + public void RepairExtraIndexFiles(string noEncryption) + { + // Extra index files will be added to the database and should have a correct link established + Dictionary options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption }; + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + TestUtils.AssertResults(backupResults); + } + + string[] dindexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*dindex*").ToArray(); + Assert.Greater(dindexFiles.Length, 0); + string origFile = dindexFiles.First(); + // Duplicate index file + string dupFile = Path.Combine(TARGETFOLDER, Path.GetFileName(origFile).Replace(".dindex", "1.dindex")); + File.Copy(origFile, dupFile); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + } + + // Check database block link + using (LocalDatabase db = new LocalDatabase(DBFILE, "Test", true)) + { + long indexVolumeId = db.GetRemoteVolumeID(Path.GetFileName(origFile)); + long duplicateVolumeId = db.GetRemoteVolumeID(Path.GetFileName(dupFile)); + Assert.AreNotEqual(-1, indexVolumeId); + Assert.AreNotEqual(-1, duplicateVolumeId); + + using (var cmd = db.Connection.CreateCommand()) + { + string sql = @"SELECT ""BlockVolumeID"" FROM ""IndexBlockLink"" WHERE ""IndexVolumeID"" = ?"; + long linkedIndexId = cmd.ExecuteScalarInt64(sql, -1, indexVolumeId); + long linkedDuplicateId = cmd.ExecuteScalarInt64(sql, -1, duplicateVolumeId); + Assert.AreEqual(linkedIndexId, linkedDuplicateId); + } + } + } + + [Test] + [Category("RepairHandler")] + public void RepairMissingDlistFile() + { + // Make two backups + var options = this.TestOptions; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + ModifyFile(); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Find and delete a dlist file + var dlistFile = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").First(); + File.Delete(dlistFile); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); + } + } + + [Test] + [Category("RepairHandler")] + [TestCase("true")] + [TestCase("false")] + public void RepairMissingDlistVolume(bool deleteRemoteFile) + { + // Make two backups + var options = this.TestOptions; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + ModifyFile(); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Find a dlist file + var dlistFile = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").First(); + if (deleteRemoteFile) + File.Delete(dlistFile); + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + Assert.AreEqual(1, cmd.ExecuteNonQuery("DELETE FROM RemoteVolume WHERE Name = ?", Path.GetFileName(dlistFile))); + + // Should catch this in validation + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); + Assert.AreEqual(2, c.List().Filesets.Count()); + } + + // Check that the entry was recreated + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + Assert.AreEqual(2, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM RemoteVolume WHERE Name LIKE '%.dlist.%' AND State != ?", -1, RemoteVolumeState.Deleted.ToString())); + + // Delete the database and check that the result is correct + File.Delete(options["dbpath"]); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + IRepairResults repairResults = c.Repair(); + TestUtils.AssertResults(repairResults); + Assert.AreEqual(2, c.List().Filesets.Count()); + + var r = c.Test(long.MaxValue); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + Assert.IsFalse(r.Verifications.Any(p => p.Value.Any())); + } + } + + [Test] + [Category("RepairHandler")] + public void RepairMissingFilesetVolume() + { + // Make two backups + var options = this.TestOptions; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Make room for a new backup + Thread.Sleep(2000); + + ModifyFile(); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Remove a fileset + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + { + var filesetId = cmd.ExecuteScalarInt64("SELECT Id FROM Fileset ORDER BY Id DESC LIMIT 1"); + Assert.AreEqual(1, cmd.ExecuteNonQuery("DELETE FROM Fileset WHERE Id = ?", filesetId)); + cmd.ExecuteNonQuery("DELETE FROM FilesetEntry WHERE FilesetId = ?", filesetId); + } + + // Should catch this in validation + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + + // Since we have removed the entry that is being checked here, we need to disable the check + var repairOptions = new Dictionary(options) { ["repair-ignore-outdated-database"] = "true" }; + using (Controller c = new Controller("file://" + this.TARGETFOLDER, repairOptions, null)) + { + IRepairResults repairResults = c.Repair(); + Assert.AreEqual(0, repairResults.Errors.Count()); + Assert.AreEqual(0, repairResults.Warnings.Where(x => x.IndexOf("RemoteFilesNewerThanLocalDatabase", StringComparison.OrdinalIgnoreCase) < 0).Count()); + Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); + Assert.AreEqual(2, c.List().Filesets.Count()); + } + + // Check that entry was recreated + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + Assert.AreEqual(2, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM Fileset")); + } + + [Test] + [Category("RepairHandler")] + public void ManufactureMissingFiles() + { + // Make two backups + var options = this.TestOptions; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Make room for a new backup + Thread.Sleep(2000); + + ModifyFile(); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + + // Remove a fileset + long filesetEntries; + long fileLookupEntries; + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + { + filesetEntries = cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FilesetEntry"); + fileLookupEntries = cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FileLookup"); + var fileId = cmd.ExecuteScalarInt64("SELECT FileId FROM FilesetEntry INNER JOIN FileLookup ON FilesetEntry.FileID = FileLookup.Id WHERE FileLookup.BlocksetID != -100 ORDER BY FilesetId DESC LIMIT 1"); + Assert.AreEqual(1, cmd.ExecuteNonQuery("DELETE FROM FileLookup WHERE Id = ?", fileId)); + } + + // Should catch this in validation + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + + using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + var repairResults = c.Repair(); + Assert.AreEqual(0, repairResults.Errors.Count()); + Assert.AreEqual(0, repairResults.Warnings.Where(x => x.IndexOf("RemoteFilesNewerThanLocalDatabase", StringComparison.OrdinalIgnoreCase) < 0).Count()); + Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); + Assert.AreEqual(2, c.List().Filesets.Count()); + } + + // Check that entry was recreated + using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var cmd = con.CreateCommand()) + { + Assert.AreEqual(filesetEntries, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FilesetEntry")); + Assert.AreEqual(fileLookupEntries, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FileLookup")); + } + } } -} +} \ No newline at end of file diff --git a/Duplicati/UnitTest/RestoreHandlerTests.cs b/Duplicati/UnitTest/RestoreHandlerTests.cs index b62510ac0..e381db79d 100644 --- a/Duplicati/UnitTest/RestoreHandlerTests.cs +++ b/Duplicati/UnitTest/RestoreHandlerTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -86,7 +86,8 @@ namespace Duplicati.UnitTest { IRestoreResults restoreResults = c.Restore(new[] { filePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); - Assert.AreEqual(0, restoreResults.Warnings.Count()); + // TODO The expected warning is expected, as the 'dont-compress-restore-paths' option results in a warning about a folder not being created before restoring a file. + Assert.AreEqual(1, restoreResults.Warnings.Count()); } // We need to strip the root part of the path. Otherwise, Path.Combine will simply return the second argument diff --git a/Duplicati/UnitTest/ResultFormatSerializerProviderTest.cs b/Duplicati/UnitTest/ResultFormatSerializerProviderTest.cs index 16143eba8..97b6e5f5b 100644 --- a/Duplicati/UnitTest/ResultFormatSerializerProviderTest.cs +++ b/Duplicati/UnitTest/ResultFormatSerializerProviderTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/UnitTest/RunScriptTests.cs b/Duplicati/UnitTest/RunScriptTests.cs index 3c74f8e96..dfa9c86d1 100644 --- a/Duplicati/UnitTest/RunScriptTests.cs +++ b/Duplicati/UnitTest/RunScriptTests.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index 73a6e9dc0..44a8a589d 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -27,6 +27,7 @@ using Duplicati.Library.Logging; using Duplicati.Library.Utility; using System.Linq; using Duplicati.Library.Common.IO; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -173,7 +174,7 @@ namespace Duplicati.UnitTest try { using (var bk = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(target, options)) - foreach (var f in bk.List()) + foreach (var f in bk.ListAsync(System.Threading.CancellationToken.None).ToBlockingEnumerable()) if (!f.IsFolder) Utility.Await(bk.DeleteAsync(f.Name, System.Threading.CancellationToken.None)); } @@ -215,7 +216,7 @@ namespace Duplicati.UnitTest Duplicati.Library.Main.Options opts = new Duplicati.Library.Main.Options(options); using (Duplicati.Library.Interface.IBackend bk = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(target, options)) - foreach (Duplicati.Library.Interface.IFileEntry fe in bk.List()) + foreach (Duplicati.Library.Interface.IFileEntry fe in bk.ListAsync(System.Threading.CancellationToken.None).ToBlockingEnumerable()) if (fe.Size > opts.VolumeSize) { string msg = string.Format("The file {0} is {1} bytes larger than allowed", fe.Name, fe.Size - opts.VolumeSize); @@ -406,6 +407,13 @@ namespace Duplicati.UnitTest restoredname = System.IO.Path.Combine(System.IO.Path.Combine(tempfolder, System.IO.Path.GetFileName(rootfolder.Split(System.IO.Path.PathSeparator)[int.Parse(s.Substring(0, six))])), s.Substring(six + 1)); } + if (!System.IO.File.Exists(sourcename)) + { + Log.WriteErrorMessage(LOGTAG, "PartialRestoreMissingFile", null, "Partial restore, missing SOURCE file: {0}", sourcename); + BasicSetupHelper.ProgressWriteLine("Partial restore missing file: " + sourcename); + throw new Exception("Unittest is broken"); + } + if (!System.IO.File.Exists(restoredname)) { Log.WriteErrorMessage(LOGTAG, "PartialRestoreMissingFile", null, "Partial restore missing file: {0}", restoredname); @@ -413,13 +421,6 @@ namespace Duplicati.UnitTest } else { - if (!System.IO.File.Exists(sourcename)) - { - Log.WriteErrorMessage(LOGTAG, "PartialRestoreMissingFile", null, "Partial restore missing file: {0}", sourcename); - BasicSetupHelper.ProgressWriteLine("Partial restore missing file: " + sourcename); - throw new Exception("Unittest is broken"); - } - TestUtils.AssertFilesAreEqual(sourcename, restoredname, verifymetadata, $"Partial restore file differs: {s}"); } } diff --git a/Duplicati/UnitTest/SecretProviderHelperTests.cs b/Duplicati/UnitTest/SecretProviderHelperTests.cs new file mode 100644 index 000000000..ccc80805b --- /dev/null +++ b/Duplicati/UnitTest/SecretProviderHelperTests.cs @@ -0,0 +1,404 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using Duplicati.Library.Utility; +using NUnit.Framework; + +namespace Duplicati.UnitTest; + +public class SecretProviderHelperTests : BasicSetupHelper +{ + private class MockedSecretProvider : ISecretProvider + { + public string Key => "mock"; + + public string DisplayName => ""; + + public string Description => ""; + + public Dictionary Secrets { get; set; } = new(); + + public IList SupportedCommands => []; + + public bool ThrowOnInit { get; set; } + + public Task InitializeAsync(System.Uri config, CancellationToken cancellationToken) + { + if (ThrowOnInit) + throw new InvalidOperationException("Initialization failed"); + + return Task.CompletedTask; + } + + public Task> ResolveSecretsAsync(IEnumerable keys, CancellationToken cancellationToken) + { + var result = new Dictionary(); + foreach (var key in keys) + { + if (!Secrets.TryGetValue(key, out var value)) + throw new KeyNotFoundException($"The key '{key}' was not found"); + + result[key] = value; + } + + return Task.FromResult(result); + } + } + + [Test] + [Category("SecretHelper")] + public void ReplaceSecretsWithDefaultSettings() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", "$key2"}, + {"key3", "value3$key"}, + }; + + var argsSys = new[] { + new System.Uri("test://host/?pass=$key2&user=$key1&other=123") + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=$key2&user=$key1&other=123") + }; + + SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await(); + + Assert.AreEqual("value1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + Assert.AreEqual("value3$key", settings["key3"]); + Assert.AreEqual("test://host/?pass=secret2&user=secret1&other=123", argsSys[0].ToString()); + Assert.AreEqual("test://host?pass=secret2&user=secret1&other=123", argsInternal[0].ToString()); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceSecretsWithSlashKeys() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key/with/slash"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", "$key/with/slash"}, + {"key3", "value3$key"}, + }; + + var argsSys = new[] { + new System.Uri("test://host/?pass=$key/with/slash&user=$key1&other=123") + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=$key/with/slash&user=$key1&other=123") + }; + + SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await(); + + Assert.AreEqual("value1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + Assert.AreEqual("value3$key", settings["key3"]); + Assert.AreEqual("test://host/?pass=secret2&user=secret1&other=123", argsSys[0].ToString()); + Assert.AreEqual("test://host?pass=secret2&user=secret1&other=123", argsInternal[0].ToString()); + } + + [Test] + [Category("SecretHelper")] + public void ReplacesSecretsWithUrlEscaping() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret 1%&+abc"; + + var settings = new Dictionary + { + {"key1", "$key1"} + }; + + var argsSys = new[] { + new System.Uri("test://host/?pass=$key1&user=user") + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=$key1&user=user") + }; + + SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await(); + + // Check no escaping is done for options + Assert.AreEqual("secret 1%&+abc", settings["key1"]); + + // Check escaping is done for the URL + Assert.AreEqual("test://host/?pass=secret+1%25%26%2Babc&user=user".ToLowerInvariant(), argsSys[0].ToString().ToLowerInvariant()); + Assert.AreEqual("test://host?pass=secret+1%25%26%2Babc&user=user", argsInternal[0].ToString()); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceSecretsWithExtendedPattern() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", "${key2}"}, + {"key3", "value3${key}"}, + {"secret-provider-pattern", "${}"}, + }; + + var argsSys = new[] { + new System.Uri("test://host/?pass=${key2}&user=${key1}&other=123"), + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=${key2}&user=${key1}&other=123"), + }; + + SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await(); + + Assert.AreEqual("value1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + Assert.AreEqual("value3${key}", settings["key3"]); + Assert.AreEqual("test://host/?pass=secret2&user=secret1&other=123", argsSys[0].ToString()); + Assert.AreEqual("test://host?pass=secret2&user=secret1&other=123", argsInternal[0].ToString()); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceSecretsWithExtendedLongPattern() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", ":sec{key2}"}, + {"key3", "value3:sec{key}"}, + {"secret-provider-pattern", ":sec{}"}, + }; + + var argsSys = new[] { + new System.Uri("test://host/?pass=:sec{key2}&user=:sec{key1}&other=123"), + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=:sec{key2}&user=:sec{key1}&other=123"), + }; + + SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await(); + + Assert.AreEqual("value1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + Assert.AreEqual("value3:sec{key}", settings["key3"]); + Assert.AreEqual("test://host/?pass=secret2&user=secret1&other=123", argsSys[0].ToString()); + Assert.AreEqual("test://host?pass=secret2&user=secret1&other=123", argsInternal[0].ToString()); + } + + [Test] + [Category("SecretHelper")] + public void CachedProviderRetainsPattern() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["sec1"] = "secret1"; + + var settings = new Dictionary + { + {"key1", "!ext{sec1}"} + }; + + var cachedProvider = SecretProviderHelper.WrapWithCache("", secretProvider, SecretProviderHelper.CachingLevel.InMemory, null, "salt", "!ext{}"); + cachedProvider.InitializeAsync(new System.Uri("mock://"), CancellationToken.None).Await(); + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + Assert.AreEqual("secret1", settings["key1"]); + + settings["key1"] = "!ext{sec1}"; + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + Assert.AreEqual("secret1", settings["key1"]); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceFailsIfKeyIsMissing() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", "$key2"}, + }; + + var argsSys = new[] { + new System.Uri("test://host?pass=$key2&user=$key1&other=123"), + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=$key2&user=$key1&other=123"), + }; + + Assert.Throws(() => SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await()); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceFailsIfValueIsEmpty() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = ""; + + var settings = new Dictionary + { + {"key1", "value1"}, + {"key2", "$key2"}, + }; + + var argsSys = new[] { + new System.Uri("test://host?pass=$key2&user=$key1&other=123"), + }; + + var argsInternal = new[] { + new Library.Utility.Uri("test://host?pass=$key2&user=$key1&other=123"), + }; + + Assert.Throws(() => SecretProviderHelper.ApplySecretProviderAsync(argsSys, argsInternal, settings, null, secretProvider, CancellationToken.None).Await()); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceUsesInMemoryCache() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "$key1"}, + {"key2", "$key2"} + }; + + var cachedProvider = SecretProviderHelper.WrapWithCache("", secretProvider, SecretProviderHelper.CachingLevel.InMemory, null, "salt", null); + cachedProvider.InitializeAsync(new System.Uri("mock://"), CancellationToken.None).Await(); + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + secretProvider.Secrets = null; + + settings["key1"] = "$key1"; + settings["key2"] = "$key2"; + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + Assert.AreEqual("secret1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceUsesPersistedCache() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "$key1"}, + {"key2", "$key2"} + }; + + using var tempFolder = new TempFolder(); + var cachedProvider = SecretProviderHelper.WrapWithCache("", secretProvider, SecretProviderHelper.CachingLevel.Persistent, tempFolder, "salt", null); + cachedProvider.InitializeAsync(new System.Uri("mock://"), CancellationToken.None).Await(); + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + secretProvider.Secrets = null; + + settings["key1"] = "$key1"; + settings["key2"] = "$key2"; + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + Assert.AreEqual("secret1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + } + + [Test] + [Category("SecretHelper")] + public void ReplaceUsesPersistedCacheOnRestart() + { + var secretProvider = new MockedSecretProvider(); + secretProvider.Secrets["key1"] = "secret1"; + secretProvider.Secrets["key2"] = "secret2"; + + var settings = new Dictionary + { + {"key1", "$key1"}, + {"key2", "$key2"} + }; + + using var tempFolder = new TempFolder(); + var cachedProvider = SecretProviderHelper.WrapWithCache("", secretProvider, SecretProviderHelper.CachingLevel.Persistent, tempFolder, "salt", null); + cachedProvider.InitializeAsync(new System.Uri("mock://"), CancellationToken.None).Await(); + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + settings["key1"] = "$key1"; + settings["key2"] = "$key2"; + + // Create a new instance of the provider that is unavailable + cachedProvider = SecretProviderHelper.WrapWithCache("", secretProvider, SecretProviderHelper.CachingLevel.Persistent, tempFolder, "salt", null); + secretProvider.Secrets = null; + secretProvider.ThrowOnInit = true; + + cachedProvider.InitializeAsync(new System.Uri("mock://"), CancellationToken.None).Await(); + + SecretProviderHelper.ApplySecretProviderAsync([], [], settings, null, cachedProvider, CancellationToken.None).Await(); + + Assert.AreEqual("secret1", settings["key1"]); + Assert.AreEqual("secret2", settings["key2"]); + } + +} diff --git a/Duplicati/UnitTest/SizeOmittingBackend.cs b/Duplicati/UnitTest/SizeOmittingBackend.cs index 679afa0ce..a1a464a91 100644 --- a/Duplicati/UnitTest/SizeOmittingBackend.cs +++ b/Duplicati/UnitTest/SizeOmittingBackend.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -59,10 +59,10 @@ namespace Duplicati.UnitTest #endregion #region IBackend implementation - public IEnumerable List() + public IAsyncEnumerable ListAsync(CancellationToken cancellationToken) { return - from n in m_backend.List() + from n in m_backend.ListAsync(cancellationToken) where !n.IsFolder select new FileEntry(n.Name); } diff --git a/Duplicati/UnitTest/SymLinkTests.cs b/Duplicati/UnitTest/SymLinkTests.cs index 7fa533e76..bd7e5f7a0 100755 --- a/Duplicati/UnitTest/SymLinkTests.cs +++ b/Duplicati/UnitTest/SymLinkTests.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; @@ -32,6 +32,68 @@ namespace Duplicati.UnitTest [TestFixture] public class SymLinkTests : BasicSetupHelper { + [Test] + [Category("SymLink")] + public void SymlinkExists() + { + // Create symlink target directory + const string targetDirName = "target"; + var targetDir = systemIO.PathCombine(this.DATAFOLDER, targetDirName); + systemIO.DirectoryCreate(targetDir); + // Create files in symlink target directory + var fileNames = new[] { "a.txt", "b.txt", "c.txt" }; + foreach (var file in fileNames) + { + var targetFile = systemIO.PathCombine(targetDir, file); + TestUtils.WriteFile(targetFile, Encoding.Default.GetBytes(file)); + } + + // Create actual symlink directory linking to the target directory + const string symlinkDirName = "symlink"; + var symlinkDir = systemIO.PathCombine(this.DATAFOLDER, symlinkDirName); + try + { + systemIO.CreateSymlink(symlinkDir, targetDir, asDir: true); + } + catch (Exception e) + { + // If client cannot create symlinks, mark test as ignored + Assert.Ignore($"Client could not create a symbolic link. Error reported: {e.Message}"); + } + + // Backup all files + Dictionary restoreOptions = new(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; + using (Controller c = new("file://" + this.TARGETFOLDER, this.TestOptions, null)) + { + IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + // Restore all files + using (Controller c = new("file://" + this.TARGETFOLDER, restoreOptions, null)) + { + IRestoreResults restoreResults = c.Restore(null); + Assert.AreEqual(0, restoreResults.Errors.Count()); + Assert.AreEqual(0, restoreResults.Warnings.Count()); + + // Verify that symlink was restored + var restoreSymlinkDir = systemIO.PathCombine(this.RESTOREFOLDER, symlinkDirName); + Assert.That(systemIO.IsSymlink(restoreSymlinkDir), Is.True); + var restoredSymlinkFullPath = systemIO.PathGetFullPath(systemIO.GetSymlinkTarget(restoreSymlinkDir)); + var symlinkTargetFullPath = systemIO.PathGetFullPath(targetDir); + Assert.That(restoredSymlinkFullPath, Is.EqualTo(symlinkTargetFullPath)); + } + + // Restore again, trying to overwrite existing symlink + using (Controller c = new("file://" + this.TARGETFOLDER, restoreOptions, null)) + { + IRestoreResults restoreResults = c.Restore(null); + Assert.AreEqual(0, restoreResults.Errors.Count()); + Assert.AreEqual(0, restoreResults.Warnings.Count()); + } + } + [Test] [Category("SymLink")] [TestCase(Options.SymlinkStrategy.Store)] diff --git a/Duplicati/UnitTest/TestUtils.cs b/Duplicati/UnitTest/TestUtils.cs index be05077ce..b5e1e5e98 100644 --- a/Duplicati/UnitTest/TestUtils.cs +++ b/Duplicati/UnitTest/TestUtils.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,24 +24,17 @@ using System.IO; using System.Collections.Generic; using Duplicati.Library.Utility; using System.Linq; -using Duplicati.Library.Logging; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Common.IO; using NUnit.Framework; -using System.Runtime.InteropServices; -using Duplicati.Library.Common; +using Duplicati.Library.Interface; namespace Duplicati.UnitTest { public static class TestUtils { - /// - /// The log tag - /// - private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(typeof(TestUtils)); - public static Dictionary DefaultOptions { get @@ -51,7 +44,7 @@ namespace Duplicati.UnitTest string auth_password = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "unittest_authpassword.txt"); if (System.IO.File.Exists(auth_password)) opts["auth-password"] = File.ReadAllText(auth_password).Trim(); - + return opts; } } @@ -89,7 +82,7 @@ namespace Duplicati.UnitTest else if (other != null) return other; else - using(var tf = new Library.Utility.TempFolder()) + using (var tf = new Library.Utility.TempFolder()) { tf.Protected = true; return "file://" + tf; @@ -118,41 +111,41 @@ namespace Duplicati.UnitTest if (!Directory.Exists(t)) Directory.CreateDirectory(t); - + try { Directory.SetCreationTimeUtc(t, Directory.GetCreationTimeUtc(c)); } - catch(Exception ex) - { + catch (Exception ex) + { if (timestampfailures++ < 20) - Console.WriteLine("Failed to set creation time on dir {0}: {1}", t, ex.Message); + Console.WriteLine("Failed to set creation time on dir {0}: {1}", t, ex.Message); } try { Directory.SetLastWriteTimeUtc(t, Directory.GetLastWriteTimeUtc(c)); } - catch(Exception ex) - { + catch (Exception ex) + { if (timestampfailures++ < 20) - Console.WriteLine("Failed to set write time on dir {0}: {1}", t, ex.Message); + Console.WriteLine("Failed to set write time on dir {0}: {1}", t, ex.Message); } - - foreach(var n in Directory.EnumerateFiles(c)) + + foreach (var n in Directory.EnumerateFiles(c)) { var tf = Path.Combine(t, Path.GetFileName(n)); File.Copy(n, tf, true); try { File.SetCreationTimeUtc(tf, System.IO.File.GetCreationTimeUtc(n)); } - catch(Exception ex) + catch (Exception ex) { if (timestampfailures++ < 20) - Console.WriteLine("Failed to set creation time on file {0}: {1}", n, ex.Message); + Console.WriteLine("Failed to set creation time on file {0}: {1}", n, ex.Message); } try { File.SetLastWriteTimeUtc(tf, System.IO.File.GetLastWriteTimeUtc(n)); } - catch(Exception ex) + catch (Exception ex) { if (timestampfailures++ < 20) - Console.WriteLine("Failed to set write time on file {0}: {1}", n, ex.Message); + Console.WriteLine("Failed to set write time on file {0}: {1}", n, ex.Message); } } - foreach(var n in Directory.EnumerateDirectories(c)) + foreach (var n in Directory.EnumerateDirectories(c)) work.Enqueue(n); } @@ -160,22 +153,6 @@ namespace Duplicati.UnitTest Console.WriteLine("Encountered additional {0} timestamp errors!", timestampfailures); } - /// - /// Returns the index of a given string, using the file system case sensitivity - /// - /// The index of the entry or -1 if no entry was found - /// The list to search - /// The string to find - private static int IndexOf(List lst, string m) - { - StringComparison sc = Duplicati.Library.Utility.Utility.ClientFilenameStringComparison; - for(int i = 0; i < lst.Count; i++) - if (lst[i].Equals(m, sc)) - return i; - - return -1; - } - /// /// Asserts that the two directory trees are equivalent; i.e., /// that they they contain the same directories and files, recursively. @@ -268,14 +245,14 @@ namespace Duplicati.UnitTest public static Dictionary Expand(this Dictionary self, object extra) { var res = new Dictionary(self); - foreach(var n in extra.GetType().GetFields()) + foreach (var n in extra.GetType().GetFields()) { var name = n.Name.Replace('_', '-'); var value = n.GetValue(extra); res[name] = value == null ? "" : value.ToString(); } - foreach(var n in extra.GetType().GetProperties()) + foreach (var n in extra.GetType().GetProperties()) { var name = n.Name.Replace('_', '-'); var value = n.GetValue(extra); @@ -295,6 +272,29 @@ namespace Duplicati.UnitTest Utility.CopyStream(new MemoryStream(contents), fileStream); } } + + public static void WriteTestFile(string path, long size) + { + var data = new byte[size]; + new Random(path.GetHashCode()).NextBytes(data); + File.WriteAllBytes(path, data); + } + + public static void AssertResults(IBasicResults results) + { + string operation = "Result"; + // Use dynamic property access for MainOperation, because it is only exposed in internal classes + PropertyInfo operationProperty = results.GetType().GetProperty("MainOperation", typeof(Library.Main.OperationMode)); + if (operationProperty != null) + { + operation = ((Library.Main.OperationMode)operationProperty.GetValue(results)).ToString(); + } + Assert.AreEqual(0, results.Errors.Count(), "{0} errors:\n{1}", operation, string.Join("\n", results.Errors)); + Assert.AreEqual(0, results.Warnings.Count(), "{0} warnings:\n{1}", operation, string.Join("\n", results.Warnings)); + + if (results is ITestResults testResults) + Assert.IsFalse(testResults.Verifications.Any(p => p.Value.Any()), "{0} verifications:\n{1}", operation, string.Join("\n", testResults.Verifications.Select(p => string.Join("\n", p.Value)))); + } } } diff --git a/Duplicati/UnitTest/TimeZoneHelperTests.cs b/Duplicati/UnitTest/TimeZoneHelperTests.cs new file mode 100644 index 000000000..996490a63 --- /dev/null +++ b/Duplicati/UnitTest/TimeZoneHelperTests.cs @@ -0,0 +1,184 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Linq; +using Duplicati.Library.Utility; +using NUnit.Framework; + +namespace Duplicati.UnitTest; + +public class TimeZoneHelperTests : BasicSetupHelper +{ + private static string CETName = OperatingSystem.IsWindows() + // For some reason CET is not recognized on Windows + // It has no effect outside the tests, as the string is not + // present outside the tests + ? "Central European Standard Time" + : "CET"; + + [Test] + [Category("TimeZoneHelper")] + public void GetTimeZoneFromAbbreviationTest() + { + var timeZone = TimeZoneHelper.FindTimeZone("EST"); + Assert.NotNull(timeZone); + + timeZone = TimeZoneHelper.FindTimeZone("Pacific Standard Time"); + Assert.NotNull(timeZone); + + timeZone = TimeZoneHelper.FindTimeZone(CETName); + Assert.NotNull(timeZone); + } + + [Test] + [Category("TimeZoneHelper")] + public void GetTimeZonesTest() + { + var timeZones = TimeZoneHelper.GetTimeZones(); + Assert.That(timeZones.Any(x => x.CurrentUtcOffset.Hours == -5)); + Assert.That(timeZones.Any(x => x.CurrentUtcOffset.Hours == -8)); + Assert.That(timeZones.Any(x => x.CurrentUtcOffset.Hours == 1)); + Assert.That(timeZones.Any(x => x.CurrentUtcOffset.Hours == 2)); + } + + [Test] + [Category("TimeZoneHelper")] + public void CheckTestSystemTimeZonesAreCorrect() + { + var timeZone = TimeZoneHelper.FindTimeZone(CETName); + var localTime = new DateTimeOffset(2024, 3, 31, 1, 59, 59, timeZone.BaseUtcOffset); + + // Check that the system time is correct + Assert.AreEqual(1, timeZone.GetUtcOffset(localTime).Hours); + Assert.AreEqual(2, timeZone.GetUtcOffset(localTime.AddSeconds(2)).Hours); + } + + [Test] + [Category("TimeZoneHelper")] + public void CheckAddIsStableOverDSTForward() + { + var timeZone = TimeZoneHelper.FindTimeZone(CETName); + var localTime = new DateTimeOffset(2024, 3, 31, 1, 59, 59, timeZone.BaseUtcOffset); + + var utcTime = localTime.ToUniversalTime().DateTime; + + var adjustedTimeSeconds = timeZone.DSTAwareAddSeconds(utcTime, 2); + var adjustedTimeHours = timeZone.DSTAwareAddHours(utcTime, 1); + var adjustedTimeDays = timeZone.DSTAwareAddDays(utcTime, 1); + Assert.AreEqual(utcTime.AddHours(-1).AddSeconds(2), adjustedTimeSeconds); + Assert.AreEqual(utcTime.AddHours(-1).AddHours(1), adjustedTimeHours); + Assert.AreEqual(utcTime.AddHours(-1).AddDays(1), adjustedTimeDays); + } + + [Test] + [Category("TimeZoneHelper")] + public void CheckAddIsStableOverDSTBackward() + { + var timeZone = TimeZoneHelper.FindTimeZone(CETName); + var dstOffset = timeZone.GetUtcOffset(new DateTimeOffset(2024, 10, 26, 14, 0, 0, timeZone.BaseUtcOffset)); + var localTime = new DateTimeOffset(2024, 10, 27, 2, 59, 59, dstOffset); + + var utcTime = localTime.ToUniversalTime().DateTime; + + var adjustedTimeSeconds = timeZone.DSTAwareAddSeconds(utcTime, 2); + var adjustedTimeHours = timeZone.DSTAwareAddHours(utcTime, 1); + var adjustedTimeDays = timeZone.DSTAwareAddDays(utcTime, 1); + Assert.AreEqual(utcTime.AddHours(1).AddSeconds(2), adjustedTimeSeconds); + Assert.AreEqual(utcTime.AddHours(1).AddHours(1), adjustedTimeHours); + Assert.AreEqual(utcTime.AddHours(1).AddDays(1), adjustedTimeDays); + } + + [Test] + [Category("TimeZoneHelper")] + public void CheckRepeatScheduleIsStableOverDSTForward() + { + var timeZone = TimeZoneHelper.FindTimeZone(CETName); + var localTime = new DateTimeOffset(2024, 3, 30, 14, 0, 0, timeZone.BaseUtcOffset); + var localTimeOfDay = localTime.TimeOfDay; + + var utcTime = localTime.ToUniversalTime().DateTime; + + var adjustedTimeSeconds = Timeparser.DSTAwareParseTimeInterval("2s", utcTime, timeZone, false); + var adjustedTimeHours = Timeparser.DSTAwareParseTimeInterval("1h", utcTime, timeZone, false); + var adjustedTimeDays = Timeparser.DSTAwareParseTimeInterval("1D", utcTime, timeZone, true); + + // Non-day-based times are not corrected for DST + Assert.AreEqual(utcTime.AddSeconds(2), adjustedTimeSeconds); + Assert.AreEqual(utcTime.AddHours(1), adjustedTimeHours); + // Day-based times are corrected for DST + Assert.AreEqual(utcTime.AddDays(1).AddHours(-1), adjustedTimeDays); + + // Since we adjust and keep the time of day, the time should be the same + var adjustedLocalTimeDays = TimeZoneInfo.ConvertTimeFromUtc(adjustedTimeDays, timeZone); + var adjustedLocalTimeOfDay = adjustedLocalTimeDays.TimeOfDay; + Assert.AreEqual(localTimeOfDay, adjustedLocalTimeOfDay); + + var origTimeSeconds = timeZone.DSTAwareAddSeconds(adjustedTimeSeconds, -2); + var origTimeHours = timeZone.DSTAwareAddHours(adjustedTimeHours, -1); + var origTimeDays = timeZone.DSTAwareAddDays(adjustedTimeDays, -1); + Assert.AreEqual(utcTime, origTimeSeconds); + Assert.AreEqual(utcTime, origTimeHours); + Assert.AreEqual(utcTime, origTimeDays); + + // Since we adjust and keep the time of day, the time should be the same, even when going back + adjustedLocalTimeDays = TimeZoneInfo.ConvertTimeFromUtc(adjustedTimeDays, timeZone); + adjustedLocalTimeOfDay = adjustedLocalTimeDays.TimeOfDay; + Assert.AreEqual(localTimeOfDay, adjustedLocalTimeOfDay); + } + + [Test] + [Category("TimeZoneHelper")] + public void CheckRepeatScheduleIsStableOverDSTBackward() + { + var timeZone = TimeZoneHelper.FindTimeZone(CETName); + var dstOffset = timeZone.GetUtcOffset(new DateTimeOffset(2024, 10, 26, 14, 0, 0, timeZone.BaseUtcOffset)); + var localTime = new DateTimeOffset(2024, 10, 26, 14, 0, 0, dstOffset); + var crossTime = new DateTimeOffset(2024, 10, 27, 2, 59, 59, dstOffset); + var localTimeOfDay = localTime.TimeOfDay; + + var utcTime = localTime.ToUniversalTime().DateTime; + var utcCrossTime = crossTime.ToUniversalTime().DateTime; + + var adjustedTimeSeconds = Timeparser.DSTAwareParseTimeInterval("2s", utcCrossTime, timeZone, false); + var adjustedTimeHours = Timeparser.DSTAwareParseTimeInterval("1h", utcCrossTime, timeZone, false); + var adjustedTimeDays = Timeparser.DSTAwareParseTimeInterval("1D", utcTime, timeZone, true); + + // Non-day-based times are not corrected for DST + Assert.AreEqual(utcCrossTime.AddSeconds(2), adjustedTimeSeconds); + Assert.AreEqual(utcCrossTime.AddHours(1), adjustedTimeHours); + // Day-based times are corrected for DST + Assert.AreEqual(utcTime.AddHours(1).AddDays(1), adjustedTimeDays); + + // Since we adjust and keep the time of day, the time should be the same + var adjustedLocalTimeDays = TimeZoneInfo.ConvertTimeFromUtc(adjustedTimeDays, timeZone); + var adjustedLocalTimeOfDay = adjustedLocalTimeDays.TimeOfDay; + Assert.AreEqual(localTimeOfDay, adjustedLocalTimeOfDay); + + var origTimeDays = timeZone.DSTAwareAddDays(adjustedTimeDays, -1); + Assert.AreEqual(utcTime, origTimeDays); + + // Since we adjust and keep the time of day, the time should be the same, even when going back + adjustedLocalTimeDays = TimeZoneInfo.ConvertTimeFromUtc(adjustedTimeDays, timeZone); + adjustedLocalTimeOfDay = adjustedLocalTimeDays.TimeOfDay; + Assert.AreEqual(localTimeOfDay, adjustedLocalTimeOfDay); + } +} diff --git a/Duplicati/UnitTest/ToolTests.cs b/Duplicati/UnitTest/ToolTests.cs new file mode 100644 index 000000000..e5ec55dad --- /dev/null +++ b/Duplicati/UnitTest/ToolTests.cs @@ -0,0 +1,529 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using NUnit.Framework; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Duplicati.UnitTest +{ + + /// + /// Tests the tools. + /// + public class ToolTests : BasicSetupHelper + { + + /// + /// Tests that the remote synchronization tool doesn't do anything when the dry run option is used. + /// + [Test] + [Category("Tools")] + public void TestDryRun() + { + var l1 = Path.Combine(TARGETFOLDER, "l1"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + + Directory.CreateDirectory(l1); + Directory.CreateDirectory(l2); + + GenerateTestData(l1, 5, 0, 0, 1024).Wait(); + + var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--dry-run" }; + + var async_call = RemoteSynchronization.Program.Main(args); + var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + + Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); + Assert.IsFalse(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are equal"); + Assert.IsTrue(!Directory.EnumerateFiles(l2).Any(), "Destination directory is not empty"); + } + + /// + /// Tests that the remote synchronization tool works with an empty source to an empty destination. + /// + [Test] + [Category("Tools")] + public void TestEmptySourceAndDestination() + { + var l1 = Path.Combine(TARGETFOLDER, "empty_src"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + + Directory.CreateDirectory(l1); + Directory.CreateDirectory(l2); + + var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm" }; + + var async_call = RemoteSynchronization.Program.Main(args); + var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + + Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); + Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); + } + + /// + /// Test that remote synchronizing an empty source to a non-empty destination deletes the destination files. + /// + [Test] + [Category("Tools")] + public void TestEmptySourceDeletesDestination() + { + var l1 = Path.Combine(TARGETFOLDER, "empty_src"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + + Directory.CreateDirectory(l1); + Directory.CreateDirectory(l2); + + GenerateTestData(l2, 5, 0, 0, 1024).Wait(); + + var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm" }; + + var async_call = RemoteSynchronization.Program.Main(args); + var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + + Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); + Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); + } + + /// + /// Test that remote synchronizing an empty source to a non-empty destination renames the destination files when the `--retention` option is used. + /// + [Test] + [Category("Tools")] + public void TestEmptySourceRenamesDestination() + { + var l1 = Path.Combine(TARGETFOLDER, "empty_src"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + + Directory.CreateDirectory(l1); + Directory.CreateDirectory(l2); + + GenerateTestData(l2, 5, 0, 0, 1024).Wait(); + + var filelist = Directory.EnumerateFiles(l2).ToList(); + var files = filelist.Select(x => File.ReadAllBytes(x)).ToList(); + + var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--retention" }; + + var async_call = RemoteSynchronization.Program.Main(args); + var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + + Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); + + var newfilelist = Directory.EnumerateFiles(l2).ToList(); + foreach (var (name, contents) in filelist.Zip(files)) + { + var filename = Path.GetFileName(name); + var newcontents = File.ReadAllBytes(newfilelist.FirstOrDefault(x => x.EndsWith(filename))); + Assert.AreEqual(contents, newcontents, "File contents are not equal"); + } + } + + /// + /// Tests passing all arguments to the main method of the remote synchronization tool. + /// + [Test] + public void TestMainMethodParsesArgumentsCorrectly() + { + string[][] testCases = + { + ["source", "destination", "--parse-arguments-only"], + ["source", "destination", "--parse-arguments-only", "--dry-run"], + ["source", "destination", "--parse-arguments-only", "--force"], + ["source", "destination", "--parse-arguments-only", "--dry-run", "--force"], + ["source", "destination", "--parse-arguments-only", "--verify-contents"], + ["source", "destination", "--parse-arguments-only", "--verify-get-after-put"], + ["source", "destination", "--parse-arguments-only", "--retry", "3"], + ["source", "destination", "--parse-arguments-only", "--log-level", "Debug"], + ["source", "destination", "--parse-arguments-only", "--log-level", "Information"], + ["source", "destination", "--parse-arguments-only", "--log-level", "Profiling"], + ["source", "destination", "--parse-arguments-only", "--log-level", "Verbose"], + ["source", "destination", "--parse-arguments-only", "--log-file", "somefile.log"], + ["source", "destination", "--parse-arguments-only", "--progress"], + ["source", "destination", "--parse-arguments-only", "--retention"], + ["source", "destination", "--parse-arguments-only", "--confirm"], + ["source", "destination", "--parse-arguments-only", "--global-options", "someglobalkey=someglobalvalue", "anotherglobalkey=anotherglobalvalue"], + ["source", "destination", "--parse-arguments-only", "--src-options", "somesrckey=somesrcvalue", "anothersrckey=anothersrcvalue"], + ["source", "destination", "--parse-arguments-only", "--dst-options", "somedstkey=somedstvalue", "anotherdstkey=anotherdstvalue"], + [ + "source", "destination", "--parse-arguments-only", + "--dry-run", "--force", "--verify-contents", "--retry", "3", "--log-level", "Debug", "--log-file", "somefile.log", "--progress", "--retention", "--confirm", + "--global-options", "someglobalkey=someglobalvalue", "anotherglobalkey=anotherglobalvalue", + "--src-options", "somesrckey=somesrcvalue", "anothersrckey=anothersrcvalue", + "--dst-options", "somedstkey=somedstvalue", "anotherdstkey=anotherdstvalue" + ], + }; + + foreach (var args in testCases) + { + int result = RemoteSynchronization.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.AreEqual(0, result, $"Failed for args: {string.Join(" ", args)}"); + } + + int failed_result = RemoteSynchronization.Program.Main(["source", "destination", "--bogus-option"]).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.AreEqual(1, failed_result, "Invalid option did not return 1"); + } + + /// + /// Tests the original inded use of the remote synchronization tool on an empty destination. + /// + [Test] + [Category("Tools")] + public void TestRemoteSynchronization() + { + var l1 = Path.Combine(TARGETFOLDER, "l1"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + var l1r = Path.Combine(RESTOREFOLDER, "l1_restore"); + var l2r = Path.Combine(RESTOREFOLDER, "l2_restore"); + + var options = TestOptions; + + var now = DateTime.Now; + GenerateTestData(DATAFOLDER, 10, 3, 3, 1024 * 1024).Wait(); + Console.WriteLine($"Generated test data in {DATAFOLDER} in {DateTime.Now - now}"); + + // Create the directories if they do not exist + foreach (var p in new string[] { l1, l2, l1r, l2r }) + { + if (!SystemIO.IO_OS.DirectoryExists(p)) + SystemIO.IO_OS.DirectoryCreate(p); + } + + // Backup the first level + using (var c = new Controller($"file://{l1}", options, null)) + { + now = DateTime.Now; + var results = c.Backup([DATAFOLDER]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); + } + + // Call the tool + now = DateTime.Now; + var exe = RemoteSynchronization.Program.Main; + string[] args = [ + $"file://{l1}", $"file://{l2}", + "--global-options", ..options.Select(x => $"{x.Key}={x.Value}"), + // Pass along multi token options to test that the parser won't fail + "--src-options", "ssh-accept-any-fingerprints=true", "ssh-keyfile=/path/to/keyfile", + "--dst-options", "some-other-key=value", "another-key=value2", + "--confirm" + ]; + var async_call = exe(args); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); + + // Verify that the directories are equal + Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); + + // Try to restore the first level + options["restore-path"] = l1r; + using (var c = new Controller($"file://{l1}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([Path.Combine(DATAFOLDER, "*")]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l1r), "Restored first level files is not equal to original files"); + + // Try to restore the second level + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); + + // Delete the l2r directory + SystemIO.IO_OS.DirectoryDelete(l2r, true); + + // Delete one file from the l2 backup + var files = Directory.EnumerateFiles(l2).ToList(); + File.Delete(files.First()); + + // Check that the restore fails + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + try + { + var results = c.Restore([]); + } + catch (RemoteListVerificationException) + { + Console.WriteLine($"Failed (as expected) to restore files to {options["restore-path"]} in {DateTime.Now - now}"); + } + } + + // Run the tool again to copy the missing file + now = DateTime.Now; + async_call = exe(args); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); + + // Try to restore the second level again + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); + + // Add some more files to the source + GenerateTestData(Path.Combine(DATAFOLDER, "brand_new_files"), 5, 2, 2, 1024 * 1024).Wait(); + + // Backup the new files to l1 + using (var c = new Controller($"file://{l1}", options, null)) + { + now = DateTime.Now; + var results = c.Backup([DATAFOLDER]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); + } + + // Run the tool again to copy the new files + now = DateTime.Now; + async_call = exe(args); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); + + // Try to restore the second level again + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); + + // Delete the l2r directory + SystemIO.IO_OS.DirectoryDelete(l2r, true); + + // Remove a directory from the source + SystemIO.IO_OS.DirectoryDelete(Path.Combine(DATAFOLDER, "dir_0"), true); + + // Backup the new files to l1 + using (var c = new Controller($"file://{l1}", options, null)) + { + now = DateTime.Now; + var results = c.Backup([DATAFOLDER]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); + } + + // Compact the backup + using (var c = new Controller($"file://{l1}", options, null)) + { + now = DateTime.Now; + var results = c.Compact(); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Compacted backup in {DateTime.Now - now}"); + } + + // Run the tool again to copy the new files + now = DateTime.Now; + async_call = exe(args); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + + // Try to restore the second level again + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); + + // Delete the l2r directory + SystemIO.IO_OS.DirectoryDelete(l2r, true); + + // Perform a forced synchronization with retention to check that retention doesn't break a restore + now = DateTime.Now; + async_call = exe([.. args, "--force", "--retention"]); + async_call.Wait(); + Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + + // Try to restore the second level again + options["restore-path"] = l2r; + using (var c = new Controller($"file://{l2}", options, null)) + { + now = DateTime.Now; + var results = c.Restore([]); + Assert.AreEqual(0, results.Errors.Count()); + Assert.AreEqual(0, results.Warnings.Count()); + Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); + } + + Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); + } + + /// + /// Tests that the remote synchronization tool verifies the contents of the files. + /// + [Test] + [Category("Tools")] + public void TestVerifies() + { + var l1 = Path.Combine(TARGETFOLDER, "l1"); + var l2 = Path.Combine(TARGETFOLDER, "l2"); + + Directory.CreateDirectory(l1); + Directory.CreateDirectory(l2); + + GenerateTestData(l1, 5, 0, 0, 1024).Wait(); + + var filenames = Directory.EnumerateFiles(l1).Take(2).ToList(); + var first_file = filenames.First(); + var second_file = filenames.Skip(1).First(); + + File.Copy(first_file, Path.Combine(l2, Path.GetFileName(first_file))); + File.Copy(second_file, Path.Combine(l2, Path.GetFileName(second_file))); + + // Touch the first file to give it a different timestamp + File.SetLastWriteTime(first_file, DateTime.Now.AddMinutes(1)); + + // Modify the second file to give it different contents, but the same size + var second_file_contents = File.ReadAllBytes(second_file); + second_file_contents[0] = (byte)(second_file_contents[0] + 1); + File.WriteAllBytes(second_file, second_file_contents); + + var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--verify-contents", "--verify-get-after-put" }; + + var async_call = RemoteSynchronization.Program.Main(args); + var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + + Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); + Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); + } + + // + // Helper methods + // + + /// + /// Compares two directories and their contents. + /// + /// The first directory. + /// The second directory. + /// `true` if the two directories contain exactly the same files and if the contents of the files are equivalent. `false` otherwise. + public static bool DirectoriesAndContentsAreEqual(string d1, string d2) + { + // Recursively get the files in the two directories + var f1s = Directory.EnumerateFiles(d1, "*", SearchOption.AllDirectories).Select(x => x[(d1.Length + 1)..]).OrderDescending(); + var f2s = Directory.EnumerateFiles(d2, "*", SearchOption.AllDirectories).Select(x => x[(d2.Length + 1)..]).OrderDescending(); + + // If the two directories do not contain the same files, return false + if (!f1s.SequenceEqual(f2s)) + return false; + + // Check that each file pair exist and have the same content + foreach (var f1 in f1s) + { + var f1full = Path.Combine(d1, f1); + if (!File.Exists(f1full)) + return false; + + var f2full = Path.Combine(d2, f1); + if (!File.Exists(f2full)) + return false; + + var c1 = File.ReadAllText(f1full); + var c2 = File.ReadAllText(f2full); + + if (!c1.SequenceEqual(c2)) + return false; + } + + return true; + } + + /// + /// Generates test data in the specified directory. + /// + /// The directory to fill with the generated data. + /// How many files the directory should have. + /// How many subdirectories the directory should have. + /// How deep the number of subdirectories within subdirectories should go. + /// The maximum size of the files to generate. + public static async Task GenerateTestData(string dir, int n_files, int n_dirs, int n_levels, int max_file_size) + { + if (!SystemIO.IO_OS.DirectoryExists(dir)) + SystemIO.IO_OS.DirectoryCreate(dir); + + var fs = Enumerable.Range(0, n_files) + .Select(i => GenerateTestFile(dir, i, max_file_size)); + var ds = n_levels > 0 ? + Enumerable.Range(0, n_dirs) + .Select(i => + { + var subdir = Path.Combine(dir, $"dir_{i}"); + return GenerateTestData(subdir, n_files, n_dirs, n_levels - 1, max_file_size); + }) + : []; + + await Task.WhenAll([.. fs, .. ds]); + } + + public static async Task GenerateTestFile(string dir, int i, int max_file_size) + { + var rnd = new Random(); + var file = Path.Combine(dir, $"file_{i}.txt"); + var size = rnd.Next(1, max_file_size); + var data = new byte[size]; + rnd.NextBytes(data); + await File.WriteAllBytesAsync(file, data); + } + + } +} \ No newline at end of file diff --git a/Duplicati/UnitTest/UriUtilityTests.cs b/Duplicati/UnitTest/UriUtilityTests.cs index 252739c16..4790d7da1 100644 --- a/Duplicati/UnitTest/UriUtilityTests.cs +++ b/Duplicati/UnitTest/UriUtilityTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -112,5 +112,36 @@ namespace Duplicati.UnitTest } } } + + [Test] + [Category("UriUtility")] + public static void TestUriParsePaths() + { + if (System.OperatingSystem.IsWindows()) + { + var a = new Library.Utility.Uri("file://c:/a/b/"); + var b = new Library.Utility.Uri("c:/a/b/"); + + Assert.AreEqual(a.ToString(), b.ToString()); + Assert.AreEqual(a.Path, b.Path); + + a = new Library.Utility.Uri("file://C:\\a\\b"); + b = new Library.Utility.Uri("C:\\a\\b"); + Assert.AreEqual(a.ToString(), b.ToString()); + Assert.AreEqual(a.Path, b.Path); + } + else + { + var a = new Library.Utility.Uri("file:///a/b"); + var b = new Library.Utility.Uri("/a/b"); + Assert.AreEqual(a.ToString(), b.ToString()); + Assert.AreEqual(a.Path, b.Path); + + a = new Library.Utility.Uri("file:///a/b/"); + b = new Library.Utility.Uri("/a/b/"); + Assert.AreEqual(a.ToString(), b.ToString()); + Assert.AreEqual(a.Path, b.Path); + } + } } } diff --git a/Duplicati/UnitTest/UtilityTests.cs b/Duplicati/UnitTest/UtilityTests.cs index 1c29d4938..35ca8b92c 100644 --- a/Duplicati/UnitTest/UtilityTests.cs +++ b/Duplicati/UnitTest/UtilityTests.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -349,8 +349,7 @@ namespace Duplicati.UnitTest string[] duplicateItems = { "A", "c" }; // Test with default comparer. - ISet actualDuplicateItems; - ISet actualUniqueItems = Utility.GetUniqueItems(collection, out actualDuplicateItems); + var actualUniqueItems = Utility.GetUniqueItems(collection, out var actualDuplicateItems); CollectionAssert.AreEquivalent(uniqueItems, actualUniqueItems); CollectionAssert.AreEquivalent(duplicateItems, actualDuplicateItems); diff --git a/Duplicati/UnitTest/WebApiTests.cs b/Duplicati/UnitTest/WebApiTests.cs index 775b9e145..a708df93c 100644 --- a/Duplicati/UnitTest/WebApiTests.cs +++ b/Duplicati/UnitTest/WebApiTests.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using NUnit.Framework; diff --git a/Duplicati/UnitTest/ZipFallbackTest.cs b/Duplicati/UnitTest/ZipFallbackTest.cs new file mode 100644 index 000000000..40399cc8b --- /dev/null +++ b/Duplicati/UnitTest/ZipFallbackTest.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using Duplicati.Library.Utility; +using NUnit.Framework; + +namespace Duplicati.UnitTest +{ + public class ZipFallbackTest : BasicSetupHelper + { + [Test] + [Category("Targeted")] + public void FallbackToSharpCompressOnDecompressLzmaStreams() + { + var testopts = TestOptions; + testopts["zip-compression-method"] = "lzma"; + testopts["zip-compression-library"] = "SharpCompress"; + testopts["blocksize"] = "50kb"; + + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Backup(new string[] { DATAFOLDER }); + Assert.AreEqual(0, r.Errors.Count()); + Assert.AreEqual(0, r.Warnings.Count()); + } + + // Delete the local database + File.Delete(DBFILE); + + // Switch back to built-in compression + testopts.Remove("zip-compression-method"); + testopts["zip-compression-library"] = "Auto"; + + // Recreate the database + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + { + var r = c.Repair(); + Assert.AreEqual(0, r.Errors.Count()); + // Ensure that we get warnings for the fallback, one for the dlist and one for the dindex + Assert.AreEqual(2, r.Warnings.Count()); + Assert.That(r.Warnings.All(x => x.Contains("lzma", StringComparison.OrdinalIgnoreCase))); + } + + // Restore files + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) + { + var r = c.Restore(null); + Assert.AreEqual(0, r.Errors.Count()); + // Ensure that we get warnings for the fallback, one for the dblock file + Assert.AreEqual(1, r.Warnings.Count()); + Assert.That(r.Warnings.All(x => x.Contains("lzma", StringComparison.OrdinalIgnoreCase))); + } + + TestUtils.AssertDirectoryTreesAreEquivalent(DATAFOLDER, RESTOREFOLDER, true, "Restore"); + + } + + [Test] + [Category("Targeted")] + [TestCase("zip-sc")] + [TestCase("zip-io")] + public void SupportZip64WithDefaultSettings(string module) + { + const long testfileSize = 5L * 1024 * 1024 * 1024; + var testopts = TestOptions; + testopts["blocksize"] = "50kb"; + + if (module == "zip-sc") + { + module = "zip"; + testopts["zip-compression-library"] = "SharpCompress"; + } + else if (module == "zip-io") + { + module = "zip"; + testopts["zip-compression-library"] = "BuiltIn"; + } + + string tempfilename; + // Create a 5GiB file + using var tempfile = new TempFile(); + { + tempfilename = Path.GetFileName(tempfile.Name); + using (var fs = File.OpenWrite(tempfile)) + fs.SetLength(testfileSize); + + var data = new byte[1024 * 1024 * 10]; + File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { control_files = tempfile.Name }), null)) + TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + } + + // Delete the local database + File.Delete(DBFILE); + + // Recreate the database + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(c.Repair()); + + var restoredfile = Path.Combine(RESTOREFOLDER, tempfilename); + try + { + // Restore control file + using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) + TestUtils.AssertResults(c.RestoreControlFiles(["*"])); + + // Check that the control file was restored + Assert.That(File.Exists(restoredfile), Is.True, "Control file was not restored"); + Assert.That(new FileInfo(restoredfile).Length, Is.EqualTo(testfileSize), "Control file had wrong size"); + } + finally + { + // Delete the control file + File.Delete(restoredfile); + } + + } + } +} diff --git a/Duplicati/WebserverCore/Abstractions/FileEntry.cs b/Duplicati/WebserverCore/Abstractions/FileEntry.cs index 320ff6842..64fff541a 100644 --- a/Duplicati/WebserverCore/Abstractions/FileEntry.cs +++ b/Duplicati/WebserverCore/Abstractions/FileEntry.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public class FileEntry diff --git a/Duplicati/WebserverCore/Abstractions/ICaptchaProvider.cs b/Duplicati/WebserverCore/Abstractions/ICaptchaProvider.cs deleted file mode 100644 index a039abff5..000000000 --- a/Duplicati/WebserverCore/Abstractions/ICaptchaProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Duplicati.WebserverCore.Abstractions; - -/// -/// A captcha provider -/// -public interface ICaptchaProvider -{ - /// - /// Check if the captcha is solved - /// - /// The captcha token - /// The captcha target - /// The captcha answer - bool SolvedCaptcha(string token, string target, string answer); - /// - /// Create a captcha - /// - /// The captcha target - /// The captcha token and the answer - (string Token, string? Answer) CreateCaptcha(string target); - /// - /// Get the captcha image - /// - /// The captcha token - byte[] GetCaptchaImage(string token); - /// - /// Gets a value indicating whether the visual captcha is disabled - /// - bool VisualCaptchaDisabled { get; } -} diff --git a/Duplicati/WebserverCore/Abstractions/ICommandlineRunService.cs b/Duplicati/WebserverCore/Abstractions/ICommandlineRunService.cs index 50edab625..40851d87e 100644 --- a/Duplicati/WebserverCore/Abstractions/ICommandlineRunService.cs +++ b/Duplicati/WebserverCore/Abstractions/ICommandlineRunService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public interface ICommandlineRunService diff --git a/Duplicati/WebserverCore/Abstractions/IEndpointV1.cs b/Duplicati/WebserverCore/Abstractions/IEndpointV1.cs index 0b0a961a2..610ed4d6f 100644 --- a/Duplicati/WebserverCore/Abstractions/IEndpointV1.cs +++ b/Duplicati/WebserverCore/Abstractions/IEndpointV1.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public interface IEndpointV1 diff --git a/Duplicati/WebserverCore/Abstractions/IHostnameValidator.cs b/Duplicati/WebserverCore/Abstractions/IHostnameValidator.cs index 12b3038af..246230942 100644 --- a/Duplicati/WebserverCore/Abstractions/IHostnameValidator.cs +++ b/Duplicati/WebserverCore/Abstractions/IHostnameValidator.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; /// diff --git a/Duplicati/WebserverCore/Abstractions/IJWTTokenProvider.cs b/Duplicati/WebserverCore/Abstractions/IJWTTokenProvider.cs index 1c513fefe..972088096 100644 --- a/Duplicati/WebserverCore/Abstractions/IJWTTokenProvider.cs +++ b/Duplicati/WebserverCore/Abstractions/IJWTTokenProvider.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; /// @@ -57,13 +77,20 @@ public interface IJWTTokenProvider /// The user ID the token is for. /// The token family ID the token is for. /// The expiration time of the token, can only be shorter than the current. + /// The JWT token. string CreateAccessToken(string userId, string tokenFamilyId, TimeSpan? expiration = null); /// + /// Creates a JWT token that can be used to access resources "forever". + /// + /// The JWT token. + string CreateForeverToken(); + /// /// Creates a JWT token that can be used to refresh an access token. /// /// The user ID the token is for. /// The token family ID the token is for. /// The counter of the token family the token is for. + /// The JWT token. string CreateRefreshToken(string userId, string tokenFamilyId, int counter); /// diff --git a/Duplicati/WebserverCore/Abstractions/ILanguageService.cs b/Duplicati/WebserverCore/Abstractions/ILanguageService.cs index 72747dedf..99d643d58 100644 --- a/Duplicati/WebserverCore/Abstractions/ILanguageService.cs +++ b/Duplicati/WebserverCore/Abstractions/ILanguageService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public interface ILanguageService diff --git a/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs b/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs index 294e1747d..d68f90a7a 100644 --- a/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs +++ b/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; /// diff --git a/Duplicati/WebserverCore/Abstractions/IRemoteController.cs b/Duplicati/WebserverCore/Abstractions/IRemoteController.cs index aeb4212bf..7afd4536f 100644 --- a/Duplicati/WebserverCore/Abstractions/IRemoteController.cs +++ b/Duplicati/WebserverCore/Abstractions/IRemoteController.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs b/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs index b4ce895d3..53b697744 100644 --- a/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs +++ b/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; /// diff --git a/Duplicati/WebserverCore/Abstractions/ISettingsService.cs b/Duplicati/WebserverCore/Abstractions/ISettingsService.cs index b333b1e25..37643c9d0 100644 --- a/Duplicati/WebserverCore/Abstractions/ISettingsService.cs +++ b/Duplicati/WebserverCore/Abstractions/ISettingsService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Abstractions/IStatusService.cs b/Duplicati/WebserverCore/Abstractions/IStatusService.cs index 0bbe7c564..2d8f8b15a 100644 --- a/Duplicati/WebserverCore/Abstractions/IStatusService.cs +++ b/Duplicati/WebserverCore/Abstractions/IStatusService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Dto; namespace Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Abstractions/ISystemInfoProvider.cs b/Duplicati/WebserverCore/Abstractions/ISystemInfoProvider.cs new file mode 100644 index 000000000..fcea47a87 --- /dev/null +++ b/Duplicati/WebserverCore/Abstractions/ISystemInfoProvider.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Globalization; + +namespace Duplicati.WebserverCore.Abstractions; + +/// +/// Produces system information. +/// +public interface ISystemInfoProvider +{ + /// + /// Gets the system information. + /// + /// The browser language. + /// The system information. + Dto.SystemInfoDto GetSystemInfo(CultureInfo? browserlanguage); +} diff --git a/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs b/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs index 1aa510df3..ff4e3f0a4 100644 --- a/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs +++ b/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; /// diff --git a/Duplicati/WebserverCore/Abstractions/IUpdateService.cs b/Duplicati/WebserverCore/Abstractions/IUpdateService.cs index d19d9486a..111dd3b49 100644 --- a/Duplicati/WebserverCore/Abstractions/IUpdateService.cs +++ b/Duplicati/WebserverCore/Abstractions/IUpdateService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public interface IUpdateService diff --git a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs index fb5f32146..cfbb63aca 100644 --- a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs +++ b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Net.WebSockets; namespace Duplicati.WebserverCore.Abstractions.Notifications; diff --git a/Duplicati/WebserverCore/Abstractions/ServerSettings.cs b/Duplicati/WebserverCore/Abstractions/ServerSettings.cs index 645304386..b13e86221 100644 --- a/Duplicati/WebserverCore/Abstractions/ServerSettings.cs +++ b/Duplicati/WebserverCore/Abstractions/ServerSettings.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Abstractions; public class ServerSettings @@ -7,27 +27,22 @@ public class ServerSettings public ServerSettings(Server.Database.ServerSettings applicationSettings) => this.applicationSettings = applicationSettings; - public string StartupDelay + public string? StartupDelay { get => applicationSettings.StartupDelayDuration; set => applicationSettings.StartupDelayDuration = value; } - public string DownloadSpeedLimit + public string? DownloadSpeedLimit { get => applicationSettings.DownloadSpeedLimit; set => applicationSettings.DownloadSpeedLimit = value; } - public string UploadSpeedLimit + public string? UploadSpeedLimit { get => applicationSettings.UploadSpeedLimit; set => applicationSettings.UploadSpeedLimit = value; } - public ThreadPriority? ThreadPriority - { - get => applicationSettings.ThreadPriorityOverride; - set => applicationSettings.ThreadPriorityOverride = value; - } public int LastWebserverPort { get => applicationSettings.LastWebserverPort; @@ -75,7 +90,7 @@ public class ServerSettings get => applicationSettings.UnackedWarning; set => applicationSettings.UnackedWarning = value; } - public string ServerListenInterface + public string? ServerListenInterface { get => applicationSettings.ServerListenInterface; set => applicationSettings.ServerListenInterface = value; @@ -85,12 +100,12 @@ public class ServerSettings get => applicationSettings.FixedInvalidBackupId; set => applicationSettings.FixedInvalidBackupId = value; } - public string UpdateChannel + public string? UpdateChannel { get => applicationSettings.UpdateChannel; set => applicationSettings.UpdateChannel = value; } - public string UsageReporterLevel + public string? UsageReporterLevel { get => applicationSettings.UsageReporterLevel; set => applicationSettings.UsageReporterLevel = value; @@ -100,7 +115,7 @@ public class ServerSettings get => applicationSettings.DisableTrayIconLogin; set => applicationSettings.DisableTrayIconLogin = value; } - public string ServerAllowedHostnames + public string? ServerAllowedHostnames { get => applicationSettings.AllowedHostnames; set => applicationSettings.SetAllowedHostnames(value); diff --git a/Duplicati/WebserverCore/Abstractions/UpdateInfo.cs b/Duplicati/WebserverCore/Abstractions/UpdateInfo.cs index eeb093e68..7eef7ae74 100644 --- a/Duplicati/WebserverCore/Abstractions/UpdateInfo.cs +++ b/Duplicati/WebserverCore/Abstractions/UpdateInfo.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.AutoUpdater; namespace Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/DayOfWeekStringEnumConverter.cs b/Duplicati/WebserverCore/DayOfWeekStringEnumConverter.cs index ef6c33b58..c0e93e3f6 100644 --- a/Duplicati/WebserverCore/DayOfWeekStringEnumConverter.cs +++ b/Duplicati/WebserverCore/DayOfWeekStringEnumConverter.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text.Json; using System.Text.Json.Serialization; @@ -12,16 +32,18 @@ public class DayOfWeekStringEnumConverter : JsonConverterFactory if (reader.TokenType != JsonTokenType.String) throw new JsonException(); - return reader.GetString()?.Trim()?.ToLowerInvariant() switch + var value = reader.GetString()?.Trim()?.ToLowerInvariant(); + + return value switch { - "sun" => DayOfWeek.Sunday, - "mon" => DayOfWeek.Monday, - "tue" => DayOfWeek.Tuesday, - "wed" => DayOfWeek.Wednesday, - "thu" => DayOfWeek.Thursday, - "fri" => DayOfWeek.Friday, - "sat" => DayOfWeek.Saturday, - _ => throw new JsonException() + "sun" or "sunday" => DayOfWeek.Sunday, + "mon" or "monday" => DayOfWeek.Monday, + "tue" or "tuesday" => DayOfWeek.Tuesday, + "wed" or "wednesday" => DayOfWeek.Wednesday, + "thu" or "thursday" => DayOfWeek.Thursday, + "fri" or "friday" => DayOfWeek.Friday, + "sat" or "saturday" => DayOfWeek.Saturday, + _ => throw new JsonException($"Invalid day of week: {value}"), }; } @@ -51,7 +73,7 @@ public class DayOfWeekStringEnumConverter : JsonConverterFactory writer.WriteStringValue("sat"); break; default: - throw new JsonException(); + throw new JsonException($"Invalid day of week: {value}"); } writer.WriteStringValue(value.ToString()); } diff --git a/Duplicati/WebserverCore/Dto/AccessTokenOutput.cs b/Duplicati/WebserverCore/Dto/AccessTokenOutput.cs deleted file mode 100644 index 61fe130e0..000000000 --- a/Duplicati/WebserverCore/Dto/AccessTokenOutput.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Duplicati.WebserverCore.Dto; - -public record AccessTokenOutput(string AccessToken); diff --git a/Duplicati/WebserverCore/Dto/AccessTokenOutputDto.cs b/Duplicati/WebserverCore/Dto/AccessTokenOutputDto.cs new file mode 100644 index 000000000..69664531c --- /dev/null +++ b/Duplicati/WebserverCore/Dto/AccessTokenOutputDto.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.WebserverCore.Dto; + +public record AccessTokenOutputDto(string AccessToken); diff --git a/Duplicati/WebserverCore/Dto/AcknowlegdementDto.cs b/Duplicati/WebserverCore/Dto/AcknowlegdementDto.cs index d5ad9922c..f57d6db27 100644 --- a/Duplicati/WebserverCore/Dto/AcknowlegdementDto.cs +++ b/Duplicati/WebserverCore/Dto/AcknowlegdementDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; public sealed record AcknowlegdementDto diff --git a/Duplicati/WebserverCore/Dto/BackupAndScheduleInputDto.cs b/Duplicati/WebserverCore/Dto/BackupAndScheduleInputDto.cs index a3b6f2d99..94ad4b148 100644 --- a/Duplicati/WebserverCore/Dto/BackupAndScheduleInputDto.cs +++ b/Duplicati/WebserverCore/Dto/BackupAndScheduleInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; diff --git a/Duplicati/WebserverCore/Dto/BackupAndScheduleOutputDto.cs b/Duplicati/WebserverCore/Dto/BackupAndScheduleOutputDto.cs index 9de9ddc37..650144330 100644 --- a/Duplicati/WebserverCore/Dto/BackupAndScheduleOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/BackupAndScheduleOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; diff --git a/Duplicati/WebserverCore/Dto/BackupDefaultsDto.cs b/Duplicati/WebserverCore/Dto/BackupDefaultsDto.cs index 03d45de7b..8a01e27c8 100644 --- a/Duplicati/WebserverCore/Dto/BackupDefaultsDto.cs +++ b/Duplicati/WebserverCore/Dto/BackupDefaultsDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto { /// diff --git a/Duplicati/WebserverCore/Dto/BackupDto.cs b/Duplicati/WebserverCore/Dto/BackupDto.cs index 9f6cab34b..da0ebfbea 100644 --- a/Duplicati/WebserverCore/Dto/BackupDto.cs +++ b/Duplicati/WebserverCore/Dto/BackupDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ChangelogDto.cs b/Duplicati/WebserverCore/Dto/ChangelogDto.cs index 7e3938241..55527e9b3 100644 --- a/Duplicati/WebserverCore/Dto/ChangelogDto.cs +++ b/Duplicati/WebserverCore/Dto/ChangelogDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/CommandLineLogOutputDto.cs b/Duplicati/WebserverCore/Dto/CommandLineLogOutputDto.cs index 59a3aede2..b0e54bf34 100644 --- a/Duplicati/WebserverCore/Dto/CommandLineLogOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/CommandLineLogOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// /// The command line log output DTO diff --git a/Duplicati/WebserverCore/Dto/CommandlineTaskStartedDto.cs b/Duplicati/WebserverCore/Dto/CommandlineTaskStartedDto.cs index 9eec3fcdc..b83d46836 100644 --- a/Duplicati/WebserverCore/Dto/CommandlineTaskStartedDto.cs +++ b/Duplicati/WebserverCore/Dto/CommandlineTaskStartedDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/CrashLogOutputDto.cs b/Duplicati/WebserverCore/Dto/CrashLogOutputDto.cs new file mode 100644 index 000000000..091c0b5f0 --- /dev/null +++ b/Duplicati/WebserverCore/Dto/CrashLogOutputDto.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.WebserverCore.Dto; + +public record CrashLogOutputDto(string? Logdata); diff --git a/Duplicati/WebserverCore/Dto/CreateBackupDto.cs b/Duplicati/WebserverCore/Dto/CreateBackupDto.cs index 739a12d95..ab2376d87 100644 --- a/Duplicati/WebserverCore/Dto/CreateBackupDto.cs +++ b/Duplicati/WebserverCore/Dto/CreateBackupDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/DeleteBackupOutputDto.cs b/Duplicati/WebserverCore/Dto/DeleteBackupOutputDto.cs index 00be33ecd..185764e91 100644 --- a/Duplicati/WebserverCore/Dto/DeleteBackupOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/DeleteBackupOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ExportArgsOnlyDto.cs b/Duplicati/WebserverCore/Dto/ExportArgsOnlyDto.cs index fa94419a1..bb6b8bb88 100644 --- a/Duplicati/WebserverCore/Dto/ExportArgsOnlyDto.cs +++ b/Duplicati/WebserverCore/Dto/ExportArgsOnlyDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ExportCommandlineDto.cs b/Duplicati/WebserverCore/Dto/ExportCommandlineDto.cs index 59edc19a3..90243fcd9 100644 --- a/Duplicati/WebserverCore/Dto/ExportCommandlineDto.cs +++ b/Duplicati/WebserverCore/Dto/ExportCommandlineDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/FilterDto.cs b/Duplicati/WebserverCore/Dto/FilterDto.cs index 9cd50f37d..2703e275f 100644 --- a/Duplicati/WebserverCore/Dto/FilterDto.cs +++ b/Duplicati/WebserverCore/Dto/FilterDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/FilterGroupsDto.cs b/Duplicati/WebserverCore/Dto/FilterGroupsDto.cs new file mode 100644 index 000000000..5b7258657 --- /dev/null +++ b/Duplicati/WebserverCore/Dto/FilterGroupsDto.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.WebserverCore.Dto; + +public sealed record FilterGroupsDto(Dictionary> FilterGroups); diff --git a/Duplicati/WebserverCore/Dto/GenerateCaptchaOutput.cs b/Duplicati/WebserverCore/Dto/GenerateCaptchaOutput.cs index 5b544f7ac..6687dad94 100644 --- a/Duplicati/WebserverCore/Dto/GenerateCaptchaOutput.cs +++ b/Duplicati/WebserverCore/Dto/GenerateCaptchaOutput.cs @@ -1,2 +1,22 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; public sealed record GenerateCaptchaOutput(string Token, string? Answer, bool NoVisualChallenge); diff --git a/Duplicati/WebserverCore/Dto/GetDbPathDto.cs b/Duplicati/WebserverCore/Dto/GetDbPathDto.cs index 80b48ca5f..b6cc3b0f9 100644 --- a/Duplicati/WebserverCore/Dto/GetDbPathDto.cs +++ b/Duplicati/WebserverCore/Dto/GetDbPathDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// @@ -5,4 +25,4 @@ namespace Duplicati.WebserverCore.Dto; /// /// Whether the database path exists. /// The database path. -public sealed record GetDbPathDto(bool Exists, string Path); +public sealed record GetDbPathDto(bool Exists, string? Path); diff --git a/Duplicati/WebserverCore/Dto/GetTaskStateDto.cs b/Duplicati/WebserverCore/Dto/GetTaskStateDto.cs index 75691b2da..3a29ecb06 100644 --- a/Duplicati/WebserverCore/Dto/GetTaskStateDto.cs +++ b/Duplicati/WebserverCore/Dto/GetTaskStateDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ImportBackupInputDto.cs b/Duplicati/WebserverCore/Dto/ImportBackupInputDto.cs index 3ba43a2d9..054b70049 100644 --- a/Duplicati/WebserverCore/Dto/ImportBackupInputDto.cs +++ b/Duplicati/WebserverCore/Dto/ImportBackupInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ImportBackupOutputDto.cs b/Duplicati/WebserverCore/Dto/ImportBackupOutputDto.cs index d8cfecacf..eb33d0721 100644 --- a/Duplicati/WebserverCore/Dto/ImportBackupOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/ImportBackupOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// /// The import backup output DTO diff --git a/Duplicati/WebserverCore/Dto/IsBackupActiveDto.cs b/Duplicati/WebserverCore/Dto/IsBackupActiveDto.cs index 90e914df3..b39714033 100644 --- a/Duplicati/WebserverCore/Dto/IsBackupActiveDto.cs +++ b/Duplicati/WebserverCore/Dto/IsBackupActiveDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/IsDbUsedElsewhereDto.cs b/Duplicati/WebserverCore/Dto/IsDbUsedElsewhereDto.cs index 89cfa4219..de5c0f3bc 100644 --- a/Duplicati/WebserverCore/Dto/IsDbUsedElsewhereDto.cs +++ b/Duplicati/WebserverCore/Dto/IsDbUsedElsewhereDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/IssueSigninTokenInputDto.cs b/Duplicati/WebserverCore/Dto/IssueSigninTokenInputDto.cs index 50e120e74..945dcf27f 100644 --- a/Duplicati/WebserverCore/Dto/IssueSigninTokenInputDto.cs +++ b/Duplicati/WebserverCore/Dto/IssueSigninTokenInputDto.cs @@ -1,2 +1,22 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; public sealed record IssueSigninTokenInputDto(string Password); diff --git a/Duplicati/WebserverCore/Dto/LicenseDto.cs b/Duplicati/WebserverCore/Dto/LicenseDto.cs index da1b99741..8af16a8ce 100644 --- a/Duplicati/WebserverCore/Dto/LicenseDto.cs +++ b/Duplicati/WebserverCore/Dto/LicenseDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/LogEntry.cs b/Duplicati/WebserverCore/Dto/LogEntry.cs index e9186e78d..0affbfeb9 100644 --- a/Duplicati/WebserverCore/Dto/LogEntry.cs +++ b/Duplicati/WebserverCore/Dto/LogEntry.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Logging; using Duplicati.Server; @@ -46,7 +66,7 @@ public sealed record LogEntry /// /// Exception data attached to the message /// - public required string Exception { get; init; } + public required string? Exception { get; init; } /// /// The backup ID, if any @@ -74,7 +94,7 @@ public sealed record LogEntry MessageID = entry.MessageID, ExceptionID = entry.ExceptionID, Type = entry.Type, - Exception = entry.Exception.ToString(), + Exception = entry.Exception?.ToString(), BackupID = entry.BackupID, TaskID = entry.TaskID }; diff --git a/Duplicati/WebserverCore/Dto/LoginInputDto.cs b/Duplicati/WebserverCore/Dto/LoginInputDto.cs index 3e1b0cf70..c727916d9 100644 --- a/Duplicati/WebserverCore/Dto/LoginInputDto.cs +++ b/Duplicati/WebserverCore/Dto/LoginInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/NotificationDto.cs b/Duplicati/WebserverCore/Dto/NotificationDto.cs index ff63dd941..0a20ce9ab 100644 --- a/Duplicati/WebserverCore/Dto/NotificationDto.cs +++ b/Duplicati/WebserverCore/Dto/NotificationDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto { /// diff --git a/Duplicati/WebserverCore/Dto/RemoteControlStatusOutput.cs b/Duplicati/WebserverCore/Dto/RemoteControlStatusOutput.cs index c2ada5dda..608b5bfae 100644 --- a/Duplicati/WebserverCore/Dto/RemoteControlStatusOutput.cs +++ b/Duplicati/WebserverCore/Dto/RemoteControlStatusOutput.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/RepairInputDto.cs b/Duplicati/WebserverCore/Dto/RepairInputDto.cs index 7114616c4..aea5de9b5 100644 --- a/Duplicati/WebserverCore/Dto/RepairInputDto.cs +++ b/Duplicati/WebserverCore/Dto/RepairInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/RestoreInputDto.cs b/Duplicati/WebserverCore/Dto/RestoreInputDto.cs index f722e68bd..564a5845c 100644 --- a/Duplicati/WebserverCore/Dto/RestoreInputDto.cs +++ b/Duplicati/WebserverCore/Dto/RestoreInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ScheduleDto.cs b/Duplicati/WebserverCore/Dto/ScheduleDto.cs index 5b329e3d0..5351f76d7 100644 --- a/Duplicati/WebserverCore/Dto/ScheduleDto.cs +++ b/Duplicati/WebserverCore/Dto/ScheduleDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/ServerStatusDto.cs b/Duplicati/WebserverCore/Dto/ServerStatusDto.cs index 70033d478..8426d0675 100644 --- a/Duplicati/WebserverCore/Dto/ServerStatusDto.cs +++ b/Duplicati/WebserverCore/Dto/ServerStatusDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server.Serialization; namespace Duplicati.WebserverCore.Dto; diff --git a/Duplicati/WebserverCore/Dto/SettingDto.cs b/Duplicati/WebserverCore/Dto/SettingDto.cs index 907a68bc1..d0273a2d9 100644 --- a/Duplicati/WebserverCore/Dto/SettingDto.cs +++ b/Duplicati/WebserverCore/Dto/SettingDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/SigninInputDto.cs b/Duplicati/WebserverCore/Dto/SigninInputDto.cs index 536c25b37..68515c46d 100644 --- a/Duplicati/WebserverCore/Dto/SigninInputDto.cs +++ b/Duplicati/WebserverCore/Dto/SigninInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// /// Represents the input for a signin operation. diff --git a/Duplicati/WebserverCore/Dto/SigninTokenOutputDto.cs b/Duplicati/WebserverCore/Dto/SigninTokenOutputDto.cs index 3230ef8cc..87ae42197 100644 --- a/Duplicati/WebserverCore/Dto/SigninTokenOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/SigninTokenOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// /// Represents the output for a signin operation. diff --git a/Duplicati/WebserverCore/Dto/SingleOperationTokenOutputDto.cs b/Duplicati/WebserverCore/Dto/SingleOperationTokenOutputDto.cs index c1afc23a7..13104137f 100644 --- a/Duplicati/WebserverCore/Dto/SingleOperationTokenOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/SingleOperationTokenOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/SolveCaptchaInputDto.cs b/Duplicati/WebserverCore/Dto/SolveCaptchaInputDto.cs index c04a42bbe..3d8529290 100644 --- a/Duplicati/WebserverCore/Dto/SolveCaptchaInputDto.cs +++ b/Duplicati/WebserverCore/Dto/SolveCaptchaInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/StartRegistrationInput.cs b/Duplicati/WebserverCore/Dto/StartRegistrationInput.cs index 263660294..997cfb2de 100644 --- a/Duplicati/WebserverCore/Dto/StartRegistrationInput.cs +++ b/Duplicati/WebserverCore/Dto/StartRegistrationInput.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/SystemInfoDto.cs b/Duplicati/WebserverCore/Dto/SystemInfoDto.cs index be3443feb..4be4f8a23 100644 --- a/Duplicati/WebserverCore/Dto/SystemInfoDto.cs +++ b/Duplicati/WebserverCore/Dto/SystemInfoDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server.Serialization.Interface; namespace Duplicati.WebserverCore.Dto; @@ -54,6 +74,11 @@ public sealed record SystemInfoDto /// public required DateTime ServerTime { get; init; } + /// + /// Gets or sets the server time zone. + /// + public required string ServerTimeZone { get; init; } + /// /// Gets or sets the OS type. /// @@ -144,6 +169,11 @@ public sealed record SystemInfoDto /// public required IEnumerable ServerModules { get; init; } + /// + /// Gets or sets the secret provider modules. + /// + public required IEnumerable SecretProviderModules { get; init; } + /// /// Gets or sets a value indicating whether alternate update URLs are being used. /// @@ -174,6 +204,32 @@ public sealed record SystemInfoDto /// public required bool BrowserLocaleSupported { get; init; } + /// + /// The timezones available on the system + /// + public required IEnumerable TimeZones { get; init; } + + /// + /// Represents a timezone. + /// + public class TimeZoneDto + { + /// + /// Gets or sets the ID. + /// + public required string ID { get; init; } + + /// + /// Gets or sets the display name. + /// + public required string DisplayName { get; init; } + + /// + /// Gets or sets the base UTC offset. + /// + public required string CurrentUTCOffset { get; init; } + } + /// /// Represents a special folder. /// diff --git a/Duplicati/WebserverCore/Dto/TaskStartedDto.cs b/Duplicati/WebserverCore/Dto/TaskStartedDto.cs index 64a5f1b21..41ea1c93f 100644 --- a/Duplicati/WebserverCore/Dto/TaskStartedDto.cs +++ b/Duplicati/WebserverCore/Dto/TaskStartedDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/TreeNodeDto.cs b/Duplicati/WebserverCore/Dto/TreeNodeDto.cs index 4ec62555f..720fdda90 100644 --- a/Duplicati/WebserverCore/Dto/TreeNodeDto.cs +++ b/Duplicati/WebserverCore/Dto/TreeNodeDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/UpdateCertificateInputDto.cs b/Duplicati/WebserverCore/Dto/UpdateCertificateInputDto.cs index 4085a9986..b3cc3f906 100644 --- a/Duplicati/WebserverCore/Dto/UpdateCertificateInputDto.cs +++ b/Duplicati/WebserverCore/Dto/UpdateCertificateInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/UpdateDbPathInputDto.cs b/Duplicati/WebserverCore/Dto/UpdateDbPathInputDto.cs index 846503d82..bbcb6d89c 100644 --- a/Duplicati/WebserverCore/Dto/UpdateDbPathInputDto.cs +++ b/Duplicati/WebserverCore/Dto/UpdateDbPathInputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Dto/WebModuleOutputDto.cs b/Duplicati/WebserverCore/Dto/WebModuleOutputDto.cs index e3e6b88e3..19a3ee83f 100644 --- a/Duplicati/WebserverCore/Dto/WebModuleOutputDto.cs +++ b/Duplicati/WebserverCore/Dto/WebModuleOutputDto.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Dto; /// diff --git a/Duplicati/WebserverCore/Duplicati.WebserverCore.csproj b/Duplicati/WebserverCore/Duplicati.WebserverCore.csproj index a583a7297..93da37923 100644 --- a/Duplicati/WebserverCore/Duplicati.WebserverCore.csproj +++ b/Duplicati/WebserverCore/Duplicati.WebserverCore.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,7 +7,7 @@ enable true Duplicati.WebserverCore - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -17,8 +17,6 @@ - - diff --git a/Duplicati/WebserverCore/DuplicatiWebserver.cs b/Duplicati/WebserverCore/DuplicatiWebserver.cs index eeb21a05f..7087bf3f0 100644 --- a/Duplicati/WebserverCore/DuplicatiWebserver.cs +++ b/Duplicati/WebserverCore/DuplicatiWebserver.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -32,24 +32,65 @@ using Duplicati.WebserverCore.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http.Json; +using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.Configuration.Json; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.OpenApi.Models; namespace Duplicati.WebserverCore; -public partial class DuplicatiWebserver +/// +/// Instance of the Duplicati webserver +/// +public class DuplicatiWebserver { - public IConfiguration Configuration { get; private set; } + /// + /// The log tag for this class + /// + private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); + /// + /// The name of the CORS policy + /// + public const string CorsPolicyName = "CustomCorsPolicy"; - public WebApplication App { get; private set; } + /// + /// The configuration for the server + /// + public required IConfiguration Configuration { get; init; } - public IServiceProvider Provider { get; private set; } + /// + /// The web application + /// + public required WebApplication App { get; init; } - public int Port { get; private set; } + /// + /// The service provider + /// + public IServiceProvider Provider => App.Services; + /// + /// The port the server is listening on + /// + public required int Port { get; init; } + + /// + /// The interfaces being listened to + /// + public required string Interface { get; init; } + + /// + /// The port the server is listening on + /// + public required bool CorsEnabled { get; init; } + + /// + /// The task that will be set when the server is terminated + /// public Task TerminationTask { get; private set; } = Task.CompletedTask; + /// + /// If Swagger should be enabled + /// #if DEBUG private static readonly bool EnableSwagger = true; #else @@ -67,20 +108,27 @@ public partial class DuplicatiWebserver /// The allowed hostnames /// If static files should be disabled /// The paths to serve as SPAs + /// The origins to allow for CORS public record InitSettings( string WebRoot, int Port, System.Net.IPAddress Interface, - X509Certificate2? Certificate, + X509Certificate2Collection? Certificate, string Servername, IEnumerable AllowedHostnames, bool DisableStaticFiles, - IEnumerable SPAPaths + IEnumerable SPAPaths, + IEnumerable CorsOrigins ); - public void InitWebServer(InitSettings settings, Connection connection) + /// + /// Creates a new webserver instance + /// + /// The settings for the server + /// The connection to the database + /// The new webserver instance + public static DuplicatiWebserver CreateWebServer(InitSettings settings, Connection connection) { - Port = settings.Port; var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { ContentRootPath = settings.WebRoot, @@ -104,7 +152,7 @@ public partial class DuplicatiWebserver options.ListenAnyIP(settings.Port, listenOptions => { if (settings.Certificate != null) - listenOptions.UseHttps(settings.Certificate); + ConfigureHttps(listenOptions, settings.Certificate); }); } else if (settings.Interface == System.Net.IPAddress.Loopback) @@ -112,7 +160,7 @@ public partial class DuplicatiWebserver options.ListenLocalhost(settings.Port, listenOptions => { if (settings.Certificate != null) - listenOptions.UseHttps(settings.Certificate); + ConfigureHttps(listenOptions, settings.Certificate); }); } else @@ -120,7 +168,7 @@ public partial class DuplicatiWebserver options.Listen(settings.Interface, settings.Port, listenOptions => { if (settings.Certificate != null) - listenOptions.UseHttps(settings.Certificate); + ConfigureHttps(listenOptions, settings.Certificate); }); } }); @@ -178,6 +226,7 @@ public partial class DuplicatiWebserver options.Audience = jwtConfig.Audience; options.SaveToken = true; options.TokenValidationParameters = JWTTokenProvider.GetTokenValidationParameters(jwtConfig); + options.Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration(); options.Events = new JwtBearerEvents { @@ -215,33 +264,50 @@ public partial class DuplicatiWebserver .AddFilter("Microsoft", LogLevel.Warning) .AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error) .AddFilter("System", LogLevel.Warning) - .AddFilter("Duplicati", LogLevel.Warning); + .AddFilter("Duplicati", LogLevel.Warning) + .AddFilter("Microsoft.AspNetCore.Diagnostics", LogLevel.None); } builder.Services.AddHttpClient(); - Configuration = builder.Configuration; - App = builder.Build(); - Provider = App.Services; + var useCors = settings.CorsOrigins != null && settings.CorsOrigins.Any(); + if (useCors) + { + builder.Services.AddCors(options => + { + options.AddPolicy( + name: CorsPolicyName, + policy => + { + policy.WithOrigins(settings.CorsOrigins!.ToArray()) + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + } - HttpClientHelper.Configure(App.Services.GetRequiredService()); + var app = builder.Build(); + HttpClientHelper.Configure(app.Services.GetRequiredService()); - App.UseAuthentication(); - App.UseAuthorization(); + if (useCors) + app.UseCors(CorsPolicyName); + + app.UseAuthentication(); + app.UseAuthorization(); if (EnableSwagger) { - App.UseSwagger(); - App.UseSwaggerUI(c => + app.UseSwagger(); + app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Duplicati"); }); } if (!settings.DisableStaticFiles) - App.UseDefaultStaticFiles(settings.WebRoot, settings.SPAPaths); + app.UseDefaultStaticFiles(settings.WebRoot, settings.SPAPaths); - App.UseExceptionHandler(app => + app.UseExceptionHandler(app => { app.Run(async context => { @@ -257,6 +323,10 @@ public partial class DuplicatiWebserver } else { + // These are unexpected exceptions, so log them + app.ApplicationServices.GetRequiredService>() + .LogError(thrownException, "Unhandled exception"); + context.Response.StatusCode = 500; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(new { Error = "An error occurred", Code = 500 })); @@ -265,18 +335,71 @@ public partial class DuplicatiWebserver }); if (connection.ApplicationSettings.RemoteControlEnabled) - App.Services.GetRequiredService().Enable(); + app.Services.GetRequiredService().Enable(); + + // Preload static system info, for better first-load experience + var _ = Task.Run(() => app.Services.GetRequiredService().GetSystemInfo(null)); + + // Get a string description of the listen interface used + var listenInterface = settings.Interface == System.Net.IPAddress.Any + ? "*" + : settings.Interface == System.Net.IPAddress.Loopback + ? "localhost" + : settings.Interface.ToString(); + + return new DuplicatiWebserver() + { + Configuration = builder.Configuration, + App = app, + Port = settings.Port, + Interface = listenInterface, + CorsEnabled = useCors + }; + } - public Task Start(InitSettings settings) + /// + /// Configures HTTPS for the server + /// + /// The listen options + /// The certificates to use + private static void ConfigureHttps(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions, X509Certificate2Collection? certificates) + { + var servedCert = certificates?.FirstOrDefault(x => x.HasPrivateKey) + ?? throw new Exception("No certificate with private key found"); + + listenOptions.UseHttps(new HttpsConnectionAdapterOptions() + { + ServerCertificate = servedCert, + ServerCertificateChain = certificates + }); + + // This does not appear to have any effect, + // but setting it anyway in case it is needed in the future + listenOptions.UseHttps(new HttpsConnectionAdapterOptions() + { + ServerCertificate = servedCert, + ServerCertificateChain = certificates + }); + } + + /// + /// Starts the webserver + /// + /// The task that will be set when the server is terminated + public Task Start() { App.MapHealthChecks("/health"); - App.AddEndpoints() + App.AddEndpoints(CorsEnabled) .UseNotifications("/notifications"); return TerminationTask = App.RunAsync(); } + /// + /// Stops the webserver + /// + /// An awaitable task public async Task Stop() { await App.StopAsync(); diff --git a/Duplicati/WebserverCore/Endpoints/V1/Acknowledgements.cs b/Duplicati/WebserverCore/Endpoints/V1/Acknowledgements.cs index 2ca4a6bcc..081e99881 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Acknowledgements.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Acknowledgements.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common.IO; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Endpoints/V1/Auth.cs b/Duplicati/WebserverCore/Endpoints/V1/Auth.cs index 4234dffed..3c7b6397a 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Auth.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Auth.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -49,7 +49,7 @@ public partial class Auth : IEndpointV1 { var result = await loginProvider.PerformLoginWithRefreshToken(refreshTokenString, ct); AddCookie(httpContextAccessor.HttpContext, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); - return new Dto.AccessTokenOutput(result.AccessToken); + return new Dto.AccessTokenOutputDto(result.AccessToken); } catch (Exception ex) { @@ -73,7 +73,7 @@ public partial class Auth : IEndpointV1 var result = await loginProvider.PerformLoginWithSigninToken(input.SigninToken, input.RememberMe ?? false, ct); if (!string.IsNullOrWhiteSpace(result.RefreshToken)) AddCookie(httpContextAccessor.HttpContext!, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); - return new Dto.AccessTokenOutput(result.AccessToken); + return new Dto.AccessTokenOutputDto(result.AccessToken); } catch (Exception ex) { @@ -95,7 +95,7 @@ public partial class Auth : IEndpointV1 var result = await loginProvider.PerformLoginWithPassword(input.Password, input.RememberMe ?? false, ct); if (!string.IsNullOrWhiteSpace(result.RefreshToken)) AddCookie(httpContextAccessor.HttpContext!, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); - return new Dto.AccessTokenOutput(result.AccessToken); + return new Dto.AccessTokenOutputDto(result.AccessToken); } catch (Exception ex) { @@ -134,6 +134,17 @@ public partial class Auth : IEndpointV1 var singleOperationToken = tokenProvider.CreateSingleOperationToken("web-api", operation); return new Dto.SingleOperationTokenOutputDto(singleOperationToken); }).RequireAuthorization(); + + group.MapPost("auth/issue-forever-token", ([FromServices] Connection connection, [FromServices] IJWTTokenProvider tokenProvider) => + { + var res = connection.ApplicationSettings.ConsumeForeverToken(); + if (res == null) + throw new UnauthorizedException("Forever tokens are not enabled"); + if (!res.Value) + throw new UnauthorizedException("Cannot generate multiple forever tokens, restart the server to generate a new one"); + + return new Dto.AccessTokenOutputDto(tokenProvider.CreateForeverToken()); + }).RequireAuthorization(); } private static void AddCookie(HttpContext context, string name, string value, DateTimeOffset expires) diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs index a9f121720..166a494cd 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text.Json; using System.Text.Json.Serialization; using Duplicati.Library.Interface; @@ -238,7 +258,7 @@ public class BackupGet : IEndpointV1 byte[] data = EncodeDataForExport(connection, backup, passphrase); - string filename = Library.Utility.Uri.UrlEncode(backup.Name + "-duplicati-config.json"); + string filename = Uri.EscapeDataString(backup.Name + "-duplicati-config.json"); if (!string.IsNullOrWhiteSpace(passphrase)) filename += ".aes"; @@ -272,7 +292,7 @@ public class BackupGet : IEndpointV1 } private static Dto.IsDbUsedElsewhereDto ExecuteGetIsdbUsedElsewhere(IBackup bk) - => new Dto.IsDbUsedElsewhereDto(Library.Main.DatabaseLocator.IsDatabasePathInUse(bk.DBPath)); + => new Dto.IsDbUsedElsewhereDto(Library.Main.CLIDatabaseLocator.IsDatabasePathInUse(bk.DBPath)); private static Dto.IsBackupActiveDto ExecuteGetIsActive(IWorkerThreadsManager workerThreadsManager, IBackup bk) { diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs index 9a87a10ba..47284e9eb 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI.Abstractions; using Duplicati.Server; using Duplicati.Server.Database; diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs index 5c144d6fc..26948be3e 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text.Json; using Duplicati.Library.RestAPI.Abstractions; using Duplicati.Server; @@ -18,9 +38,9 @@ public class BackupPutDelete : IEndpointV1 => ExecutePut(GetBackup(connection, id), connection, input)) .RequireAuthorization(); - group.MapDelete("/backup/{id}", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromServices] ICaptchaProvider captchaProvider, [FromServices] LiveControls liveControls, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id, [FromQuery(Name = "delete-remote-files")] bool? delete_remote_files, [FromQuery(Name = "delete-local-db")] bool? delete_local_db, [FromQuery(Name = "captcha-token")] string? captcha_token, [FromQuery(Name = "captcha-answer")] string? captcha_answer, [FromQuery] bool? force) => + group.MapDelete("/backup/{id}", ([FromServices] Connection connection, [FromServices] IWorkerThreadsManager workerThreadsManager, [FromServices] LiveControls liveControls, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id, [FromQuery(Name = "delete-remote-files")] bool? delete_remote_files, [FromQuery(Name = "delete-local-db")] bool? delete_local_db, [FromQuery] bool? force) => { - var res = ExecuteDelete(GetBackup(connection, id), workerThreadsManager, captchaProvider, liveControls, delete_remote_files ?? false, delete_local_db, captcha_token, captcha_answer, force ?? false); + var res = ExecuteDelete(GetBackup(connection, id), workerThreadsManager, liveControls, delete_remote_files ?? false, delete_local_db, force ?? false); if (res.Status != "OK" && httpContextAccessor.HttpContext != null) httpContextAccessor.HttpContext.Response.StatusCode = 500; return res; @@ -112,17 +132,8 @@ public class BackupPutDelete : IEndpointV1 } } - private static Dto.DeleteBackupOutputDto ExecuteDelete(IBackup backup, IWorkerThreadsManager workerThreadsManager, ICaptchaProvider captchaProvider, LiveControls liveControls, bool delete_remote_files, bool? delete_local_db, string? captcha_token, string? captcha_answer, bool force) + private static Dto.DeleteBackupOutputDto ExecuteDelete(IBackup backup, IWorkerThreadsManager workerThreadsManager, LiveControls liveControls, bool delete_remote_files, bool? delete_local_db, bool force) { - if (delete_remote_files) - { - if (string.IsNullOrWhiteSpace(captcha_token) || string.IsNullOrWhiteSpace(captcha_answer)) - throw new UnauthorizedException("Missing captcha"); - - if (!captchaProvider.SolvedCaptcha(captcha_token, "DELETE /backup/" + backup.ID, captcha_answer)) - throw new ForbiddenException("Invalid captcha"); - } - if (workerThreadsManager.WorkerThread!.Active) { try @@ -136,8 +147,10 @@ public class BackupPutDelete : IEndpointV1 return new Dto.DeleteBackupOutputDto("failed", "backup-in-progress", nt?.TaskID); - bool hasPaused = liveControls.State == LiveControls.LiveControlState.Paused; - liveControls.Pause(); + bool hasPaused = liveControls.State != LiveControls.LiveControlState.Paused; + if (hasPaused) + liveControls.Pause(true); + nt.Abort(); for (int i = 0; i < 10; i++) if (workerThreadsManager.WorkerThread.Active) diff --git a/Duplicati/WebserverCore/Endpoints/V1/BackupDefaults.cs b/Duplicati/WebserverCore/Endpoints/V1/BackupDefaults.cs index 72463c362..918eb7a46 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/BackupDefaults.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/BackupDefaults.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common.IO; using Duplicati.Library.RestAPI; using Duplicati.Server.Database; diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs index 4efd17711..9c15d3c13 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; @@ -10,8 +30,8 @@ public class Backups : IEndpointV1 { public static void Map(RouteGroupBuilder group) { - group.MapGet("/backups", ([FromServices] Connection connection) - => ExecuteGet(connection)) + group.MapGet("/backups", ([FromServices] Connection connection, [FromQuery] string? orderBy = null) + => ExecuteGet(connection, orderBy)) .RequireAuthorization(); group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb) @@ -28,10 +48,91 @@ public class Backups : IEndpointV1 }).RequireAuthorization(); } - private static IEnumerable ExecuteGet(Connection connection) + private static IEnumerable ExecuteGet(Connection connection, string? orderBy) { var schedules = connection.Schedules; - var backups = connection.Backups; + var backups = connection.Backups.AsEnumerable(); + + IEnumerable ApplySorting(IEnumerable backups, string sort) + { + var asc = true; + sort = sort.ToLowerInvariant().Trim(); + if (sort.StartsWith("-")) + { + asc = false; + sort = sort.Substring(1); + } + else if (sort.StartsWith("+")) + { + asc = true; + sort = sort.Substring(1); + } + + Func? selector = sort switch + { + "name" => x => x.Backup.Name, + "id" => x => x.Backup.ID, + "lastrun" => x => + { + if (x.Backup.Metadata == null) + return null; + x.Backup.Metadata.TryGetValue("LastBackupStarted", out var res); + return res; + } + , + "nextrun" => x => x.Schedule?.Time, + "schedule" => x => string.IsNullOrWhiteSpace(x.Schedule?.Repeat), + "backend" => x => Library.Utility.Utility.GuessScheme(x.Backup.TargetURL), + "sourcesize" => x => + { + if (x.Backup.Metadata == null) + return null; + x.Backup.Metadata.TryGetValue("SourceFilesSize", out var res); + if (long.TryParse(res, out var l)) + return l; + return null; + } + , + "destinationsize" => x => + { + if (x.Backup.Metadata == null) + return null; + x.Backup.Metadata.TryGetValue("TargetFilesSize", out var res); + if (long.TryParse(res, out var l)) + return l; + return null; + } + , + "duration" => x => + { + if (x.Backup.Metadata == null) + return null; + x.Backup.Metadata.TryGetValue("LastBackupDuration", out var res); + return res; + } + , + _ => null + }; + + // Ignore unknown sort fields + if (selector != null) + { + if (backups is IOrderedEnumerable backupsOrdered) + { + return asc + ? backupsOrdered.ThenBy(selector) + : backupsOrdered.ThenByDescending(selector); + } + else + { + return asc + ? backups.OrderBy(selector) + : backups.OrderByDescending(selector); + } + } + + return backups; + } var all = backups.Select(n => new { @@ -40,7 +141,7 @@ public class Backups : IEndpointV1 Schedule = schedules.FirstOrDefault(x => x.Tags != null && x.Tags.Contains("ID=" + n.ID)) }); - return all.Select(x => new Dto.BackupAndScheduleOutputDto() + var res = all.Select(x => new Dto.BackupAndScheduleOutputDto() { Backup = new Dto.BackupDto() { @@ -79,7 +180,19 @@ public class Backups : IEndpointV1 AllowedDays = x.Schedule.AllowedDays } }); + + // Use DB setting if not set + if (string.IsNullOrWhiteSpace(orderBy)) + orderBy = connection.ApplicationSettings.BackupListSortOrder; + + // Apply sorting, if any + if (!string.IsNullOrWhiteSpace(orderBy)) + foreach (var direction in orderBy.Split(",")) + res = ApplySorting(res, direction); + + return res; } + private static Dto.ImportBackupOutputDto ExecuteImport(Connection connection, bool cmdline, bool import_metadata, bool direct, string passphrase, string tempfile) { try @@ -186,7 +299,7 @@ public class Backups : IEndpointV1 { if (existingDb) { - backup.DBPathSetter = Library.Main.DatabaseLocator.GetDatabasePath(data.Backup.TargetURL, null, false, false); + backup.DBPathSetter = Library.Main.CLIDatabaseLocator.GetDatabasePathForCLI(data.Backup.TargetURL, null, false, false); if (string.IsNullOrWhiteSpace(data.Backup.DBPath)) throw new Exception("Unable to find remote db path?"); } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Bugreport.cs b/Duplicati/WebserverCore/Endpoints/V1/Bugreport.cs index 54b5603e7..f9ae32eac 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Bugreport.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Bugreport.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; @@ -9,7 +29,7 @@ public class Bugreport : IEndpointV1 { public static void Map(RouteGroupBuilder group) { - group.MapGet("/bugreport/{reportid}", ([FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromRoute] long reportid, [FromQuery] string token, CancellationToken ct) => + group.MapGet("/bugreport/{reportid}", async ([FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromRoute] long reportid, [FromQuery] string token, CancellationToken ct) => { // Custom authorization check var singleOperationToken = jWTTokenProvider.ReadSingleOperationToken(token); @@ -30,7 +50,7 @@ public class Bugreport : IEndpointV1 response.ContentLength = fs.Length; response.ContentType = "application/octet-stream"; response.Headers.Append("Content-Disposition", $"attachment; filename={filename}"); - fs.CopyToAsync(response.Body, ct); + await fs.CopyToAsync(response.Body, ct).ConfigureAwait(false); } }); } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Captcha.cs b/Duplicati/WebserverCore/Endpoints/V1/Captcha.cs deleted file mode 100644 index 648cba6f6..000000000 --- a/Duplicati/WebserverCore/Endpoints/V1/Captcha.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Duplicati.WebserverCore.Abstractions; -using Microsoft.AspNetCore.Mvc; - -namespace Duplicati.WebserverCore.Endpoints.V1; - -public class Captcha : IEndpointV1 -{ - public static void Map(RouteGroupBuilder group) - { - group.MapGet("/captcha/{token}", ([FromServices] ICaptchaProvider captchaProvider, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string token, CancellationToken ct) => - { - var jpeg = captchaProvider.GetCaptchaImage(token); - var response = httpContextAccessor.HttpContext!.Response; - response.ContentLength = jpeg.Length; - response.ContentType = "image/jpeg"; - response.Body.WriteAsync(jpeg, ct); - }); - - group.MapPost("/captcha", ([FromServices] ICaptchaProvider captchaProvider, [FromBody] Dto.SolveCaptchaInputDto input) => - { - var (token, answer) = captchaProvider.CreateCaptcha(input.target); - return new Dto.GenerateCaptchaOutput( - token, - answer, - captchaProvider.VisualCaptchaDisabled - ); - }) - .RequireAuthorization(); - - - group.MapPost("/captcha/{token}", ([FromServices] ICaptchaProvider captchaProvider, [FromRoute] string token, [FromBody] Dto.SolveCaptchaInputDto input) => - { - if (captchaProvider.SolvedCaptcha(token, input.target, input.answer ?? "")) - return new { success = true }; - return new { success = false }; - }) - .RequireAuthorization(); - - } -} diff --git a/Duplicati/WebserverCore/Endpoints/V1/Changelog.cs b/Duplicati/WebserverCore/Endpoints/V1/Changelog.cs index 78a7dc79b..f48e16766 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Changelog.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Changelog.cs @@ -1,3 +1,24 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.AutoUpdater; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; @@ -32,7 +53,7 @@ public class Changelog : IEndpointV1 var path = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? ".", "changelog.txt"); return new Dto.ChangelogDto() { - Version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(), + Version = UpdaterManager.SelfVersion.Version, Changelog = System.IO.File.ReadAllText(path) }; } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs b/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs index 268369ee3..157dff3bf 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Commandline.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; diff --git a/Duplicati/WebserverCore/Endpoints/V1/Filesystem.cs b/Duplicati/WebserverCore/Endpoints/V1/Filesystem.cs index 38c9af1ad..ca64d1fc2 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Filesystem.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Filesystem.cs @@ -1,5 +1,26 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Common.IO; using Duplicati.Library.Snapshots; +using Duplicati.Library.Utility; using Duplicati.Server; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; @@ -90,8 +111,7 @@ public class Filesystem : IEndpointV1 && di.IsReady // Only try to create TreeNode entries for drives who were ready 'now' ) .Select(TryCreateTreeNodeForDrive) // This will try to create a TreeNode for selected drives - .Where(tn => tn != null) // This filters out such entries that could not be created - .Select(x => x!); + .WhereNotNull(); // This filters out such entries that could not be created } else { @@ -129,11 +149,11 @@ public class Filesystem : IEndpointV1 text = x.text, iconCls = x.iconCls, cls = "folder", - leaf = false, - hidden = false, - symlink = false, - temporary = false, - systemFile = false, + leaf = x.leaf, + hidden = x.hidden, + symlink = x.symlink, + temporary = x.temporary, + systemFile = x.systemFile, fileSize = -1, resolvedpath = x.id, check = false @@ -234,11 +254,11 @@ public class Filesystem : IEndpointV1 { var attr = SystemIO.IO_OS.GetFileAttributes(s); var isSymlink = SystemIO.IO_OS.IsSymlink(s, attr); - var isFolder = (attr & FileAttributes.Directory) != 0; + var isFolder = attr.HasFlag(FileAttributes.Directory); var isFile = !isFolder; - var isHidden = (attr & FileAttributes.Hidden) != 0; - bool isSystem = (attr & FileAttributes.System) != 0; - bool isTemporary = (attr & FileAttributes.Temporary) != 0; + var isHidden = attr.HasFlag(FileAttributes.Hidden); + bool isSystem = attr.HasFlag(FileAttributes.System); + bool isTemporary = attr.HasFlag(FileAttributes.Temporary); long fileSize = -1; var accessible = isFile || canAccess(s); diff --git a/Duplicati/WebserverCore/Endpoints/V1/HyperV.cs b/Duplicati/WebserverCore/Endpoints/V1/HyperV.cs index 86617f77a..98f8fed9e 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/HyperV.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/HyperV.cs @@ -1,6 +1,27 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Runtime.Versioning; using System.Security.Principal; using Duplicati.Library.Snapshots; +using Duplicati.Library.Snapshots.Windows; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; @@ -38,7 +59,7 @@ public class HyperV : IEndpointV1 { if (string.IsNullOrEmpty(key)) { - hypervUtility.QueryHyperVGuestsInfo(); + hypervUtility.QueryHyperVGuestsInfo(SnapshotProvider.AlphaVSS); return hypervUtility.Guests.Select(x => new Dto.TreeNodeDto() { id = x.ID.ToString(), @@ -57,7 +78,7 @@ public class HyperV : IEndpointV1 } else { - hypervUtility.QueryHyperVGuestsInfo(true); + hypervUtility.QueryHyperVGuestsInfo(SnapshotProvider.AlphaVSS, true); var foundVMs = hypervUtility.Guests.FindAll(x => x.ID.Equals(new Guid(key))); if (foundVMs.Count != 1) diff --git a/Duplicati/WebserverCore/Endpoints/V1/Licenses.cs b/Duplicati/WebserverCore/Endpoints/V1/Licenses.cs index 89e2f0cc5..63e47aa80 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Licenses.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Licenses.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; namespace Duplicati.WebserverCore.Endpoints.V1; diff --git a/Duplicati/WebserverCore/Endpoints/V1/LogData.cs b/Duplicati/WebserverCore/Endpoints/V1/LogData.cs index e6d457eb6..13c39324a 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/LogData.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/LogData.cs @@ -1,6 +1,28 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; using Duplicati.Library.RestAPI; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; +using Duplicati.WebserverCore.Dto; using Microsoft.AspNetCore.Mvc; namespace Duplicati.WebserverCore.Endpoints.V1; @@ -16,9 +38,13 @@ public class LogData : IEndpointV1 group.MapGet("/logdata/log", ([FromServices] Connection connection, [FromQuery] long? offset, [FromQuery] int? pagesize) => ExecuteGetLog(connection, offset, pagesize ?? 100)) .RequireAuthorization(); + + group.MapGet("/logdata/crashlog", () + => ExecuteGetCrashLog()) + .RequireAuthorization(); } - private static Dto.LogEntry[] ExecuteLogPoll(Library.Logging.LogMessageType level, long id, long offset, int pagesize) + private static LogEntry[] ExecuteLogPoll(Library.Logging.LogMessageType level, long id, long offset, int pagesize) { pagesize = Math.Max(1, Math.Min(500, pagesize)); return FIXMEGlobal.LogHandler.AfterID(id, level, pagesize).Select(x => Dto.LogEntry.FromInternalEntry(x)).ToArray(); @@ -35,25 +61,28 @@ public class LogData : IEndpointV1 return res; } + private static CrashLogOutputDto ExecuteGetCrashLog() + => new CrashLogOutputDto(CrashlogHelper.GetLastCrashLog()); + public static List> DumpTable(System.Data.IDbCommand cmd, string tablename, string pagingfield, long? offset, long pagesize) { var result = new List>(); pagesize = Math.Max(10, Math.Min(500, pagesize)); - cmd.CommandText = "SELECT * FROM \"" + tablename + "\""; + cmd.CommandText = $"SELECT * FROM \"{tablename}\""; if (!string.IsNullOrEmpty(pagingfield) && offset != null) { var p = cmd.CreateParameter(); p.Value = offset.Value; cmd.Parameters.Add(p); - cmd.CommandText += " WHERE \"" + pagingfield + "\" < ?"; + cmd.CommandText += $" WHERE \"{pagingfield}\" < ?"; } if (!string.IsNullOrEmpty(pagingfield)) - cmd.CommandText += " ORDER BY \"" + pagingfield + "\" DESC"; - cmd.CommandText += " LIMIT " + pagesize.ToString(); + cmd.CommandText += $" ORDER BY \"{pagingfield}\" DESC"; + cmd.CommandText += $" LIMIT {pagesize}"; using (var rd = cmd.ExecuteReader()) { diff --git a/Duplicati/WebserverCore/Endpoints/V1/MSSql.cs b/Duplicati/WebserverCore/Endpoints/V1/MSSql.cs index a5fd4c897..b4f4e6607 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/MSSql.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/MSSql.cs @@ -1,6 +1,27 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Runtime.Versioning; using System.Security.Principal; using Duplicati.Library.Snapshots; +using Duplicati.Library.Snapshots.Windows; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; @@ -36,7 +57,7 @@ public class MSSql : IEndpointV1 try { - mssqlUtility.QueryDBsInfo(); + mssqlUtility.QueryDBsInfo(SnapshotProvider.AlphaVSS); if (string.IsNullOrEmpty(key)) { diff --git a/Duplicati/WebserverCore/Endpoints/V1/Notification.cs b/Duplicati/WebserverCore/Endpoints/V1/Notification.cs index 08a7d16c2..f6329f508 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Notification.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Notification.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server.Database; using Duplicati.Server.Serialization.Interface; diff --git a/Duplicati/WebserverCore/Endpoints/V1/ProgressState.cs b/Duplicati/WebserverCore/Endpoints/V1/ProgressState.cs index 0dd9ab92d..b24980e9e 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/ProgressState.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/ProgressState.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs index bedd9622b..b8e57796b 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs @@ -1,3 +1,24 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.RestAPI; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; @@ -12,6 +33,10 @@ public class RemoteControl : IEndpointV1 => GetStatus(registration, remoteController)) .RequireAuthorization(); + // Don't allow these in agent-mode + if (FIXMEGlobal.Origin == "Agent") + return; + group.MapPost("/remotecontrol/enable", ([FromServices] IRemoteControllerRegistration registration, [FromServices] IRemoteController remoteController) => EnableRemoteControl(registration, remoteController)) .RequireAuthorization(); diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs index 0702fe989..8771de22d 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs @@ -1,4 +1,26 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using Duplicati.Library.RestAPI; using Duplicati.Server; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; @@ -27,11 +49,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1 private static Dto.GetDbPathDto ExecuteDbPath(string uri) { - var path = Library.Main.DatabaseLocator.GetDatabasePath(uri, null, false, false); + var path = CLIDatabaseLocator.GetDatabasePathForCLI(uri, null, false, false); return new Dto.GetDbPathDto(!string.IsNullOrWhiteSpace(path), path); } - private static Dictionary ParseUrlOptions(Library.Utility.Uri uri) + private static Dictionary ParseUrlOptions(Library.Utility.Uri uri) { var qp = uri.QueryParameters; @@ -42,7 +64,7 @@ namespace Duplicati.WebserverCore.Endpoints.V1 return opts; } - private static IEnumerable ConfigureModules(IDictionary opts) + private static IEnumerable ConfigureModules(IDictionary opts) { // TODO: This works because the generic modules are implemented // with pre .NetCore logic, using static methods @@ -65,10 +87,15 @@ namespace Duplicati.WebserverCore.Endpoints.V1 } } - private static TupleDisposeWrapper GetBackend(string url) + private static async Task GetBackend(string url, CancellationToken cancelToken) { var uri = new Library.Utility.Uri(url); var opts = ParseUrlOptions(uri); + + var tmp = new[] { uri }; + await SecretProviderHelper.ApplySecretProviderAsync([], tmp, opts, Library.Utility.TempFolder.SystemTempPath, FIXMEGlobal.SecretProvider, cancelToken); + url = tmp[0].ToString(); + var modules = ConfigureModules(opts); var backend = Library.DynamicLoader.BackendLoader.GetBackend(url, new Dictionary()); return new TupleDisposeWrapper(backend, modules); @@ -80,7 +107,7 @@ namespace Duplicati.WebserverCore.Endpoints.V1 try { - wrapper = GetBackend(url); + wrapper = await GetBackend(url, cancelToken); using (var b = wrapper.Backend) { diff --git a/Duplicati/WebserverCore/Endpoints/V1/ServerSetting.cs b/Duplicati/WebserverCore/Endpoints/V1/ServerSetting.cs index 5c8ce41aa..b43f1d46c 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/ServerSetting.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/ServerSetting.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -185,7 +185,7 @@ public class ServerSetting : IEndpointV1 if (prop == null) throw new NotFoundException("Key not found"); - var dict = new Dictionary + var dict = new Dictionary { { key, value } }; diff --git a/Duplicati/WebserverCore/Endpoints/V1/ServerState.cs b/Duplicati/WebserverCore/Endpoints/V1/ServerState.cs index 04f4c9376..55615149e 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/ServerState.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/ServerState.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Dto; @@ -18,7 +38,7 @@ public class ServerState : IEndpointV1 group.MapPost("/serverstate/pause", ([FromServices] IStatusService statusService, [FromServices] LiveControls liveControls, - [FromQuery] string? duration) => ExecutePause(liveControls, duration)).RequireAuthorization(); + [FromQuery] string? duration, [FromQuery] bool? pauseTransfers) => ExecutePause(liveControls, duration, pauseTransfers)).RequireAuthorization(); group.MapPost("/serverstate/resume", ([FromServices] IStatusService statusService, [FromServices] LiveControls liveControls) => @@ -60,7 +80,7 @@ public class ServerState : IEndpointV1 return status; } - private static void ExecutePause(LiveControls liveControls, string? duration) + private static void ExecutePause(LiveControls liveControls, string? duration, bool? pauseTransfer) { var ts = TimeSpan.Zero; if (duration != null) @@ -74,9 +94,9 @@ public class ServerState : IEndpointV1 } if (ts.TotalMilliseconds > 0) - liveControls.Pause(ts); + liveControls.Pause(ts, pauseTransfer ?? false); else - liveControls.Pause(); + liveControls.Pause(pauseTransfer ?? false); } private static void ExecuteResume(LiveControls liveControls) diff --git a/Duplicati/WebserverCore/Endpoints/V1/SystemInfo.cs b/Duplicati/WebserverCore/Endpoints/V1/SystemInfo.cs index 14c27cf6e..d2ddaf234 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/SystemInfo.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/SystemInfo.cs @@ -1,6 +1,24 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Globalization; -using Duplicati.Library.RestAPI; -using Duplicati.Server; using Duplicati.WebserverCore.Abstractions; using Microsoft.AspNetCore.Mvc; @@ -10,65 +28,14 @@ public class SystemInfo : IEndpointV1 { public static void Map(RouteGroupBuilder group) { - group.MapGet("/systeminfo", ([FromServices] ILanguageService languageService) => Execute(languageService.GetLanguage())).RequireAuthorization(); + group.MapGet("/systeminfo", ([FromServices] ILanguageService languageService, [FromServices] ISystemInfoProvider systemInfoProvider) => Execute(systemInfoProvider, languageService.GetLanguage())).RequireAuthorization(); group.MapGet("/systeminfo/filtergroups", () => ExecuteFilterGroups()).RequireAuthorization(); } - private static Dto.SystemInfoDto Execute(CultureInfo? browserlanguage) - { - browserlanguage ??= CultureInfo.InvariantCulture; + private static Dto.SystemInfoDto Execute(ISystemInfoProvider systemInfoProvider, CultureInfo? browserlanguage) + => systemInfoProvider.GetSystemInfo(browserlanguage); - return new Dto.SystemInfoDto() - { - APIVersion = 1, - PasswordPlaceholder = FIXMEGlobal.PASSWORD_PLACEHOLDER, - ServerVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString(), - ServerVersionName = License.VersionNumbers.Version, - ServerVersionType = Library.AutoUpdater.UpdaterManager.SelfVersion.ReleaseType, - RemoteControlRegistrationUrl = Duplicati.Library.RemoteControl.RegisterForRemote.DefaultRegisterationUrl, - StartedBy = FIXMEGlobal.Origin, - DefaultUpdateChannel = Library.AutoUpdater.AutoUpdateSettings.DefaultUpdateChannel.ToString(), - DefaultUsageReportLevel = Library.UsageReporter.Reporter.DefaultReportLevel, - ServerTime = DateTime.Now, - OSType = OperatingSystem.IsWindows() ? "Windows" : OperatingSystem.IsLinux() ? "Linux" : OperatingSystem.IsMacOS() ? "MacOS" : "Unknown", - OSVersion = Library.UsageReporter.OSInfoHelper.PlatformString, - DirectorySeparator = Path.DirectorySeparatorChar, - PathSeparator = Path.PathSeparator, - CaseSensitiveFilesystem = Library.Utility.Utility.IsFSCaseSensitive, - MachineName = Environment.MachineName, - PackageTypeId = Library.AutoUpdater.UpdaterManager.PackageTypeId, - UserName = OperatingSystem.IsWindows() ? System.Security.Principal.WindowsIdentity.GetCurrent().Name : Environment.UserName, - NewLine = Environment.NewLine, - CLRVersion = Environment.Version.ToString(), - Options = Server.Serializable.ServerSettings.Options, - CompressionModules = Server.Serializable.ServerSettings.CompressionModules, - EncryptionModules = Server.Serializable.ServerSettings.EncryptionModules, - BackendModules = Server.Serializable.ServerSettings.BackendModules, - GenericModules = Server.Serializable.ServerSettings.GenericModules, - WebModules = Server.Serializable.ServerSettings.WebModules, - ConnectionModules = Server.Serializable.ServerSettings.ConnectionModules, - ServerModules = Server.Serializable.ServerSettings.ServerModules, - UsingAlternateUpdateURLs = Library.AutoUpdater.AutoUpdateSettings.UsesAlternateURLs, - LogLevels = Enum.GetNames(typeof(Library.Logging.LogMessageType)), - SpecialFolders = SpecialFolders.Nodes.Select(n => new Dto.SystemInfoDto.SpecialFolderDto { ID = n.id, Path = n.resolvedpath }), - BrowserLocale = new Dto.SystemInfoDto.LocaleDto() - { - Code = browserlanguage.Name, - EnglishName = browserlanguage.EnglishName, - DisplayName = browserlanguage.NativeName - }, - SupportedLocales = Library.Localization.LocalizationService.SupportedCultures - .Select(x => new Dto.SystemInfoDto.LocaleDto - { - Code = x, - EnglishName = new CultureInfo(x).EnglishName, - DisplayName = new CultureInfo(x).NativeName - }), - BrowserLocaleSupported = Library.Localization.LocalizationService.isCultureSupported(browserlanguage) - }; - } - - private static object ExecuteFilterGroups() - => new { FilterGroups = Library.Utility.FilterGroups.GetFilterStringMap() }; + private static Dto.FilterGroupsDto ExecuteFilterGroups() + => new Dto.FilterGroupsDto(Library.Utility.FilterGroups.GetFilterStringMap()); } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs b/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs index 56deafdfe..6a466c4b9 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; @@ -9,16 +29,14 @@ public class Tasks : IEndpointV1 { private enum TaskStopState { - StopAfterCurrentFile, - StopNow, + Stop, Abort } public static void Map(RouteGroupBuilder group) { group.MapGet("/tasks", Execute).RequireAuthorization(); group.MapGet("/task/{taskid}", ([FromRoute] long taskId) => ExecuteGet(taskId)).RequireAuthorization(); - group.MapPost("/task/{taskid}/stopaftercurrentfile", ([FromRoute] long taskId) => ExecutePost(taskId, TaskStopState.StopAfterCurrentFile)).RequireAuthorization(); - group.MapPost("/task/{taskid}/stopnow", ([FromRoute] long taskId) => ExecutePost(taskId, TaskStopState.StopNow)).RequireAuthorization(); + group.MapPost("/task/{taskid}/stop", ([FromRoute] long taskId) => ExecutePost(taskId, TaskStopState.Stop)).RequireAuthorization(); group.MapPost("/task/{taskid}/abort", ([FromRoute] long taskId) => ExecutePost(taskId, TaskStopState.Abort)).RequireAuthorization(); } @@ -77,11 +95,8 @@ public class Tasks : IEndpointV1 switch (stopState) { - case TaskStopState.StopAfterCurrentFile: - task.Stop(allowCurrentFileToFinish: true); - break; - case TaskStopState.StopNow: - task.Stop(allowCurrentFileToFinish: false); + case TaskStopState.Stop: + task.Stop(); break; case TaskStopState.Abort: task.Abort(); diff --git a/Duplicati/WebserverCore/Endpoints/V1/UISettings.cs b/Duplicati/WebserverCore/Endpoints/V1/UISettings.cs index e05e356b9..ca9d4cce7 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/UISettings.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/UISettings.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; using Microsoft.AspNetCore.Mvc; @@ -10,7 +30,7 @@ public class UISettings : IEndpointV1 { group.MapGet("/uisettings", ([FromServices] Connection connection) => ExecuteGet(connection)).RequireAuthorization(); group.MapGet("/uisettings/{scheme}", ([FromServices] Connection connection, [FromRoute] string scheme) => ExecuteSchemeGet(connection, scheme)).RequireAuthorization(); - group.MapPatch("/uisettings/{scheme}", ([FromServices] Connection connection, [FromRoute] string scheme, [FromBody] Dictionary settings) => ExecutePatch(connection, scheme, settings)).RequireAuthorization(); + group.MapPatch("/uisettings/{scheme}", ([FromServices] Connection connection, [FromRoute] string scheme, [FromBody] Dictionary settings) => ExecutePatch(connection, scheme, settings)).RequireAuthorization(); } private static IEnumerable ExecuteGet(Connection connection) @@ -19,6 +39,6 @@ public class UISettings : IEndpointV1 private static IDictionary ExecuteSchemeGet(Connection connection, string scheme) => connection.GetUISettings(scheme); - private static void ExecutePatch(Connection connection, string scheme, IDictionary settings) + private static void ExecutePatch(Connection connection, string scheme, IDictionary settings) => connection.UpdateUISettings(scheme, settings); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Endpoints/V1/UpdateCertificate.cs b/Duplicati/WebserverCore/Endpoints/V1/UpdateCertificate.cs index 9d2bac571..e5e5eb9c6 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/UpdateCertificate.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/UpdateCertificate.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Security.Cryptography.X509Certificates; using Duplicati.Library.Logging; using Duplicati.Server.Database; @@ -19,7 +39,7 @@ public class UpdateCertificate : IEndpointV1 private static void ExecuteUpdate(Connection connection, Dto.UpdateCertificateInputDto input) { - X509Certificate2 certificate; + X509Certificate2Collection certificate; try { // Load certificate first, to give better error message if it is invalid diff --git a/Duplicati/WebserverCore/Endpoints/V1/Updates.cs b/Duplicati/WebserverCore/Endpoints/V1/Updates.cs index 987339a76..86e252dbf 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Updates.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Updates.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs index e91788289..7c35c171f 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs @@ -1,4 +1,27 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using Duplicati.Library.RestAPI; +using Duplicati.Server; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; using Microsoft.AspNetCore.Mvc; @@ -10,17 +33,23 @@ public record WebModules : IEndpointV1 public static void Map(RouteGroupBuilder group) { group.MapGet("/webmodules", ExecuteGet).RequireAuthorization(); - group.MapPost("/webmodule/{modulekey}", ([FromRoute] string modulekey, [FromBody] Dictionary options) => ExecutePost(modulekey, options)).RequireAuthorization(); + group.MapPost("/webmodule/{modulekey}", ([FromRoute] string modulekey, [FromBody] Dictionary options, CancellationToken cancellationToken) => ExecutePost(modulekey, options, cancellationToken)).RequireAuthorization(); } private static IEnumerable ExecuteGet() => Library.DynamicLoader.WebLoader.Modules; - private static Dto.WebModuleOutputDto ExecutePost(string modulekey, Dictionary options) + private static async Task ExecutePost(string modulekey, Dictionary inputOptions, CancellationToken cancellationToken) { var m = Library.DynamicLoader.WebLoader.Modules.FirstOrDefault(x => x.Key.Equals(modulekey, StringComparison.OrdinalIgnoreCase)) ?? throw new NotFoundException("No such module found"); + var options = Runner.GetCommonOptions(); + foreach (var k in inputOptions.Keys) + options[k] = inputOptions[k]; + + await SecretProviderHelper.ApplySecretProviderAsync([], [], options, Library.Utility.TempFolder.SystemTempPath, FIXMEGlobal.SecretProvider, cancellationToken); + return new Dto.WebModuleOutputDto( Status: "OK", Result: m.Execute(options) diff --git a/Duplicati/WebserverCore/Exceptions/BadRequestException.cs b/Duplicati/WebserverCore/Exceptions/BadRequestException.cs index 4c8a1c1de..ef197778b 100644 --- a/Duplicati/WebserverCore/Exceptions/BadRequestException.cs +++ b/Duplicati/WebserverCore/Exceptions/BadRequestException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class BadRequestException(string? Message = null) : UserReportedHttpException(Message ?? "Bad Request") diff --git a/Duplicati/WebserverCore/Exceptions/ConflictException.cs b/Duplicati/WebserverCore/Exceptions/ConflictException.cs index f0936c33a..7a8d37ab9 100644 --- a/Duplicati/WebserverCore/Exceptions/ConflictException.cs +++ b/Duplicati/WebserverCore/Exceptions/ConflictException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions { public class ConflictException(string Message) : UserReportedHttpException(Message ?? "Conflict") diff --git a/Duplicati/WebserverCore/Exceptions/ForbiddenException.cs b/Duplicati/WebserverCore/Exceptions/ForbiddenException.cs index 3d07ba60d..bff9f3167 100644 --- a/Duplicati/WebserverCore/Exceptions/ForbiddenException.cs +++ b/Duplicati/WebserverCore/Exceptions/ForbiddenException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class ForbiddenException(string Message) : UserReportedHttpException(Message) { diff --git a/Duplicati/WebserverCore/Exceptions/InvalidHostnameException.cs b/Duplicati/WebserverCore/Exceptions/InvalidHostnameException.cs index aeeefaee2..e1a5f1d33 100644 --- a/Duplicati/WebserverCore/Exceptions/InvalidHostnameException.cs +++ b/Duplicati/WebserverCore/Exceptions/InvalidHostnameException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class InvalidHostnameException(string? Hostname = null) : UserReportedHttpException($"Invalid hostname: {Hostname}") diff --git a/Duplicati/WebserverCore/Exceptions/NotFoundException.cs b/Duplicati/WebserverCore/Exceptions/NotFoundException.cs index 8051a9a90..0122f09eb 100644 --- a/Duplicati/WebserverCore/Exceptions/NotFoundException.cs +++ b/Duplicati/WebserverCore/Exceptions/NotFoundException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class NotFoundException(string? Message) : UserReportedHttpException(Message ?? "Not found") diff --git a/Duplicati/WebserverCore/Exceptions/ServerErrorException.cs b/Duplicati/WebserverCore/Exceptions/ServerErrorException.cs index 7cf9bcfc3..e5244162e 100644 --- a/Duplicati/WebserverCore/Exceptions/ServerErrorException.cs +++ b/Duplicati/WebserverCore/Exceptions/ServerErrorException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class ServerErrorException(string Message) : UserReportedHttpException(Message) diff --git a/Duplicati/WebserverCore/Exceptions/ServiceUnavailableException.cs b/Duplicati/WebserverCore/Exceptions/ServiceUnavailableException.cs index ac6059bc3..df41546eb 100644 --- a/Duplicati/WebserverCore/Exceptions/ServiceUnavailableException.cs +++ b/Duplicati/WebserverCore/Exceptions/ServiceUnavailableException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions { public class ServiceUnavailableException(string Message) : UserReportedHttpException(Message) diff --git a/Duplicati/WebserverCore/Exceptions/TextOutputErrorException.cs b/Duplicati/WebserverCore/Exceptions/TextOutputErrorException.cs index b870ff4e0..db882e724 100644 --- a/Duplicati/WebserverCore/Exceptions/TextOutputErrorException.cs +++ b/Duplicati/WebserverCore/Exceptions/TextOutputErrorException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; diff --git a/Duplicati/WebserverCore/Exceptions/UnauthorizedException.cs b/Duplicati/WebserverCore/Exceptions/UnauthorizedException.cs index 26e37ebb8..0a9228393 100644 --- a/Duplicati/WebserverCore/Exceptions/UnauthorizedException.cs +++ b/Duplicati/WebserverCore/Exceptions/UnauthorizedException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions; public class UnauthorizedException(string Message) : UserReportedHttpException(Message) diff --git a/Duplicati/WebserverCore/Exceptions/UserReportedHttpException.cs b/Duplicati/WebserverCore/Exceptions/UserReportedHttpException.cs index 8a4366ffe..c370c3250 100644 --- a/Duplicati/WebserverCore/Exceptions/UserReportedHttpException.cs +++ b/Duplicati/WebserverCore/Exceptions/UserReportedHttpException.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Exceptions { public abstract class UserReportedHttpException(string? Message = null) diff --git a/Duplicati/WebserverCore/Extensions/ServiceCollectionsExtensions.cs b/Duplicati/WebserverCore/Extensions/ServiceCollectionsExtensions.cs index c2e08a7eb..7f3e80ad3 100644 --- a/Duplicati/WebserverCore/Extensions/ServiceCollectionsExtensions.cs +++ b/Duplicati/WebserverCore/Extensions/ServiceCollectionsExtensions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.IO; using Duplicati.Library.RestAPI; using Duplicati.Library.RestAPI.Abstractions; @@ -41,13 +61,14 @@ public static class ServiceCollectionsExtensions .AddSingleton() .AddSingleton() .AddTransient() - .AddSingleton() .AddSingleton() .AddTransient() .AddTransient() .AddTransient() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton() + .AddSingleton(); return services; } diff --git a/Duplicati/WebserverCore/Extensions/StringExtension.cs b/Duplicati/WebserverCore/Extensions/StringExtension.cs index 932fe0ff6..5586a6ff9 100644 --- a/Duplicati/WebserverCore/Extensions/StringExtension.cs +++ b/Duplicati/WebserverCore/Extensions/StringExtension.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text; namespace Duplicati.WebserverCore.Extensions; diff --git a/Duplicati/WebserverCore/Extensions/WebApplicationExtensions.cs b/Duplicati/WebserverCore/Extensions/WebApplicationExtensions.cs index c29a3db78..254551ef9 100644 --- a/Duplicati/WebserverCore/Extensions/WebApplicationExtensions.cs +++ b/Duplicati/WebserverCore/Extensions/WebApplicationExtensions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Reflection; using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Middlewares; @@ -6,12 +26,12 @@ namespace Duplicati.WebserverCore.Extensions; public static class WebApplicationExtensions { - public static WebApplication AddEndpoints(this WebApplication application) + public static WebApplication AddEndpoints(this WebApplication application, bool useCors) { - return AddV1(application); + return AddV1(application, useCors); } - private static WebApplication AddV1(WebApplication application) + private static WebApplication AddV1(WebApplication application, bool useCors) { var mapperInterfaceType = typeof(IEndpointV1); var endpoints = @@ -26,6 +46,9 @@ public static class WebApplicationExtensions if (!string.IsNullOrWhiteSpace(PreSharedKeyFilter.PreSharedKey)) group = group.AddEndpointFilter(); + if (useCors) + group.RequireCors(DuplicatiWebserver.CorsPolicyName); + foreach (var endpoint in endpoints) { var methodMap = endpoint.GetMethod(nameof(IEndpointV1.Map), BindingFlags.Static | BindingFlags.Public); diff --git a/Duplicati/WebserverCore/Middlewares/HostnameFilter.cs b/Duplicati/WebserverCore/Middlewares/HostnameFilter.cs index 614f26613..4e8577aeb 100644 --- a/Duplicati/WebserverCore/Middlewares/HostnameFilter.cs +++ b/Duplicati/WebserverCore/Middlewares/HostnameFilter.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; using Duplicati.WebserverCore.Exceptions; diff --git a/Duplicati/WebserverCore/Middlewares/JWTProvider.cs b/Duplicati/WebserverCore/Middlewares/JWTProvider.cs index 470aadd95..95abe8888 100644 --- a/Duplicati/WebserverCore/Middlewares/JWTProvider.cs +++ b/Duplicati/WebserverCore/Middlewares/JWTProvider.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; @@ -32,6 +52,7 @@ public record JWTConfig public class JWTTokenProvider(JWTConfig jWTConfig) : IJWTTokenProvider { private const string TemporaryFamilyId = "temporary"; + private const string ForeverTokenUserId = "forever-token"; public string CreateSingleOperationToken(string userId, string operation) => GenerateToken([ new Claim(Claims.Type, TokenType.SingleOperationToken.ToString()), @@ -52,6 +73,13 @@ public class JWTTokenProvider(JWTConfig jWTConfig) : IJWTTokenProvider new Claim(Claims.Family, tokenFamilyId) ], DateTime.Now, expires: DateTime.Now.AddMinutes(Math.Min(jWTConfig.AccessTokenDurationInMinutes, expiration?.TotalMinutes ?? jWTConfig.AccessTokenDurationInMinutes))); + public string CreateForeverToken() + => GenerateToken([ + new Claim(Claims.Type, TokenType.AccessToken.ToString()), + new Claim(Claims.UserId, ForeverTokenUserId), + new Claim(Claims.Family, TemporaryFamilyId) + ], DateTime.Now, expires: DateTime.Now.AddYears(10)); + public string CreateRefreshToken(string userId, string tokenFamilyId, int counter) => GenerateToken([ new Claim(Claims.Type, TokenType.RefreshToken.ToString()), diff --git a/Duplicati/WebserverCore/Middlewares/LanguageFilter.cs b/Duplicati/WebserverCore/Middlewares/LanguageFilter.cs index 215db3ea5..a376baee5 100644 --- a/Duplicati/WebserverCore/Middlewares/LanguageFilter.cs +++ b/Duplicati/WebserverCore/Middlewares/LanguageFilter.cs @@ -1,3 +1,24 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Globalization; using Duplicati.Library.Localization; namespace Duplicati.WebserverCore.Middlewares; @@ -10,7 +31,7 @@ public class LanguageFilter : IEndpointFilter ? LocalizationService.ParseCulture(locale) : null; - using (ci == null ? null : LocalizationService.TemporaryContext(ci)) + using (LocalizationService.TemporaryContext(ci ?? CultureInfo.CurrentUICulture)) return await next(context); } diff --git a/Duplicati/WebserverCore/Middlewares/PreSharedKeyFilter.cs b/Duplicati/WebserverCore/Middlewares/PreSharedKeyFilter.cs index 38a8d486f..bbc374afa 100644 --- a/Duplicati/WebserverCore/Middlewares/PreSharedKeyFilter.cs +++ b/Duplicati/WebserverCore/Middlewares/PreSharedKeyFilter.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Middlewares; diff --git a/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.Debug.cs b/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.Debug.cs index 00e892369..dab8f0c08 100644 --- a/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.Debug.cs +++ b/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.Debug.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. #if DEBUG using System.Formats.Tar; using System.IO.Compression; @@ -21,6 +41,17 @@ internal static class NpmSpaHelper { var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + // Handle move not working across drive boundaries on Windows + if (OperatingSystem.IsWindows() && Path.GetPathRoot(tempPath) != Path.GetPathRoot(targetPath)) + { + tempPath = Path.GetFullPath(targetPath); + if (targetPath.EndsWith(Path.DirectorySeparatorChar)) + tempPath = tempPath[..-1]; + tempPath += "-tmp"; + if (Directory.Exists(tempPath)) + Directory.Delete(tempPath, true); + } + try { // Download and extract the package @@ -136,7 +167,7 @@ internal static class NpmSpaHelper return null; // Package is not installed, install it - var packageFolder = Path.Combine(basepath, "node_modules", packageId); + var packageFolder = Path.GetFullPath(Path.Combine(basepath, "node_modules", packageId)); if (!Directory.Exists(packageFolder)) return InstallNpmPackage(packageUrl, packageFolder); diff --git a/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.cs b/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.cs index 52117cf59..7c6119076 100644 --- a/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.cs +++ b/Duplicati/WebserverCore/Middlewares/StaticFilesMiddleware.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; @@ -123,7 +143,7 @@ public static class StaticFilesExtensions { // Serve the static file var file = new FileInfo(Path.Combine(spaConfig.BasePath, path.Value.Substring(spaConfig.Prefix.Length).TrimStart('/'))); - if (file.FullName.StartsWith(spaConfig.BasePath) && file.Exists && fileTypeMappings.Mappings.TryGetValue(Path.GetExtension(file.Extension), out var contentType)) + if (file.FullName.StartsWith(spaConfig.BasePath, Library.Utility.Utility.ClientFilenameStringComparison) && file.Exists && fileTypeMappings.Mappings.TryGetValue(Path.GetExtension(file.Extension), out var contentType)) { context.Response.ContentType = contentType; context.Response.StatusCode = 200; diff --git a/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs b/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs index bdc72e47d..5187fcdc6 100644 --- a/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs +++ b/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Net.WebSockets; using System.Text; using Duplicati.WebserverCore.Abstractions.Notifications; diff --git a/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs b/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs index 8b4da7c2b..257bb3782 100644 --- a/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs +++ b/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Collections.Concurrent; using System.Net.WebSockets; using Duplicati.Library.Logging; diff --git a/Duplicati/WebserverCore/Options/NotificationsOptions.cs b/Duplicati/WebserverCore/Options/NotificationsOptions.cs index 916eed0b7..c795ece89 100644 --- a/Duplicati/WebserverCore/Options/NotificationsOptions.cs +++ b/Duplicati/WebserverCore/Options/NotificationsOptions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Options; public class NotificationsOptions diff --git a/Duplicati/WebserverCore/Options/StaticFilesOptions.cs b/Duplicati/WebserverCore/Options/StaticFilesOptions.cs index 9b7f7551e..4c9bdd022 100644 --- a/Duplicati/WebserverCore/Options/StaticFilesOptions.cs +++ b/Duplicati/WebserverCore/Options/StaticFilesOptions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Options; public class StaticFilesOptions diff --git a/Duplicati/WebserverCore/Services/CaptchaService.cs b/Duplicati/WebserverCore/Services/CaptchaService.cs deleted file mode 100644 index ba253f612..000000000 --- a/Duplicati/WebserverCore/Services/CaptchaService.cs +++ /dev/null @@ -1,218 +0,0 @@ -using Duplicati.WebserverCore.Abstractions; -using Duplicati.WebserverCore.Exceptions; -using SixLabors.Fonts; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Drawing.Processing; - -namespace Duplicati.WebserverCore.Services; - -public class CaptchaService : ICaptchaProvider -{ - private readonly object m_lock = new(); - private readonly Dictionary m_captchas = []; - private readonly bool m_disableVisualCaptcha; - - public CaptchaService(ISettingsService settings) - { - m_disableVisualCaptcha = settings.GetSettings().DisableVisualCaptcha; - } - - /// - /// List of possible system fonts, ordered by preference - /// - private static readonly Dictionary FontNamePreference = new string[] { - "Arial", "Verdana", "FreeSans", "Tahoma", "Helvetica", "Times New Roman", "Courier New", "Andale Mono" - } - .Select((x, i) => new { Key = x, Value = i }) - .ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase); - - private class CaptchaEntry - { - public readonly string Answer; - public readonly string Target; - public int Attempts; - public readonly DateTime Expires; - - public CaptchaEntry(string answer, string target) - { - Answer = answer; - Target = target; - Attempts = 4; - Expires = DateTime.Now.AddMinutes(2); - } - } - - public (string Token, string? Answer) CreateCaptcha(string target) - { - var answer = CaptchaUtil.CreateRandomAnswer(minlength: 6, maxlength: 6); - var nonce = Guid.NewGuid().ToString(); - - string token; - using (var ms = new MemoryStream()) - { - var bytes = System.Text.Encoding.UTF8.GetBytes(answer + nonce); - ms.Write(bytes, 0, bytes.Length); - ms.Position = 0; - using (var hasher = Library.Utility.HashFactory.CreateHasher(Library.Utility.HashFactory.SHA256)) - token = Library.Utility.Utility.Base64PlainToBase64Url(Convert.ToBase64String(hasher.ComputeHash(ms))); - } - - lock (m_lock) - { - var expired = m_captchas.Where(x => x.Value.Expires < DateTime.Now).Select(x => x.Key).ToArray(); - foreach (var x in expired) - m_captchas.Remove(x); - - if (m_captchas.Count > 3) - throw new ServiceUnavailableException("Too many captchas, wait 2 minutes and try again"); - - m_captchas[token] = new CaptchaEntry(answer, target); - } - - return (token, m_disableVisualCaptcha ? answer : null); - } - - public byte[] GetCaptchaImage(string token) - { - if (m_disableVisualCaptcha) - throw new NotFoundException("No such entry"); - - string? answer = null; - lock (m_lock) - { - m_captchas.TryGetValue(token, out var tp); - if (tp != null && tp.Expires > DateTime.Now) - answer = tp.Answer; - } - - if (string.IsNullOrWhiteSpace(answer)) - throw new NotFoundException("No such entry"); - - using var image = CaptchaUtil.CreateCaptcha(answer); - using var ms = new MemoryStream(); - image.SaveAsJpeg(ms); - return ms.ToArray(); - } - - public bool SolvedCaptcha(string token, string target, string answer) - { - lock (m_lock) - { - m_captchas.TryGetValue(token ?? string.Empty, out var tp); - if (tp == null) - return false; - - if (tp.Attempts > 0) - tp.Attempts--; - - return tp.Attempts >= 0 && string.Equals(tp.Answer, answer, StringComparison.OrdinalIgnoreCase) && tp.Target == target && tp.Expires >= DateTime.Now; - } - } - - public bool VisualCaptchaDisabled => m_disableVisualCaptcha; - - - public static class CaptchaUtil - { - /// - /// A lookup string with characters to use - /// - private static readonly string DEFAULT_CHARS = "ACDEFGHJKLMNPQRTUVWXY34679"; - - /// - /// Approximate the size in pixels of text drawn at the given fontsize - /// - /// The text to measure - /// The font to use - private static int ApproxTextWidth(string text, Font font) - => (int)TextMeasurer.MeasureSize(text, new TextOptions(font) { KerningMode = KerningMode.Standard }).Width; - - /// - /// Creates a random answer. - /// - /// The random answer. - /// The list of allowed chars, supply a character multiple times to change frequency. - /// The minimum answer length. - /// The maximum answer length. - public static string CreateRandomAnswer(string? allowedchars = null, int minlength = 10, int maxlength = 12) - { - allowedchars = allowedchars ?? DEFAULT_CHARS; - var rnd = new Random(); - var len = rnd.Next(Math.Min(minlength, maxlength), Math.Max(minlength, maxlength) + 1); - if (len <= 0) - throw new ArgumentException($"The values {minlength} and {maxlength} gave a final length of {len} and it must be greater than 0"); - - return new string(Enumerable.Range(0, len).Select(x => allowedchars[rnd.Next(0, allowedchars.Length)]).ToArray()); - } - - /// - /// Creates a captcha image. - /// - /// The captcha image. - /// The captcha solution string. - /// The size of the image, omit to get a size based on the string. - /// The size of the font used to create the captcha, in pixels. - public static Image CreateCaptcha(string answer, Size size = default(Size), float fontsize = 40) - { - var fontFamily = SystemFonts.Collection.Families.OrderBy(x => - { - if (FontNamePreference.TryGetValue(x.Name, out var val)) - return val; - return int.MaxValue; - }).FirstOrDefault(); - - if (string.IsNullOrWhiteSpace(fontFamily.Name)) - throw new Exception("No usable font found"); - - var font = fontFamily.CreateFont(fontsize); - var text_width = ApproxTextWidth(answer, font); - if (size.Width == 0 || size.Height == 0) - size = new Size((int)(text_width * 1.2), (int)(fontsize * 1.2)); - - var image = new Image(size.Width, size.Height); - var rnd = new Random(); - var stray_x = (int)fontsize / 2; - var stray_y = size.Height / 4; - var ans_stray_x = (int)fontsize / 3; - var ans_stray_y = size.Height / 6; - - image.Mutate(ctx => - { - ctx.Fill(Color.White); - - // Apply a background string to make it hard to do OCR - foreach (var color in new[] { Color.Yellow, Color.LightGreen, Color.GreenYellow }) - { - var backgroundFont = font; - ctx.DrawText(CreateRandomAnswer(minlength: answer.Length, maxlength: answer.Length), backgroundFont, color, new PointF(rnd.Next(-stray_x, stray_x), rnd.Next(-stray_y, stray_y))); - } - - var spacing = (size.Width / (int)fontsize) + rnd.Next(0, stray_x); - - // Create vertical background lines - for (var i = rnd.Next(0, stray_x); i < size.Width; i += spacing) - { - var color = Color.FromRgb((byte)rnd.Next(256), (byte)rnd.Next(256), (byte)rnd.Next(256)); - ctx.DrawLine(color, 1, new PointF(i + rnd.Next(-stray_x, stray_x), rnd.Next(0, stray_y)), new PointF(i + rnd.Next(-stray_x, stray_x), size.Height - rnd.Next(0, stray_y))); - } - - spacing = (size.Height / (int)fontsize) + rnd.Next(0, stray_y); - - // Create horizontal background lines - for (var i = rnd.Next(0, stray_y); i < size.Height; i += spacing) - { - var color = Color.FromRgb((byte)rnd.Next(256), (byte)rnd.Next(256), (byte)rnd.Next(256)); - ctx.DrawLine(color, 1, new PointF(rnd.Next(0, stray_x), i + rnd.Next(-stray_y, stray_y)), new PointF(size.Width - rnd.Next(0, stray_x), i + rnd.Next(-stray_y, stray_y))); - } - - // Draw the actual answer - var answerColor = Color.FromRgb((byte)rnd.Next(256), (byte)rnd.Next(256), (byte)rnd.Next(256)); - ctx.DrawText(answer, font, answerColor, new PointF(((size.Width - text_width) / 2) + rnd.Next(-ans_stray_x, ans_stray_x), ((size.Height - fontsize) / 2) + rnd.Next(-ans_stray_y, ans_stray_y))); - }); - - return image; - } - } -} diff --git a/Duplicati/WebserverCore/Services/CommandlineRunService.cs b/Duplicati/WebserverCore/Services/CommandlineRunService.cs index 29968748b..81bff62b4 100644 --- a/Duplicati/WebserverCore/Services/CommandlineRunService.cs +++ b/Duplicati/WebserverCore/Services/CommandlineRunService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text; using Duplicati.Library.RestAPI; using Duplicati.Library.RestAPI.Abstractions; diff --git a/Duplicati/WebserverCore/Services/HostnameValidator.cs b/Duplicati/WebserverCore/Services/HostnameValidator.cs index 90ddf3a3f..9675d9875 100644 --- a/Duplicati/WebserverCore/Services/HostnameValidator.cs +++ b/Duplicati/WebserverCore/Services/HostnameValidator.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Net; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs b/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs new file mode 100644 index 000000000..338b098cd --- /dev/null +++ b/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.RemoteControl; + +namespace Duplicati.WebserverCore.Services; + +/// +/// Interface for handling remote control messages. +/// +public interface IRemoteControllerHandler +{ + /// + /// Prepares metadata for a remote control connection. + /// + /// The initial metadata + /// The metadata to use + Task> OnConnect(Dictionary metadata); + + /// + /// Re-keys the remote control connection. + /// + /// The data to re-key with + /// An awaitable task + Task ReKey(ClaimedClientData data); + + /// + /// Handles a control message. + /// + /// The control message to handle + /// An awaitable task + Task OnControl(KeepRemoteConnection.ControlMessage message); + + /// + /// Handles a command message. + /// + /// The command message to handle + /// An awaitable task + Task OnMessage(KeepRemoteConnection.CommandMessage commandMessage); +} \ No newline at end of file diff --git a/Duplicati/WebserverCore/Services/LanguageService.cs b/Duplicati/WebserverCore/Services/LanguageService.cs index 3978a18c8..9b7e52f4b 100644 --- a/Duplicati/WebserverCore/Services/LanguageService.cs +++ b/Duplicati/WebserverCore/Services/LanguageService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; namespace Duplicati.WebserverCore.Services diff --git a/Duplicati/WebserverCore/Services/LoginProvider.cs b/Duplicati/WebserverCore/Services/LoginProvider.cs index e740c8881..b9501705c 100644 --- a/Duplicati/WebserverCore/Services/LoginProvider.cs +++ b/Duplicati/WebserverCore/Services/LoginProvider.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Logging; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs b/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs new file mode 100644 index 000000000..6664e2918 --- /dev/null +++ b/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs @@ -0,0 +1,120 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Net.Http.Headers; +using Duplicati.Library.Logging; +using Duplicati.Library.RemoteControl; +using Duplicati.Library.RestAPI; +using Duplicati.Server.Database; +using Duplicati.WebserverCore.Abstractions; +using Newtonsoft.Json; + +namespace Duplicati.WebserverCore.Services; + +/// +/// Controller for handling remote control messages. +/// +/// The connection to the database +/// The HTTP client factory +/// The JWT token provider +public class RemoteControllerHandler(Connection connection, IHttpClientFactory httpClientFactory, IJWTTokenProvider jwtTokenProvider) : IRemoteControllerHandler +{ + /// + /// The log tag for this class. + /// + private static readonly string LOGTAG = Log.LogTagFromType(); + + /// + public Task> OnConnect(Dictionary metadata) + { + metadata["feature:additional-report-url"] = connection.ApplicationSettings.AdditionalReportUrl; + return Task.FromResult(metadata); + } + + /// + public Task ReKey(ClaimedClientData data) + { + var oldCerts = connection.ApplicationSettings.RemoteControlConfig == null + ? null + : JsonConvert.DeserializeObject(connection.ApplicationSettings.RemoteControlConfig)?.ServerCertificates; + + connection.ApplicationSettings.RemoteControlConfig = JsonConvert.SerializeObject(new RemoteControlConfig + { + Token = data.JWT, + ServerCertificates = data.ServerCertificates ?? oldCerts ?? [], + ServerUrl = data.ServerUrl, + CertificateUrl = data.CertificateUrl + }); + + if (!FIXMEGlobal.SettingsEncryptionKeyProvidedExternally) + { + // TODO: Implement changing the encryption key + // if (!string.IsNullOrWhiteSpace(data.LocalEncryptionKey) && data.LocalEncryptionKey != connection.ApplicationSettings.SettingsEncryptionKey) + // connection.ChangeDbKey(keydata.LocalEncryptionKey); + } + + return Task.CompletedTask; + } + + /// + public async Task OnControl(KeepRemoteConnection.ControlMessage message) + { + // Make method async + await Task.CompletedTask; + + Dictionary? result = null; + + Log.WriteMessage(LogMessageType.Verbose, LOGTAG, "OnControl", "Received control message: {0}", message); + try + { + if (string.Equals(message.ControlRequestMessage.Command, ControlRequestMessage.ConfigureReportUrlSet, StringComparison.OrdinalIgnoreCase)) + FIXMEGlobal.DataConnection.ApplicationSettings.AdditionalReportUrl = message.ControlRequestMessage.Parameters.GetValueOrDefault(ControlRequestMessage.ConfigureReportUrlParameter); + + if (string.Equals(message.ControlRequestMessage.Command, ControlRequestMessage.ConfigureReportUrlGet, StringComparison.OrdinalIgnoreCase)) + result = new Dictionary { { ControlRequestMessage.ConfigureReportUrlParameter, FIXMEGlobal.DataConnection.ApplicationSettings.AdditionalReportUrl } }; + } + catch (Exception ex) + { + Log.WriteMessage(LogMessageType.Error, LOGTAG, "OnControl", ex, "Failed to handle control message: {0}", message); + message.Respond(new ControlResponseMessage(false, null, ex.Message)); + return; + } + + message.Respond(new ControlResponseMessage(true, result, null)); + } + + + /// + public async Task OnMessage(KeepRemoteConnection.CommandMessage commandMessage) + { + using var httpClient = httpClientFactory.CreateClient(); + var token = jwtTokenProvider.CreateAccessToken("remote-control", jwtTokenProvider.TemporaryFamilyId, TimeSpan.FromMinutes(2)); + + httpClient.BaseAddress = new Uri($"{(connection.ApplicationSettings.UseHTTPS ? "https" : "http")}://127.0.0.1:{connection.ApplicationSettings.LastWebserverPort}"); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var psk = Middlewares.PreSharedKeyFilter.PreSharedKey; + if (!string.IsNullOrWhiteSpace(psk)) + httpClient.DefaultRequestHeaders.Add(Middlewares.PreSharedKeyFilter.HeaderName, psk); + + await commandMessage.Handle(httpClient); + } +} diff --git a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs index d6df3389e..62c416317 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -55,7 +55,7 @@ public sealed record RemoteControlConfig /// /// The database connection /// The HTTP client factory -public class RemoteControllerRegistrationService(Connection connection, IHttpClientFactory httpClientFactory) : IRemoteControllerRegistration +public class RemoteControllerRegistrationService(Connection connection, IHttpClientFactory httpClientFactory, IRemoteController remoteController) : IRemoteControllerRegistration { /// /// The registration process controller this service is wrapping. @@ -117,6 +117,10 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli ServerUrl = claimData.ServerUrl }); + // Automatically connect after we are registered + if (remoteController.CanEnable) + remoteController.Enable(); + }); } diff --git a/Duplicati/WebserverCore/Services/RemoteControllerService.cs b/Duplicati/WebserverCore/Services/RemoteControllerService.cs index fc76784c5..9286f87ff 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerService.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Net.Http.Headers; using Duplicati.Library.RemoteControl; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; @@ -31,9 +30,8 @@ namespace Duplicati.WebserverCore.Services; /// Controller for toggling remote control. /// /// The connection to the database -/// The HTTP client factory -/// The JWT token provider -public class RemoteControllerService(Connection connection, IHttpClientFactory httpClientFactory, IJWTTokenProvider jwtTokenProvider) : IRemoteController +/// The remote controller handler +public class RemoteControllerService(Connection connection, IRemoteControllerHandler controllerHandler) : IRemoteController { /// /// Gets a value indicating whether remote control is enabled. @@ -73,7 +71,7 @@ public class RemoteControllerService(Connection connection, IHttpClientFactory h if (!CanEnable) throw new InvalidOperationException("Remote control is not configured"); - var config = JsonConvert.DeserializeObject(connection.ApplicationSettings.RemoteControlConfig) + var config = JsonConvert.DeserializeObject(connection.ApplicationSettings.RemoteControlConfig ?? string.Empty) ?? throw new InvalidOperationException("Invalid remote control configuration"); _keepRemoteConnection = KeepRemoteConnection.CreateRemoteListener( @@ -82,55 +80,15 @@ public class RemoteControllerService(Connection connection, IHttpClientFactory h config.CertificateUrl, config.ServerCertificates, CancellationToken.None, - ReKey, - OnMessage + controllerHandler.OnConnect, + controllerHandler.ReKey, + controllerHandler.OnControl, + controllerHandler.OnMessage ); connection.ApplicationSettings.RemoteControlEnabled = true; } - /// - /// Re-keys the remote control connection. - /// - /// The data to re-key with - /// An awaitable task - private Task ReKey(ClaimedClientData data) - { - var oldCerts = connection.ApplicationSettings.RemoteControlConfig == null - ? null - : JsonConvert.DeserializeObject(connection.ApplicationSettings.RemoteControlConfig)?.ServerCertificates; - - connection.ApplicationSettings.RemoteControlConfig = JsonConvert.SerializeObject(new RemoteControlConfig - { - Token = data.JWT, - ServerCertificates = data.ServerCertificates ?? oldCerts ?? [], - ServerUrl = data.ServerUrl, - CertificateUrl = data.CertificateUrl - }); - - // TODO: Implement changing the encryption key - // if (!string.IsNullOrWhiteSpace(data.LocalEncryptionKey) && data.LocalEncryptionKey != connection.ApplicationSettings.SettingsEncryptionKey) - // connection.ChangeDbKey(keydata.LocalEncryptionKey); - - return Task.CompletedTask; - } - - /// - /// Handles a command message. - /// - /// The command message to handle - /// An awaitable task - private async Task OnMessage(KeepRemoteConnection.CommandMessage commandMessage) - { - using var httpClient = httpClientFactory.CreateClient(); - var token = jwtTokenProvider.CreateAccessToken("remote-control", jwtTokenProvider.TemporaryFamilyId, TimeSpan.FromMinutes(2)); - - httpClient.BaseAddress = new Uri($"{(connection.ApplicationSettings.UseHTTPS ? "https" : "http")}://127.0.0.1:{connection.ApplicationSettings.LastWebserverPort}"); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - - await commandMessage.Handle(httpClient); - } - /// public void Disable() { diff --git a/Duplicati/WebserverCore/Services/SchedulerService.cs b/Duplicati/WebserverCore/Services/SchedulerService.cs index 7e4e05c0d..15296edf2 100644 --- a/Duplicati/WebserverCore/Services/SchedulerService.cs +++ b/Duplicati/WebserverCore/Services/SchedulerService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Utility; using Duplicati.Server; diff --git a/Duplicati/WebserverCore/Services/Settings/SettingsService.cs b/Duplicati/WebserverCore/Services/Settings/SettingsService.cs index b8160fbe2..543c76f3c 100644 --- a/Duplicati/WebserverCore/Services/Settings/SettingsService.cs +++ b/Duplicati/WebserverCore/Services/Settings/SettingsService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; namespace Duplicati.WebserverCore.Services.Settings; diff --git a/Duplicati/WebserverCore/Services/StatusService.cs b/Duplicati/WebserverCore/Services/StatusService.cs index cebb829d7..7c3845b8b 100644 --- a/Duplicati/WebserverCore/Services/StatusService.cs +++ b/Duplicati/WebserverCore/Services/StatusService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.RestAPI; using Duplicati.Library.RestAPI.Abstractions; using Duplicati.Server; @@ -34,7 +54,7 @@ public class StatusService( ProgramState = GetProgramState(), HasWarning = settingsService.GetSettings().UnackedWarning, HasError = settingsService.GetSettings().UnackedError, - EstimatedPauseEnd = liveControls.EstimatedPauseEnd, + EstimatedPauseEnd = liveControls.EstimatedPauseEnd.ToUniversalTime(), SuggestedStatusIcon = MapStateToIcon(), UpdateDownloadLink = settingsService.GetSettings().NewVersionUpdateUrl }; @@ -45,7 +65,7 @@ public class StatusService( private void PullLiveControls(ServerStatusDto status) { - status.EstimatedPauseEnd = liveControls.EstimatedPauseEnd; + status.EstimatedPauseEnd = liveControls.EstimatedPauseEnd.ToUniversalTime(); status.ProgramState = GetProgramState(); status.SuggestedStatusIcon = MapStateToIcon(); } diff --git a/Duplicati/WebserverCore/Services/SystemInfoProvider.cs b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs new file mode 100644 index 000000000..724732457 --- /dev/null +++ b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs @@ -0,0 +1,338 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Globalization; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Localization; +using Duplicati.Library.RestAPI; +using Duplicati.Server; +using Duplicati.Server.Serialization.Interface; +using Duplicati.WebserverCore.Abstractions; +using Duplicati.WebserverCore.Dto; + +namespace Duplicati.WebserverCore.Services; + +/// +/// Produces system information. +/// +public class SystemInfoProvider : ISystemInfoProvider +{ + /// + /// System information that does not change during runtime. + /// + private sealed record StaticSystemInformation + { + /// + /// Gets or sets the API version. + /// + public required int APIVersion { get; init; } + + /// + /// Gets or sets the password placeholder. + /// + public required string PasswordPlaceholder { get; init; } + + /// + /// Gets or sets the server version. + /// + public required string? ServerVersion { get; init; } + + /// + /// Gets or sets the server version name. + /// + public required string ServerVersionName { get; init; } + + /// + /// Gets or sets the server version type. + /// + public required string? ServerVersionType { get; init; } + + /// + /// The default URL to present for remote control registration + /// + public required string RemoteControlRegistrationUrl { get; init; } + + /// + /// Gets or sets the started by. + /// + public required string StartedBy { get; init; } + + /// + /// Gets or sets the default update channel. + /// + public required string DefaultUpdateChannel { get; init; } + + /// + /// Gets or sets the default usage report level. + /// + public required string DefaultUsageReportLevel { get; init; } + + /// + /// Gets or sets the OS type. + /// + public required string OSType { get; init; } + + /// + /// Gets or sets the OS version. + /// + public required string OSVersion { get; init; } + + /// + /// Gets or sets the directory separator. + /// + public required char DirectorySeparator { get; init; } + + /// + /// Gets or sets the path separator. + /// + public required char PathSeparator { get; init; } + + /// + /// Gets or sets a value indicating whether the filesystem is case sensitive. + /// + public required bool CaseSensitiveFilesystem { get; init; } + + /// + /// Gets or sets the machine name. + /// + public required string MachineName { get; init; } + + /// + /// Gets or sets the package type ID. + /// + public required string PackageTypeId { get; init; } + + /// + /// Gets or sets the user name. + /// + public required string UserName { get; init; } + + /// + /// Gets or sets the new line character. + /// + public required string NewLine { get; init; } + + /// + /// Gets or sets the CLR version. + /// + public required string CLRVersion { get; init; } + + /// + /// Gets or sets the options. + /// + public required Library.Interface.ICommandLineArgument[] Options { get; init; } + + /// + /// Gets or sets the compression modules. + /// + public required IDynamicModule[] CompressionModules { get; init; } + + /// + /// Gets or sets the encryption modules. + /// + public required IDynamicModule[] EncryptionModules { get; init; } + + /// + /// Gets or sets the backend modules. + /// + public required IDynamicModule[] BackendModules { get; init; } + + /// + /// Gets or sets the generic modules. + /// + public required IDynamicModule[] GenericModules { get; init; } + + /// + /// Gets or sets the web modules. + /// + public required IDynamicModule[] WebModules { get; init; } + + /// + /// Gets or sets the connection modules. + /// + public required IDynamicModule[] ConnectionModules { get; init; } + + /// + /// Gets or sets the server modules. + /// + public required object[] ServerModules { get; init; } + + /// + /// Gets or sets the secret provider modules. + /// + public required IDynamicModule[] SecretProviderModules { get; init; } + + /// + /// Gets or sets a value indicating whether alternate update URLs are being used. + /// + public required bool UsingAlternateUpdateURLs { get; init; } + + /// + /// Gets or sets the log levels. + /// + public required string[] LogLevels { get; init; } + + /// + /// Gets or sets the special folders. + /// + public required SystemInfoDto.SpecialFolderDto[] SpecialFolders { get; init; } + + /// + /// Gets or sets the supported locales. + /// + public required SystemInfoDto.LocaleDto[] SupportedLocales { get; init; } + + /// + /// The timezones available on the system + /// + public required IEnumerable TimeZones { get; init; } + } + + /// + /// Creates a new instance of the class. + /// + /// The new instance. + private static StaticSystemInformation CreateStaticSystemInformation() + => new StaticSystemInformation + { + APIVersion = 1, + PasswordPlaceholder = FIXMEGlobal.PASSWORD_PLACEHOLDER, + ServerVersion = UpdaterManager.SelfVersion.Version, + ServerVersionName = License.VersionNumbers.VERSION_NAME, + ServerVersionType = UpdaterManager.SelfVersion.ReleaseType, + RemoteControlRegistrationUrl = Library.RemoteControl.RegisterForRemote.DefaultRegisterationUrl, + StartedBy = FIXMEGlobal.Origin, + DefaultUpdateChannel = AutoUpdateSettings.DefaultUpdateChannel.ToString(), + DefaultUsageReportLevel = Library.UsageReporter.Reporter.DefaultReportLevel, + OSType = UpdaterManager.OperatingSystemName, + OSVersion = Library.UsageReporter.OSInfoHelper.PlatformString, + DirectorySeparator = Path.DirectorySeparatorChar, + PathSeparator = Path.PathSeparator, + CaseSensitiveFilesystem = Library.Utility.Utility.IsFSCaseSensitive, + MachineName = Environment.MachineName, + PackageTypeId = UpdaterManager.PackageTypeId, + UserName = OperatingSystem.IsWindows() ? System.Security.Principal.WindowsIdentity.GetCurrent().Name : Environment.UserName, + NewLine = Environment.NewLine, + CLRVersion = Environment.Version.ToString(), + Options = Server.Serializable.ServerSettings.Options, + CompressionModules = Server.Serializable.ServerSettings.CompressionModules, + EncryptionModules = Server.Serializable.ServerSettings.EncryptionModules, + BackendModules = Server.Serializable.ServerSettings.BackendModules, + GenericModules = Server.Serializable.ServerSettings.GenericModules, + WebModules = Server.Serializable.ServerSettings.WebModules, + ConnectionModules = Server.Serializable.ServerSettings.ConnectionModules, + ServerModules = Server.Serializable.ServerSettings.ServerModules, + SecretProviderModules = Server.Serializable.ServerSettings.SecretProviderModules, + UsingAlternateUpdateURLs = AutoUpdateSettings.UsesAlternateURLs, + LogLevels = Enum.GetNames(typeof(Library.Logging.LogMessageType)), + SpecialFolders = SpecialFolders.Nodes.Select(n => new Dto.SystemInfoDto.SpecialFolderDto { ID = n.id, Path = n.resolvedpath }).ToArray(), + SupportedLocales = Library.Localization.LocalizationService.SupportedCultures + .Select(x => new Dto.SystemInfoDto.LocaleDto + { + Code = x, + EnglishName = new CultureInfo(x).EnglishName, + DisplayName = new CultureInfo(x).NativeName + }).ToArray(), + TimeZones = Library.Utility.TimeZoneHelper.GetTimeZones() + .Select(x => new Dto.SystemInfoDto.TimeZoneDto + { + ID = x.Id, + DisplayName = x.DisplayName, + CurrentUTCOffset = x.CurrentUtcOffset.ToString() + }), + }; + + /// + /// The lock guarding the _systemInfoBases cache. + /// + private readonly object _lock = new(); + + /// + /// Cached static system information, the key is the language code. + /// + private Dictionary _systemInfoBases = new(StringComparer.OrdinalIgnoreCase); + + /// + public SystemInfoDto GetSystemInfo(CultureInfo? browserlanguage) + { + browserlanguage ??= CultureInfo.InvariantCulture; + var currentLanguage = LocalizationService.CurrentUI?.Culture.Name; + if (string.IsNullOrWhiteSpace(currentLanguage)) + currentLanguage = CultureInfo.CurrentUICulture.Name; + + StaticSystemInformation? systeminfo; + + lock (_lock) + { + if (!_systemInfoBases.TryGetValue(currentLanguage, out systeminfo)) + { + systeminfo = CreateStaticSystemInformation(); + _systemInfoBases[currentLanguage] = systeminfo; + } + } + + // Return the system information, patch in dynamic values + return new SystemInfoDto() + { + APIVersion = systeminfo.APIVersion, + PasswordPlaceholder = systeminfo.PasswordPlaceholder, + ServerVersion = systeminfo.ServerVersion, + ServerVersionName = systeminfo.ServerVersionName, + ServerVersionType = systeminfo.ServerVersionType, + RemoteControlRegistrationUrl = systeminfo.RemoteControlRegistrationUrl, + StartedBy = systeminfo.StartedBy, + DefaultUpdateChannel = systeminfo.DefaultUpdateChannel, + DefaultUsageReportLevel = systeminfo.DefaultUsageReportLevel, + ServerTime = DateTime.Now, + ServerTimeZone = TimeZoneInfo.Local.Id, + OSType = systeminfo.OSType, + OSVersion = systeminfo.OSVersion, + DirectorySeparator = systeminfo.DirectorySeparator, + PathSeparator = systeminfo.PathSeparator, + CaseSensitiveFilesystem = systeminfo.CaseSensitiveFilesystem, + MachineName = systeminfo.MachineName, + PackageTypeId = systeminfo.PackageTypeId, + UserName = systeminfo.UserName, + NewLine = systeminfo.NewLine, + CLRVersion = systeminfo.CLRVersion, + Options = systeminfo.Options, + CompressionModules = systeminfo.CompressionModules, + EncryptionModules = systeminfo.EncryptionModules, + BackendModules = systeminfo.BackendModules, + GenericModules = systeminfo.GenericModules, + WebModules = systeminfo.WebModules, + ConnectionModules = systeminfo.ConnectionModules, + ServerModules = systeminfo.ServerModules, + SecretProviderModules = systeminfo.SecretProviderModules, + UsingAlternateUpdateURLs = systeminfo.UsingAlternateUpdateURLs, + LogLevels = systeminfo.LogLevels, + SpecialFolders = systeminfo.SpecialFolders, + BrowserLocale = new SystemInfoDto.LocaleDto() + { + Code = browserlanguage.Name, + EnglishName = browserlanguage.EnglishName, + DisplayName = browserlanguage.NativeName + }, + SupportedLocales = systeminfo.SupportedLocales, + BrowserLocaleSupported = Library.Localization.LocalizationService.IsCultureSupported(browserlanguage), + TimeZones = systeminfo.TimeZones, + }; + } +} diff --git a/Duplicati/WebserverCore/Services/TokenFamilyStore.cs b/Duplicati/WebserverCore/Services/TokenFamilyStore.cs index ae6ecf8c4..914d5a113 100644 --- a/Duplicati/WebserverCore/Services/TokenFamilyStore.cs +++ b/Duplicati/WebserverCore/Services/TokenFamilyStore.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.Library.Main.Database; using Duplicati.Server.Database; using Duplicati.WebserverCore.Abstractions; diff --git a/Duplicati/WebserverCore/Services/UpdateService.cs b/Duplicati/WebserverCore/Services/UpdateService.cs index 3e5618e5a..9dc7d4967 100644 --- a/Duplicati/WebserverCore/Services/UpdateService.cs +++ b/Duplicati/WebserverCore/Services/UpdateService.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using Duplicati.WebserverCore.Abstractions; namespace Duplicati.WebserverCore.Services; diff --git a/Duplicati/WebserverCore/Services/UpdateServiceLogger.cs b/Duplicati/WebserverCore/Services/UpdateServiceLogger.cs index 2aa829c18..78abd37b8 100644 --- a/Duplicati/WebserverCore/Services/UpdateServiceLogger.cs +++ b/Duplicati/WebserverCore/Services/UpdateServiceLogger.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace Duplicati.WebserverCore.Services; public static partial class UpdateServiceLogger diff --git a/Duplicati/WebserverCore/Services/WorkerThreadsManager.cs b/Duplicati/WebserverCore/Services/WorkerThreadsManager.cs index 748e2e0d8..829837f4f 100644 --- a/Duplicati/WebserverCore/Services/WorkerThreadsManager.cs +++ b/Duplicati/WebserverCore/Services/WorkerThreadsManager.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. #nullable enable using Duplicati.Library.IO; using Duplicati.Library.RestAPI; @@ -27,9 +47,9 @@ public class WorkerThreadsManager(ILiveControls liveControls, IScheduler schedul } } - public void UpdateThrottleSpeeds() + public void UpdateThrottleSpeeds(string? uploadSpeed, string? downloadSpeed) { - WorkerThread?.CurrentTask?.UpdateThrottleSpeed(); + WorkerThread?.CurrentTask?.UpdateThrottleSpeed(uploadSpeed, downloadSpeed); } public long AddTask(Runner.IRunnerData data, bool skipQueue = false) diff --git a/Duplicati/WindowsService/Duplicati.WindowsService.csproj b/Duplicati/WindowsService/Duplicati.WindowsService.csproj index 144337a1e..0482d4890 100644 --- a/Duplicati/WindowsService/Duplicati.WindowsService.csproj +++ b/Duplicati/WindowsService/Duplicati.WindowsService.csproj @@ -3,7 +3,7 @@ net8.0 Duplicati.WindowsService.Implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Duplicati/WindowsService/Program.cs b/Duplicati/WindowsService/Program.cs index 010ebe7f8..21b71782a 100644 --- a/Duplicati/WindowsService/Program.cs +++ b/Duplicati/WindowsService/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a @@ -24,25 +24,25 @@ using System.Linq; using System.Reflection; using System.ServiceProcess; using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Utility; namespace Duplicati.WindowsService { public class Program { + [STAThread] public static int Main(string[] args) { Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.WindowsService); if (!OperatingSystem.IsWindows()) - { throw new NotSupportedException("Unsupported Operating System"); - } - var install = args != null && args.Any(x => string.Equals("install", x, StringComparison.OrdinalIgnoreCase)); + var install = args != null && args.Any(x => string.Equals("install", x, StringComparison.OrdinalIgnoreCase) || string.Equals("install-only", x, StringComparison.OrdinalIgnoreCase)); var uninstall = args != null && args.Any(x => string.Equals("uninstall", x, StringComparison.OrdinalIgnoreCase)); - var install_agent = args != null && args.Any(x => string.Equals("install-agent", x, StringComparison.OrdinalIgnoreCase)); + var install_agent = args != null && args.Any(x => string.Equals("install-agent", x, StringComparison.OrdinalIgnoreCase) || string.Equals("install-only-agent", x, StringComparison.OrdinalIgnoreCase)); var uninstall_agent = args != null && args.Any(x => string.Equals("uninstall-agent", x, StringComparison.OrdinalIgnoreCase)); - var help = args != null && args.Any(x => string.Equals("help", x, StringComparison.OrdinalIgnoreCase)); + var help = args != null && HelpOptionExtensions.IsArgumentAnyHelpString(args); if (help) { @@ -50,9 +50,11 @@ namespace Duplicati.WindowsService Console.WriteLine("|To run from a console, run the Duplicati.Server.exe instead of Duplicati.WindowsService.exe"); Console.WriteLine(); Console.WriteLine("Supported commands (Must be run as Administrator):"); - Console.WriteLine(" install:\r\n Installs the service"); + Console.WriteLine(" install:\r\n Installs and starts the service"); + Console.WriteLine(" install-only:\r\n Installs the service"); Console.WriteLine(" uninstall:\r\n Uninstalls the service"); - Console.WriteLine(" install-agent:\r\n Installs the agent service"); + Console.WriteLine(" install-agent:\r\n Installs and starts the agent service"); + Console.WriteLine(" install-only-agent:\r\n Installs the agent service"); Console.WriteLine(" uninstall-agent:\r\n Uninstalls the agent service"); Console.WriteLine(); Console.WriteLine("It is possible to pass arguments to Duplicati.Server.exe, simply add them to the commandline:"); @@ -67,12 +69,21 @@ namespace Duplicati.WindowsService else if (install || uninstall) { // Remove the install and uninstall flags if they are present - var commandline = Library.Utility.Utility.WrapAsCommandLine(args.Where(x => !(string.Equals("install", x, StringComparison.OrdinalIgnoreCase) || string.Equals("uninstall", x, StringComparison.OrdinalIgnoreCase)))); + var commandline = Library.Utility.Utility.WrapAsCommandLine(args.Where(x => !(string.Equals("install", x, StringComparison.OrdinalIgnoreCase) || string.Equals("uninstall", x, StringComparison.OrdinalIgnoreCase) || string.Equals("install-only", x, StringComparison.OrdinalIgnoreCase))).ToArray()); var selfexec = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Duplicati.WindowsService.exe"); // --uninstall + --install = reinstall if (uninstall) { + try + { + ServiceInstaller.StopService(ServiceControl.SERVICE_NAME); + } + catch (Exception ex) + { + Console.WriteLine("Duplicati service stop failed, a reboot may be required. Exception: {0}", ex.Message); + } + try { ServiceInstaller.DeleteService(ServiceControl.SERVICE_NAME); @@ -92,6 +103,11 @@ namespace Duplicati.WindowsService { ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME, ServiceControl.DISPLAY_NAME, ServiceControl.SERVICE_DESCRIPTION, "\"" + selfexec + "\"" + " SERVER " + commandline); Console.WriteLine("Duplicati service installation succeeded."); + if (!args.Any(x => string.Equals("install-only", x, StringComparison.OrdinalIgnoreCase))) + { + ServiceInstaller.StartService(ServiceControl.SERVICE_NAME); + Console.WriteLine("Duplicati service started."); + } } catch (Exception ex) { @@ -103,12 +119,24 @@ namespace Duplicati.WindowsService else if (install_agent || uninstall_agent) { // Remove the install and uninstall flags if they are present - var commandline = Library.Utility.Utility.WrapAsCommandLine(args.Where(x => !(string.Equals("install-agent", x, StringComparison.OrdinalIgnoreCase) || string.Equals("uninstall-agent", x, StringComparison.OrdinalIgnoreCase)))); + var commandline = Library.Utility.Utility.WrapAsCommandLine( + args.Where(x => !(string.Equals("install-agent", x, StringComparison.OrdinalIgnoreCase) || string.Equals("uninstall-agent", x, StringComparison.OrdinalIgnoreCase) || string.Equals("install-only-agent", x, StringComparison.OrdinalIgnoreCase))) + .Prepend("run") + ); var selfexec = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Duplicati.WindowsService.exe"); // --uninstall + --install = reinstall if (uninstall_agent) { + try + { + ServiceInstaller.StopService(ServiceControl.SERVICE_NAME_AGENT); + } + catch (Exception ex) + { + Console.WriteLine("Duplicati service stop failed, a reboot may be required. Exception: {0}", ex.Message); + } + try { ServiceInstaller.DeleteService(ServiceControl.SERVICE_NAME_AGENT); @@ -127,11 +155,16 @@ namespace Duplicati.WindowsService try { ServiceInstaller.InstallService(ServiceControl.SERVICE_NAME_AGENT, ServiceControl.DISPLAY_NAME_AGENT, ServiceControl.SERVICE_DESCRIPTION_AGENT, "\"" + selfexec + "\"" + " AGENT " + commandline); - Console.WriteLine("Duplicati service installation succeeded."); + Console.WriteLine("Duplicati agent service installation succeeded."); + if (!args.Any(x => string.Equals("install-only-agent", x, StringComparison.OrdinalIgnoreCase))) + { + ServiceInstaller.StartService(ServiceControl.SERVICE_NAME_AGENT); + Console.WriteLine("Duplicati agent service started."); + } } catch (Exception ex) { - Console.WriteLine("Duplicati service installation failed. Exception: {0}", ex.Message); + Console.WriteLine("Duplicati agent service installation failed. Exception: {0}", ex.Message); return 1; } } diff --git a/Duplicati/WindowsService/ServiceControl.cs b/Duplicati/WindowsService/ServiceControl.cs index a523e4896..f1e5004bc 100644 --- a/Duplicati/WindowsService/ServiceControl.cs +++ b/Duplicati/WindowsService/ServiceControl.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using Duplicati.Library.AutoUpdater; using Duplicati.Service; @@ -30,8 +30,10 @@ namespace Duplicati.WindowsService [SupportedOSPlatform("windows")] public class ServiceControl : System.ServiceProcess.ServiceBase { - private const string LOG_SOURCE = "Duplicati"; - private const string LOG_NAME = "Application"; + private const string LOG_SOURCE = "Duplicati Service"; + private const string LOG_NAME = "Duplicati 2"; + private const string SERVER_LOG_NAME = "Duplicati 2:Duplicati Server"; + private const string AGENT_LOG_NAME = "Duplicati 2:Duplicati Agent"; public const string SERVICE_NAME = "Duplicati"; public const string DISPLAY_NAME = "Duplicati service"; public const string SERVICE_DESCRIPTION = "Duplicati running as a Windows Service"; @@ -88,7 +90,11 @@ namespace Duplicati.WindowsService .ToArray(); if (!startargs.Any(x => x.StartsWith("--windows-eventlog=", StringComparison.OrdinalIgnoreCase))) - startargs = startargs.Union(new string[] { "--windows-eventlog=" + LOG_SOURCE }).ToArray(); + startargs = startargs.Union(new string[] { "--windows-eventlog=" + ( + m_executable == PackageHelper.NamedExecutable.Agent + ? AGENT_LOG_NAME + : SERVER_LOG_NAME) + }).ToArray(); if (m_verbose_messages) m_eventLog.WriteEntry("Starting..."); diff --git a/Duplicati/WindowsService/ServiceInstaller.cs b/Duplicati/WindowsService/ServiceInstaller.cs index 6934388e4..9ee4fe537 100644 --- a/Duplicati/WindowsService/ServiceInstaller.cs +++ b/Duplicati/WindowsService/ServiceInstaller.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2024, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Runtime.InteropServices; @@ -58,6 +58,51 @@ namespace Duplicati.WindowsService public string lpDescription; } + private enum SC_STATUS_TYPE : int + { + SC_STATUS_PROCESS_INFO = 0 + } + + private const uint SERVICE_CONTROL_STOP = 0x1; + private const uint SERVICE_CONTROL_CONTINUE = 0x3; + private enum SERVICE_STATUS : uint + { + SERVICE_STOPPED = 0x00000001, + SERVICE_START_PENDING = 0x00000002, + SERVICE_STOP_PENDING = 0x00000003, + SERVICE_RUNNING = 0x00000004, + SERVICE_CONTINUE_PENDING = 0x00000005, + SERVICE_PAUSE_PENDING = 0x00000006, + SERVICE_PAUSED = 0x00000007, + } + + [StructLayout(LayoutKind.Sequential)] + private struct SERVICE_STATUS_PROCESS + { + public uint dwServiceType; + public uint dwCurrentState; + public uint dwControlsAccepted; + public uint dwWin32ExitCode; + public uint dwServiceSpecificExitCode; + public uint dwCheckPoint; + public uint dwWaitHint; + public uint dwProcessId; + public uint dwServiceFlags; + } + + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool StartServiceW(IntPtr hService, uint dwNumServiceArgs, string[] lpServiceArgVectors); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ControlService(IntPtr hService, uint dwControl, ref SERVICE_STATUS lpServiceStatus); + + [DllImport("advapi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool QueryServiceStatusEx(IntPtr hService, SC_STATUS_TYPE lpServiceStatus, ref SERVICE_STATUS_PROCESS lpServiceStatusProcess, int cbBufSize, out uint pcbBytesNeeded); + /// /// Access to the service. Before granting the requested access, the /// system checks the access token of the calling process. @@ -433,6 +478,104 @@ namespace Duplicati.WindowsService } } + public static void StartService(string ServiceName) + { + var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); + + try + { + if (scMgrHandle == IntPtr.Zero) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during start service (OpenSCManager)"); + + var serviceHandle = OpenService(scMgrHandle, ServiceName, (uint)SERVICE_ACCESS.SERVICE_ALL_ACCESS); + + if (serviceHandle == IntPtr.Zero) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during start service (OpenService)"); + + try + { + var ssp = new SERVICE_STATUS_PROCESS(); + var status = QueryServiceStatusEx(serviceHandle, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(), out uint needed); + if (!status) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during start service (QueryServiceStatusEx)"); + + var runningStates = new[] { + (uint)SERVICE_STATUS.SERVICE_RUNNING , + (uint)SERVICE_STATUS.SERVICE_CONTINUE_PENDING, + (uint)SERVICE_STATUS.SERVICE_START_PENDING, + }; + if (Array.IndexOf(runningStates, ssp.dwCurrentState) >= 0) + return; + + if (ssp.dwCurrentState == (uint)SERVICE_STATUS.SERVICE_STOP_PENDING || ssp.dwCurrentState == (uint)SERVICE_STATUS.SERVICE_PAUSE_PENDING) + throw new Exception("Service is in a pending state"); + + if (ssp.dwCurrentState == (uint)SERVICE_STATUS.SERVICE_PAUSED) + { + var sc_status = new SERVICE_STATUS(); + if (!ControlService(serviceHandle, SERVICE_CONTROL_CONTINUE, ref sc_status)) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during start service (ControlService)"); + } + else + { + if (StartServiceW(serviceHandle, 0, null) == false) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during start service (StartServiceW)"); + } + } + finally + { + CloseServiceHandle(serviceHandle); + } + } + finally + { + CloseServiceHandle(scMgrHandle); + } + } + + public static void StopService(string ServiceName) + { + var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); + + try + { + if (scMgrHandle == IntPtr.Zero) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during stop service (OpenSCManager)"); + + var serviceHandle = OpenService(scMgrHandle, ServiceName, (uint)SERVICE_ACCESS.SERVICE_ALL_ACCESS); + + if (serviceHandle == IntPtr.Zero) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during stop service (OpenService)"); + + var ssp = new SERVICE_STATUS_PROCESS(); + var status = QueryServiceStatusEx(serviceHandle, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(), out uint needed); + if (!status) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during stop service (QueryServiceStatusEx)"); + + if (ssp.dwCurrentState == (uint)SERVICE_STATUS.SERVICE_STOPPED) + return; + + if (ssp.dwCurrentState != (uint)SERVICE_STATUS.SERVICE_STOP_PENDING) + { + var sc_status = new SERVICE_STATUS(); + if (!ControlService(serviceHandle, SERVICE_CONTROL_STOP, ref sc_status)) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during stop service (ControlService)"); + } + + while (ssp.dwCurrentState != (uint)SERVICE_STATUS.SERVICE_STOPPED) + { + var dwWaitTime = (int)Math.Max(10000, Math.Min(1000, ssp.dwWaitHint / 10)); + System.Threading.Thread.Sleep(dwWaitTime); + if (!QueryServiceStatusEx(serviceHandle, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, ref ssp, Marshal.SizeOf(), out needed)) + throw new Exception($"Win32 error {Marshal.GetLastWin32Error().ToString()} during stop service (QueryServiceStatusEx)"); + } + } + finally + { + CloseServiceHandle(scMgrHandle); + } + } + public static void DeleteService(string ServiceName) { var scMgrHandle = OpenSCManager(null, null, (uint)SCM_ACCESS.SC_MANAGER_ALL_ACCESS); diff --git a/Executables/net8/Duplicati.Agent/Duplicati.Agent.csproj b/Executables/net8/Duplicati.Agent/Duplicati.Agent.csproj index e07476bba..bbdcc1020 100644 --- a/Executables/net8/Duplicati.Agent/Duplicati.Agent.csproj +++ b/Executables/net8/Duplicati.Agent/Duplicati.Agent.csproj @@ -4,11 +4,13 @@ Exe net8.0 The Duplicati Agent implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + ..\..\..\Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.ico + diff --git a/Executables/net8/Duplicati.Agent/Program.cs b/Executables/net8/Duplicati.Agent/Program.cs index 99db0eea3..243b5750e 100644 --- a/Executables/net8/Duplicati.Agent/Program.cs +++ b/Executables/net8/Duplicati.Agent/Program.cs @@ -1,11 +1,32 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Threading.Tasks; +using Duplicati.Library.Crashlog; -namespace Duplicati.Server.Net8 +namespace Duplicati.Agent.Net8 { // Wrapper class to keep code independent public static class Program { public static Task Main(string[] args) - => Duplicati.Agent.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Agent.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.AutoUpdater/Duplicati.CommandLine.AutoUpdater.csproj b/Executables/net8/Duplicati.CommandLine.AutoUpdater/Duplicati.CommandLine.AutoUpdater.csproj index 9c6d2e7d7..c31a7d5b5 100644 --- a/Executables/net8/Duplicati.CommandLine.AutoUpdater/Duplicati.CommandLine.AutoUpdater.csproj +++ b/Executables/net8/Duplicati.CommandLine.AutoUpdater/Duplicati.CommandLine.AutoUpdater.csproj @@ -4,11 +4,12 @@ Exe net8.0 AutoUpdater tool for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine.AutoUpdater/Program.cs b/Executables/net8/Duplicati.CommandLine.AutoUpdater/Program.cs index c97a85a64..0aadc3549 100644 --- a/Executables/net8/Duplicati.CommandLine.AutoUpdater/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.AutoUpdater/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.AutoUpdater.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.Library.AutoUpdater.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Library.AutoUpdater.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.BackendTester/Duplicati.CommandLine.BackendTester.csproj b/Executables/net8/Duplicati.CommandLine.BackendTester/Duplicati.CommandLine.BackendTester.csproj index e48943a9a..bd2ea4b7b 100644 --- a/Executables/net8/Duplicati.CommandLine.BackendTester/Duplicati.CommandLine.BackendTester.csproj +++ b/Executables/net8/Duplicati.CommandLine.BackendTester/Duplicati.CommandLine.BackendTester.csproj @@ -4,11 +4,12 @@ Exe net8.0 A backend debugging tool for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine.BackendTester/Program.cs b/Executables/net8/Duplicati.CommandLine.BackendTester/Program.cs index 9f4f7bcb5..4df109a11 100644 --- a/Executables/net8/Duplicati.CommandLine.BackendTester/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.BackendTester/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.BackendTester.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.CommandLine.BackendTester.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.BackendTester.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.BackendTool/Duplicati.CommandLine.BackendTool.csproj b/Executables/net8/Duplicati.CommandLine.BackendTool/Duplicati.CommandLine.BackendTool.csproj index a45023adb..472749772 100644 --- a/Executables/net8/Duplicati.CommandLine.BackendTool/Duplicati.CommandLine.BackendTool.csproj +++ b/Executables/net8/Duplicati.CommandLine.BackendTool/Duplicati.CommandLine.BackendTool.csproj @@ -4,11 +4,12 @@ Exe net8.0 Tool for file-level access to remote destinations - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine.BackendTool/Program.cs b/Executables/net8/Duplicati.CommandLine.BackendTool/Program.cs index 4987b2077..b3e44edbc 100644 --- a/Executables/net8/Duplicati.CommandLine.BackendTool/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.BackendTool/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.BackendTool.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.CommandLine.BackendTool.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.BackendTool.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj b/Executables/net8/Duplicati.CommandLine.RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj index 4fb38c689..1764dd0af 100644 --- a/Executables/net8/Duplicati.CommandLine.RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj +++ b/Executables/net8/Duplicati.CommandLine.RecoveryTool/Duplicati.CommandLine.RecoveryTool.csproj @@ -4,11 +4,12 @@ Exe net8.0 RecoveryTool for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine.RecoveryTool/Program.cs b/Executables/net8/Duplicati.CommandLine.RecoveryTool/Program.cs index 3f68ef5af..c6eeabae1 100644 --- a/Executables/net8/Duplicati.CommandLine.RecoveryTool/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.RecoveryTool/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.RecoveryTool.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.CommandLine.RecoveryTool.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.RecoveryTool.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.SecretTool/Duplicati.CommandLine.SecretTool.csproj b/Executables/net8/Duplicati.CommandLine.SecretTool/Duplicati.CommandLine.SecretTool.csproj new file mode 100644 index 000000000..ab5355fc1 --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SecretTool/Duplicati.CommandLine.SecretTool.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + The Duplicati Secret Tool + Copyright © 2025 Team Duplicati, MIT license + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Executables/net8/Duplicati.CommandLine.SecretTool/Program.cs b/Executables/net8/Duplicati.CommandLine.SecretTool/Program.cs new file mode 100644 index 000000000..e59aab013 --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SecretTool/Program.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Threading.Tasks; +using Duplicati.Library.Crashlog; + +namespace Duplicati.CommandLine.SecretTool.Net8 +{ + // Wrapper class to keep code independent + public static class Program + { + public static Task Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.SecretTool.Program.Main(args)); + } +} \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.ServerUtil/Duplicati.CommandLine.ServerUtil.csproj b/Executables/net8/Duplicati.CommandLine.ServerUtil/Duplicati.CommandLine.ServerUtil.csproj index 17003d361..1aab9d017 100644 --- a/Executables/net8/Duplicati.CommandLine.ServerUtil/Duplicati.CommandLine.ServerUtil.csproj +++ b/Executables/net8/Duplicati.CommandLine.ServerUtil/Duplicati.CommandLine.ServerUtil.csproj @@ -4,11 +4,12 @@ Exe net8.0 Server CLI implementation of Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine.ServerUtil/Program.cs b/Executables/net8/Duplicati.CommandLine.ServerUtil/Program.cs index 909ba4492..60a923c36 100644 --- a/Executables/net8/Duplicati.CommandLine.ServerUtil/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.ServerUtil/Program.cs @@ -1,4 +1,25 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Threading.Tasks; +using Duplicati.Library.Crashlog; namespace Duplicati.CommandLine.ServerUtil.Net8 { @@ -6,6 +27,6 @@ namespace Duplicati.CommandLine.ServerUtil.Net8 public static class Program { public static Task Main(string[] args) - => Duplicati.CommandLine.ServerUtil.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.ServerUtil.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Duplicati.CommandLine.SharpAESCrypt.csproj b/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Duplicati.CommandLine.SharpAESCrypt.csproj index f6a9c3465..3bf5fcc3b 100644 --- a/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Duplicati.CommandLine.SharpAESCrypt.csproj +++ b/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Duplicati.CommandLine.SharpAESCrypt.csproj @@ -5,7 +5,7 @@ net8.0 The Server SharpAESCrypt implementation Duplicati.CommandLine.SharpAESCrypt - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -17,6 +17,7 @@ + diff --git a/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Program.cs b/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Program.cs index b50dc43fd..ea4991b96 100644 --- a/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.SharpAESCrypt/Program.cs @@ -1,9 +1,36 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System; +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.SharpAESCrypt.Net8 { // Wrapper class to keep code independent public static class Program { - public static void Main(string[] args) - => global::SharpAESCrypt.Program.CommandLineMain(args); + public static int Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => + { + global::SharpAESCrypt.Program.CommandLineMain(args); + return Environment.ExitCode; + }); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.Snapshots/Duplicati.CommandLine.Snapshots.csproj b/Executables/net8/Duplicati.CommandLine.Snapshots/Duplicati.CommandLine.Snapshots.csproj index 1ea7e7aa3..bf78b5deb 100644 --- a/Executables/net8/Duplicati.CommandLine.Snapshots/Duplicati.CommandLine.Snapshots.csproj +++ b/Executables/net8/Duplicati.CommandLine.Snapshots/Duplicati.CommandLine.Snapshots.csproj @@ -5,7 +5,7 @@ net8.0 The Server Snapshots implementation Duplicati.CommandLine.Snapshots - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license @@ -17,6 +17,7 @@ + diff --git a/Executables/net8/Duplicati.CommandLine.Snapshots/Program.cs b/Executables/net8/Duplicati.CommandLine.Snapshots/Program.cs index c16de29eb..dd54cf036 100644 --- a/Executables/net8/Duplicati.CommandLine.Snapshots/Program.cs +++ b/Executables/net8/Duplicati.CommandLine.Snapshots/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.Snapshots.Net8 { // Wrapper class to keep code independent public static class Program { - public static void Main(string[] args) - => Duplicati.Library.Snapshots.Program.Main(args); + public static int Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Library.Snapshots.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.SourceTool/Duplicati.CommandLine.SourceTool.csproj b/Executables/net8/Duplicati.CommandLine.SourceTool/Duplicati.CommandLine.SourceTool.csproj new file mode 100644 index 000000000..038a3b98d --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SourceTool/Duplicati.CommandLine.SourceTool.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + SyncTool for Duplicati + Copyright © 2025 Team Duplicati, MIT license + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Executables/net8/Duplicati.CommandLine.SourceTool/Program.cs b/Executables/net8/Duplicati.CommandLine.SourceTool/Program.cs new file mode 100644 index 000000000..490864a25 --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SourceTool/Program.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + +namespace Duplicati.CommandLine.SourceTool.Net8 +{ + // Wrapper class to keep code independent + public static class Program + { + public static int Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => SourceTool.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult()); + } +} \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine.SyncTool/Duplicati.CommandLine.SyncTool.csproj b/Executables/net8/Duplicati.CommandLine.SyncTool/Duplicati.CommandLine.SyncTool.csproj new file mode 100644 index 000000000..2ce4cf329 --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SyncTool/Duplicati.CommandLine.SyncTool.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + SyncTool for Duplicati + Copyright © 2025 Team Duplicati, MIT license + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Executables/net8/Duplicati.CommandLine.SyncTool/Program.cs b/Executables/net8/Duplicati.CommandLine.SyncTool/Program.cs new file mode 100644 index 000000000..c0e13737d --- /dev/null +++ b/Executables/net8/Duplicati.CommandLine.SyncTool/Program.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + +namespace Duplicati.CommandLine.SyncTool.Net8 +{ + // Wrapper class to keep code independent + public static class Program + { + public static int Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => RemoteSynchronization.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult()); + } +} \ No newline at end of file diff --git a/Executables/net8/Duplicati.CommandLine/Duplicati.CommandLine.csproj b/Executables/net8/Duplicati.CommandLine/Duplicati.CommandLine.csproj index 5fc6eb51a..5823101e8 100644 --- a/Executables/net8/Duplicati.CommandLine/Duplicati.CommandLine.csproj +++ b/Executables/net8/Duplicati.CommandLine/Duplicati.CommandLine.csproj @@ -4,11 +4,12 @@ Exe net8.0 Commandline implementation of Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.CommandLine/Program.cs b/Executables/net8/Duplicati.CommandLine/Program.cs index 7c006a0c0..85eb8c9a6 100644 --- a/Executables/net8/Duplicati.CommandLine/Program.cs +++ b/Executables/net8/Duplicati.CommandLine/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.CommandLine.CLI.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.CommandLine.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.CommandLine.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj b/Executables/net8/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj index 12f91f8c7..39e0afe29 100644 --- a/Executables/net8/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj +++ b/Executables/net8/Duplicati.GUI.TrayIcon/Duplicati.GUI.TrayIcon.csproj @@ -5,11 +5,12 @@ net8.0 The Duplicati Tray implementation ..\..\..\Duplicati\GUI\Duplicati.GUI.TrayIcon\Duplicati.ico - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.GUI.TrayIcon/Program.cs b/Executables/net8/Duplicati.GUI.TrayIcon/Program.cs index 4158deee8..6010b9937 100644 --- a/Executables/net8/Duplicati.GUI.TrayIcon/Program.cs +++ b/Executables/net8/Duplicati.GUI.TrayIcon/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.GUI.TrayIcon.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.GUI.TrayIcon.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.GUI.TrayIcon.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.Server/Duplicati.Server.csproj b/Executables/net8/Duplicati.Server/Duplicati.Server.csproj index 99740ab88..2d9736d43 100644 --- a/Executables/net8/Duplicati.Server/Duplicati.Server.csproj +++ b/Executables/net8/Duplicati.Server/Duplicati.Server.csproj @@ -4,11 +4,12 @@ Exe net8.0 The Duplicati Server implementation - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.Server/Program.cs b/Executables/net8/Duplicati.Server/Program.cs index 245111167..d6b4fdf3d 100644 --- a/Executables/net8/Duplicati.Server/Program.cs +++ b/Executables/net8/Duplicati.Server/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.Server.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.Server.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Server.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.Service/Duplicati.Service.csproj b/Executables/net8/Duplicati.Service/Duplicati.Service.csproj index 98694f410..c244cbbf1 100644 --- a/Executables/net8/Duplicati.Service/Duplicati.Service.csproj +++ b/Executables/net8/Duplicati.Service/Duplicati.Service.csproj @@ -4,11 +4,12 @@ Exe net8.0 Service controller for Duplicati - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.Service/Program.cs b/Executables/net8/Duplicati.Service/Program.cs index 33b210f93..2f47de13d 100644 --- a/Executables/net8/Duplicati.Service/Program.cs +++ b/Executables/net8/Duplicati.Service/Program.cs @@ -1,9 +1,31 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Crashlog; + namespace Duplicati.Service.Net8 { // Wrapper class to keep code independent public static class Program { public static int Main(string[] args) - => Duplicati.Service.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Service.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/net8/Duplicati.WindowsService/Duplicati.WindowsService.csproj b/Executables/net8/Duplicati.WindowsService/Duplicati.WindowsService.csproj index 69461582b..b23796fb9 100644 --- a/Executables/net8/Duplicati.WindowsService/Duplicati.WindowsService.csproj +++ b/Executables/net8/Duplicati.WindowsService/Duplicati.WindowsService.csproj @@ -5,11 +5,12 @@ net8.0 WindowsService for Duplicati Duplicati.WindowsService - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license + diff --git a/Executables/net8/Duplicati.WindowsService/Program.cs b/Executables/net8/Duplicati.WindowsService/Program.cs index e5d98988c..e1dfbc00b 100644 --- a/Executables/net8/Duplicati.WindowsService/Program.cs +++ b/Executables/net8/Duplicati.WindowsService/Program.cs @@ -1,4 +1,25 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Runtime.Versioning; +using Duplicati.Library.Crashlog; namespace Duplicati.WindowsService.Net8 { @@ -6,6 +27,6 @@ namespace Duplicati.WindowsService.Net8 public static class Program { public static int Main(string[] args) - => Duplicati.WindowsService.Program.Main(args); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.WindowsService.Program.Main(args)); } } \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 43d9e81e3..52fe4579c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 2024, The Duplicati Team +Copyright (C) 2025, The Duplicati Team https://duplicati.com, hello@duplicati.com Permission is hereby granted, free of charge, to any person obtaining a diff --git a/LiveTests/Duplicati.Backend.Tests/Azure/AzureTests.cs b/LiveTests/Duplicati.Backend.Tests/Azure/AzureTests.cs new file mode 100644 index 000000000..a492f605c --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Azure/AzureTests.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Azure; + +/// +/// Tests for Dropbox backend +/// +[TestClass] +public sealed class AzureTests : BaseTest +{ + static readonly string[] AdditionalArguments = ["--wait-after-upload=10s", "--wait-after-delete=10s"]; + + /// + /// Basic Azure test. + /// + [TestMethod] + public Task TestAzureBlob() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_AZURE_ACCOUNTNAME","TESTCREDENTIAL_AZURE_ACCESSKEY","TESTCREDENTIAL_AZURE_CONTAINERNAME" + ]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"azure://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_CONTAINERNAME")}?auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_ACCOUNTNAME")}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_AZURE_ACCESSKEY")!)}" + }.Concat(Parameters.GlobalTestParameters).Concat(AdditionalArguments).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/B2/B2Tests.cs b/LiveTests/Duplicati.Backend.Tests/B2/B2Tests.cs new file mode 100644 index 000000000..d45bf2774 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/B2/B2Tests.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.B2; + +/// +/// Backblaze B2 Tests +/// +[TestClass] +public sealed class B2Tests : BaseTest +{ + [TestMethod] + public Task TestB2() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_B2_BUCKET", "TESTCREDENTIAL_B2_FOLDER", "TESTCREDENTIAL_B2_USERNAME", "TESTCREDENTIAL_B2_PASSWORD"]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"b2://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_B2_BUCKET")}/{Environment.GetEnvironmentVariable("TESTCREDENTIAL_B2_FOLDER")}?auth-username={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_B2_USERNAME")!)}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_B2_PASSWORD")!)}", + + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Base/BaseSFTPGOTest.cs b/LiveTests/Duplicati.Backend.Tests/Base/BaseSFTPGOTest.cs new file mode 100644 index 000000000..71b497ead --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Base/BaseSFTPGOTest.cs @@ -0,0 +1,99 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Base; + +/// +/// Base class for tests that require a running SFTPGo instance which will +/// be used to test the FTP, Webdav & SSH backends. +/// +/// It handles the creation of the necessary configuration files and certificates +/// +public class BaseSftpgoTest : BaseTest +{ + protected static readonly string TEST_USER_NAME = "testuser"; + protected static readonly string TEST_USER_PASSWORD = "testpassword"; + protected static readonly string CERTIFICATE_FILE = "fullchain.pem"; + protected static readonly string CERTIFICATE_PRIVATE_KEY_FILE = "privkey.pem"; + + /// + /// Creates a josn file with the configuration for the SFTPGo instance + /// + /// directory in which to create the file + protected void CreateUsersFile(DirectoryInfo directoryInfo) + { + var sftpgoConfig = "{\"users\":[{\"id\":1,\"status\":1,\"username\":\"testuser\",\"password\":\"$2a$10$BTqu.odQz6jLQAi70KbQJOYBcRHpbhP4uxqcfFXZalq4zUsQZGj/6\",\"has_password\":true,\"home_dir\":\"/srv/sftpgo/data/testuser\",\"uid\":0,\"gid\":0,\"max_sessions\":0,\"quota_size\":0,\"quota_files\":0,\"permissions\":{\"/\":[\"*\"]},\"upload_data_transfer\":0,\"download_data_transfer\":0,\"total_data_transfer\":0,\"created_at\":1729521839754,\"updated_at\":1729521839754,\"last_password_change\":1729521839754,\"filters\":{\"hooks\":{\"external_auth_disabled\":false,\"pre_login_disabled\":false,\"check_password_disabled\":false},\"totp_config\":{\"secret\":{}}},\"filesystem\":{\"provider\":0,\"osconfig\":{},\"s3config\":{\"access_secret\":{}},\"gcsconfig\":{\"credentials\":{}},\"azblobconfig\":{\"account_key\":{},\"sas_url\":{}},\"cryptconfig\":{\"passphrase\":{}},\"sftpconfig\":{\"password\":{},\"private_key\":{},\"key_passphrase\":{}},\"httpconfig\":{\"password\":{},\"api_key\":{}}}}],\"groups\":[],\"folders\":[],\"admins\":[{\"id\":1,\"status\":1,\"username\":\"admin\",\"password\":\"$2a$10$HJGY10EuEPPajgut3OMTte5NXqbn1RXMTzy8BUA639a6qzinOjIx6\",\"permissions\":[\"*\"],\"filters\":{\"require_two_factor\":false,\"totp_config\":{\"secret\":{}},\"preferences\":{}},\"created_at\":1729521817148,\"updated_at\":1729521817148,\"last_login\":1729521817152}],\"api_keys\":[],\"shares\":[],\"event_actions\":[],\"event_rules\":[],\"roles\":[],\"ip_lists\":[],\"configs\":{},\"version\":16}"; + + using var usersFile = File.Create(Path.Combine(directoryInfo.FullName, "users.json")); + + using var userFileWritter = new StreamWriter(usersFile); + + userFileWritter.WriteLine(sftpgoConfig); + + } + + /// + /// Create a self signed certificate to be used on WebDav and SFTP connections. + /// + /// Its created on a temporary directory and the private key and public key are saved in PEM format. + /// + /// It is up to the caller to delete the files albeit its in the OS temporary directory. + /// + /// Returns the directory in which certificate was created + protected DirectoryInfo CreateHttpsCertificates() + { + using var rsa = RSA.Create(2048); + + var request = new CertificateRequest( + $"CN=TestCertificateForWebdav", + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + request.CertificateExtensions.Add( + new X509KeyUsageExtension( + X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, + false)); + + request.CertificateExtensions.Add( + new X509EnhancedKeyUsageExtension( + [new Oid("1.3.6.1.5.5.7.3.1")], false)); + + var certificate = request.CreateSelfSigned( + DateTimeOffset.Now.AddDays(-1), + DateTimeOffset.Now.AddDays(30)); + + if (OperatingSystem.IsWindows()) certificate.FriendlyName = "Test Certificate"; + + var directoryInfo = Directory.CreateTempSubdirectory("TemporaryCertificates"); + + using var privateKeyFile = File.Create(Path.Combine(directoryInfo.FullName, CERTIFICATE_PRIVATE_KEY_FILE)); + using var publicKeyFile = File.Create(Path.Combine(directoryInfo.FullName, CERTIFICATE_FILE)); + + using var privateKeyWriter = new StreamWriter(privateKeyFile); + using var publicKeyWriter = new StreamWriter(publicKeyFile); + privateKeyWriter.WriteLine(certificate.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem()); + publicKeyWriter.WriteLine(certificate.ExportCertificatePem()); + + return directoryInfo; + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Base/BaseTest.cs b/LiveTests/Duplicati.Backend.Tests/Base/BaseTest.cs new file mode 100644 index 000000000..9c12a2fd3 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Base/BaseTest.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.Backend.Tests.Base; + +public class BaseTest +{ + + /// + /// Checks if the environment variables are set and if not fails with detailed about the missing variables + /// + /// Array of variables to be checked + protected void CheckRequiredEnvironment(string[] requiredEnvironmentVariables) + { + var missingVariables = requiredEnvironmentVariables + .Where(variable => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(variable))) + .ToList(); + + if (missingVariables.Any()) Assert.Fail($"Required environment variables not set: {string.Join(", ", missingVariables)}"); + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Base/PreliminaryTests.cs b/LiveTests/Duplicati.Backend.Tests/Base/PreliminaryTests.cs new file mode 100644 index 000000000..227e81999 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Base/PreliminaryTests.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Base; + + +/// +/// Tests to check if the TestContainers is available +/// +[TestClass] +public class PreliminaryTests +{ + [TestMethod] + public async Task TestContainersEngineReady() + { + using var dockerClientConfiguration = + TestcontainersSettings.OS.DockerEndpointAuthConfig.GetDockerClientConfiguration(ResourceReaper + .DefaultSessionId); + + using var dockerClient = dockerClientConfiguration.CreateClient(); + + var versionResponse = await dockerClient.System.GetSystemInfoAsync() + .ConfigureAwait(false); + + if (!(versionResponse.ServerVersion.Contains("Testcontainers Desktop") || + versionResponse.ServerVersion.Contains("testcontainers cloud"))) + { + Assert.Fail("Testcontainers engine not found"); + } + + Console.WriteLine($"Engine configured: {versionResponse.ServerVersion}"); + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Box/BoxTests.cs b/LiveTests/Duplicati.Backend.Tests/Box/BoxTests.cs new file mode 100644 index 000000000..d393a2118 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Box/BoxTests.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Box; + +/// +/// Box.com Tests +/// +[TestClass] +public sealed class BoxTests : BaseTest +{ + [TestMethod] + public Task TestBox() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_BOX_FOLDER", "TESTCREDENTIAL_BOX_AUTHID"]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"box://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_BOX_FOLDER")}/?authid={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_BOX_AUTHID")!)}", + + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/CIFS/SMBTests.cs b/LiveTests/Duplicati.Backend.Tests/CIFS/SMBTests.cs new file mode 100644 index 000000000..400370764 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/CIFS/SMBTests.cs @@ -0,0 +1,120 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using DotNet.Testcontainers.Images; + +namespace Duplicati.Backend.Tests.SMB; + +/// +/// SMB Tests +/// +[TestClass] +public sealed class SMBTests : BaseSftpgoTest +{ + + /// + /// Test Smb with TestContainers creating a Samba Server with TestContainers. + /// + /// This test has no requirement of environment variables. + /// + [TestMethod] + public async Task TestSmb() + { + var outputConsumer = new OutputConsumer(); + var randomPassword = GeneratePassword(); + var testFilePath = Path.Combine(Path.GetTempPath(), "samba-test"); + Directory.CreateDirectory(testFilePath); + // Create smb.conf + var smbConfig = @"[global] +workgroup = WORKGROUP +server string = Samba Server +log file = /var/log/samba/log.%m +max log size = 50 +security = user +passdb backend = tdbsam + +[testshare1] +path = /shares/testshare1 +valid users = smbuser1 +writable = yes +browseable = yes"; + + await File.WriteAllTextAsync(Path.Combine(testFilePath, "smb.conf"), smbConfig); + + // Create entrypoint script + var entrypoint = $@"#!/bin/bash +useradd -M -s /sbin/nologin smbuser1 +mkdir -p /shares/testshare1 +chown smbuser1:smbuser1 /shares/testshare1 +chmod 700 /shares/testshare1 +(echo {randomPassword}; echo {randomPassword}) | smbpasswd -a smbuser1 -s +smbpasswd -e smbuser1 +smbd --foreground --no-process-group --debug-stdout"; + + await File.WriteAllTextAsync(Path.Combine(testFilePath, "entrypoint.sh"), entrypoint); + + var container = new ContainerBuilder() + .WithImage("ubuntu:22.04") + .WithImagePullPolicy(PullPolicy.Missing) + .WithCommand("/bin/bash", "-c", "apt-get update && " + + "DEBIAN_FRONTEND=noninteractive apt-get install -y samba && " + + "bash /etc/samba/entrypoint.sh") + .WithResourceMapping(testFilePath, "/etc/samba", UnixFileModes.UserRead | + UnixFileModes.UserWrite | UnixFileModes.GroupRead | UnixFileModes.OtherRead) + .WithPortBinding(139, 139) + .WithPortBinding(445, 445) + .WithOutputConsumer(outputConsumer) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(139)) + .Build(); + + Console.WriteLine("Starting container with wait strategy for port 139"); + await container.StartAsync(); + Console.WriteLine("Samba has started and its ready to accept connections"); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"smb://localhost/testshare1/new/?transport=directtcp&auth-domain&auth-username=smbuser1&auth-password={randomPassword}", + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + Console.WriteLine(await outputConsumer.GetStreamsOutput()); + if (exitCode != 0) + { + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + await container.StopAsync(); + } + + /// + /// Simple helper to generate a random password for the test. + /// + /// Only alphanumeric characters are used. + /// + /// Lenght of the password defaulting to 32 + private string GeneratePassword(int length = 32) + { + const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var random = new Random(); + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Dropbox/DropBoxTests.cs b/LiveTests/Duplicati.Backend.Tests/Dropbox/DropBoxTests.cs new file mode 100644 index 000000000..a9309af90 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Dropbox/DropBoxTests.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Dropbox; + +/// +/// Tests for Dropbox backend +/// +[TestClass] +public sealed class DropBoxTests : BaseTest +{ + /// + /// Basic Dropbox test. There are no adicional parameters to be set or tested. + /// + [TestMethod] + public Task TestDropBox() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_DROPBOX_FOLDER","TESTCREDENTIAL_DROPBOX_TOKEN"]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"dropbox://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_DROPBOX_FOLDER")}/?authid={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_DROPBOX_TOKEN"))}" + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.csproj b/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.csproj new file mode 100644 index 000000000..40d1709a6 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.csproj @@ -0,0 +1,27 @@ + + + + enable + Exe + net8.0 + enable + enable + Commandline implementation of Duplicati + Copyright © 2025 Team Duplicati, MIT license + + + + + + + + + + + + + + + + + diff --git a/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln b/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln new file mode 100644 index 000000000..88b2a38ae --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Duplicati.Backend.Tests.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Backend.Tests", "Duplicati.Backend.Tests.csproj", "{6D888116-3424-4DE4-8AE3-D01A44A3EE38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.CommandLine.BackendTester", "..\..\Duplicati\CommandLine\BackendTester\Duplicati.CommandLine.BackendTester.csproj", "{41756E86-799B-4142-9948-D9D94054BA15}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D888116-3424-4DE4-8AE3-D01A44A3EE38}.Release|Any CPU.Build.0 = Release|Any CPU + {41756E86-799B-4142-9948-D9D94054BA15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41756E86-799B-4142-9948-D9D94054BA15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41756E86-799B-4142-9948-D9D94054BA15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41756E86-799B-4142-9948-D9D94054BA15}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/LiveTests/Duplicati.Backend.Tests/FTP/FTPTests.cs b/LiveTests/Duplicati.Backend.Tests/FTP/FTPTests.cs new file mode 100644 index 000000000..80abd0f23 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/FTP/FTPTests.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.FTP; + +/// +/// FTP Tests +/// +[TestClass] +public sealed class FtpTests : BaseSftpgoTest +{ + + /// + /// Test a simple FTP connection, no SSL + /// + /// + [TestMethod] + public async Task TestSimpleFtp() + { + var outputConsumer = new OutputConsumer(); + var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute | + UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute | + UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute; + + var temporaryKeysDir = CreateHttpsCertificates(); + + CreateUsersFile(temporaryKeysDir); + + var container = new ContainerBuilder() + .WithImage("drakkan/sftpgo") + .WithEnvironment("SFTPGO_LOG_LEVEL", "debug") + .WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json") + .WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__PORT", "2121") + .WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__END", "20999") + .WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__START", "20000") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY", "1") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_FILE", CERTIFICATE_FILE) + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_KEY_FILE", CERTIFICATE_PRIVATE_KEY_FILE) + .WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions) + .WithPortBinding(2121, 2121) + + .WithOutputConsumer(outputConsumer); + + container = Enumerable.Range(20000, 1001) + .Aggregate(container, (current, port) => current.WithPortBinding(port, port)); + + var builtContainer = container + .WithOutputConsumer(outputConsumer) + .Build(); + + Console.WriteLine("Starting container"); + await builtContainer.StartAsync(); + + // Once started we will already cleanup temporary directory + temporaryKeysDir.Delete(true); + + Console.WriteLine("Waiting X seconds"); + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Running this ignoring the certificate + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"ftp://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:2121/", + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) + { + Console.WriteLine(await outputConsumer.GetStreamsOutput()); + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + await builtContainer.StopAsync(); + } + + /// + /// Test FTP connection with SSL/TLS + /// + /// + [TestMethod] + public async Task TestFtpSsl() + { + var outputConsumer = new OutputConsumer(); + var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute | + UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute | + UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute; + + var temporaryKeysDir = CreateHttpsCertificates(); + + CreateUsersFile(temporaryKeysDir); + + var container = new ContainerBuilder() + .WithImage("drakkan/sftpgo") + .WithEnvironment("SFTPGO_LOG_LEVEL", "debug") + .WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json") + .WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__PORT", "2121") + .WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__END", "22999") + .WithEnvironment("SFTPGO_FTPD__PASSIVE_PORT_RANGE__START", "22000") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__ACTIVE_CONNECTIONS_SECURITY", "1") + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_FILE", CERTIFICATE_FILE) + .WithEnvironment("SFTPGO_FTPD__BINDINGS__0__CERTIFICATE_KEY_FILE", CERTIFICATE_PRIVATE_KEY_FILE) + .WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions) + .WithPortBinding(2122, 2121) + + .WithOutputConsumer(outputConsumer); + + container = Enumerable.Range(22000, 1000) + .Aggregate(container, (current, port) => current.WithPortBinding(port, port)); + + var builtContainer = container + .WithOutputConsumer(outputConsumer) + .Build(); + + Console.WriteLine("Starting container"); + await builtContainer.StartAsync(); + + // Once started we will already cleanup temporary directory + temporaryKeysDir.Delete(true); + + Console.WriteLine("Waiting X seconds"); + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Running this ignoring the certificate + var exitCode = CommandLine.BackendTester.Program.Main( + ["ftp://testuser:testpassword@localhost:2122/", "use-ssl=true"]); + + if (exitCode != 0) + { + Console.WriteLine(await outputConsumer.GetStreamsOutput()); + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + await builtContainer.StopAsync(); + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Filen/FilenTests.cs b/LiveTests/Duplicati.Backend.Tests/Filen/FilenTests.cs new file mode 100644 index 000000000..eca69c016 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Filen/FilenTests.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Filen; + +/// +/// Box.com Tests +/// +[TestClass] +public sealed class FilenTests : BaseTest +{ + [TestMethod] + public Task TestFilen() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_FILEN_FOLDER", "TESTCREDENTIAL_FILEN_USERNAME", "TESTCREDENTIAL_FILEN_PASSWORD"]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"filen://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_FILEN_FOLDER")}?auth-username={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_FILEN_USERNAME")!)}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_FILEN_PASSWORD")!)}", + + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/GlobalUsings.cs b/LiveTests/Duplicati.Backend.Tests/GlobalUsings.cs new file mode 100644 index 000000000..063a1e5b5 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/GlobalUsings.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +global using System.Security.Cryptography; +global using System.Security.Cryptography.X509Certificates; +global using System.Text; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using Duplicati.Backend.Tests.Base; +global using Duplicati.Backend.Tests.Utility; +global using Microsoft.VisualStudio.TestTools.UnitTesting; \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Google Drive/GoogleDriveTests.cs b/LiveTests/Duplicati.Backend.Tests/Google Drive/GoogleDriveTests.cs new file mode 100644 index 000000000..c660b7e45 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Google Drive/GoogleDriveTests.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Google_Drive; + +/// +/// Google Drive tests +/// +[TestClass] +public sealed class GoogleDriveTests : BaseTest +{ + + static readonly string[] AdditionalArguments = ["--wait-after-upload=10s", "--wait-after-delete=10s"]; + [TestMethod] + public Task TestGoogleDriveSimple() + { + CheckRequiredEnvironment(["TESTCREDENTIAL_GOOGLEDRIVE_TOKEN", "TESTCREDENTIAL_GOOGLEDRIVE_FOLDER"]); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"googledrive:///{Environment.GetEnvironmentVariable("TESTCREDENTIAL_GOOGLEDRIVE_FOLDER")}?authid={Environment.GetEnvironmentVariable("TESTCREDENTIAL_GOOGLEDRIVE_TOKEN")}", + + }.Concat(Parameters.GlobalTestParameters).Concat(AdditionalArguments).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + + } + + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Parameters.cs b/LiveTests/Duplicati.Backend.Tests/Parameters.cs new file mode 100644 index 000000000..222885b52 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Parameters.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests; + +/// +/// Glboal parameters to be used on all tests +/// +public static class Parameters +{ + // Standard parameters for the backend tester + public static string[] GlobalTestParameters => + [ + "--auto-clean", + "--auto-create-folder", + "--extended-chars=_-", + "--force", + $"--max-file-size={Environment.GetEnvironmentVariable("MAX_FILE_SIZE") ?? "1000kb"}", + $"--number-of-files={Environment.GetEnvironmentVariable("NUMBER_OF_FILES") ?? "20"}" + ]; +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/README.md b/LiveTests/Duplicati.Backend.Tests/README.md new file mode 100644 index 000000000..c15ba6fcb --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/README.md @@ -0,0 +1,146 @@ + +# Environment variables for tests + +On Github actions these are mapped 1:1 to secrets, even the non password fields are stored in secrets. + +## General + +These control the size and number of files generated. + +``` +MAX_FILE_SIZE default is 1000kb +NUMBER_OF_FILES default is 20 +``` + +## Backends CI Status + +| Backend | CI Planned | CI Status | +|----------|------------|-----------| +| pCloud | Planned | Live | +| WebDav | Planned | Live | +| Shhv2 | Planned | Live | +| S3 | Planned | Live | +| Ftp | Planned | Live | +| Planned | Planned | Live | +| Dropbox | Planned | Live | +| AlternativeFTP | Planned | Live | +| AzureBlob | Planned | Live | +| CIFS | Planned | Live | +| Google Drive | Planned | Live | +| Box | Planned | Live | +| TahoeLAFS | Pending Decision | | +| TescentCOS | Pending Decision | | +| Storj | Pending Decision | | +| Sia | Pending Decision | | +| SharePoint | Pending Decision | | +| Rclone | Pending Decision | | +| OneDrive | Pending Decision | | +| OpenStack | Pending Decision | | +| Mega | Pending Decision | | +| Jottacloud | Pending Decision | | +| IDrive2 | Planned | | +| File | Planned | | +| CloudFiles | Pending Decision | | +| backBlaze | Planned | Live | +| AliyunOSS | Pending Decision | | + + +## Backends that do not require Environment variables + +* FTP _(TestContainers required)_ +* SSH _(TestContainers required)_ +* Webdav _(TestContainers required)_ +* CIFS _(TestContainers required)_ + +Please note that TestContainers token has to be configured in secrets/Github actions yml. + +## Backends that require Environment variables + +## Box.com + +``` +TESTCREDENTIAL_BOX_FOLDER +TESTCREDENTIAL_BOX_AUTHID +``` + +## Backblaze B2 + +Backblaze B2 credentials are mapped to the following environment variables: + +``` +TESTCREDENTIAL_B2_BUCKET +TESTCREDENTIAL_B2_FOLDER +TESTCREDENTIAL_B2_USERNAME +TESTCREDENTIAL_B2_PASSWORD +``` + +## Google Drive: + +Google Drive credentials are mapped to the following environment variables: + +``` +TESTCREDENTIAL_GOOGLEDRIVE_FOLDER +TESTCREDENTIAL_GOOGLEDRIVE_TOKEN +``` + +## S3 + +S3 credentials are mapped to the following environment variables: + +Attention: **AWS TESTCREDENTIAL_S3_SECRET is URI escaped automatically, supply the raw value.** + +``` +TESTCREDENTIAL_S3_KEY +TESTCREDENTIAL_S3_SECRET +TESTCREDENTIAL_S3_BUCKETNAME +TESTCREDENTIAL_S3_REGION +``` + +## Dropbox + +Dropbox credentials are mapped to the following environment variables: + +``` +TESTCREDENTIAL_DROPBOX_FOLDER +TESTCREDENTIAL_DROPBOX_TOKEN +``` + +## Azure Blob + +Attention: **TESTCREDENTIAL_AZURE_ACCESSKEY is URI escaped automatically, supply the raw value.** + + +``` +TESTCREDENTIAL_AZURE_ACCOUNTNAME +TESTCREDENTIAL_AZURE_ACCESSKEY +TESTCREDENTIAL_AZURE_CONTAINERNAME +``` + +## pCloud Native API + +``` +TESTCREDENTIAL_PCLOUD_SERVER +TESTCREDENTIAL_PCLOUD_TOKEN +TESTCREDENTIAL_PCLOUD_FOLDER +``` +For PCloud the server is the API server(eapi.pcloud.com for EU hosted account or api.pcloud.com for non EU). The token is the OAuth token. + +## Running the tests + +[TestContainers](https://testcontainers.org/) is a pre-requisite for SSH, FTP and Webdav tests. It is not required for the other tests. + +Set the environment variables as described above, then run the tests using the following commands: + +Minimal Verbosity: + +`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=normal"` + +Running with full verbosity (useful if tests are failing): + +`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=detailed"` + +Running specific tests: + +`dotnet test Duplicati.Backend.Tests.sln --logger:"console;verbosity=detailed" --filter="Name=TestDropBox"` + + diff --git a/LiveTests/Duplicati.Backend.Tests/S3/S3Tests.cs b/LiveTests/Duplicati.Backend.Tests/S3/S3Tests.cs new file mode 100644 index 000000000..157760799 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/S3/S3Tests.cs @@ -0,0 +1,76 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.S3; + +/// +/// S3 Tests +/// +[TestClass] +public sealed class S3Tests : BaseTest +{ + /// + /// Perform tests using AWS Client + /// + /// + [TestMethod] + public Task TestS3WithAwsClient() + { + return TestS3WithClient("aws"); + } + + /// + /// Perform tests using Minio Client + /// + /// + [TestMethod] + public Task TestS3WithMinioClient() + { + return TestS3WithClient("minio"); + } + + /// + /// Performs tests with selected client. The S3 tests don't have any additional parameters to be set or tested. + /// + /// client to be used, either aws or minio + /// + private Task TestS3WithClient(string client) + { + CheckRequiredEnvironment([ + "TESTCREDENTIAL_S3_KEY", + "TESTCREDENTIAL_S3_SECRET", + "TESTCREDENTIAL_S3_BUCKETNAME", + "TESTCREDENTIAL_S3_REGION" + ]); + + Console.WriteLine( $"s3://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_BUCKETNAME")}/?s3-location-constraint={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_REGION")}&s3-storage-class=&s3-client={client}&auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_KEY")}&auth-password={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_SECRET")}"); + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"s3://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_BUCKETNAME")}/?s3-location-constraint={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_REGION")}&s3-storage-class=&s3-client={client}&auth-username={Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_KEY")}&auth-password={Uri.EscapeDataString(Environment.GetEnvironmentVariable("TESTCREDENTIAL_S3_SECRET")!)}" + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/SSH/SSHTests.cs b/LiveTests/Duplicati.Backend.Tests/SSH/SSHTests.cs new file mode 100644 index 000000000..5366edb0d --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/SSH/SSHTests.cs @@ -0,0 +1,86 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.SSH; + +/// +/// SSH Tests +/// +[TestClass] +public sealed class SshTests : BaseSftpgoTest +{ + + /// + /// Tests connecting to SSH using password authentication + /// + [TestMethod] + public async Task TestSSHWithPassword() + { + + var outputConsumer = new OutputConsumer(); + var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute | + UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute | + UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute; + + var temporaryKeysDir = CreateHttpsCertificates(); + + CreateUsersFile(temporaryKeysDir); + + var container = new ContainerBuilder() + .WithImage("drakkan/sftpgo") + .WithEnvironment("SFTPGO_LOG_LEVEL", "debug") + .WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json") + .WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0") + .WithEnvironment("SFTPGO_SFTPD__BINDINGS__0__PORT", "3333") + .WithPortBinding(3000, 3333) + .WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions) + .WithOutputConsumer(outputConsumer) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(3333)) + .Build(); + + Console.WriteLine("Starting container"); + await container.StartAsync(); + + // Once started we will already cleanup temporary directory + temporaryKeysDir.Delete(true); + + Console.WriteLine("Waiting X seconds"); + await Task.Delay(TimeSpan.FromSeconds(1)); + + // Console.WriteLine($"TCP Connect testing, @::1 is open = { await IsPortOpenAsync("::1",3000) }"); + // Console.WriteLine($"TCP Connect testing, @localhost is open = { await IsPortOpenAsync("localhost",3000) }"); + + var exitCode = CommandLine.BackendTester.Program.Main( + new[] + { + $"ssh://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@127.0.0.1:3000", + "--ssh-accept-any-fingerprints" + }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) + { + Console.WriteLine(await outputConsumer.GetStreamsOutput()); + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + await container.StopAsync(); + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Utility/Output.cs b/LiveTests/Duplicati.Backend.Tests/Utility/Output.cs new file mode 100644 index 000000000..0323aee30 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Utility/Output.cs @@ -0,0 +1,60 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Utility; +public class OutputConsumer : IOutputConsumer +{ + private readonly MemoryStream _stdout = new(); + private readonly MemoryStream _stderr = new(); + + public bool Enabled => true; + + public Stream Stdout => _stdout; + + public Stream Stderr => _stderr; + + public void Dispose() + { + _stdout.Dispose(); + _stderr.Dispose(); + } + + public async Task GetStreamsOutput() + { + var output = new StringBuilder(); + + _stdout.Position = 0; + using (var reader = new StreamReader(_stdout, leaveOpen: true)) + { + output.AppendLine("STDOUT:"); + output.AppendLine(await reader.ReadToEndAsync()); + } + + _stderr.Position = 0; + using (var reader = new StreamReader(_stderr, leaveOpen: true)) + { + output.AppendLine("STDERR:"); + output.AppendLine(await reader.ReadToEndAsync()); + } + + return output.ToString(); + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/Webdav/WebdavTests.cs b/LiveTests/Duplicati.Backend.Tests/Webdav/WebdavTests.cs new file mode 100644 index 000000000..de95aba08 --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/Webdav/WebdavTests.cs @@ -0,0 +1,141 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.Webdav; + +[TestClass] +public sealed class WebDavTests : BaseSftpgoTest +{ + + [TestMethod] + public async Task TestWebdavHttps() + { + var outputConsumer = new OutputConsumer(); + var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute | + UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute | + UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute; + + var temporaryKeysDir = CreateHttpsCertificates(); + + CreateUsersFile(temporaryKeysDir); + + var container = new ContainerBuilder() + .WithImage("drakkan/sftpgo") + .WithEnvironment("SFTPGO_LOG_LEVEL", "debug") + .WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__ENABLE_HTTPS", "1") + .WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__PORT", "8090") + .WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__CERTIFICATE_FILE", "fullchain.pem") + .WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__CERTIFICATE_KEY_FILE", "privkey.pem") + .WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json") + .WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0") + .WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions) + .WithPortBinding(8090, 8090) + .WithOutputConsumer(outputConsumer) + .Build(); + + Console.WriteLine("Starting container"); + await container.StartAsync(); + + // Once started we will already cleanup temporary directory + temporaryKeysDir.Delete(true); + + Console.WriteLine("Waiting X seconds"); + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Print logs + Console.WriteLine("Fetching logs..."); + var containerStartupLogs = await outputConsumer.GetStreamsOutput(); + Console.WriteLine(containerStartupLogs); + + // Running this ignoring the certificate + var exitCode = CommandLine.BackendTester.Program.Main( + new[] { $"webdavs://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:8090/", "--accept-any-ssl-certificate=true" }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) + { + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + // Running not ignoring the certificate, should return non-zero exit code + exitCode = CommandLine.BackendTester.Program.Main( + new[] { $"webdavs://{TEST_USER_NAME}:{TEST_USER_PASSWORD}@localhost:8090/", "--accept-any-ssl-certificate=false" }.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode == 0) + { + Assert.Fail("BackendTester is returning zero when it should return non-zero exit code, check logs for details"); + } + + Console.WriteLine("Stopping container"); + await container.StopAsync(); + + } + + [TestMethod] + public async Task TestWebdavHttp() + { + var outputConsumer = new OutputConsumer(); + var filePermissions = UnixFileModes.UserRead | UnixFileModes.UserWrite | UnixFileModes.UserExecute | + UnixFileModes.GroupRead | UnixFileModes.GroupWrite | UnixFileModes.GroupExecute | + UnixFileModes.OtherRead | UnixFileModes.OtherWrite | UnixFileModes.OtherExecute; + + var temporaryKeysDir = CreateHttpsCertificates(); + + CreateUsersFile(temporaryKeysDir); + + var container = new ContainerBuilder() + .WithImage("drakkan/sftpgo") + .WithEnvironment("SFTPGO_LOG_LEVEL", "debug") + .WithEnvironment("SFTPGO_WEBDAVD__BINDINGS__0__PORT", "8090") + .WithEnvironment("SFTPGO_LOADDATA_FROM", "/var/lib/sftpgo/users.json") + .WithEnvironment("SFTPGO_LOADDATA_CLEAN", "0") + .WithResourceMapping(temporaryKeysDir, "/var/lib/sftpgo/", filePermissions) + .WithPortBinding(8091, 8090) + .WithOutputConsumer(outputConsumer) + .Build(); + + Console.WriteLine("Starting container"); + await container.StartAsync(); + + // Once started we will already cleanup temporary directory + temporaryKeysDir.Delete(true); + + Console.WriteLine("Waiting X seconds"); + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Print logs + Console.WriteLine("Fetching logs..."); + var containerStartupLogs = await outputConsumer.GetStreamsOutput(); + Console.WriteLine(containerStartupLogs); + + // Running this ignoring the certificate + var exitCode = CommandLine.BackendTester.Program.Main( + ["webdav://testuser:testpassword@localhost:8091/"]); + + if (exitCode != 0) + { + Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + } + + Console.WriteLine("Stopping container"); + await container.StopAsync(); + + } +} \ No newline at end of file diff --git a/LiveTests/Duplicati.Backend.Tests/pCloud/pCloudTests.cs b/LiveTests/Duplicati.Backend.Tests/pCloud/pCloudTests.cs new file mode 100644 index 000000000..12f198d4a --- /dev/null +++ b/LiveTests/Duplicati.Backend.Tests/pCloud/pCloudTests.cs @@ -0,0 +1,78 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +namespace Duplicati.Backend.Tests.pCloud; + +/// +/// pCloud Tests for CI. +/// +/// These tests cannot be parallelized as they will share credentials and folders +/// +[TestClass] +[DoNotParallelize] +public sealed class pCloudTests : BaseTest +{ + /// + /// Perform tests using pCloud API + /// + /// + [TestMethod] + public Task TestpCoudAPI() + { + CheckRequiredEnvironment([ + "TESTCREDENTIAL_PCLOUD_SERVER", + "TESTCREDENTIAL_PCLOUD_TOKEN", + "TESTCREDENTIAL_PCLOUD_FOLDER" + ]); + + var testURI = $"pcloud://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_SERVER")}/{Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_FOLDER")}?&authid={Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_TOKEN")}"; + + // Specifically disable streaming to pass tests. + var exitCode = CommandLine.BackendTester.Program.Main(new[] {testURI, "--disable-streaming-transfers"}.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } + + /// + /// Perform tests using pCloud API + /// + /// + [TestMethod] + public Task TestpCoudAPIWithStreaming() + { + CheckRequiredEnvironment([ + "TESTCREDENTIAL_PCLOUD_SERVER", + "TESTCREDENTIAL_PCLOUD_TOKEN", + "TESTCREDENTIAL_PCLOUD_FOLDER" + ]); + + var testURI = $"pcloud://{Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_SERVER")}/{Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_FOLDER")}?&authid={Environment.GetEnvironmentVariable("TESTCREDENTIAL_PCLOUD_TOKEN")}"; + + var exitCode = CommandLine.BackendTester.Program.Main(new[] {testURI}.Concat(Parameters.GlobalTestParameters).ToArray()); + + if (exitCode != 0) Assert.Fail("BackendTester is returning non-zero exit code, check logs for details"); + + return Task.CompletedTask; + } + +} \ No newline at end of file diff --git a/Localizations/duplicati/localization-bn.mo b/Localizations/duplicati/localization-bn.mo index ab6dcb1f2..140042563 100644 Binary files a/Localizations/duplicati/localization-bn.mo and b/Localizations/duplicati/localization-bn.mo differ diff --git a/Localizations/duplicati/localization-bn.po b/Localizations/duplicati/localization-bn.po index afe1b4276..78e900e5b 100644 --- a/Localizations/duplicati/localization-bn.po +++ b/Localizations/duplicati/localization-bn.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: code smite , 2024\n" "Language-Team: Bengali (https://app.transifex.com/duplicati/teams/67655/bn/)\n" @@ -44,8 +44,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -147,78 +147,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 -msgid "aliases" +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" msgstr "" -#: Library/Interface/Strings.cs:25 -msgid "default value" +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" msgstr "" #: Library/Interface/Strings.cs:26 -msgid "[DEPRECATED]" +msgid "aliases" msgstr "" #: Library/Interface/Strings.cs:27 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:28 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "সাইজ" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "অজানা" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -335,9 +351,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -354,9 +369,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -367,9 +381,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -449,41 +462,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -492,47 +495,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -828,6 +979,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -860,110 +1016,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1438,7 +1490,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1446,22 +1502,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1469,28 +1525,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1502,42 +1558,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1554,7 +1610,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1565,7 +1621,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1810,43 +1866,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1962,6 +2020,11 @@ msgstr "" msgid "Dropbox" msgstr "" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1994,6 +2057,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2019,6 +2088,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2139,9 +2272,410 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "অন্য একটি ধারক চালু আছে এবং অবহিত করা হয়েছে" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2231,7 +2765,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2254,55 +2788,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "পুরানো লগ ডেটা পরিষ্কার করুন" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2310,11 +2855,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2322,86 +2867,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2409,34 +2993,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2451,32 +3071,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2484,68 +3104,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2930,48 +3517,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2979,11 +3584,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2991,27 +3596,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3026,88 +3631,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3116,12 +3723,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3134,11 +3741,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3151,30 +3769,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3182,44 +3801,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3228,22 +3847,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3255,11 +3874,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3269,11 +3888,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3281,11 +3900,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3293,41 +3912,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3339,11 +3968,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3351,71 +3980,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3424,11 +4045,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3436,43 +4057,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3480,11 +4101,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3492,62 +4113,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3559,49 +4180,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3609,11 +4230,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3623,11 +4244,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3637,11 +4258,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3653,111 +4274,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3765,11 +4386,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3777,74 +4398,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3852,11 +4473,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3864,11 +4485,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3876,11 +4497,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3890,11 +4511,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3902,22 +4523,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3925,15 +4546,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3943,42 +4564,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3986,11 +4607,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -3998,11 +4619,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4010,11 +4631,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4023,11 +4644,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4036,11 +4657,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4048,11 +4669,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4060,57 +4681,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4164,29 +4934,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4272,131 +5019,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4409,21 +5168,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4431,11 +5190,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4445,25 +5204,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4471,68 +5230,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4545,144 +5304,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4690,52 +5449,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4744,16 +5503,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4762,78 +5521,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5048,11 +5807,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-ca.mo b/Localizations/duplicati/localization-ca.mo index c4da8e778..f712d2cb6 100644 Binary files a/Localizations/duplicati/localization-ca.mo and b/Localizations/duplicati/localization-ca.mo differ diff --git a/Localizations/duplicati/localization-ca.po b/Localizations/duplicati/localization-ca.po index 893897586..85f54c5da 100644 --- a/Localizations/duplicati/localization-ca.po +++ b/Localizations/duplicati/localization-ca.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Marc Riera , 2024\n" "Language-Team: Catalan (https://app.transifex.com/duplicati/teams/67655/ca/)\n" @@ -44,8 +44,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -147,78 +147,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 -msgid "aliases" +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" msgstr "" -#: Library/Interface/Strings.cs:25 -msgid "default value" +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" msgstr "" #: Library/Interface/Strings.cs:26 -msgid "[DEPRECATED]" +msgid "aliases" msgstr "" #: Library/Interface/Strings.cs:27 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:28 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booleà" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeració" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Senyaladors" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Enter" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Ruta" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Mida" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Cadena" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Desconeguda" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "No es pot crear la carpeta perquè ja existeix" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancel·lat" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -335,9 +351,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -354,9 +369,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -367,9 +381,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -449,41 +462,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -492,47 +495,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -828,6 +979,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -860,110 +1016,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1438,7 +1490,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1446,22 +1502,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1469,28 +1525,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1502,42 +1558,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1554,7 +1610,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1565,7 +1621,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1810,43 +1866,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1962,6 +2020,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1994,6 +2057,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2019,6 +2088,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2139,6 +2272,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2163,7 +2697,7 @@ msgstr "" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2229,7 +2763,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2252,55 +2786,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2308,11 +2853,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2320,86 +2865,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Carpeta d'emmagatzematge temporal" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2407,34 +2991,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2449,32 +3069,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2482,68 +3102,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2928,48 +3515,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2977,11 +3582,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2989,27 +3594,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3024,88 +3629,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3114,12 +3721,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3132,11 +3739,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3149,30 +3767,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3180,44 +3799,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3226,22 +3845,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3253,11 +3872,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3267,11 +3886,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3279,11 +3898,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3291,41 +3910,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nom de la còpia de seguretat" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3337,11 +3966,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3349,71 +3978,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3422,11 +4043,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3434,43 +4055,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3478,11 +4099,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Desactiva la compactació automàtica" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3490,62 +4111,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3557,49 +4178,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3607,11 +4228,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3621,11 +4242,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3635,11 +4256,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3651,111 +4272,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Permet que la contrasenya canviï" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3763,11 +4384,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3775,74 +4396,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3850,11 +4471,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3862,11 +4483,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3874,11 +4495,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3888,11 +4509,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3900,22 +4521,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3923,15 +4544,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3941,42 +4562,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3984,11 +4605,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -3996,11 +4617,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4008,11 +4629,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4021,11 +4642,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4034,11 +4655,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4046,11 +4667,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4058,57 +4679,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4162,29 +4932,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configura les peticions HTTP" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4270,131 +5017,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "S'ha produït un error en executar l'script «{0}»: {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4407,21 +5166,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4429,11 +5188,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4443,25 +5202,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4469,68 +5228,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4543,144 +5302,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "La plantilla del missatge" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4688,52 +5447,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtre de missatges de registre" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4742,16 +5501,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4760,78 +5519,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5046,11 +5805,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-cs.mo b/Localizations/duplicati/localization-cs.mo index 14edc7c2e..5a04a7b98 100644 Binary files a/Localizations/duplicati/localization-cs.mo and b/Localizations/duplicati/localization-cs.mo differ diff --git a/Localizations/duplicati/localization-cs.po b/Localizations/duplicati/localization-cs.po index 93f9adec9..1ca3772bc 100644 --- a/Localizations/duplicati/localization-cs.po +++ b/Localizations/duplicati/localization-cs.po @@ -7,17 +7,17 @@ # Petr Rezek , 2017 # M D, 2024 # Jakub Loucký , 2024 -# Pavel Borecki , 2024 # Lukáš Tyrychtr , 2024 +# Pavel Borecki , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Lukáš Tyrychtr , 2024\n" +"Last-Translator: Pavel Borecki , 2024\n" "Language-Team: Czech (https://app.transifex.com/duplicati/teams/67655/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -48,13 +48,13 @@ msgstr "" #: Library/Encryption/Strings.cs:32 msgid "Set thread level utilized for crypting" -msgstr "" +msgstr "Nastavit kolik vláken použít pro šifrování" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." -msgstr "" +msgstr "Volba --{0} už není používána a byla označena jako zastaralá." #: Library/Encryption/Strings.cs:37 #, csharp-format @@ -159,78 +159,94 @@ msgstr "Nepodařilo se vyvolat nástroj GnuPG – program nevypíše výstup" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Nezdar při volání GnuPG, program neskončí" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "alternativní názvy" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "výchozí hodnota" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[ZASTARALÉ]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "hodnot" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Logická hodnota" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Vyčíslení" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Příznaky" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Celé číslo" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Popis umístění" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Velikost" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Textový řetězec" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Časové období" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Neznámé" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Složka nemůže být vytvořena, protože už existuje" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Požadovaná složka neexistuje" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Stornováno" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Požadovaný soubor neexistuje" @@ -355,9 +371,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -374,9 +389,8 @@ msgstr "Doménové jméno uživatele kterým se připojit k serveru." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -389,9 +403,8 @@ msgstr "" "Uživatelské jméno sloužící pro připojení se k serveru. Je možné ho zadat " "také prostřednictvím proměnné prostředí „AUTH_USERNAME“." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -482,41 +495,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternativní FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -527,55 +530,214 @@ msgstr "" "Heslo sloužící pro připojení se k serveru. Je možné ho zadat také " "prostřednictvím proměné prostředí „AUTH_PASSWORD“." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Jako ochrana před poruchami na síti, jsou veškerá nahraná data ověřována. " -"Použitím této volby sice zrychlíte nahrávání, ale bez ověřování bude méně " -"spolehlivé." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Vypnout ověřování nahraného" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Složka {0} nebyla nalezena, zpráva: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Složka {0} nebyla nalezena. Zpráva: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Soubor {0} byl sice nahrán, ale vrácená velikost byla {1} a přitom měla být " +"{2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Jako ochrana před výpadky sítě nebo serveru jsou nahraná data následně " +"zkontrolována. Touto volbou je možné to vypnout a zrychlit tak nahrávání, " +"ale za cenu nižší spolehlivosti." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Vypnout ověřování nahraného" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Pokud je tento příznak nastaven, typ FTP datového spojení bude změněn na " +"vybranou volbu." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Nastavit typ FTP datového spojení" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Pokud je tento příznak nastaven, režim šifrování FTP bude změněn na vybranou" +" volbu." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Nastavit režim šifrování FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Tento příznak řídí zásady pro SSL které použít pokud je zapnuto šifrování." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Nastavit zásady pro SSL které použít když je zapnuté šifrování" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Některé FTP servery potřebují k oznámení správné velikosti souboru malé " +"zpoždění. Požadované zpoždění záleží na topologii sítě. Pokud narazíte na " +"chyby týkající se chybné velikosti nahrávaných souboru, zkuste přidat pár " +"sekund zpoždení." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Přidat prodlevu pro nahrání souboru" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 msgid "Google Cloud Storage configuration module" -msgstr "" +msgstr "Modul nastavení pro úložiště Google Cloud" #: Library/Backend/GoogleServices/GCSConfig.cs:73 msgid "Expose Google Cloud Storage configuration as a web module" @@ -663,6 +825,8 @@ msgid "" "This backend can read and write data to Google Drive. Allowed format is " "\"googledrive://folder/subfolder\"." msgstr "" +"Tato podpůrná vrstva může číst a zapisovat data na Google disk. Umožněný " +"formát je „googledrive://slozka/podslozka“." #: Library/Backend/GoogleServices/Strings.cs:45 msgid "Google Drive" @@ -878,6 +1042,11 @@ msgstr "" msgid "Specify storage class" msgstr "Určuje třídu úložiště" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -910,124 +1079,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternativní FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Složka {0} nebyla nalezena. Zpráva: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Soubor {0} byl sice nahrán, ale vrácená velikost byla {1} a přitom měla být " -"{2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Jako ochrana před výpadky sítě nebo serveru jsou nahraná data následně " -"zkontrolována. Touto volbou je možné to vypnout a zrychlit tak nahrávání, " -"ale za cenu nižší spolehlivosti." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Pokud je tento příznak nastaven, typ FTP datového spojení bude změněn na " -"vybranou volbu." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Nastavit typ FTP datového spojení" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Pokud je tento příznak nastaven, režim šifrování FTP bude změněn na vybranou" -" volbu." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Nastavit režim šifrování FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Tento příznak řídí zásady pro SSL které použít pokud je zapnuto šifrování." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Nastavit zásady pro SSL které použít když je zapnuté šifrování" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Některé FTP servery potřebují k oznámení správné velikosti souboru malé " -"zpoždění. Požadované zpoždění záleží na topologii sítě. Pokud narazíte na " -"chyby týkající se chybné velikosti nahrávaných souboru, zkuste přidat pár " -"sekund zpoždení." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Přidat prodlevu pro nahrání souboru" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Chyba při mazání souboru: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Chyba čtení souboru: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Chyba při zápisu souboru: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul pro vytváření SSH soukromých/veřejných klíčů" @@ -1535,7 +1586,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Zda by měla být použitá třía HttpClient" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1543,11 +1598,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1557,11 +1612,11 @@ msgstr "" "použito výchozí pro OneDrive nebo OneDrive for Business prostřednictvím " "„{0}“." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Volitelný identifikátor úložiště" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1569,29 +1624,29 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "Identifikátor místa" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Nebyl zadán identifikátor místa" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Použity odporující si identifikátory místa: zadáno {0} ale nalezeno {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1603,41 +1658,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Skupina v Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "Identifikátor skupiny" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "E-mailová adresa skupiny" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Pro danou e-mailovou adresu nebyly nalezeny žádné skupiny: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Pro zadanou e-mailovou adresu {0} bylo nalezeno vícero skupiny:" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1656,7 +1711,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1667,7 +1722,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1926,43 +1981,45 @@ msgstr "Nebylo zadáno heslo" msgid "No username given" msgstr "Nebylo zadáno uživatelské jméno" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2099,6 +2156,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2133,6 +2195,12 @@ msgstr "" "Server odpověděl chybovým kódem {0} ({1}), což značí že nepodporuje WebDAV " "spojení " +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Složka {0} nebyla nalezena, zpráva: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2161,6 +2229,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Vypsat PROPFIND odpověď" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Přijmout libovolný serverový certifikát" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Volitelně přijmout známý SSL certifikát" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2286,9 +2418,410 @@ msgstr "Neočekávaný chybový kód: {0} – {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Je spuštěná jiná instance a byla upozorněna" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2308,13 +2841,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Podporované argumenty příkazového řádku:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2343,8 +2874,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Pokud se filtry nacházejí už v souboru s parametry, není možné je zadávat zároveň i na příkazovém řádku. Pro zadání filtrů uvnitř souboru s parametry použijte zvláštní volby --{0}, --{1}, nebo --{2}.\n" -"Každý filtr je třeba předeslat buď + nebo - a vícero filtrů je třeba spojit pomocí {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2382,7 +2911,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2390,7 +2919,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Zjištěna nepodporovaná verze SQLite ({0}), je třeba, aby byla {1} a vyšší" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2403,42 +2931,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Port na kterém webový server bude očekávat spojení. Je možné zadat vícero " -"hodnot oddělovaných čárkou." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Certifikát a klíč ve formátu PKCS #12 které webový server použije pro SSL. " -"Jsou podporovány pouze klíče RSA/DSA." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Heslo k dešifrování PKCS12 souboru s certifikátem." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Rozhraní na kterém webový server očekává spojení. Speciální hodnoty „*“ a " -"„any“ (libovolné) znamená libovolné rozhraní. Speciální hodnota „loopback“ " -"znamená zpětnou smyčku (loopback)." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Heslo vyžadované pro přístup k webovému serveru. Tato hodnota je uložena " -"takže ji nebude třeba zadávat při každém spuštění. Pokud není zadáno, je " -"heslo vypnut." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2448,132 +2977,165 @@ msgstr "" " „*“ (hvězdička), jsou umožněny všechny názvy strojů a kontrola názvu stroje" " je vypnutá." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Nastavte čas, po jehož uplynutí budou data protokolu smazána z databáze." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Odstranit staré záznamy událostí" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati potřebuje malou databázi pro uchovávání všech nastavení. Pomocí " -"této předvolby zvolíte kam jsou nastavení ukládána. Tuto předvolbu je možné " -"také nastavit pomocí proměnné prostředí {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Tato volba nastaví šifrovací klíč sloužící pomíchání místní databáze s " -"nastaveními. Tuto volbu je možné nastavit také pomocí proměnné prostředí " -"{0}. Pomocí volby --{1} je možné pomíchání databáze vypnout." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Složka pro dočasné ukládání" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2581,15 +3143,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2598,24 +3197,21 @@ msgstr "" "Nedaří se nalézt platné datum pro dané počáteční datum {0}, interval " "opakování {1} a dny, kdy je umožněno {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Server je spuštěn a očekává spojení na {0}, portu {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"S poskytnutými parametry není možné vytvořit SSL certifikát. Podrobnosti " -"výjimky: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Nedaří se otevřít soket pro očekávání spojení, vyzkoušené porty: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2627,22 +3223,22 @@ msgstr "Chyba načtení sestavení {0}, chybové hlášení: {1}" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "Chyba načtení procesu typu {0} sestavení {1}, chybové hlášení: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2650,11 +3246,11 @@ msgstr "" "Tato předvolba určuje intenzitu použité komprese. Nastavení na 0 (nula) jí " "vypíná a nastavení na 9 znamená nejúčinnější (ale nejpomalejší)." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2662,75 +3258,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Soubor nenalezen: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Experimentální*: 7z archiv s podporou algoritmu LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimentální – 7z archiv" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archiv není otevřen pro zápis" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archiv není otevřen pro čtení" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Daný soubor není součástí tohoto archivu." - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Počet vláken použitých při LZMA 2 kompresi. Jako výchozí se použije počet " -"procesorových jader." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Počet vláken použitých ke kompresi" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Tato předvolba určuje jaký kompresní algoritmus použít. Zapnutí této " -"předvolby způsobí že 7z použije rychlejší algoritmus, což se projeví na o " -"něco horším kompresním poměru." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "záloha" @@ -3161,48 +3719,66 @@ msgstr "Omezit velikost zálohovaných souborů" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Priorita vlákna" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Omezit velikost svazků" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3214,11 +3790,11 @@ msgstr "" "nových svazků – při čtení existujícího souboru je pro výběr kompresního " "modulu použit název souboru." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Vyberte který modul použít pro komprimaci" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3230,27 +3806,27 @@ msgstr "" "svazků – při čtení existujícího souboru je pro výběr šifrovacího modulu " "použit název souboru." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Vyberte který modul použít pro šifrování" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3278,34 +3854,36 @@ msgstr "" "používá správu logických svazků (LVM) a vyžaduje práva správce systému " "(root)." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Popis umístění ve kterém budou svazky umístěny dokud nebudou odeslány" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Množství svazků které vytvořit dopředu" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3313,45 +3891,45 @@ msgstr "" "Maximální počet souběžných asymetrických nahrávání. Nastavte na nulu pro " "zrušení limitu." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Umožněný počet souběžných nahrávání" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Zaznamenávat vnitřní informace do souboru" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Úroveň podrobnosti záznamů událostí" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3359,11 +3937,11 @@ msgstr "" "Pokud Duplicati zjistí že cílová složka chybí, automaticky ji vytvoří. " "Pomocí této předvolby je možné zabránit automatickému vytváření složek." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3378,14 +3956,14 @@ msgstr "" "středníkem a je možné použít většinu podob GUID, včetně těch se složenými " "závorkami." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Středníkem oddělovaný seznam guid idenfikátorů VSS zapisovačů které vynechat" " (pouze Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3407,11 +3985,22 @@ msgstr "" "Tato funkce je podporována pouze na systému Windows a vyžaduje práva správce" " systému." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3424,30 +4013,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Ověřovat nahrané soubory vypsáním jejich obsahu" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Nahrávat soubory souběžně" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3455,11 +4045,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Nerecyklovat spojení" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3469,33 +4059,33 @@ msgstr "" "ohlásí počet opakovaných pokusů. Zapnutím této předvolby budou při " "opětovných pokusech rovnou zobrazovány chybová hlášení." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Při opakovaném pokusu zobrazit chybové hlášení" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Nahrávat prázdné záložní soubory" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3504,22 +4094,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Práh varování před vyčerpáním kvóty" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3531,11 +4121,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Zacházení se symbolickými odkazy (symlink)" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3551,11 +4141,11 @@ msgstr "" "se. Volba „{2}“ bude ignorovat všechny pevné odkazy s více než jedním " "odkazem." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Zacházení se symbolickými odkazy (hardlink)" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3563,11 +4153,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Vynechávat soubory na základě atributů" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3579,41 +4169,51 @@ msgstr "" "které slouží k přístupu k obsahu zachyceného stavu. Toto obejití problému " "může zrychlit přístup k souborů pod systémem Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Namapovat zachycené stavy jako disky (pouze Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Název zálohy" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3625,11 +4225,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Spravovat přípony souborů, které nelze komprimovat" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3641,71 +4241,63 @@ msgstr "" "seznamů souborů. Mějte na paměti, že po vytvoření souboru na protějšku už s " "touto hodnotou nelze hýbat." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Velikost bloků pro kontrolní součty" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Seznam souborů u kterých zkoumat změny" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Umístění místní stavové databáze" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Seznam smazaných souborů" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Snížit využití paměti zakázáním vyhledávání v paměti" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Udržovat mezipaměť bloků v operační paměti" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Při spuštění se backendu nedotazovat" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3719,11 +4311,11 @@ msgstr "" "databáze. Daní za to je že velké indexové soubory zabírají více místa na " "vzdáleném úložišti a přitom nemusí být nikdy použity." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3735,43 +4327,43 @@ msgstr "" "bude uvolněn. Tato hodnota je procento z každého ze svazků a celkového " "úložiště." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Maximum zbytečného místa v procentech" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Hashovací algoritmus použitý na bloky" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Hashovací algoritmus použitý na soubory" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3783,11 +4375,11 @@ msgstr "" " Pomocí této předvolby toto automatické zkompaktňování vypnete a bude se dít" " pouze ručním spouštěním příkazu compact." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Zakázat automatické zmenšení" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3799,11 +4391,11 @@ msgstr "" "zajistí, že velké svazky které mohou mít pár bajtů ztraceného prostoru " "nejsou stahovány a přepisovány." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Velikost svazku může být nejvýše" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3813,11 +4405,11 @@ msgstr "" " vynutit seskupení malých souborů. Malé objemy budou vždy kombinovány když " "mohou zaplnit celý svazek." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Malých svazků nejvýše" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3827,42 +4419,42 @@ msgstr "" " a hledat existující bloky. To je dost pomalá operace ale může snížit objem " "stahovaných dat." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Při obnově použít místní údaje o souborech" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Uchovávat verzí nazpět" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Tuto volbu použijte k nastavení časového období, po které mají být " "uchovávány zálohy." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Zachovat všechny verze v časovém období" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3882,31 +4474,31 @@ msgstr "" "tyto.“ Tato volba také podporuje použití „U“ pro označení neomezeného " "časového intervalu." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Snížit počet verzí smazáním starých mezidobých záloh" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Toto volbu použijte, pokud chcete pokračovat i v případě, že chybí některé " "zdrojové záznamy." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignorovat chybějící zdrojové prvky" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Při obnovování přepsat soubory" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3914,11 +4506,11 @@ msgstr "" "Pomocí této předvolby zvyšte množství výstupu vytvářeného při spouštění " "volby. Obecně tato předvolba vytvoří řádek pro každý zpracovaný soubor." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Vypisovat více informací o průběhu" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3926,11 +4518,11 @@ msgstr "" "Pomocí této předvolby je možné zvýšit množství výstupu vytvářeného jako " "výsledek operace, včetně všech názvů souborů." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Vypsat plné výsledky" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3942,11 +4534,11 @@ msgstr "" "všech vzdálených souborů a může být použit pro ověření neporušenosti " "souborů." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Určit, zda mají být nahrány ověřovací soubory" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3956,11 +4548,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Množství vzorků které otestovat po provedení zálohy" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3970,11 +4562,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Procento vzorků které po záloze vyzkoušet" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3986,41 +4578,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Velikost vyrovnávací paměti čtení" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Umožnit změnu heslové fráze" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Vypsat pouze sady souborů" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4030,11 +4622,11 @@ msgstr "" "souborů. Vypnutí ukládání metadat zrychlí operaci zálohování a obnovy, ale " "velikost záloh příliš neovlivní." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4043,11 +4635,11 @@ msgstr "" "bránit v přístupu k souborům. Pomocí této předvolby budou obnovena i " "přístupová práva." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Obnovit přístupová práva souboru" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4057,11 +4649,11 @@ msgstr "" "tak, že vše proběhlo úspěšně. Pomocí této předvolby kontrolu vypnete a " "vyhnete se tak čekání na toto ověření." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Přeskočit kontrolu obnoveného souboru" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4071,28 +4663,28 @@ msgstr "" "objem stahovaných dat. Pomocí této předvolby tuto optimalizaci přeskočíte a " "použijete pouze vzdálená data." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Nepoužívat místní data" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4100,11 +4692,11 @@ msgstr "" "Pomocí této předvolby zvýšíte důkladnost ověřování kontrolováním otisku " "(hash) bloků načítaných ze svazku před vkládáním dat do obnovených souborů." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Zkontrolovat hashe bloků" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4117,11 +4709,11 @@ msgstr "" "všechny informace. Výslednou databázi lze prohledávat, ale nelze ji použít " "pro obnovení dat." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Opravit databázi s cestami" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4129,11 +4721,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Vynutit místní a jazyková nastavení" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4143,24 +4735,24 @@ msgstr "" "„Dnes“ nebo „Minulý čtvrtek“. Nastavením této volby budou zobrazovány " "skutečné datumy, například „12. listopad 2018, 8:01“." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Obsluhovat souborovou komunikaci s podpůrnou vrstvou (backend) pomocí " "vláknovaných rour (pipe)" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4170,22 +4762,22 @@ msgstr "" "vláken. Nastavení této hodnoty na nulu nebo méně bude dynamicky vyvažovat " "počet aktivních vláken tak, aby odpovídalo hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Omezit počet souběžných vláken" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Tuto volbu použijte pro nastavení počtu procesů které provádějí pořizování " "otisků dat." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Určete počet souběžných procesů vytváření otisků" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4193,11 +4785,11 @@ msgstr "" "Tuto volbu použijte pro nastavení počtu procesů které provádějí komprimaci " "výstupních dat." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Určete počet souběžných procesů komprimace" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4207,11 +4799,11 @@ msgstr "" " souborů, který je sloučením minulé kompletní zálohy a obsahu který byl " "nahrán při nekompletní zálohovací relaci." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4219,11 +4811,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4231,11 +4823,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4243,11 +4835,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Povolit odstranění všech množin souborů" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4262,11 +4854,11 @@ msgstr "" "vytvořit kopii všech platných položek v databázi. Nastavením tohoto umožní " "Duplicati provádět operaci VACUUM dle potřeby." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4274,11 +4866,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Vypnout skener načítání dopředu" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4288,11 +4880,11 @@ msgstr "" "zálohování. Pokud kontroly vypnete, nezapomeňte pravidelně spouštět příkazy " "check, abyste se ujistili, že vše funguje, jak má." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Vypnout kontroly konzistence seznamu souborů" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4300,15 +4892,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Nezálohovat při napájení z akumulátorů" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Stupeň podrobností záznamu událostí do souboru" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4323,42 +4915,42 @@ msgstr "" "obsaženy, pokud nezačínají na „-“. Regulární výrazy jsou podporovány v " "hranatých závorkách. Příklad: „Path*{0}+*Mail*{0}-[.*DNS]“" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Stupeň podrobnosti informací na konzoli" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4370,11 +4962,11 @@ msgstr "" "nazvaný něco jako „.nezalohovat“ a umístění tohoto souboru do složek, které " "by neměly být zálohovány." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Seznam souborů ze kterého jsou vynechány složky" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4382,11 +4974,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4394,11 +4986,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4411,11 +5003,11 @@ msgstr "" " tuto volbu. Dále nezapomeňte pro vykazování dalších dat nastavit buď " "--{0}={2} nebo --{1}={2}" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4424,11 +5016,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4436,11 +5028,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4448,11 +5040,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4461,50 +5202,50 @@ msgstr "" "Kryptografická knihovna nepodporuje znovupoužitelné transformace pro " "hashovací algoritmus {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" "Kryptografická knihovna nepodporuje tento algoritmus tvorby otisku (hash) " "{0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "Heslo existující zálohy nemůže být změněno" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Zachycený stav se nepodařilo vytvořit: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Nepodařilo se smazat soubor {0}, testuje se existence souboru" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "Zotaveno z problému z pokusu o smazání neexistujícího souboru {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Nepodařilo se zotavit z chyby při mazání souboru {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Operace smazání {0} se nezdařila, protože soubor nebyl nalezen " "(FileNotFound), náhradně bude vypsán obsah" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Výpis indikuje, že soubor {0} byl úspěšné smazán" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Výpis indikuje, že soubor {0} nebyl smazán" @@ -4568,29 +5309,6 @@ msgstr "" msgid "Configure http requests" msgstr "Nastavit http požadavky" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Přijmout libovolný serverový certifikát" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Volitelně přijmout známý SSL certifikát" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4690,7 +5408,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Nastavit Hyper-V modul" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4698,125 +5428,125 @@ msgstr "" "Tento modul funguje vnitřně pro zpracovávání (parse) zdrojových parametrů " "pro zálohování Microsoft SQL databází" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Nastavit modul pro Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Spustit skript" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Spouštět skript při ukončování" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Skript „{0}“ skončil se stavem (exit code) {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Skript „{0}“ vrátil chybový kód {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Při spuštění spustit vyžadovaný skript" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Chyba při vykonávání skriptu „{0}“: {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Vykonávání skriptu „{0}“ překročilo časový limit" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Spouštět skript při spouštení" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Skript „{0}“ vrátil chybové zprávy: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Tento modul může po ukončení operace zaslat e-mail" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Odeslat e-mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4829,21 +5559,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Text zprávy" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Heslo SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4855,11 +5585,11 @@ msgstr "" "\n" "Petr Novák , Jan Novák , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Příjemci e-mailu" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4869,25 +5599,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Odesilatel e-mailu" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Zprávy k odeslání" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4895,11 +5625,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "URL adresa SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4908,61 +5638,61 @@ msgstr "" "Toto nastavení dodává předmět e-mailu. Hodnoty jsou nahrazeny jak je popsáno" " v popisu pro --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Předmět e-mailu" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP uživatelské jméno" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Celá SMTP komunikace: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Nepodařilo se poslat e-mail prostřednictvím serveru: {0}, zpráva: {1}, " "opětovný pokus s {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-mail byl úspěšně odeslán prostřednictvím serveru: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Tento modul poskytuje podporu pro zasílání stavových hlášení pomocí XMPP " "zpráv" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Modul hlášení prostřednictvím XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP e-mail příjemce" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4975,35 +5705,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Šablona zprávy" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Uživatelské jméno XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Heslo XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5012,110 +5742,110 @@ msgstr "" "Je možné zadat něco z „{0}“, „{1}“, „{2}“, „{3}“. \n" "Je možné zadat vícero voleb oddělovaných čárkou, např. „{0},{1}“. Speciální hodnota „{4}“ je zkratka pro „{0},{1},{2},{3}“ a způsobí, že zpráva bude odeslána o všech zálohovacích operacích." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Odeslat zprávy pro veškeré operace" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Při přihlašování k Jabber serveru byl překročen časový limit" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Tento modul poskytuje podporu zasílání stavových hlášení pomocí HTTP zpráv" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Modul HTTP hlášení" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Název parametru který poslat jako zprávu" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Dodatečné parametry, které se mají přidat k HTTP zprávě" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5123,46 +5853,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Zprávu se nepodařilo odeslat: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtr zpráv záznamu událostí" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5170,7 +5900,7 @@ msgstr "" "Tuto volbu použijte pro nastavení nejvyššího počtu řádků záznamu událostí, " "které zahrnout do výkazu. Nula nebo záporná hodnota znamená neomezeno." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5179,16 +5909,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Formát není podporován: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Neplatná hodnota velikosti: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Ověřování SSL certifikátu bylo vyvoláno v nesprávném pořadí" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5197,7 +5927,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5206,72 +5936,72 @@ msgstr "" "Nepodařilo se ověřit otisk (hash) certifikátu, chybové hlášení: {0}, název " "SSL chyby: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Dočasná složka neexistuje: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" "Nepodařilo se zpracovat (parse) segment: {0}, neplatné celé kladné číslo" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Neplatný specifikátor: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Nezpracovaná data: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "URI adresa není platná: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "V URI adrese chybí název stroje: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bajtů" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Řetězec „{0}“ se nepodařilo převést na datum" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Nelze číst a zapisovat do stejného proudu" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5509,11 +6239,11 @@ msgstr "Vypnout výstup na konzoli" msgid "This link may provide additional information: {0}" msgstr "Tento odkaz může poskytnout další podrobnosti: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Vyp/zap. automatické aktualizace" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-da.mo b/Localizations/duplicati/localization-da.mo index e367d4771..7373242f5 100644 Binary files a/Localizations/duplicati/localization-da.mo and b/Localizations/duplicati/localization-da.mo differ diff --git a/Localizations/duplicati/localization-da.po b/Localizations/duplicati/localization-da.po index c2b66eb30..aca626f76 100644 --- a/Localizations/duplicati/localization-da.po +++ b/Localizations/duplicati/localization-da.po @@ -4,22 +4,22 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Mikki Sørensen , 2017 # Michael Fogh Kristensen , 2018 # Nicolai Simonsen , 2018 # Niels Langkilde, 2020 # Henning Markussen, 2024 -# Rune Henriksen , 2024 -# Mikki Sørensen , 2024 # Kenneth Skovhede , 2024 +# Rune Henriksen , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Kenneth Skovhede , 2024\n" +"Last-Translator: Rune Henriksen , 2024\n" "Language-Team: Danish (https://app.transifex.com/duplicati/teams/67655/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -52,8 +52,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -164,78 +164,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Fejl under kald af GnuPG, programmet vil ikke stoppe" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliasser" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "standard værdi" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[FORÆLDET]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "værdier" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boolean" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeration" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flag" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Heltal" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Sti" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Størrelse" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Streng" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Tidsperiode" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Ukendt" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Mappen kan ikke oprettes, fordi den allerede eksisterer" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Den ønskede mappe eksisterer ikke" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Annulleret" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Den ønskede fil eksisterer ikke" @@ -364,9 +380,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -383,9 +398,8 @@ msgstr "Domæne navnet på brugeren som forbinder til serveren" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -398,9 +412,8 @@ msgstr "" "Brugernavnet der bruges til at forbinde til serveren. Brugernavnet kan også " "angives via miljøvariablen \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -491,41 +504,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternative FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -536,50 +539,207 @@ msgstr "" "Kodeordet der bruges til at forbinde til serveren. Kodeordet kan også " "angives via miljøvariablen \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"For at beskytte mod netværksfejl vil alle uploads blive forsøgt bekræftet. " -"Brug denne indstilling til at deaktivere denne bekræftelse for at gøre " -"upload hurtigere, men mindre pålidelig." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Deaktiver upload verifikation" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Mappen {0} blev ikke fundet, besked: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Mappen {0} blev ikke fundet. Beskeden var: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Filen {0} blev uploaded men den rapporterede størrelse var {1} og den burde " +"være {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"For at beskytte mod netværksfejl og serverfejl, bliver hver upload " +"verificeret. Brug denne indstilling til at deaktivere verifikationen for at " +"gøre uploads processen hurtigere, men mindre pålidelig." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Deaktiver upload verifikation" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Hvis denne indstilling er sat vil FTP data forbindelsen blive ændret til den" +" valgte indstilling." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Angiv FTP data forbindelsestypen" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Hvis denne indstilling er sat vil FTP krypteringen blive ændret til den " +"valgte indstilling." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Angiv FTP krypteringsindstilling" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Denne indstilling styrer hvilken SSL politik der skal anvendes når " +"kryptering er aktiveret." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Angiv hvilken SSL politik der skal bruges når kryptering er aktiveret." + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Tilføj forsinkelse efter upload af fil" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -879,6 +1039,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -911,122 +1076,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternative FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Mappen {0} blev ikke fundet. Beskeden var: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Filen {0} blev uploaded men den rapporterede størrelse var {1} og den burde " -"være {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"For at beskytte mod netværksfejl og serverfejl, bliver hver upload " -"verificeret. Brug denne indstilling til at deaktivere verifikationen for at " -"gøre uploads processen hurtigere, men mindre pålidelig." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Hvis denne indstilling er sat vil FTP data forbindelsen blive ændret til den" -" valgte indstilling." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Angiv FTP data forbindelsestypen" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Hvis denne indstilling er sat vil FTP krypteringen blive ændret til den " -"valgte indstilling." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Angiv FTP krypteringsindstilling" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Denne indstilling styrer hvilken SSL politik der skal anvendes når " -"kryptering er aktiveret." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Angiv hvilken SSL politik der skal bruges når kryptering er aktiveret." - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Tilføj forsinkelse efter upload af fil" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Fejl ved sletning af fil: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Fejl ved læsning af fil: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Fejl ved skrivning af fil: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1510,7 +1559,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1518,22 +1571,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1541,28 +1594,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1574,41 +1627,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 gruppe" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Gruppens e-mail-adresse" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Ingen grupper fundet med den angivne e-mail-adresse: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Flere grupper med den givne e-mail-adresse: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1626,7 +1679,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1637,7 +1690,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1886,43 +1939,45 @@ msgstr "Password ikke sat" msgid "No username given" msgstr "Brugernavn ikke sat" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2040,6 +2095,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2072,6 +2132,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Mappen {0} blev ikke fundet, besked: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2097,6 +2163,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accepter enhvert servercertifikat" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2217,10 +2347,410 @@ msgstr "Uventet fejlkode: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" -"En instans af programmet kører allerede og denne instans blev aktiveret" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2240,13 +2770,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Understøttede kommandolinje argumenter:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2275,10 +2803,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filtre kan ikke angives på kommandolinen hvis der også er filtre i parameter" -" filen. Brug de specielle indstillinger --{0}, --{1}, eller --{2} til at " -"angive filter inde i parameter filen. Hvert filter skal starte med enten + " -"eller -, og flere filter skal sammensættes med {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2316,7 +2840,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2324,8 +2848,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Ikke-understøttet version af SQLite opdaget ({0}), skal være {1} eller " -"højere" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2338,42 +2860,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Den port, webserveren lytter til. Flere værdier kan angives med et komma " -"imellem." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Webserverens SSL certifikatet og nøglefil i PKCS #12 format. Kun RSA / DSA " -"nøgler understøttes." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Koden til dekryptering af certifikatet i PKCS #12-filen." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Det netværksinterface, webserveren lytter til. De specielle værdier \"*\" og" -" \"any\" betyder all interfaces. Specialværdien \"loopback\" betyder " -"loopback-adapteren." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Koden der angives for at få adgang til webserveren. Denne indstilling er " -"gemt, så du ikke behøver at angive den ved hver opstart. Hvis der angives en" -" tom streng, deaktiveres adgangskoden." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2382,132 +2905,164 @@ msgstr "" "Hostnavne der er accepteret, separeret med semikolon. Hvis nogle af " "hostnavne er \"*\" vil alle hostnavne være tilladt." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Indstil den tid, hvorefter logdata vil blive fjernet fra databasen." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Ryd gammel logdata" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati skal gemme en lille database med alle indstillinger. Brug denne " -"indstilling til at vælge, hvor indstillingerne er gemt. Denne mulighed kan " -"også indstilles med miljøvariablen {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Denne indstilling angiver krypteringsnøglen, der bruges til at obfuskere " -"databasen med lokale indstillinger. Denne mulighed kan også indstilles med " -"miljøvariablen {0}. Brug indstillingen --{1} for at deaktivere " -"obfuskeringen." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Midlertidig mappe" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2515,15 +3070,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2532,24 +3124,21 @@ msgstr "" "Kan ikke finde en gyldig dato, givet startdatoen {0}, gentagelsesintervallet" " {1} og de tilladte dage {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Serveren er startet og lytter på {0}, port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Kunne ikke oprette SSL-certifikat ved hjælp af de angivne parametre. " -"Fejldetaijer: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Kunne ikke åbne et socket til at lytte på, forsøgte disse porte: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2561,32 +3150,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2594,70 +3183,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Fil ikke fundet: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "* Eksperimentel *: 7z arkiv med LZMA2 support." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Eksperimentelt - 7z arkiv" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Arkivet er ikke åbnet for skrivning" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Arkivet er ikke åbnet for læsning" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Den pågældende fil er ikke en del af dette arkiv" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Antal tråde brugt til komprimering" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "backup" @@ -3045,48 +3601,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Tråd prioritet" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3094,11 +3668,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Vælg hvilket modul, der skal bruges til komprimering" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3106,27 +3680,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Vælg det modul, der skal bruges til kryptering" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3141,88 +3715,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Antal samtidige uploads tilladt" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Log interne oplysninger til en fil" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Log informationsniveau" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3231,12 +3807,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3249,11 +3825,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3266,30 +3853,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3297,44 +3885,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Genbrug ikke forbindelser" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Upload tomme backup filer" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3343,22 +3931,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3370,11 +3958,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Symlink håndtering" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3384,11 +3972,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Hardlink håndtering" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3396,11 +3984,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3408,41 +3996,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Navn på backupen" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3454,11 +4052,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Administrer ikke-komprimerbare filtyper" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3466,71 +4064,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Liste over filer, der skal undersøges for ændringer" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Sti til den lokale tilstandsdatabase" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Liste over slettede filer" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3539,11 +4129,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3551,43 +4141,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Hash-algoritme, der anvendes til blokke" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Hashalgoritmen der bruges til filer" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3595,11 +4185,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3607,62 +4197,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Volumenstørrelsestærskel" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Brug lokale fildata, ved gendannelse" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Antal versioner, der skal beholdes" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3674,49 +4264,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Overskriv filer ved genoprettelse" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3724,11 +4314,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3738,11 +4328,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3752,11 +4342,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3768,111 +4358,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3880,11 +4470,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3892,74 +4482,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3967,11 +4557,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3979,11 +4569,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3991,11 +4581,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4005,11 +4595,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4017,22 +4607,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4040,15 +4630,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4058,42 +4648,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4101,11 +4691,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4113,11 +4703,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4125,11 +4715,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4138,11 +4728,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4151,11 +4741,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4163,11 +4753,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4175,57 +4765,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Kunne ikke oprette snapshot: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4279,29 +5018,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accepter enhvert servercertifikat" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4387,131 +5103,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Kør script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Kør et script når handling udført" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Scriptet \"{0}\" returneres med kode {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Kør et påkrævet script ved opstart" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Kør et script ved opstart" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Skriptet \"{0}\" rapporterede fejlmeddelelser: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Send mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4524,21 +5252,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Meddelelsesteksten" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP Password" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4546,11 +5274,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "E-mail-modtager(e)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4560,25 +5288,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "E-mail afsender" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Beskeder, der skal sendes" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4586,72 +5314,72 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP Url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "E-mail emnet" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP brugernavn" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Hele SMTP-kommunikationen: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Kunne ikke sende e-mail med server: {0}, meddelelse: {1}, forsøger igen med " "{2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email sendt med succes ved hjælp af server: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Dette modul giver support til afsendelse af statusrapporter via XMPP-" "meddelelser" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP rapport modul" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP modtager email" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4664,35 +5392,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Meddelelsesskabelonen" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP brugernavnet" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP-adgangskoden" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4701,111 +5429,111 @@ msgstr "" "Du kan angive én af \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "Du kan angive flere muligheder adskilt med komma f.eks. \"{0},{1}\". Specialværdien \"{4}\" er en forenkling af \"{0},{1},{2},{3}\" og vil medføre at alle backup aktiviteter sender en besked." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Send meddelelser fra alle operationer" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Timeout opstod, mens du loggede ind på jabber-serveren" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Dette modul giver support til afsendelse af statusrapporter via HTTP-" "meddelelser" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP rapport modul" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Navnet på parameteren som beskeden bliver sendt som" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Ekstra parametre, der skal tilføjes til http-beskeden" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4813,52 +5541,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4867,16 +5595,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Ugyldig størrelsesværdi: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "SSL-certifikat-validatoren blev kaldt i en forkert rækkefølge" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4885,7 +5613,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -4894,71 +5622,71 @@ msgstr "" "Fejl under godkendelse af certifikat hash, fejlmeddelelse: {0}, SSL-" "fejlnavn: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Midlertidig mappe eksisterer ikke: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Kunne ikke behandle segmentet: {0}, ugyldigt heltal" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Ugyldig specifikation: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Ubehandlet data: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Uri er ugyldig: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Uri mangler et værtsnavn: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Strengen \"{0}\" kunne ikke oversættes til en dato" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Kan ikke læse og skrive på den samme stream" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5181,11 +5909,11 @@ msgstr "Deaktiver konsol udskrift" msgid "This link may provide additional information: {0}" msgstr "Det her link kan give nyttige oplysninger: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Skift indstillinger for automatiske opdateringer" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-de.mo b/Localizations/duplicati/localization-de.mo index b8d2839c9..c29223376 100644 Binary files a/Localizations/duplicati/localization-de.mo and b/Localizations/duplicati/localization-de.mo differ diff --git a/Localizations/duplicati/localization-de.po b/Localizations/duplicati/localization-de.po index 757bf7735..b027db701 100644 --- a/Localizations/duplicati/localization-de.po +++ b/Localizations/duplicati/localization-de.po @@ -6,15 +6,17 @@ # Translators: # Jürg Rast , 2016 # F M, 2016 +# S M , 2016 # M D, 2017 # Sven Dummis , 2017 # tobsen , 2017 +# Bruno Holliger , 2017 # Michael Arlt , 2017 # Ian Jobs , 2017 # ForGorNorPor, 2017 # Chris K , 2017 # jakob ecker , 2017 -# Markus Greitner, 2017 +# Felix, 2017 # Simon Walter , 2017 # Christian Fröhlich, 2017 # Tim Parth , 2017 @@ -32,23 +34,20 @@ # RiseT, 2019 # Ansgar, 2020 # Felix Kaba , 2020 +# Beppo, 2021 # Andre G , 2024 -# Beppo, 2024 -# Felix, 2024 -# S M , 2024 # Martin Posselt , 2024 -# sfahrenholz, 2024 -# Bruno Holliger , 2024 # agrajaghh , 2024 +# sfahrenholz, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: agrajaghh , 2024\n" +"Last-Translator: sfahrenholz, 2024\n" "Language-Team: German (https://app.transifex.com/duplicati/teams/67655/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -81,8 +80,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -193,78 +192,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Fehler beim Aufrufen von GnuPG, das Programm beendet sich nicht." -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "Alternativnamen" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "Standardwert" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[VERALTET]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "Werte" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boolean" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Aufzählung" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Optionen" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Integer" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Pfad" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Größe" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Zeitspanne" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Unbekannt" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Ordner kann nicht erstellt werden, weil dieser bereits existiert" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Der angeforderte Ordner existiert nicht" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Abgebrochen" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Die angefragte Datei existiert nicht" @@ -396,9 +411,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -417,9 +431,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -432,9 +445,8 @@ msgstr "" "Der Benutzername wird verwendet um sich mit dem Server zu verbinden. Es kann" " auch über die Umgebungsvariable \"AUTH_USERNAME\" gesetzt werden." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -526,41 +538,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternative FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -571,50 +573,213 @@ msgstr "" "Das Passwort wird verwendet um sich mit dem Server zu verbinden. Es kann " "auch über die Umgebungsvariable \"AUTH_PASSWORD\" gesetzt werden." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Zum Schutz vor Netzwerkfehlern, wird jeder Upload verifiziert. Verwende " -"diese Option für die Deaktivieren der Verifikation um den Upload schneller " -"aber weniger zuverlässig zu machen." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Deaktiviere Upload-Überprüfung" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Der Ordner {0} wurde nicht gefunden, Nachricht: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Der Ordner {0} konnte nicht gefunden werden. Nachricht: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Die Datei {0} wurde hochgeladen, aber die zurückgegebene Dateigröße ist {1} " +"und erwartet wurde {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Jeder Upload wird überpüft um sich vor Netzwerk- oder Serverfehlern zu " +"schützen. Nutze diese Option um die Überprüfung abzuschalten und den Upload " +"dadurch schneller, aber weniger verlässlich zu machen." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Deaktiviere Upload-Überprüfung" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Wenn diese Option aktiv ist, wird die FTP Datenverbindung auf die gewählte " +"Option geändert." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Stelle den FTP Datenverbindungstyp ein" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Wenn diese Option aktiv ist, dann wird der FTP Verschlüsselungsmodus auf die" +" gewählte Option gesetzt." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Stelle den FTP Verschlüsselungsmodus ein" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Dieses Flag steuert die SSL-Richtlinie, welche verwendet wird, wenn die " +"Verschlüsselung aktiviert ist." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Konfiguriere die SSL-Richtlinie, welche verwendet wird, wenn die " +"Verschlüsselung aktiviert ist." + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Einige FTP-Server benötigen eine kleine Verzögerung, bevor sie die korrekte " +"Dateigröße melden. Die erforderliche Verzögerung hängt von der " +"Netzwerktopologie ab. Wenn Sie Fehler im Zusammenhang mit der nicht " +"übereinstimmenden Upload-Größe feststellen, versuchen Sie, eine Verzögerung " +"von einigen Sekunden hinzuzufügen." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Hinzufügen einer Verzögerung nach dem Hochladen einer Datei" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -920,6 +1085,11 @@ msgstr "" msgid "Specify storage class" msgstr "Angabe der Speicherklasse" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -952,128 +1122,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternative FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Der Ordner {0} konnte nicht gefunden werden. Nachricht: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Die Datei {0} wurde hochgeladen, aber die zurückgegebene Dateigröße ist {1} " -"und erwartet wurde {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Jeder Upload wird überpüft um sich vor Netzwerk- oder Serverfehlern zu " -"schützen. Nutze diese Option um die Überprüfung abzuschalten und den Upload " -"dadurch schneller, aber weniger verlässlich zu machen." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Wenn diese Option aktiv ist, wird die FTP Datenverbindung auf die gewählte " -"Option geändert." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Stelle den FTP Datenverbindungstyp ein" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Wenn diese Option aktiv ist, dann wird der FTP Verschlüsselungsmodus auf die" -" gewählte Option gesetzt." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Stelle den FTP Verschlüsselungsmodus ein" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Dieses Flag steuert die SSL-Richtlinie, welche verwendet wird, wenn die " -"Verschlüsselung aktiviert ist." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Konfiguriere die SSL-Richtlinie, welche verwendet wird, wenn die " -"Verschlüsselung aktiviert ist." - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Einige FTP-Server benötigen eine kleine Verzögerung, bevor sie die korrekte " -"Dateigröße melden. Die erforderliche Verzögerung hängt von der " -"Netzwerktopologie ab. Wenn Sie Fehler im Zusammenhang mit der nicht " -"übereinstimmenden Upload-Größe feststellen, versuchen Sie, eine Verzögerung " -"von einigen Sekunden hinzuzufügen." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Hinzufügen einer Verzögerung nach dem Hochladen einer Datei" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Fehler beim Löschen der Datei: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Fehler beim Lesen der Datei: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Fehler beim Schreiben der Datei: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul zum Generieren privater/öffentlicher SSH-Schlüssel" @@ -1585,7 +1633,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Gibt an, ob die HttpClient Klasse genutzt werden soll" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1593,11 +1645,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1607,11 +1659,11 @@ msgstr "" "sein, wird das Standard OneDrive oder OneDrive for Business Laufwerk via " "'{0}' genutzt." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Optionale ID vom Laufwerk" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1619,28 +1671,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID der Seite" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Keine Seiten-ID wurde bereitgestellt" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "Widersprüchliche Seiten-IDs genutzt: {0} gegeben, aber {1} gefunden" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1652,42 +1704,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Gruppe" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID von der Gruppe" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "E-Mail Adresse der Gruppe" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Keine Gruppe mit der gegebenen E-Mail Adresse wurde gefunden: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "" "Es wurden mehrere Gruppen mit der gegebenen E-Mail Adresse gefunden: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "Widersprüchliche Gruppen-Id genutzt: {0} gegeben, aber {1} gefunden" @@ -1705,7 +1757,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1716,7 +1768,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1978,43 +2030,45 @@ msgstr "Kein Passwort angegeben" msgid "No username given" msgstr "Kein Benutzername angegeben" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2153,6 +2207,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2187,6 +2246,12 @@ msgstr "" "Der Server meldete den Fehlercode {0} ({1}), welcher anzeigt, dass der " "Server keine WebDAV-Verbindungen akzeptiert." +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Der Ordner {0} wurde nicht gefunden, Nachricht: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2215,6 +2280,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Dump die PROPFIND Antwort" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Jedes Serverzertifikat akzeptieren" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Optional ein bekanntes SSL-Zertifikat akzeptieren" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2341,9 +2470,410 @@ msgstr "Unerwarteter Fehlercode: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Eine andere Instanz läuft und wurde benachrichtigt" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2363,13 +2893,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Unterstützte Befehlszeilenargumente:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2398,11 +2926,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filter können nicht in der Kommandozeile angegeben werden, wenn bereits " -"Filter in der Parameterdatei enthalten sind. Verwende die Spezialoptionen " -"--{0}, --{1} oder --{2}, um Filter innerhalb der Parameterdatei anzugeben. " -"Jeder Filter muss als Präfix entweder ein + oder ein - enthalten und mehrere" -" Filter müssen mit {3} verknüpft werden." #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2440,7 +2963,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2448,8 +2971,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Nicht unterstützte Version von SQLite erkannt ({0}), muss {1} oder höher " -"sein" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2462,42 +2983,45 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Der Port auf dem der Webserver läuft. Mehrere Werte können mit einem Komma " -"getrennt angegeben werden." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Zertifikats- und Schlüsseldatei im PKCS#12-Format, welche der Webserver für " -"SSL nutzt. Es werden nur RSA/DSA Schlüssel unterstützt." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Passwort für die Entschlüsselung der Zertifikat-PKCS #12-Datei." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" +"Das Zertifikat und die Schlüsseldatei in PKCS #12 Format welches der " +"Webserver für SSL verwendet." + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Die Schnittstelle, worauf der Webserver auf ankommende Verbindungen wartet. " -"Die speziellen Werte \"*\" und \"any\" bedeuten alle Schnittstellen. Der " -"besondere Wert \"loopback\" steht für den \"Loopback-Adapter\"." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Das Passwort wird für den Zugriff auf den Webserver benötigt. Diese Option " -"wird gespeichert, so dass diese nicht bei jedem Start gesetzt werden muss. " -"Ein leerer Wert deaktiviert das Passwort." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2507,134 +3031,166 @@ msgstr "" "einer der Hostnamen \"*\" ist, sind alle Hostnamen erlaubt und die " "Überprüfung des Hostnamens ist deaktiviert." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Festlegen der Zeit, nach der Protokolldaten aus der Datenbank gelöscht " "werden." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Alte Protokolldaten bereinigen" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati muss eine kleine Datenbank mit allen Einstellungen speichern. Mit " -"dieser Option wählen Sie aus wohin die Einstellungen gespeichert werden. " -"Diese Option kann auch mit der Umgebungsvariablen {0} gesetzt werden." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Diese Option setzt den Schlüssel, um die lokale Einstellungsdatenbank zu " -"verschlüsseln. Diese kann ebenso durch die Umgebungsvariable {0} gesetzt " -"werden. Benutzen Sie die Option --{1}, um das Verschlüsseln der Datenbank zu" -" deaktivieren." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Temporärer Speicherordner" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2642,15 +3198,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2659,24 +3252,21 @@ msgstr "" "Mit den Eingaben zu Anfangsdatum {0}, Wiederholungsintervall {1} und " "erlaubte Tage {2} konnte kein gültiges Datum gefunden werden." -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Server gestartet und hört auf {0}, Port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"SSL-Zertifikat konnte nicht mit den angegebenen Parametern erstellt werden. " -"Fehlerinformation: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Öffnen von Socket nicht möglich, geprüfte Ports: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2689,22 +3279,22 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Fehler beim Laden des Prozesstyps {0} Assembly {1}, Fehlermeldung: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2712,11 +3302,11 @@ msgstr "" "Diese Option legt den verwendeten Komprimierungsgrad fest. Ein Null bedeutet" " keine Kompression, eine 9 führt zur maximalen Kompression." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2724,75 +3314,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Datei nicht gefunden: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Experimentell*: 7z Archiv mit Unterstützung für LZMA2 " - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimentell - 7z Archiv" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archiv nicht zum Schreiben geöffnet" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archiv nicht zum Lesen geöffnet" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Die angegebene Datei ist nicht Bestandteil dieses Archivs" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Die Anzahl der verwendeten Threads im LZMA-2-Komprimierung. Der Standardwert" -" ist die Anzahl der Prozessorkerne." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Anzahl der Threads für die Komprimierung" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Diese Option steuert den verwendeten Komprimierungsalgorithmus. Durch " -"Aktivieren dieser Option wird 7z dazu verwendet, den schnellen Algorithmus, " -"die etwas geringere Kompression zu verwenden." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "Sicherung" @@ -3245,48 +3797,66 @@ msgstr "Beschränken der Größe der zu sichernden Dateien" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Thread Priorität" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Beschränkt die Größe der Volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3299,11 +3869,11 @@ msgstr "" "einer vorhandenen Datei wird der Dateiname zur Auswahl des " "Komprimierungsmoduls verwendet." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Wähle, welches Modul zur Komprimierung verwendet wird." -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3316,27 +3886,27 @@ msgstr "" "Lesen einer vorhandenen Datei wird der Dateiname zur Auswahl des " "Verschlüsselungsmoduls verwendet." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Auswahl für des Moduls für die Verschlüsselung" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3364,36 +3934,38 @@ msgstr "" "benötigt. Unter Linux benützt Duplicate den Logical Volume Manager (LVM) und" " erfordert root-Berechtigungen." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" "Der Pfad unter dem abgeschlossene Datenträger gesichert werden bis sie " "hochgeladen werden" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Die Anzahl an Datenträgern, die im Vorfeld erstellt werden" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3402,45 +3974,45 @@ msgstr "" "Uploads die zulässig ist. Wird auf Null gesetzt, um den Grenzwert zu " "deaktivieren." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Die Anzahl der zulässigen gleichzeitigen Uploads" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Interne Protokollinformationen in einer Datei speichern" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Protokollinformationsstufe" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3449,11 +4021,11 @@ msgstr "" "erstellt. Das Aktivieren dieser Option verhindert das automatische Erstellen" " des Ordners " -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3468,14 +4040,14 @@ msgstr "" "ein Semikolon getrennt werden wobei die meisten GUID Formen erlaubt sind, " "auch mit und ohne geschweifte Klammern." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Eine mit Semikolon getrennte Liste von GUIDs von auszuschließenden VSS " "Writers (Nur Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3493,11 +4065,22 @@ msgstr "" "\"On\" führt dazu, dass Duplicati versucht das USN zu verwenden. Schlägt dies fehl, wird im Protokoll eine Warnmeldung ausgegeben. \n" "\"Required\": Duplicati bricht die Sicherung ab, wenn die USN-Verwendung fehlschlägt. Diese Funktion wird nur unter Windows unterstützt und erfordert Administratorrechte." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3510,30 +4093,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Überprüfe Uploads durch Auflisten des Inhalts" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Dateien synchron hochladen" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3541,11 +4125,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Verbindungen nicht wiederverwenden" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3556,33 +4140,33 @@ msgstr "" "damit die Fehlermeldungen angezeigt werden, wenn eine Wiederholung " "durchgeführt wird." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Fehlermeldungen bei erneutem Versuch anzeigen" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Leere Sicherungsdateien hochladen" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3591,22 +4175,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Schwellwert zur Warnung vor geringem Anteil" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3618,11 +4202,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Symlink-Handhabung" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3638,11 +4222,11 @@ msgstr "" "eindeutigen Pfad. Die Option \"{2}\" ignoriert alle Hardlinks mit mehreren " "Links." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Hardlink-Handhabung" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3650,11 +4234,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Dateien mit folgenden Attributen ausschließen" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3667,41 +4251,51 @@ msgstr "" " den Zugriff auf den Inhalt des Snapshots erlauben. Dieser Workaround kann " "auf Windows XP den Dateizugriff beschleunigen." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Snapshots einem Laufwerksbuchstaben zuweisen (Nur Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Name der Sicherung" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3713,13 +4307,13 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" "Verwalten von Dateinameerweiterungen, die nicht-komprimierbare Daten " "enthalten" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3732,72 +4326,64 @@ msgstr "" "von Dateilisten. Wichtig: Dieser Wert darf nicht geändert werden, wenn " "bereits Remote-Dateien erstellt wurden." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Blockgröße für Verwendung beim Hashing" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Liste von Dateien, die auf Änderungen untersucht werden" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Pfad zur lokalen Datenbank" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Liste von gelöschten Dateien" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Reduziert den Speicherbedarf, indem In-Memory-Lookups deaktivieren wird" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Speichere einen Block-Cache im Arbeitsspeicher." - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Backend beim Start nicht abfragen" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3812,11 +4398,11 @@ msgstr "" "Indexdateien mehr Remote-Speicherplatz beanspruchen und möglicherweise " "niemals verwendet werden." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3829,43 +4415,43 @@ msgstr "" "Prozentsatz, dieser Wert wird für jedes Volumen und den Gesamtspeicher " "verwendet." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Der maximal vergeudete Speicherplatz in Prozent" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Benutze Hash-Algorithmus für Blöcke" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Benutze Hash-Algorithmus für Dateien" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3878,11 +4464,11 @@ msgstr "" "Komprimierung zu deaktivieren und nur bei Verwendung des Kompaktbefehls " "anzuwenden." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Automatische Kompression deaktiveren" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3895,11 +4481,11 @@ msgstr "" "Bytes unnötigen Speicherplatz belegen, verhindert dies, dass diese " "unnötigerweise heruntergeladen und neu beschrieben werden." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Volumengröße Schwellwert" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3909,11 +4495,11 @@ msgstr "" "erzwingt dieser Parameter das Gruppieren von kleinen Dateien. Die kleinen " "Volumen werden kombiniert, wenn diese ein Gesamtvolumen füllen." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maximale Anzahl von kleinen Volumen" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3923,42 +4509,42 @@ msgstr "" "System zu finden. Dies ist ein ziemlich langsamer Vorgang, kann aber die " "Größe der Downloads beschränken." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Verwende Daten von lokale Datei bei Wiederherstellung" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Behalte eine Anzahl von Versionen" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Verwende diese Option, um den Zeitraum festzulegen, in dem Sicherungen " "beibehalten werden." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Behalte alle Versionen innerhalb einer Zeitspanne" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3979,33 +4565,33 @@ msgstr "" "werden gelöscht\". Diese Option unterstützt auch die Verwendung des " "Bezeichners \"U\", um ein unbegrenztes Zeitintervall anzugeben." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Verringern Sie die Anzahl der Versionen, indem Sie alte Zwischenversionen " "löschen" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Verwenden diese Option, um fortzufahren, auch wenn einige Quelleneinträge " "fehlen." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignoriere fehlende Quelleneinträge" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Dateien beim Wiederherstellen überschreiben" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -4014,11 +4600,11 @@ msgstr "" "Option generiert wird. Im Allgemeinen erzeugt diese Option für jede " "verarbeitete Datei eine Zeile." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Mehr Fortschrittsinformationen ausgeben" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -4026,11 +4612,11 @@ msgstr "" "Benutzen sie diese Option um den Detailgrad der Ausgabe von Operationen zu " "erhöhen. Diese beinhaltet alle Dateinamen." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Gib alle Ergebnisse aus" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -4042,11 +4628,11 @@ msgstr "" "Größe sowie die SHA256-Hashwerte aller Remote-Dateien. Diese kann zur " "Überprüfung der Integrität der Dateien verwendet werden." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Ermittle ob Prüfungs-Dateien hochgeldaen wurden" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4056,11 +4642,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Die Anzahl der zu testenden Samples nach einer Sicherung" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4070,11 +4656,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Der Prozentsatz der zu testenden Stichproben nach einer Sicherung" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4086,41 +4672,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Größe des Buffers zum Dateien lesen" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Ändern der Passphrase erlauben" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Nur Sicherungssätze anzeigen" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4131,11 +4717,11 @@ msgstr "" "Wiederherstellungsvorgänge beschleunigt, die Dateigröße wird nicht " "wesentlich beeinflusst." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4144,11 +4730,11 @@ msgstr "" "sonst eventuell nicht auf Ihre Dateien zugreifen könnten. Mithilfe dieser " "Option werden auch Dateiberechtigungen wiederhergestellt." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Dateizugriffsrechte wiederherstellen" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4159,11 +4745,11 @@ msgstr "" "Wiederherstellung erfolgreich war. Verwenden Sie diese Option, um die " "Prüfung zu deaktivieren und das Warten auf die Überprüfung zu vermeiden." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Überprüfung wiederhergestellter Dateien überspringen" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4173,28 +4759,28 @@ msgstr "" "der heruntergeladenen Daten zu minimieren. Verwenden Sie diese Option, um " "diese Optimierung zu deaktivieren und nur Server-Daten zu verwenden." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Lokale Daten nicht verwenden" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4203,11 +4789,11 @@ msgstr "" "Hash der Blöcke überprüfen, die aus einem Volume gelesen wurden, bevor die " "wiederhergestellten Dateien mit den Daten gepatcht werden." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Prüfe Block Hashe" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4220,11 +4806,11 @@ msgstr "" "aller Informationen. Die resultierende Datenbank kann durchsucht, jedoch " "nicht zur Wiederherstellung von Daten mit verwendet werden." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Datenbank mit Pfaden reparieren" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4232,11 +4818,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Sprachumgebungseinstellung erzwingen" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4246,22 +4832,22 @@ msgstr "" "oder \"Letzter Donnerstag\". Wenn Sie diese Option aktivieren, werden nur " "die aktuellen Daten angezeigt, z.B. \"Nov 12, 2018, 8:01 AM\"." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Benutze Thread-Pipes für die Dateikommunikation mit dem Backend" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4271,22 +4857,22 @@ msgstr "" "festzulegen. Ist der Wert 0 oder kleiner, wird die Anzahl der aktiven " "Threads dynamisch an die Hardware angepasst." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Beschränke die Anzahl gleichzeitiger Threads" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Verwende diese Option, um die Anzahl der Prozesse festzulegen, die das " "Hashing von Daten durchführen." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Gebe die Anzahl gleichzeitiger Hashing-Prozesse an" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4294,11 +4880,11 @@ msgstr "" "Verwende diese Option, um die Anzahl der Prozesse festzulegen, die die " "Datenausgabe komprimieren." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Gebe die Anzahl gleichzeitiger Komprimierungsprozesse an" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4309,11 +4895,11 @@ msgstr "" " Sicherung und den Inhalten, die in der unvollständigen Backup Sitzung " "hochgeladen wurden." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4321,11 +4907,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4333,11 +4919,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4345,11 +4931,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Erlaubt das Entfernen aller Sicherungssätze" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4365,11 +4951,11 @@ msgstr "" "Kopie aller gültigen Einträge in der Datenbank erstellen. Wird diese Option " "auf true gesetzt, führt Duplicati nach eigenem Ermessen VACUUM-Vorgänge aus." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4377,11 +4963,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Deaktiviert den Read-Ahead-Scanner" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4392,11 +4978,11 @@ msgstr "" "deaktivieren, stellen Sie sicher, dass Sie regelmäßig Überprüfungsbefehle " "ausführen, um sicherzustellen, dass alles wie erwartet funktioniert." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Deaktivieren der Konsistenzprüfung für Dateiliste" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4404,16 +4990,16 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" "Deaktiviere die Sicherung, falls sich das Gerät im Batteriebetrieb befindet" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Protokolldatei Informationslevel" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4428,42 +5014,42 @@ msgstr "" "diese nicht mit '-' beginnen. Reguläre Ausdrücke werden in eckige Klammern " "unterstützt. Beispiel: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Konsoleninformationsstufe" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4475,11 +5061,11 @@ msgstr "" "Verwendung ist eine Datei namens \".nobackup\" in den auszuschließenden " "Ordner abzulegen, welcher nicht in die Sicherung aufgenommen werden soll." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Liste der Dateinamen, die Ordner ausschließen" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4487,11 +5073,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4499,11 +5085,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4516,11 +5102,11 @@ msgstr "" "Datenbankabfragen zu protokollieren. Bitte beachten, entweder ---{0}={2} " "oder --{1}={2} festzulegen, um die zusätzlichen Protokolldaten zu loggen" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4529,11 +5115,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4541,11 +5127,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4553,11 +5139,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4566,51 +5301,51 @@ msgstr "" "Die Verschlüsselungs-Bibliothek unterstützt wiederverwendbare " "Transformationen für den Hash-Algorithmus {0} nicht" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" "Die Verschlüsselungs-Bibliothek unterstützt den Hash-Algorithmus {0} nicht" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "Die Passphrase für eine existierende Sicherung kann nicht geändert werden" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Fehler beim Erstellen des Snapshots: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Löschung der Datei fehlgeschlagen {0}, prüfe ob Datei existiert" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Fehler überwunden, beim Versuch die nicht existierende Datei {0} zu löschen" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Überwinden eines Fehlers beim Löschen der Datei {0} fehlgeschlagen" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Löschen fehlgeschlagen für {0}, die Datei wurde nicht gefunden, zeige " "Inhalte." -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Die Auflistung zeigt an, dass die Datei {0} korrekt gelöscht wurde." -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Die Auflistung bestätigt, dass die Datei {0} nicht gelöscht wurde" @@ -4675,29 +5410,6 @@ msgstr "" msgid "Configure http requests" msgstr "http-Anforderungen konfigurieren" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Jedes Serverzertifikat akzeptieren" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Optional ein bekanntes SSL-Zertifikat akzeptieren" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4798,7 +5510,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Hyper-V konfigurieren" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4806,127 +5530,127 @@ msgstr "" "Dieses Modul arbeitet intern um Quellparameter zu erfassen die für ein " "Backup einer Microsoft SQL Server Datenbank nötig sind." -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Microsoft SQL Server-Modul konfigurieren" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Skript ausführen" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Skript beim Beenden ausführen" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Das Skript \"{0}\" liefert den Exit-Code {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Das Skript \"{0}\" wurde mit Exit-Code {1}{2} beendet" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Erforderliches Skript beim Start ausführen" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Fehler beim Ausführen des Skripts \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Zeitüberschreitung beim Ausführen des Skripts \"{0}\"" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Skript beim Start ausführen " -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Das Skript \"{0}\" berichtete Fehlermeldungen: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" "Dieses Modul kann E-Mails versenden, nachdem eine Operation abgeschlossen " "wurde" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "E-Mail senden" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4939,21 +5663,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Der Nachrichtentext" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP-Passwort" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4965,11 +5689,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "E-Mail-Empfänger" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4979,25 +5703,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "E-Mail-Absender" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Die zu sendenden Nachrichten" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -5005,11 +5729,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP-URL" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -5018,61 +5742,61 @@ msgstr "" "Diese Einstellung liefert den E-Mail-Betreff. Die Werte werden ersetzt, wie " "in der Beschreibung für --{0} beschrieben." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Der E-Mail-Betreff" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP-Benutzername" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Gesamte SMTP-Kommunikation: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Fehler beim E-Mail senden mit dem Server: {0}, Nachricht: {1}, Wiederholen " "mit {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-Mail erfolgreich über den Server gesendet: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Dieses Modul unterstützt das Senden von Statusberichten über XMPP-" "Nachrichten" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP Report Modul" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP-Empfänger-E-Mail" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5085,35 +5809,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Die Nachrichtenvorlage" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Der XMPP-Benutzername" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Das XMPP-Passwort" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5122,109 +5846,109 @@ msgstr "" "Wähle zwischen \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Mehrere Optionen können durch Komma voneinander getrennt angegeben werden, z.B: \"{0},{1}\". Der besondere Wert \"{4}\" ist ein Kürzel für \"{0},{1},{2},{3}\" und wird alle Backupvorgänge veranlassen eine Meldung zu versenden." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Senden von Nachrichten für alle Operationen" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Zeitüberschreitung beim Anmelden beim Jabber-Server" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "Dieses Modul erlaubt das Senden eines Status via HTTP-Meldungen" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP Report-Modul" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Der Name des Parameters mit dem die Nachricht übertragen wird." -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Zusätzliche Parameter für die HTTP-Meldung" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5232,46 +5956,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Fehler beim Senden der Nachricht: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Protokollnachrichtenfilter" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5280,7 +6004,7 @@ msgstr "" "festzulegen, die in den Bericht aufgenommen werden sollen. Keine oder " "negative Werte bedeuten unbegrenzt Einträge." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5289,17 +6013,17 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Das Format ist nicht unterstützt: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Ungültige Größenangabe: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" "Der SSL-Zertifikatsprüfer wurde in einer ungültigen Reihenfolge aufgerufen." -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5308,7 +6032,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5317,71 +6041,71 @@ msgstr "" "Fehler beim Überprüfen des Zertifikat-Hashes, Fehlermeldung: {0}, SSL-" "Fehlername: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Temporärer Ordner existiert nicht: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Fehler beim Parsen des Segments: {0}, ungültige Ganzzahl" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Ungültige Angabe: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Ungeparste Daten: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Die Uri ist ungültig: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Dem Uri fehlt ein Hostname: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} Bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Die Zeichenkette \"{0}\" kann nicht in ein Datum geparst werden" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Lesen und Schreiben auf dem gleichen Stream nicht möglich" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5626,11 +6350,11 @@ msgstr "Konsolenausgabe deaktivieren" msgid "This link may provide additional information: {0}" msgstr "Dieser Link enthält möglicherweise zusätzliche Informationen: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Automatische Aktualisierungen umschalten" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-en_GB.mo b/Localizations/duplicati/localization-en_GB.mo index 7da6cb951..6e8ffe3b5 100644 Binary files a/Localizations/duplicati/localization-en_GB.mo and b/Localizations/duplicati/localization-en_GB.mo differ diff --git a/Localizations/duplicati/localization-en_GB.po b/Localizations/duplicati/localization-en_GB.po index c3b5d7449..b21fbd6d2 100644 --- a/Localizations/duplicati/localization-en_GB.po +++ b/Localizations/duplicati/localization-en_GB.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Andi Chandler , 2024\n" "Language-Team: English (United Kingdom) (https://app.transifex.com/duplicati/teams/67655/en_GB/)\n" @@ -42,13 +42,14 @@ msgstr "Empty passphrase not allowed" msgid "" "Use this option to set the thread level allowed for AES crypt operations." msgstr "" +"Use this option to set the thread level allowed for AES crypt operations." #: Library/Encryption/Strings.cs:32 msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -156,78 +157,94 @@ msgstr "Failure while invoking GnuPG, program won't flush output" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Failure while invoking GnuPG, program won't terminate" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliases" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "default value" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DEPRECATED]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "values" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boolean" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeration" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flags" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Integer" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Path" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Size" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Timespan" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Unknown" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "The folder cannot be created because it already exists" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "The requested folder does not exist" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancelled" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "The requested file does not exist" @@ -355,9 +372,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -374,9 +390,8 @@ msgstr "The domain name of the user used to connect to the server." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -389,9 +404,8 @@ msgstr "" "The username used to connect to the server. This may also be supplied as the" " environment variable \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -481,41 +495,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternative FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -526,50 +530,207 @@ msgstr "" "The password used to connect to the server. This may also be supplied as the" " environment variable \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "The folder {0} was not found. Message: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"The file {0} was uploaded but the returned size was {1} and it was expected " +"to be {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Disable upload verification" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configure the FTP data connection type" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configure the FTP encryption mode" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "This flag controls the SSL policy to use when encryption is enabled." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Configure the SSL policy to use when encryption is enabled" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Add a delay after uploading a file" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -875,6 +1036,11 @@ msgstr "" msgid "Specify storage class" msgstr "Specify storage class" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -907,122 +1073,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternative FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "The folder {0} was not found. Message: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configure the FTP data connection type" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configure the FTP encryption mode" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "This flag controls the SSL policy to use when encryption is enabled." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Configure the SSL policy to use when encryption is enabled" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Add a delay after uploading a file" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Error on deleting file: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Error reading file: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Error writing file: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Module for generating SSH private/public keys" @@ -1529,7 +1579,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Whether the HttpClient class should be used" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1537,11 +1591,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1550,11 +1604,11 @@ msgstr "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Optional ID of the drive" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1562,28 +1616,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID of the site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "No site ID was provided" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "Conflicting site IDs used: given {0} but found {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1595,41 +1649,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Group" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID of the group" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Email address of the group" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "No groups were found with the given email address: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Multiple groups were found with the given email address: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "Conflicting group IDs used: given {0} but found {1}" @@ -1647,7 +1701,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1658,7 +1712,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1917,43 +1971,45 @@ msgstr "No password given" msgid "No username given" msgstr "No username given" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2086,6 +2142,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2120,6 +2181,12 @@ msgstr "" "The server returned the error code {0} ({1}), indicating that the server " "does not support WebDAV connections" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "The folder {0} was not found, message: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2148,6 +2215,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Dump the PROPFIND response" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accept any server certificate" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Optionally accept a known SSL certificate" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2272,9 +2403,410 @@ msgstr "Unexpected error code: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Another instance is running, and was notified" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2294,13 +2826,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Supported commandline arguments:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2329,10 +2859,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filters cannot be specified on the commandline if filters are also present " -"in the parameter file. Use the special --{0}, --{1}, or --{2} options to " -"specify filters inside the parameter file. Each filter must be prefixed with" -" either a + or a -, and multiple filters must be joined with {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2370,14 +2896,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "Unsupported version of SQLite detected ({0}), must be {1} or higher" +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2390,43 +2916,44 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"The port the webserver listens on. Multiple values may be supplied with a " -"comma in between." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" -"The interface the webserver listens on. The special values \"*\" and \"any\"" -" means any interface. The special value \"loopback\" means the loopback " -"adapter." +"The certificate and key file in PKCS #12 format the webserver use for SSL." msgstr "" -"The interface the webserver listens on. The special values \"*\" and \"any\"" -" means any interface. The special value \"loopback\" means the loopback " -"adapter." #: Library/RestAPI/Strings.cs:36 -msgid "" -"The password required to access the webserver. This option is saved so you " -"do not need to set it on each run. Setting an empty value disables the " -"password." +msgid "The password for decryption of the provided certificate PKCS #12 file." msgstr "" -"The password required to access the webserver. This option is saved so you " -"do not need to set it on each run. Setting an empty value disables the " -"password." #: Library/RestAPI/Strings.cs:37 msgid "" +"The interface the webserver listens on. The special values \"*\" and \"any\"" +" means any interface. The special value \"loopback\" means the loopback " +"adapter." +msgstr "" + +#: Library/RestAPI/Strings.cs:38 +msgid "" +"The password required to access the webserver. This option is saved so you " +"do not need to set it on each run. Setting an empty value disables the " +"password." +msgstr "" + +#: Library/RestAPI/Strings.cs:39 +msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." @@ -2435,131 +2962,164 @@ msgstr "" "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Set the time after which log data will be purged from the database." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Clean up old log data" -#: Library/RestAPI/Strings.cs:42 -#, csharp-format -msgid "" -"Duplicati needs to store a small database with all settings. Use this option" -" to choose where the settings are stored. This option can also be set with " -"the environment variable {0}." -msgstr "" -"Duplicati needs to store a small database with all settings. Use this option" -" to choose where the settings are stored. This option can also be set with " -"the environment variable {0}." - -#: Library/RestAPI/Strings.cs:43 -msgid "Set the folder where settings are stored" -msgstr "" - #: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" +"Duplicati needs to store a small database with all settings. Use this option" +" to choose where the settings are stored. This option can also be set with " +"the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:45 +msgid "Set the folder where settings are stored" +msgstr "" + +#: Library/RestAPI/Strings.cs:46 +#, csharp-format +msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"This option sets the encryption key used to scramble the local settings " -"database. This option can also be set with the environment variable {0}. Use" -" the option --{1} to disable the database scrambling." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Temporary storage folder" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2567,41 +3127,75 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 -#, csharp-format -msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " -"disable database encryption with the option --{1}" -msgstr "" - -#: Library/RestAPI/Strings.cs:68 -#, csharp-format -msgid "" -"Unable to find a valid date, given the start date {0}, the repetition " -"interval {1} and the allowed days {2}" -msgstr "" -"Unable to find a valid date, given the start date {0}, the repetition " -"interval {1} and the allowed days {2}" - -#: Library/RestAPI/Strings.cs:73 -#, csharp-format -msgid "Server has started and is listening on {0}, port {1}" -msgstr "Server has started and is listening on {0}, port {1}" - #: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" msgstr "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" #: Library/RestAPI/Strings.cs:75 #, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " +"disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 +#, csharp-format +msgid "" +"Unable to find a valid date, given the start date {0}, the repetition " +"interval {1} and the allowed days {2}" +msgstr "" +"Unable to find a valid date, given the start date {0}, the repetition " +"interval {1} and the allowed days {2}" + +#: Library/RestAPI/Strings.cs:88 +#, csharp-format +msgid "Server has started and is listening on {0}, port {1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 +#, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Unable to open a socket for listening, tried ports: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2613,22 +3207,22 @@ msgstr "Failed to load assembly {0}, error message: {1}" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "Failed to load process type {0} assembly {1}, error message: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2636,11 +3230,11 @@ msgstr "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2648,75 +3242,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "File not found: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Experimental*: 7z Archive with LZMA2 support." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimental - 7z Archive" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archive not opened for writing" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archive not opened for reading" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "The given file is not part of this archive" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Number of threads used in compression" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "backup" @@ -3148,48 +3704,66 @@ msgstr "Limit the size of files being backed up" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Thread priority" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limit the size of the volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3201,11 +3775,11 @@ msgstr "" "volumes, when reading an existing file, the filename is used to select the " "compression module." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Select what module to use for compression" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3217,27 +3791,27 @@ msgstr "" "volumes, when reading an existing file, the filename is used to select the " "encryption module." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Select what module to use for encryption" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3263,34 +3837,36 @@ msgstr "" "and requires administrative privileges. On Linux this uses Logical Volume " "Management (LVM) and requires root privileges." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "The path where ready volumes are placed until uploaded" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "The number of volumes to create ahead of time" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3298,45 +3874,45 @@ msgstr "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "The number of concurrent uploads allowed" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Log internal information to a file" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Log information level" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3344,57 +3920,68 @@ msgstr "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 -msgid "" -"Use this option to exclude faulty writers from a snapshot. This is " -"equivalent to the -wx flag of the vshadow.exe tool, except that it only " -"accepts writer class GUIDs, and not component names or instance GUIDs. " -"Multiple GUIDs must be separated with a semicolon, and most forms of GUIDs " -"are allowed, including with and without curly braces." -msgstr "" -"Use this option to exclude faulty writers from a snapshot. This is " -"equivalent to the -wx flag of the vshadow.exe tool, except that it only " -"accepts writer class GUIDs, and not component names or instance GUIDs. " -"Multiple GUIDs must be separated with a semicolon, and most forms of GUIDs " -"are allowed, including with and without curly braces." - -#: Library/Main/Strings.cs:132 -msgid "" -"A semicolon separated list of guids of VSS writers to exclude (Windows only)" -msgstr "" -"A semicolon separated list of guids of VSS writers to exclude (Windows only)" - -#: Library/Main/Strings.cs:133 -msgid "" -"This setting controls the usage of NTFS USN numbers, which allows Duplicati " -"to obtain a list of files and folders much faster. If this is set to " -"\"off\", Duplicati will not attempt to use USN. Setting this to \"auto\" " -"makes Duplicati attempt to use USN, and fail silently if that was not " -"allowed or supported. A setting of \"on\" will also make Duplicati attempt " -"to use USN, but will produce a warning message in the log if it fails. " -"Setting it to \"required\" will make Duplicati abort the backup if the USN " -"usage fails. This feature is only supported on Windows and requires " -"administrative privileges." -msgstr "" -"This setting controls the usage of NTFS USN numbers, which allows Duplicati " -"to obtain a list of files and folders much faster. If this is set to " -"\"off\", Duplicati will not attempt to use USN. Setting this to \"auto\" " -"makes Duplicati attempt to use USN, and fail silently if that was not " -"allowed or supported. A setting of \"on\" will also make Duplicati attempt " -"to use USN, but will produce a warning message in the log if it fails. " -"Setting it to \"required\" will make Duplicati abort the backup if the USN " -"usage fails. This feature is only supported on Windows and requires " -"administrative privileges." - #: Library/Main/Strings.cs:134 +msgid "" +"Use this option to exclude faulty writers from a snapshot. This is " +"equivalent to the -wx flag of the vshadow.exe tool, except that it only " +"accepts writer class GUIDs, and not component names or instance GUIDs. " +"Multiple GUIDs must be separated with a semicolon, and most forms of GUIDs " +"are allowed, including with and without curly braces." +msgstr "" +"Use this option to exclude faulty writers from a snapshot. This is " +"equivalent to the -wx flag of the vshadow.exe tool, except that it only " +"accepts writer class GUIDs, and not component names or instance GUIDs. " +"Multiple GUIDs must be separated with a semicolon, and most forms of GUIDs " +"are allowed, including with and without curly braces." + +#: Library/Main/Strings.cs:135 +msgid "" +"A semicolon separated list of guids of VSS writers to exclude (Windows only)" +msgstr "" +"A semicolon separated list of guids of VSS writers to exclude (Windows only)" + +#: Library/Main/Strings.cs:136 +msgid "" +"This setting controls the usage of NTFS USN numbers, which allows Duplicati " +"to obtain a list of files and folders much faster. If this is set to " +"\"off\", Duplicati will not attempt to use USN. Setting this to \"auto\" " +"makes Duplicati attempt to use USN, and fail silently if that was not " +"allowed or supported. A setting of \"on\" will also make Duplicati attempt " +"to use USN, but will produce a warning message in the log if it fails. " +"Setting it to \"required\" will make Duplicati abort the backup if the USN " +"usage fails. This feature is only supported on Windows and requires " +"administrative privileges." +msgstr "" +"This setting controls the usage of NTFS USN numbers, which allows Duplicati " +"to obtain a list of files and folders much faster. If this is set to " +"\"off\", Duplicati will not attempt to use USN. Setting this to \"auto\" " +"makes Duplicati attempt to use USN, and fail silently if that was not " +"allowed or supported. A setting of \"on\" will also make Duplicati attempt " +"to use USN, but will produce a warning message in the log if it fails. " +"Setting it to \"required\" will make Duplicati abort the backup if the USN " +"usage fails. This feature is only supported on Windows and requires " +"administrative privileges." + +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3407,30 +3994,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Verify uploads by listing contents" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Upload files synchronously" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3438,11 +4026,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Do not re-use connections" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3452,33 +4040,33 @@ msgstr "" "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Show error messages when a retry is performed" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Upload empty backup files" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3487,22 +4075,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Threshold for warning about low quota" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3514,11 +4102,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Symlink handling" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3533,11 +4121,11 @@ msgstr "" "information, and treat each hardlink as a unique path. The option \"{2}\" " "will ignore all hardlinks with more than one link." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Hardlink handling" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3545,11 +4133,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Exclude files by attribute" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3561,41 +4149,51 @@ msgstr "" "then used to access the contents of a snapshot. This workaround can speed up" " file access on Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Map snapshots to a drive (Windows only)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Name of the backup" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3607,11 +4205,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Manage non-compressible file extensions" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3623,71 +4221,63 @@ msgstr "" "cause a large overhead on storage of file lists. Note that the value cannot " "be changed after remote files are created." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Block size used in hashing" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "List of files to examine for changes" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Path to the local state database" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "List of deleted files" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Reduce memory footprint by disabling in-memory lookups" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Store an in-memory block cache" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Do not query backend at startup" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3701,11 +4291,11 @@ msgstr "" "tradeoff is that larger index files take up more remote space and which may " "never be used." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3717,43 +4307,43 @@ msgstr "" "contain before being reclaimed. This value is a percentage used on each " "volume and the total storage." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "The maximum wasted space in percent" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "The hash algorithm used on blocks" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "The hash algorithm used on files" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3765,11 +4355,11 @@ msgstr "" "Use this option to disable such automatic compacting and only compact when " "running the compact command." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Disable automatic compacting" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3781,11 +4371,11 @@ msgstr "" "ensures that large volumes which may have a few bytes wasted space are not " "downloaded and rewritten." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Volume size threshold" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3795,11 +4385,11 @@ msgstr "" "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maximum number of small volumes" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3809,40 +4399,40 @@ msgstr "" " blocks. This is a fairly slow operation but can limit the size of " "downloads." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Use local file data when restoring" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Keep a number of versions" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "Use this option to set the timespan in which backups are kept." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Keep all versions within a timespan" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3862,29 +4452,29 @@ msgstr "" "also supports using the specifier \"U\" to indicate an unlimited time " "interval." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Reduce number of versions by deleting old intermediate backups" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "Use this option to continue even if some source entries are missing." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignore missing source elements" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Overwrite files when restoring" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3892,11 +4482,11 @@ msgstr "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Output more progress information" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3904,11 +4494,11 @@ msgstr "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Output full results" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3920,11 +4510,11 @@ msgstr "" "of all the remote files and can be used to verify the integrity of the " "files." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Determine if verification files are uploaded" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3934,11 +4524,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "The number of samples to test after a backup" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3948,11 +4538,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "The percentage of samples to test after a backup" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3964,41 +4554,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Size of the file read buffer" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Allow the passphrase to change" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "List only filesets" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4008,11 +4598,11 @@ msgstr "" " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4020,11 +4610,11 @@ msgstr "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restore file permissions" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4034,11 +4624,11 @@ msgstr "" "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Skip restored file check" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4048,28 +4638,28 @@ msgstr "" "of downloaded data. Use this option to skip this optimisation and only use " "remote data." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Do not use local data" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4077,11 +4667,11 @@ msgstr "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Check block hashes" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4093,11 +4683,11 @@ msgstr "" "locate certain content without needing to reconstruct all information. The " "resulting database can be searched, but cannot be used to restore data with." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Repair database with paths" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4105,11 +4695,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Force the locale setting" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4119,22 +4709,22 @@ msgstr "" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Handle file communication with backend using threaded pipes" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4144,21 +4734,21 @@ msgstr "" "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Limit number of concurrent threads" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Use this option to set the number of processes that perform hashing of data." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Specify the number of concurrent hashing processes" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4166,11 +4756,11 @@ msgstr "" "Use this option to set the number of processes that perform compression of " "output data." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Specify the number of concurrent compression processes" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4180,11 +4770,11 @@ msgstr "" "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4192,11 +4782,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4204,11 +4794,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4216,11 +4806,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Allow removing all filesets" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4236,11 +4826,11 @@ msgstr "" "this to true will allow Duplicati to perform VACUUM operations at its " "discretion." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4248,11 +4838,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Disable the read-ahead scanner" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4262,11 +4852,11 @@ msgstr "" "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Disable filelist consistency checks" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4274,15 +4864,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Disable the backup when on battery power" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Log file information level" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4297,42 +4887,42 @@ msgstr "" "they start with '-'. Regular expressions are supported within hard braces. " "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Console information level" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4344,11 +4934,11 @@ msgstr "" "file named something like \".nobackup\" and place this file into folders " "that should not be backed up." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "List of filenames that exclude folders" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4356,11 +4946,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4368,11 +4958,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4385,11 +4975,11 @@ msgstr "" "remember to set either --{0}={2} or --{1}={2} to report the additional log " "data" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4398,11 +4988,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4410,11 +5000,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4422,11 +5012,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4435,47 +5174,47 @@ msgstr "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "The cryptolibrary does not support the hash algorithm {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "The passphrase cannot be changed for an existing backup" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Failed to create a snapshot: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Failed to delete file {0}, testing if file exists" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Recovered from problem with attempting to delete non-existing file {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Failed to recover from error deleting file {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "Delete operation failed for {0} with FileNotFound, listing contents" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Listing indicates file {0} was deleted correctly" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Listing confirms file {0} was not deleted" @@ -4538,29 +5277,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configure http requests" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accept any server certificate" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Optionally accept a known SSL certificate" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4658,7 +5374,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configure Hyper-V module" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4666,125 +5394,125 @@ msgstr "" "This module works internally to parse source parameters to backup Microsoft " "SQL Server databases" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configure Microsoft SQL Server module" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Run script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Run a script on exit" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "The script \"{0}\" returned with exit code {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "The script \"{0}\" returned with exit code {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Run a required script on startup" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Error while executing script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Execution of the script \"{0}\" timed out" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Run a script on startup" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "The script \"{0}\" reported error messages: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "This module can send email after an operation completes" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Send mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4797,21 +5525,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "The message body" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP Password" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4823,11 +5551,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email recipient(s)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4837,25 +5565,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Email sender" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "The messages to send" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4863,11 +5591,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP Url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4876,59 +5604,59 @@ msgstr "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "The email subject" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP Username" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Whole SMTP communication: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Failed to send email with server: {0}, message: {1}, retrying with {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email sent successfully using server: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "This module provides support for sending status reports via XMPP messages" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP report module" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP recipient email" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4941,35 +5669,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "The message template" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "The XMPP username" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "The XMPP password" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4978,110 +5706,110 @@ msgstr "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Send messages for all operations" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Timeout occurred while logging in to Jabber server" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "This module provides support for sending status reports via HTTP messages" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP report module" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "The name of the parameter to send the message as" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Extra parameters to add to the HTTP message" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5089,46 +5817,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Failed to send message: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Log message filter" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5136,7 +5864,7 @@ msgstr "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5145,16 +5873,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "The format is not supported: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Invalid size value: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "The SSL certificate validator was called in an incorrect order" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5163,7 +5891,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5172,71 +5900,71 @@ msgstr "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Temporary folder does not exist: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Failed to parse the segment: {0}, invalid integer" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Invalid specifier: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Unparsed data: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "The Uri is invalid: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "The Uri is missing a hostname: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "The string \"{0}\" could not be parsed into a date" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Cannot read and write on the same stream" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5468,11 +6196,11 @@ msgstr "Disable console output" msgid "This link may provide additional information: {0}" msgstr "This link may provide additional information: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Toggle automatic updates" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-es.mo b/Localizations/duplicati/localization-es.mo index d33f92122..ad296035b 100644 Binary files a/Localizations/duplicati/localization-es.mo and b/Localizations/duplicati/localization-es.mo differ diff --git a/Localizations/duplicati/localization-es.po b/Localizations/duplicati/localization-es.po index 29dedc2d0..d58e32376 100644 --- a/Localizations/duplicati/localization-es.po +++ b/Localizations/duplicati/localization-es.po @@ -4,21 +4,22 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Martín Gómez González , 2017 # Dagoberto Rodriguez , 2018 # Miguel Angel Gabriel , 2024 +# Sebastián Emanuel Magariños , 2024 # Jose Couto , 2024 -# Martín Gómez González , 2024 -# Joaquín Entrialgo, 2024 # Pruebas, 2024 +# Joaquín Entrialgo, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Pruebas, 2024\n" +"Last-Translator: Joaquín Entrialgo, 2024\n" "Language-Team: Spanish (https://app.transifex.com/duplicati/teams/67655/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -46,16 +47,18 @@ msgstr "No se permite una frase de seguridad vacía" msgid "" "Use this option to set the thread level allowed for AES crypt operations." msgstr "" +"Utilice esta opción para establecer el nivel de subproceso permitido para " +"las operaciones de cifrado AES." #: Library/Encryption/Strings.cs:32 msgid "Set thread level utilized for crypting" -msgstr "" +msgstr "Establecer el nivel de subproceso utilizado para el cifrado." -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." -msgstr "" +msgstr "La opción --{0} ya no está disponibles y fue deprecada," #: Library/Encryption/Strings.cs:37 #, csharp-format @@ -72,6 +75,12 @@ msgid "" "program is available via the PATH environment variable. It is possible to " "supply the path to GPG using the option --{0}." msgstr "" +"El módulo de cifrado GPG utiliza el programa GNU Privacy Guard para cifrar y" +" descifrar archivos. Requiere que el ejecutable gpg esté disponible en el " +"sistema. En Windows se supone que esto está en la carpeta de instalación " +"predeterminada en archivos de programa, en Linux y OSX se supone que el " +"programa está disponible a través de la variable de entorno PATH. Es posible" +" proporcionar la ruta a GPG usando la opción --{0}." #: Library/Encryption/Strings.cs:42 msgid "GNU Privacy Guard, external" @@ -113,6 +122,8 @@ msgid "" "The path to the GNU Privacy Guard program. If not supplied, Duplicati will " "search for \"gpg2\" and \"gpg\" on the system." msgstr "" +"El camino hacia el programa GNU Privacy Guard. Si no se suministra, " +"Duplicati buscará \"gpg2\" y \"gpg\" en el sistema." #: Library/Encryption/Strings.cs:49 msgid "The path to GnuPG" @@ -162,78 +173,94 @@ msgstr "Fallo al invocar GnuPG, el programa no limpiará la salida" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Fallo al invocar GnuPG, el programa no terminará" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "alias" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valor por defecto" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[OBSOLETO]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valores" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boleana" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeración" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Indicadores" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Número entero" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Ruta" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Tamaño" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Cadena" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Intervalo de tiempo" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Desconocido" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "La carpeta no se puede crear porque ya existe" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "No existe la carpeta solicitada" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancelado" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "El archivo solicitado no existe" @@ -369,9 +396,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -389,9 +415,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -404,9 +429,8 @@ msgstr "" "El nombre usuario utilizado para conectar al servidor. También puede ser " "suministrada como variable de entorno \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -498,41 +522,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternativo" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -543,50 +557,209 @@ msgstr "" "La contraseña utilizada para conectarse al servidor. También puede ser " "suministrada como variable de entorno \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Para protegerse contra fallos en la red, se intentará verificar cada carga. " -"Utilice esta opción para deshabilitar esta verificación para que la carga " -"sea más rápida pero menos confiable." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Desactivar la verificación de subida" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "No se encontró la carpeta {0}, mensaje: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "No se encontró la carpeta {0}. Mensaje: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"El archivo fue subido {0} pero el tamaño devuelto fue de {1} y se esperaba " +"que fuese de {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Para proteger contra fallos de red o servidor cada subida se intenta " +"verificar. Utilice esta opción para desactivar la verificación para hacer la" +" subida más rápida pero menos fiable." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Desactivar la verificación de subida" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Si se establece este indicador, se cambiará el tipo de conexión de datos FTP" +" a la opción seleccionada." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurar el tipo de conexión de datos FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Si se define este indicador, se cambiará el modo de cifrado de FTP a la " +"opción seleccionada." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurar el modo de cifrado del FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Este indicador controla la política SSL cuando se habilita el cifrado." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Configurar la política SSL cuando se habilita la encriptación" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Algunos servidores FTP necesitan un pequeño retraso antes de informar el " +"tamaño de archivo correcto. El retraso requerido depende de la topología de " +"la red. Si experimenta errores relacionados con el tamaño de carga que no " +"coincide, intente agregar un retraso de unos segundos." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Agrega un retraso después de cargar un archivo" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -893,6 +1066,11 @@ msgstr "" msgid "Specify storage class" msgstr "Especificar la clase de almacenamiento" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -925,124 +1103,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternativo" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "No se encontró la carpeta {0}. Mensaje: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"El archivo fue subido {0} pero el tamaño devuelto fue de {1} y se esperaba " -"que fuese de {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Para proteger contra fallos de red o servidor cada subida se intenta " -"verificar. Utilice esta opción para desactivar la verificación para hacer la" -" subida más rápida pero menos fiable." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Si se establece este indicador, se cambiará el tipo de conexión de datos FTP" -" a la opción seleccionada." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurar el tipo de conexión de datos FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Si se define este indicador, se cambiará el modo de cifrado de FTP a la " -"opción seleccionada." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurar el modo de cifrado del FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Este indicador controla la política SSL cuando se habilita el cifrado." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Configurar la política SSL cuando se habilita la encriptación" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Algunos servidores FTP necesitan un pequeño retraso antes de informar el " -"tamaño de archivo correcto. El retraso requerido depende de la topología de " -"la red. Si experimenta errores relacionados con el tamaño de carga que no " -"coincide, intente agregar un retraso de unos segundos." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Agrega un retraso después de cargar un archivo" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Error al eliminar archivo: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Error al leer archivo: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Error al escribir archivo: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Módulo para la generación de llaves privada/pública SSH" @@ -1551,7 +1611,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Si se debe usar la clase HttpClient" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1559,11 +1623,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1573,11 +1637,11 @@ msgstr "" " unidad predeterminada de OneDrive o OneDrive for Business se utilizará a " "través de '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ID opcional de la unidad" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1585,30 +1649,30 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID del sitio" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "No se ha proporcionado ID del sitio" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Se utilizaron ID de sitios en conflicto: se proporcionó {0} pero se encontró" " {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1620,45 +1684,45 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Grupo Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID del grupo" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Dirección de correo electrónico del grupo" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "" "No se encontraron grupos con la dirección de correo electrónico " "proporcionada: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "" "Se encontraron varios grupos con la dirección de correo electrónico " "proporcionada: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1678,7 +1742,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1689,7 +1753,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1950,43 +2014,45 @@ msgstr "Sin contraseña dada" msgid "No username given" msgstr "Ningún nombre de usuario dado" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2122,6 +2188,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2156,6 +2227,12 @@ msgstr "" "El servidor devolvió el código de error {0} ({1}), indicando que el servidor" " no soporta las conexiones WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "No se encontró la carpeta {0}, mensaje: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2184,6 +2261,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Borrar la respuesta PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Acepta cualquier certificado del servidor" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Opcionalmente aceptar un certificado SSL conocido" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2308,9 +2449,410 @@ msgstr "Código de error inesperado: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Otra instancia se está ejecutando y fue notificada" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2330,13 +2872,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Argumentos de línea de comandos admitidos: \n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2365,11 +2905,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Los filtros no pueden ser indicados por medio de la línea de comandos si " -"también se encuentran en el archivo de parámetros. Use la opción especial " -"--{0}, --{1}, o --{2} para especificar filtros en el archivo de parámetros. " -"Cada filtro debe ir precedido por un + o un - y múltiples filtros deben ser " -"unidos con {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2407,7 +2942,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2415,7 +2950,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Detectada versión no compatible de SQLite ({0}), debe ser {1} o superior" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2428,42 +2962,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"El puerto del servidor web está escuchando. Se pueden suministrar múltiples " -"valores separándolos con un coma en medio." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"El certificado y el archivo de claves en formato PKCS # 12 que utiliza el " -"servidor web para SSL. Solo se admiten claves RSA / DSA." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "La contraseña de descifrado del archivo de certificado PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"La interfaz en la que el servidor web escucha. Los valores especiales " -""*" y "any" significan cualquier interfaz. El valor " -"especial "loopback" significa el adaptador de loopback." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"La contraseña requerida para acceder al servidor web. Esta opción se guarda " -"para que no tenga que configurarla en cada ejecución. Establecer un valor " -"vacío desactiva la contraseña." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2473,134 +3008,166 @@ msgstr "" " de los nombres de máquina es "*", se permiten todos los nombres " "de máquina y se dehabilita la comprobación del nombre de máquina." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Establece el tiempo tras el cual los datos del registro se eliminarán de la " "base de datos." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Limpiar datos del registro antiguos" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati necesita almacenar una pequeña base de datos con todos los " -"ajustes. Utilice esta opción para elegir dónde se almacenan. Esta opción " -"también se puede configurar con la variable de entorno {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Esta opción establece la clave de cifrado utilizada para codificar la base " -"de datos de configuración local. Esta opción también se puede configurar con" -" la variable de entorno {0}. Utilice la opción --{1} para deshabilitar la " -"codificación de la base de datos." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Carpeta de almacenamiento temporal" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2608,15 +3175,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2625,24 +3229,21 @@ msgstr "" "No ha sido posible encontrar una fecha válida, dada la fecha de inicio {0}, " "el intervalo de repetición {1} y los dias permitidos {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "El servidor fue iniciado y escuchando en {0}, puerto {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"No se puede crear un certificado SSL usando los parámetros proporcionados. " -"Detalles de la excepción: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "No se puede abrir un socket para escuchar, intentando puertos: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2655,22 +3256,22 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Error al cargar el tipo de proceso {0} conjunto {1}, mensaje de error: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2678,11 +3279,11 @@ msgstr "" "Esta opción controla el nivel de compresión utilizado. Un ajuste de cero no " "comprime, y un ajuste de 9 es la compresión máxima." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2690,75 +3291,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Archivo no encontrado: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Experimental*: Archivo 7z con soporte LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimental - Archivo 7z" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archivo sin abrir para escritura" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archivo sin abrir para lectura" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "El archivo no es parte de este archivo" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"El número de hilos utilizados en la compresión del LZMA 2. Por defecto, el " -"número de núcleos de procesador." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Número de subprocesos usados en la comprensión" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Esta opción controla el algoritmo de compresión usado. Habilitar esta opción" -" hará que 7z utilice un algoritmo más rápido que produce un poco menos de " -"compresión." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "copia de seguridad" @@ -3205,48 +3768,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Prioridad del hilo" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limitar el tamaño de los volúmenes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3258,11 +3839,11 @@ msgstr "" " se crean nuevos volúmenes, cuando se lee un archivo existente, el nombre " "del archivo se utiliza para seleccionar el módulo de compresión." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Seleccione qué módulo usar para la compresión" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3275,27 +3856,27 @@ msgstr "" "existente, el nombre del archivo se utiliza para seleccionar el módulo de " "cifrado." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Seleccione qué módulo utilizar para la encriptación" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3323,34 +3904,36 @@ msgstr "" "volumen (VSS) y requiere privilegios administrativos. En Linux esto usa el " "Logical Volume Management (LVM) y requiere privilegios de root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "La ruta donde se colocan los volúmenes listos hasta que se cargan" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "El número de volúmenes a crear por adelantado" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3358,45 +3941,45 @@ msgstr "" "Cuando se realizan cargas asincrónicas, el número máximo de cargas " "simultáneas permitidas. Ponga a cero para desactivar el límite." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "El número de cargas simultáneas permitidas" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Registre la información interna en un archivo" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Nivel de información del registro" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3405,11 +3988,11 @@ msgstr "" "automáticamente. Active esta opción para evitar la creación automática de la" " carpeta." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3424,14 +4007,14 @@ msgstr "" "separarse con un punto y coma, y se permiten la mayoría de las formas de " "GUID, incluso con y sin llaves." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Una lista separada con punto y coma de los escritores de VSS a excluir (sólo" " en Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3454,11 +4037,22 @@ msgstr "" "la USN falla. Esta característica sólo está soportada en Windows y requiere " "privilegios administrativos." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3471,30 +4065,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Verificar las subidas listando los contenidos" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Subir archivos sincrónicamente" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3502,11 +4097,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "No reutilizar las conexiones" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3516,33 +4111,33 @@ msgstr "" "informa del número de reintentos. Habilite esta opción para que se muestren " "los mensajes de error cuando se realice un reintento." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Mostrar mensajes de error cuando se realiza un reintento" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Subir archivos de copias de seguridad vacíos" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3551,22 +4146,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Umbral de advertencia sobre cuota baja" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3578,11 +4173,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Manejo del enlace simbólico" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3598,11 +4193,11 @@ msgstr "" "como un camino único. La opción \"{2}\" ignorará todos los enlaces duros con" " más de un enlace." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Manejo de los enlaces duros" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3610,11 +4205,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Excluir archivos por atributo" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3626,41 +4221,51 @@ msgstr "" " utilizan para acceder al contenido de una instantánea. Esta solución puede " "acelerar el acceso a los archivos en Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Instantáneas de mapas a una unidad (sólo en Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nombre de la copia de seguridad" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3672,11 +4277,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Administra las extensiones de archivos no comprimibles" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3689,72 +4294,64 @@ msgstr "" "de las listas de archivos. Tenga en cuenta que el valor no se puede cambiar " "después de crear los archivos remotos." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "El tamaño del bloque utilizado en el hashing" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista de archivos para examinar los cambios" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Ruta de acceso a la base de datos de estado local" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista de archivos eliminados" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Reduce el consumo de memoria al inhabilitar las búsquedas en la memoria" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Almacenar un caché de bloque en la memoria" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "No consultar el servidor en el arranque." -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3769,11 +4366,11 @@ msgstr "" "archivos índice de mayor tamaño ocupan más espacio remoto y que tal vez " "nunca se utilicen." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3786,43 +4383,43 @@ msgstr "" " Este valor es un porcentaje que se utiliza en cada volumen y en el total " "del almacenamiento." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "El máximo espacio desperdiciado en porcentaje" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "El algoritmo de hash utilizado en bloques" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "El algoritmo de hash utilizado en archivos" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3835,11 +4432,11 @@ msgstr "" "para desactivar dicha compactación automática y compacte sólo cuando ejecute" " el comando compactar." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Desactivar compactación automática" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3851,11 +4448,11 @@ msgstr "" "tamaño del volumen. Esto asegura que los grandes volúmenes que pueden tener " "unos pocos bytes de espacio desperdiciado no se descarguen y se reescriban." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Tamaño límite del volumen" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3865,11 +4462,11 @@ msgstr "" "valor puede forzar la agrupación de archivos pequeños. Los volúmenes " "pequeños siempre se combinarán cuando puedan llenar un volumen entero." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Número máximo de volúmenes pequeños" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3879,42 +4476,42 @@ msgstr "" "encontrar los bloques existentes. Esta es una operación bastante lenta pero " "puede limitar el tamaño de las descargas." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Utilizar los datos del archivo local al restaurar" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Mantener un número de versiones" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Utilice esta opción para establecer el tiempo en el que se guardan las " "copias de seguridad." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Mantener todas las versiones dentro de un intervalo de tiempo" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3935,33 +4532,33 @@ msgstr "" " seguridad más antiguas que esta\". Esta opción también admite el uso del " "especificador \"U\" para indicar un intervalo de tiempo ilimitado." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Reducir el número de versiones eliminando las viejas copias de seguridad " "intermedias" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Utilice esta opción para continuar aunque falten algunas entradas del " "origen." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Omitir elementos que faltan de la fuente" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Sobrescribir los archivos al restaurar" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3970,11 +4567,11 @@ msgstr "" "ejecutar una opción. Generalmente esta opción producirá una línea por cada " "archivo procesado." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Obtener más información sobre los progresos realizados" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3982,11 +4579,11 @@ msgstr "" "Utilice esta opción para aumentar la cantidad de producción generada como " "resultado de la operación, incluyendo todos los nombres de archivo." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Resultados completos de la salida" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3998,11 +4595,11 @@ msgstr "" "tamaño y los hashes SHA256 de todos los archivos remotos y puede ser usado " "para verificar la integridad de los archivos." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Determinar si los archivos de verificación están subidos" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4012,11 +4609,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "El número de muestras a comprobar después de una copia de seguridad" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4026,11 +4623,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "El porcentaje de muestras a probar después de una copia de seguridad" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4042,41 +4639,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "El tamaño de la memoria intermedia de lectura de archivos" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Permite cambiar la frase de seguridad" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Lista sólo conjuntos de archivos" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4087,11 +4684,11 @@ msgstr "" " metadatos acelerará las operaciones de copia de seguridad y restauración, " "pero no afecta mucho al tamaño del archivo." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4100,11 +4697,11 @@ msgstr "" "acceso a sus archivos. Utilice esta opción para restaurar los permisos " "también." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restaurar permisos de archivos" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4115,11 +4712,11 @@ msgstr "" " esta opción para desactivar la comprobación y evitar esperar a la " "verificación." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Omitir el control de archivos restaurados" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4129,28 +4726,28 @@ msgstr "" "minimizar la cantidad de datos descargados. Utilice esta opción para omitir " "esta optimización y utilizar sólo datos remotos." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "No usar datos locales" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4159,11 +4756,11 @@ msgstr "" " bloques leídos de un volumen antes de parchear los archivos restaurados con" " los datos." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Comprobar hash del bloque" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4177,11 +4774,11 @@ msgstr "" "buscar en la base de datos resultante, pero no se puede utilizar para " "restaurar los datos de la misma." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Reparar base de datos con rutas" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4189,11 +4786,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Forzar la configuración regional" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4204,24 +4801,24 @@ msgstr "" "muestran las fechas reales, \"12 de noviembre de 2018, 8:01 AM\" por " "ejemplo." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Gestionar la comunicación de los archivos con el servidor mediante el uso de" " hilos por tuberías." -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4231,22 +4828,22 @@ msgstr "" " establecer este valor en cero o menos se equilibrará dinámicamente el " "número de hilos activos para ajustarse al hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Limitar el número de hilos concurrentes" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Utilice esta opción para establecer el número de procesos que realizan el " "hashing de datos." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Especificar el número de procesos de hashing simultáneos" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4254,11 +4851,11 @@ msgstr "" "Utilice esta opción para establecer el número de procesos que realizan la " "compresión de los datos de salida." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Especificar el número de procesos de compresión simultáneos" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4269,11 +4866,11 @@ msgstr "" "seguridad completada y el contenido que se subió en la sesión de copia de " "seguridad incompleta." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4281,11 +4878,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4293,11 +4890,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4305,11 +4902,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Permitir la eliminación de todos los conjuntos de archivos" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4325,11 +4922,11 @@ msgstr "" "en la base de datos. Poner esto en verdadero permitirá a Duplicati realizar " "operaciones de VACÍO a su discreción." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4337,11 +4934,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Desactivar el escáner de lectura anticipada" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4353,11 +4950,11 @@ msgstr "" "de verificación regulares para asegurarse de que todo funciona como se " "espera." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Deshabilitar los controles de consistencia de la lista de archivos" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4365,17 +4962,17 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" "Deshabilitar la copia de seguridad cuando se usa la alimentación de la " "batería" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Nivel de información del archivo de registro" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4390,42 +4987,42 @@ msgstr "" "supone que incluyen, a menos que empiecen con '-'. Las expresiones regulares" " son soportadas entre comillas. Ejemplo: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Nivel de información de la consola" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4437,11 +5034,11 @@ msgstr "" " Un uso común sería tener un archivo llamado algo así como \".nobackup\" y " "colocar este archivo en carpetas que no deben ser respaldadas." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Lista de nombres de archivos que excluyen las carpetas" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4449,11 +5046,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4461,11 +5058,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4479,11 +5076,11 @@ msgstr "" "configurar --{0}={2} o --{1}={2} para reportar los datos de registro " "adicionales" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4492,11 +5089,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4504,11 +5101,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4516,11 +5113,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4529,51 +5275,51 @@ msgstr "" "La criptolibrería no soporta transformaciones reutilizables para el " "algoritmo hash {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "La criptolibrería no soporta el algoritmo hash {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "No se puede cambiar la frase de seguridad de una copia de seguridad " "existente" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Fallo al crear una instantánea: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "No se pudo eliminar el archivo {0}, probando si ya existe el archivo" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Se recuperó de un problema al intentar eliminar el archivo no existente {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "No se pudo recuperar de un error borrando el archivo {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "La operación de eliminación falló para {0} con Archivo No Encontrado, " "listando los contenidos" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "El listado indica que el archivo {0} fue borrado correctamente" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "El listado confirma que el archivo {0} no fue eliminado." @@ -4637,29 +5383,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurar solicitudes http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Acepta cualquier certificado del servidor" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Opcionalmente aceptar un certificado SSL conocido" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4758,7 +5481,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configurar módulo Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4766,127 +5501,127 @@ msgstr "" "Este módulo funciona internamente para analizar los parámetros del origen " "para respaldar las bases de datos de Microsoft SQL Server." -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configura el módulo de Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Ejecutar script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Ejecutar un script al salir" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "El guión \"{0}\" devolvió el código de salida {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "El guión \"{0}\" devolvió el código de salida {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Ejecutar un script necesario en el inicio" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Error mientras se ejecutaba el script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "La ejecución del script \"{0}\" ha caducado" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Ejecutar un script al inicio" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "El script \"{0}\" reporto los mensajes de error: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" "Este módulo puede enviar un correo electrónico después de completar una " "operación" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Enviar correo" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4899,21 +5634,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "El cuerpo del mensaje" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Contraseña SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4925,11 +5660,11 @@ msgstr "" "\n" "Muestra de Pedro , Muestra de Juan , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Destinatario(s) de correo electrónico" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4939,25 +5674,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Correo electrónico del remitente" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Los mensajes para enviar" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4965,11 +5700,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "Url SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4978,61 +5713,61 @@ msgstr "" "Esta configuración proporciona el asunto del correo electrónico. Los valores" " se reemplazan como se describe en la descripción de --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Asunto del correo electrónico" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Usuario SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Comunicación SMTP completa: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Ha fallado el envío de correo electrónico con el servidor: {0}, mensaje: " "{1}, reintentando con {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Correo electrónico enviado con éxito usando el servidor: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Este modulo proporciona soporte para enviar los reportes de estado a través " "mensajes XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Módulo de reporte XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "Correo electrónico destinatario XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5045,35 +5780,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "La plantilla de mensaje" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Nombre de usuario de XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Contraseña de XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5082,112 +5817,112 @@ msgstr "" "Puede especificar uno de \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "Puede proporcionar múltiples opciones con un separador de comas, por ejemplo, \"{0},{1}\". El valor especial \"{4}\" es una abreviatura de \"{0},{1},{2},{3}\" y hará que todas las operaciones de respaldo envíen un mensaje." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Enviar mensajes para todas las operaciones" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" "Se agotó el límite de tiempo mientras iniciaba sesión en servidor de Jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Este modulo proporciona soporte para enviar los reportes de estado a través " "mensajes HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP modulo de reporte" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "El nombre del parámetro para enviar el mensaje como " -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Parámetros extra para añadir al mensaje http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5195,46 +5930,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Fallo en el envío del mensaje: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtro de mensajes de registro" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5243,7 +5978,7 @@ msgstr "" "que se incluirán en el informe. Los valores cero o negativos significan " "ilimitado." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5252,16 +5987,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "El formato no es soportado: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Valor de tamaño inválido: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "La validación del certificado SSL fue llamada en un orden incorrecto" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5270,7 +6005,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5279,71 +6014,71 @@ msgstr "" "Fallo al validar el hash del certificado, mensaje de error: {0}, nombre de " "error del SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "La carpeta temporal no existe: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "No se pudo analizar el segmento: {0}, entero no válido" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Especificado no válido: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Sin analizar datos: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Uri es inválido: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "En Uri falta el nombre de host: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "La cadena \"{0}\" no se pudo analizar en una fecha" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "No se puede leer y escribir en la misma secuencia" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5581,11 +6316,11 @@ msgstr "Desactivar salida por consola" msgid "This link may provide additional information: {0}" msgstr "Este enlace puede proporcionar información adicional: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Activar actualizaciones automáticas" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-fi.mo b/Localizations/duplicati/localization-fi.mo index a1a8111d4..eac6b7321 100644 Binary files a/Localizations/duplicati/localization-fi.mo and b/Localizations/duplicati/localization-fi.mo differ diff --git a/Localizations/duplicati/localization-fi.po b/Localizations/duplicati/localization-fi.po index 24e3e90aa..ae27c9a2d 100644 --- a/Localizations/duplicati/localization-fi.po +++ b/Localizations/duplicati/localization-fi.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Kari Koskinen , 2024\n" "Language-Team: Finnish (https://app.transifex.com/duplicati/teams/67655/fi/)\n" @@ -47,8 +47,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -158,78 +158,94 @@ msgstr "Virhe kutsuttaessa ohjelmaa GnuPG. Ohjelma ei tulosta." msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Virhe kutsuttaessa ohjelmaa GnuPG. Ohjelma ei lopeta." -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliakset" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "oletusarvo" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[POISTUMASSA]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "arvot" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "totuusarvot" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Numerointi" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Liput" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Kokonaisluku" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Polku" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Koko" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Merkkijono" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Aikaväli" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Tuntematon" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Kansiota ei voitu luoda, koska se oli jo olemassa" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Pyydetty kansio ei ole olemassa" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Toimenpide keskeytettiin" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Haluttua tiedostoa ei ole olemassa." @@ -355,9 +371,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -374,9 +389,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -389,9 +403,8 @@ msgstr "" "Käyttäjätunnus palvelimelle. Tämä voidaan asettaa myös ympäristömuuttujassa " "\"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -482,41 +495,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Vaihtoehtoinen FTP-asiakaskohjelma" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -527,50 +530,201 @@ msgstr "" "Salasana palvelimelle. Tämä voidaan asettaa myös ympäristömuuttujassa " "\"AUTH_PASSWORD\"" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Kaikkien tiedostosiirtojen onnistuminen koetetaan varmistaa verkosta " -"johtuvien ongelmien havaitsemiseksi. Tämä valitsin poistaa tarkistuksen " -"tehden siirroista nopeampia, mutta epäluotettaampia." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Poista käytöstä latausten tarkistus" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Hakemistoa {0} ei löytynyt. Viesti oli: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Hakemistoa {0} ei löytynyt. Viesti oli: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Tiedosto {0} ladattiin palvelimelle, mutta ilmoitetu koko oli {1}. Odotettu " +"koko oli {2}." + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Kaikkien tiedostosiirtojen onnistuminen yritetään varmistaa verkosta tai " +"palvelimesta johtuvien ongelmien havaitsemiseksi. Tämä valitsin poistaa " +"tarkistuksen tehden siirroista nopeampia, mutta epäluotettaampia." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Poista käytöstä latausten tarkistus" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "Tämä valitsin asettaa FTP data-yhteyden tyypin annettuun arvoon." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Aseta FTP data-yhteyden tyyppi." + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "Tämä valitsin asettaa FTP-yhteyden salauksen annettuun arvoon." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Aseta FTP-yhteyden salaustapa." + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Tämä valitsin asettaa SSL-politiikan, jota käytetään salatuissa yhteyksissä." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Aseta SSL-politiikka salattuja yhteyksiä varten." + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -877,6 +1031,11 @@ msgstr "" msgid "Specify storage class" msgstr "Valitse tallennusluokka" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -909,116 +1068,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Vaihtoehtoinen FTP-asiakaskohjelma" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Hakemistoa {0} ei löytynyt. Viesti oli: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Tiedosto {0} ladattiin palvelimelle, mutta ilmoitetu koko oli {1}. Odotettu " -"koko oli {2}." - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Kaikkien tiedostosiirtojen onnistuminen yritetään varmistaa verkosta tai " -"palvelimesta johtuvien ongelmien havaitsemiseksi. Tämä valitsin poistaa " -"tarkistuksen tehden siirroista nopeampia, mutta epäluotettaampia." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "Tämä valitsin asettaa FTP data-yhteyden tyypin annettuun arvoon." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Aseta FTP data-yhteyden tyyppi." - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "Tämä valitsin asettaa FTP-yhteyden salauksen annettuun arvoon." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Aseta FTP-yhteyden salaustapa." - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Tämä valitsin asettaa SSL-politiikan, jota käytetään salatuissa yhteyksissä." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Aseta SSL-politiikka salattuja yhteyksiä varten." - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Moduuli yksityisten SSH-avainten luomista varten." @@ -1505,7 +1554,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1513,22 +1566,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1536,28 +1589,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "sivuston tunniste" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1569,42 +1622,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 -ryhmä" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "Ryhmän tunniste" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Ryhmän sähköpostiosoite" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1621,7 +1674,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1632,7 +1685,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1877,43 +1930,45 @@ msgstr "Et antanut salasanaa" msgid "No username given" msgstr "Et antanut käyttäjätunnusta" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2045,6 +2100,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2077,6 +2137,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Hakemistoa {0} ei löytynyt. Viesti oli: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2105,6 +2171,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Tallenna PROPFIND-vastaus" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Hyväksy kaiki ssl-sertifikaatit" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Hyväksy tietty SSL-sertifikaatti" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2229,9 +2359,410 @@ msgstr "Odottamaton virhe: {0}-{1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Toinen instanssi on olemassa. Sille ilmoitettiin" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2251,13 +2782,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Komentoriviargumentit:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2286,11 +2815,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Suodattimia ei voi asettaa komentorivillä, jos asetustiedosto sisältää " -"suodattimia. Käytä erityisiä valitsimia --{0}, --{1} tai --{2} " -"määrittääksesi suodattimet asetustiedostossa. Jokaisen suodattimen täytyy " -"alkaa joko + tai - merkillä. Useat peräkkäiset suodattimet täytyy liittää " -"merkillä {3}." #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2328,14 +2852,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "Ei-tuettu versio SQLite:sta: {0}. Täytyy olla {1} tai korkeampi." +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2348,172 +2872,208 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Portti, jota www-palvelin kuuntelee. Voit antaa useita arvoja pilkulla " -"erotettuna." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Web-palvelimen SSL-sertifikaatti ja sertifikaatin avain PKCS #12 -muodossa. " -"Ainoastaan RSA/DSA -avaimet on tuettuja." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "PKCS #12 avaintiedoston salasana." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" +"WWW-palvelimen SSL-seritfikaatti ja PKCS #12 -muotoinen avaintiedosto." + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Verkkorajapinta, jota www-palvelin kuuntelee. Erityiset arvot \"*\" ja " -"\"any\" tarkoittavat kaikkia mahdollisia verkkorajapintoja. " -"Arvolla\"loopback\" www-palvelin kuuntelee vain paikallisia yhteyksiä." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"WWW-palvelimen salasana. Tämä arvo tallennetaan, joten sitä ei tarvitse " -"antaa joka kerta. Tämän asettaminen tyhjäksi merkkijonoksi poistaa salasanan" -" käytöstä." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Aseta lokitietojen säilytysaika" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Poista vanhat lokitiedot" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati tallentaa asetuksensa pieneen tietokantaa. Tämä valitsin asettaa " -"asetustietokannan siajinnin. Tämä asetus voidaan antaa myös " -"ympäristömuuttujassa {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Tämä asetus asettaa tietokannan salausavaimen. Tämä asetus voidaan antaa " -"myös ympäristömuuttujassa {0}. Valitsin --{1} poistaa salauksen käytöstä." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Kansio tilapäistiedotoille" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2521,15 +3081,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2538,25 +3135,21 @@ msgstr "" "Aloituspäivällä {0}, varmuuskopioiden välillä {1} ja sallituilla päivillä " "{2} ei löydy sopivaa päivää." -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Palvelin käynnistyi ja kuntelee verkkorajapintaa {0} ja porttia {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"SSL-sertifikaatin luominen annetuilla arvoilla epäonnistui. Virheilmoitus: " -"{0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" -"Pistokkeen luominen kuuntelua varten epäonnistui. koetettiin portteja: {0}" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2570,22 +3163,22 @@ msgstr "" "Prosessin tyypin {0} lataus kirjastosta {1} epäonnistui. Virheilmoitus oli: " "{2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2593,11 +3186,11 @@ msgstr "" "Tämä valitsin asettaa pakkaustason. Taso 0 ei pakkaa dataa ollenkaan ja taso" " 9 on paras mahdollinen pakkaus." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2605,73 +3198,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "Tiedostoa ei löydy: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Arkistoa ei avattu kirjoittamista varten" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Arkistoa ei avattu lukemista varten" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Annettu tiedosto ei ole tässä arkistossa" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"LZMA2 -pakkauksessa käytettävien säikeiden määrä. Oletusarvo on " -"prosessoriytimien määrä." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Pakkauksessa käytettävien säikeiden lukumäärä" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Tämä valitsin asettaa käytettävän pakkausalgoritmin. Tämä valitsin valitsee " -"nopean 7z-pakkauksen. Se tuottaa hieman suurempia tiedostoja." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "Tiedostoa ei löydy: {0}" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -3104,48 +3660,66 @@ msgstr "Varmuuskopioitavien tiedotojen kokoraja" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Säikeiden prioriteetti" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Rajoita datatiedostojen kokoa" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3153,11 +3727,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Valitse pakkausmoduuli" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3165,27 +3739,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Valitse salausmoduuli" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3200,78 +3774,80 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Tilapäiskansio siirtoa odottaville datatiedostoille" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Siirtoa odottavien datatiedostojen enimmäismäärä" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Lokiin tallennettavat tiedot" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3279,11 +3855,11 @@ msgstr "" "Jos kohdekansio etäpalvelimella puuttuu, Duplicati luo sen automaattisesti. " "Tämä estää poistaa automaattisen kansion luomisen." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3297,13 +3873,13 @@ msgstr "" "instanssien GUID:ja. Useat GUID:it erotetaan puolipisteellä. Useimmat GUID-" "tyypit ovat sallittuja, mukaanlukien kaarisulkeilla tai ilman olevat." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Puolipistein erotettu lista VSS-kirjoittajista (vain Windows-järjestelmillä)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3316,11 +3892,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3333,30 +3920,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Tarkista siirtojen onnistuminen listaamalla etäpalvelimen tiedostot" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Lataa tiedostot varmuuskopioinnin aikana" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3364,11 +3952,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Älä uudelleenkäytä yhteyttä." -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3378,33 +3966,33 @@ msgstr "" "vain uudelleenyritysten lukumäärän. Tällä valitsimella Duplicati tulostaa " "virheilmoituksen jokaisella yrityksellä." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Näytä virheilmoitus uudelleenyrityksen jälkeen" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Lataa tyhjätkin varmuuskopiot" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3413,22 +4001,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3440,11 +4028,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Symbolisten linkkien tallentaminen" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3454,11 +4042,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Kovien linkkien käsittely" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3466,11 +4054,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Ohita tiedostoja ominaisuuksien perusteella" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3482,41 +4070,51 @@ msgstr "" "vedoksen tiedostojen lukemiseen. Tämä voi nopeuttaa varmuuskopioita " "tietokoneissa, joissa on Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Liitä vedokset levynä (vain Windowsilla)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Varmuuskopion nimi" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3528,11 +4126,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Hallitse pakkautumattomien tiedostojen listaa" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3544,72 +4142,64 @@ msgstr "" " lohkokokoa käytettäessä lohkolistat vievät enemmän tilaa. Huomioi, että " "tätä arvoa ei voi muuttaa etätiedostojen luonnin jälkeen." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Tiivisteen laskennassa käytettävä lohkon koko" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista mahdollisesti muuttuneista tiedostoista" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Polku paikallisen tilan sisältävään tietokantaan" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista poistetuista tiedostoista" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Pienennä muistinkäyttöä poistamalla muistissa tapahtuva vertailu käytöstä" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Älä listaa tiedostoja etäpalvelimella aloitettaessa" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3623,11 +4213,11 @@ msgstr "" "hakemistotiedostot vievät etäpalvelimella enemmän tilaa, jota ei välttämättä" " koskaan tarvita." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3640,43 +4230,43 @@ msgstr "" "datan osuus prosentteina. Arvoa sovelletaan kuhunkin lohkotiedostoon ja koko" " tallennettuun dataan." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Tarpeettoman datan osuus prosentteina" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Lohkojen tarkastussummien laskemiseen käytettävä algoritmi" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Tiedostojen tarkastussummien laskemiseen käytettävä algoritmi" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3689,11 +4279,11 @@ msgstr "" "valitsin poistaa automaattisen tiivistämisen käytöstä. Tällöin varmuuskopio " "tiivistetään vain komennolla \"compact\"." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Poista automaattinen tiivistäminen käytöstä" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3704,11 +4294,11 @@ msgstr "" "oletuksena alle 20 prosenttia jätetään tiivistämättä. Tämä vähentää " "siirrettävän datan määrää." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Datatiedostojen muutosten alaraja" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3717,11 +4307,11 @@ msgstr "" "Tämä asetus määrää kuinka paljon etäpalvelimella saa olla pieniä tiedostoja " "ennen kuin ne yhdistetään yhdeksi lohkotiedostoksi." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Pienten tiedostojen määrä" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3731,40 +4321,40 @@ msgstr "" "omalla koneella. Tämä on hidasta, mutta voi vähentää etäpalvelimelta " "ladattavan datan määrää." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Käytä paikallisia tiedostoja apuna palautettaessa" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Säilytettävien versioiden lukumäärä" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "Aseta ajanjakso, jolta varmuuskopiot säilytetään." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Säilytä varmuuskopiot tältä ajanjaksolta" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3776,31 +4366,31 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Käytä tätä valitsinta jatkaaksesi vaikka jotkut varmuuskopioitavat kohteet " "puuttuisivatkin." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ohita puuttuvat lähteet" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Ylikirjoita tiedostostot palauttaessasi" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3808,21 +4398,21 @@ msgstr "" "Tällä valitsimella Duplicati tulostaa enmmän tilatietoja. Yleensä tämä " "tarkoittaa riviä kutakin käsiteltyä tiedostoa kohden." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Tulosta enmmän tilatietoja" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3834,11 +4424,11 @@ msgstr "" " ja SHA256-tarkastussummat. Tämän avulla varmuskopion eheyden voi tarkastaa " "etäpalvelimella." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Lataa varmistustiedostot etäpalvelimelle" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3848,11 +4438,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Varmuuskopion jälkeen tarkastettavien tiedostojen lukumäärä" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3862,11 +4452,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3878,41 +4468,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Lukupuskurin koko" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Salli salauslauseen vaihtaminen" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Listaa vain eri versiot varmuuskopiossa" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -3923,11 +4513,11 @@ msgstr "" "nopeuttaa varmuuskopiointia ja tiedostojen palauttamista, mutta ei vaikuta " "varmuuskopioiden kokoon merkittävästi." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -3936,11 +4526,11 @@ msgstr "" "tiedostojen lukemisen. Tällä valitsimella Duplicati palauttaa myös " "tiedostojen oikeudet." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Palauta tiedostojen oikeudet" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -3951,11 +4541,11 @@ msgstr "" "tarkastussumman laskemisen käytöstä. Tällöin palautettujen tiedostojen " "eheyttä ei tarkasteta." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Älä tarkasta palautettuja tiedostoja." -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -3966,28 +4556,28 @@ msgstr "" "paikallisen datan hyödyntämisen käytöstä ja käyttää vain etäpalvelimella " "olevaa dataa." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Älä käytä paikallista dataa" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -3995,11 +4585,11 @@ msgstr "" "Tällä valitsimella Duplicati tarkastaa koko palautetun tiedoston lisäksi " "kunkin lohkon tarkastustsumman." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Tarkasta lohkojen tarkastussummat" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4011,11 +4601,11 @@ msgstr "" "nopeampaa, mutta sen tiedot eivät riitä tiedostojen palauttamiseen. Voit " "käyttää sitä palautettavien tiedostojen etsimiseen." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Korjaa tietokanta poluista" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4023,74 +4613,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Pakota Duplicati käyttämään tiettyjä lokaaliasetuksia" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Kommunikoi taustamoduulin kanssa käyttäen säieturvallisia putkia" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4098,11 +4688,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4110,11 +4700,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4122,11 +4712,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Salli kaikkien tiedostojen poisto" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4136,11 +4726,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4148,22 +4738,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4171,15 +4761,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4189,42 +4779,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4232,11 +4822,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4244,11 +4834,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4256,11 +4846,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4269,11 +4859,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4282,11 +4872,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4294,11 +4884,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4306,11 +4896,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4318,48 +5057,48 @@ msgid "" msgstr "" "Salauskirjasto ei tue uudelleenkäytettäviä muunnoksia tiivistefunktiolle {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "Salauskirjasto ei tue tiivistefunktiota {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "Olemassaolevan varmuuskopion salasanaa ei voi vaihtaa" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Vedoksen luominen epäonnistui: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Tiedostoa {0} ei voitu poistaa. Tarkistetaan, onko tiedosto olemassa." -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "Toipuminen olemattoman tiedoston \"{0}\" poistamisesta onnistui" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Ei voitu toipua virheestä poistettaessa tiedostoa {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Tiedoston {0} poistaminen epäonnistui. Virhe oli FileNotFound, listing " "contents" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4416,29 +5155,6 @@ msgstr "Tämä moduuli mahdollistaa http-yhteyden asetusten muuttamisen" msgid "Configure http requests" msgstr "Säädä http-pyyntöjä" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Hyväksy kaiki ssl-sertifikaatit" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Hyväksy tietty SSL-sertifikaatti" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4529,7 +5245,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Hyper-V-moduuin asetukset" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4537,125 +5265,125 @@ msgstr "" "Tämä moduuli jäsentää parametrit Microsoft SQL-palvelimen varmuuskopioimista" " varten" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Microsoft SQL-palvelinmoduulin asetukset" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Suorita skripti" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Suorita skripti lopetettaessa" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Skriptin \"{0}\" paluuarvo oli {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Suorita pakollinen skripti ennen toimenpidettä" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Virhe suoritettaessa skriptiä \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Skripti \"{0}\" aikakatkaistiin" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Suorita skripti ennen toimenpidettä" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Skripti \"{0}\" antoi virheilmoituksen: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Tämä moduuli lähettää sähköpostin operaation jälkeen." -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Lähetä sähköposti" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4668,21 +5396,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Viestin runko" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP-palvelimen salasana" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4690,11 +5418,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Sähköpostin vastaanottajat" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4704,25 +5432,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Sähköpostin lähettäjä" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Lähetettävä viesti" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4730,11 +5458,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP palvelimen osoite" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4743,60 +5471,60 @@ msgstr "" "Voit antaa tällä valitsimela sähköpostin otsakkeen. Muuttujat korvataan, " "kuten valitsimen --{0} ohjeessa on selitetty." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Sähköpostin otsake" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Käyttäjätunnus SMTP-palvelimelle" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Koko SMTP-neuvottelu: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Sähköpostin lähetys palvelimen {0} kautta epäonnistui. Virheilmoitus oli: " "{1} Yritetään uudelleen palvelimen {2} kautta." -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Sähköposti lähetettiin onnistuneesti käyttäen palvelinta {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Tämä moduuli mahdollistaa raporttien lähettämisen XMPP-palvelun kautta" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Moduuli raporttien lähettämiseksi XMPP-palvelun kautta" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP-vastaanottajan osoite" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4809,35 +5537,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Viestin malli" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP-käyttäjätunus" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP-palvelun salasana" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4848,110 +5576,110 @@ msgstr "" "\"{0},{1}\". Erityinen arvo \"{4}\" on sama kuin \"{0},{1},{2},{3}\", ja sen" " valitsemalla jokaisesta varmuuskopiontioperaatiosta lähetetään viesti." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Lähetä viesti kaikkien toimenpiteiden jälkeen" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "kirjautuminen Jabber-palvelimelle aikakatkaistiin " -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Tämä moduuli mahdollistaa tilaraporttien lähetyksen HTTP -viestien avulla." -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP -raportointimodulli" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Ylimääräiset parametrit HTTP-viestiin" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4959,52 +5687,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Viestin lähetys epäonnistui: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5013,16 +5741,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Epäkelpo koko: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "SSL sertifikaatin validaattoria kutsuttiin väärässä järjestyksessä" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5031,7 +5759,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5040,71 +5768,71 @@ msgstr "" "Sertifikaatin sormenjäljen tarkastus epäonistui. Virheilmoitus: {0}, SSL-" "virheen nimi: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Tilapäiskansiota {0} ei ole olemassa" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Lohkon jäsentäminen ei onnistunut: {0}, epäkelpo kokonaisluku" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Epäkelpo tunniste: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Jäsentämätön data: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Osoite on epäkelpo: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Palvelimen nimi puuttuu: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} tavua" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GT" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KT" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MT" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TT" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Merkkijonoa \"{0}\" ei voitu lukea päivämääränä" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Ei voida lukea ja kirjoittaa samaan tiedosto-osoittimeen" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5326,11 +6054,11 @@ msgstr "Poista käytöstä tulostus konsoliin" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Ota käyttöön automaattiset päivitykset" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-fr.mo b/Localizations/duplicati/localization-fr.mo index 36ef5a461..c5877ac41 100644 Binary files a/Localizations/duplicati/localization-fr.mo and b/Localizations/duplicati/localization-fr.mo differ diff --git a/Localizations/duplicati/localization-fr.po b/Localizations/duplicati/localization-fr.po index ad2215995..14265f47b 100644 --- a/Localizations/duplicati/localization-fr.po +++ b/Localizations/duplicati/localization-fr.po @@ -4,6 +4,7 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Arnaud COURCOUX , 2016 # 0xDEADC0DE, 2017 # glannes31 , 2017 # franck aubert , 2017 @@ -11,26 +12,25 @@ # Tanguy Falconnet , 2017 # Thibaut B, 2017 # Louis MILCENT , 2017 +# François TERROT , 2017 +# Fida Ben Hassine , 2018 # c2d8fff08ea91a3e49f9105aca49898d, 2018 # Léonard Gagnon , 2019 +# Buggi, 2019 # Cédric Goby , 2020 # Glaude Ratinier, 2024 # Hadrien DUSSUEL , 2024 -# Buggi, 2024 -# Josse du PLESSIS , 2024 -# Arnaud COURCOUX , 2024 -# François TERROT , 2024 -# Fida Ben Hassine , 2024 # L P , 2024 +# Josse du PLESSIS , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: L P , 2024\n" +"Last-Translator: Josse du PLESSIS , 2024\n" "Language-Team: French (https://app.transifex.com/duplicati/teams/67655/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -63,8 +63,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -178,78 +178,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Echec dans l'appel de GnuPG, l'application ne s'arrêtera pas" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "alias" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valeur par défaut" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DÉPRÉCIÉ]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valeurs" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booléen" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Énumération" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Drapeaux" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Entier" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Chemin" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Taille" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Texte" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Intervalle de temps" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Inconnu" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Le répertoire ne peut être créé car il existe déjà" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Le dossier demandé n'existe pas" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Annulé" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Le fichier demandé n'existe pas" @@ -382,9 +398,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -401,9 +416,8 @@ msgstr "Nom de domaine de l'utilisateur utilisé pour se connecter au serveur." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -416,9 +430,8 @@ msgstr "" "Le nom d'utilisateur utilisé pour se connecter au serveur. Il peut également" " être fourni comme une variable d'environnement \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -510,41 +523,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternatif" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -555,50 +558,210 @@ msgstr "" "Mot de passe utilisé pour se connecter au serveur. Il peut également être " "fourni comme une variable d'environnement \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Pour se protéger des problèmes réseaux, chaque téléversement sera " -"automatiquement vérifié. Utilisez cette option pour désactiver cette " -"vérification afin que le téléchargement soit plus rapide mais moins fiable." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Désactiver la vérification de téléversement" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Le dossier {0} n'a pas été trouvé, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Le dossier {0} introuvable. Message : {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Le fichier {0} a été téléversé, mais la taille renvoyée était {1} alors " +"qu'elle était supposée être {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Pour protéger contre les échecs de réseau ou serveur, une tentative de " +"vérification sera effectuée sur chaque téléversement. Utiliser cette option " +"pour désactiver cette vérification pour rendre le téléversement plus rapide," +" mais moins fiable." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Désactiver la vérification de téléversement" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Si ce drapeau est sélectionné, les données de type FTP seront changées pour " +"l'option sélectionné." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurer les types de connexion de donnée FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Si ce drapeau est sélectionné, le mode de chiffrement FTP sera changé pour " +"l'option sélectionnée." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurer le mode de chiffrement FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Ce drapeau contrôle la police SSL utilisée quand le chiffrement est activé." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Configure la police SSL à utiliser quand le chiffrement est activé" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Certains serveurs FTP n'affichent pas instantanément la taille correcte du " +"fichier. Le délai dépend de la topologie du réseau. En cas d'erreur liée à " +"la taille du fichier téléversé, essayer d'ajouter un délai de quelques " +"secondes." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Ajouter un délai après le téléversement d'un fichier" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -904,6 +1067,11 @@ msgstr "" msgid "Specify storage class" msgstr "Définissez une classe de stockage." +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -936,125 +1104,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternatif" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Le dossier {0} introuvable. Message : {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Le fichier {0} a été téléversé, mais la taille renvoyée était {1} alors " -"qu'elle était supposée être {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Pour protéger contre les échecs de réseau ou serveur, une tentative de " -"vérification sera effectuée sur chaque téléversement. Utiliser cette option " -"pour désactiver cette vérification pour rendre le téléversement plus rapide," -" mais moins fiable." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Si ce drapeau est sélectionné, les données de type FTP seront changées pour " -"l'option sélectionné." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurer les types de connexion de donnée FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Si ce drapeau est sélectionné, le mode de chiffrement FTP sera changé pour " -"l'option sélectionnée." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurer le mode de chiffrement FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Ce drapeau contrôle la police SSL utilisée quand le chiffrement est activé." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Configure la police SSL à utiliser quand le chiffrement est activé" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Certains serveurs FTP n'affichent pas instantanément la taille correcte du " -"fichier. Le délai dépend de la topologie du réseau. En cas d'erreur liée à " -"la taille du fichier téléversé, essayer d'ajouter un délai de quelques " -"secondes." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Ajouter un délai après le téléversement d'un fichier" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Erreur sur la suppression du fichier: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Erreur sur la lecture du fichier: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Erreur sur l'enregistrement du fichier: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Module pour générer un couple de clé SSH privée/publique" @@ -1566,7 +1615,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Si la classe HttpClient devait être utilisée" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1574,11 +1627,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1588,11 +1641,11 @@ msgstr "" "spécifié, le lecteur OneDrive ou OneDrive for Business par défaut sera " "utilisé via '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ID facultatif du lecteur" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1600,29 +1653,29 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID du site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Aucun ID de site n'a été fourni" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Identifiants de site en conflit utilisés : donnés {0} mais trouvés {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1634,41 +1687,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Groupe Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID du groupe" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Adresse e-mail du groupe" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Aucun groupe n'a été trouvé avec l'adresse e-mail donnée : {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Plusieurs groupes ont été trouvés avec l'adresse email donnée : {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1687,7 +1740,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1698,7 +1751,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1958,43 +2011,45 @@ msgstr "Mot de passe non renseigné" msgid "No username given" msgstr "Utilisateur non renseigné" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2133,6 +2188,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2167,6 +2227,12 @@ msgstr "" "Le serveur a renvoyé le code d'erreur {0} ({1}), indiquant que le serveur ne" " supporte pas les connexion WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Le dossier {0} n'a pas été trouvé, message: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2195,6 +2261,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Vider la réponse PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accepter n'importe quel certificat de serveur" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Acceptez éventuellement un certificat SSL connu" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2319,9 +2449,410 @@ msgstr "Code d'erreur inattendu : {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Une autre instance est en cours et a été avertie" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2341,13 +2872,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Arguments de ligne de commande supportés:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2376,11 +2905,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Les filtres ne peuvent pas être précisés dans la ligne de commande si les " -"filtres sont aussi présents dans le fichier de paramètres. Utilisez les " -"options spéciales --{0}, --{1} ou --{2} pour préciser les filtres dans le " -"fichier de paramètre. Chaque filtre doit être préfixé avec soit un + ou un -" -" et les filtres multiples doivent être joints avec {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2418,7 +2942,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2426,8 +2950,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Version de SQLite non prise en charge ({0} détectée, {1} ou supérieure " -"requise)" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2440,41 +2962,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Le port sur lequel le serveur Web écoute. Plusieurs valeurs, séparées par " -"une virgule, peuvent être fournies." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Le certificat et le fichier de clé dans PKCS # 12 formatent l'utilisation du" -" serveur Web pour SSL. Seules les clés RSA / DSA sont prises en charge" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Mot de passe pour déchiffrer le certificat PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"L'interface sur laquelle le serveur web écoute. Les valeurs spéciales \"*\" " -"et \"any\" signifient n'importe quelle interface. La valeur spéciale " -"\"loopback\" signifie l'adaptateur de bouclage." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Mot de passe d'accès au serveur web (une valeur vide désactive le mot de " -"passe)" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2484,133 +3008,164 @@ msgstr "" "d'hôte est \"*\", tous les noms d'hôte sont autorisés et la vérification du " "nom d'hôte est désactivée." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Délai de conservation des données de journalisation" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Nettoyer les anciennes données du journal" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati a besoin de stocker une petite base de données avec tous les " -"paramètres. Utilisez cette option pour choisir où les paramètres sont " -"stockés. Cette option peut également être définie avec la variable " -"d'environnement {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Cette option définit la clé de chiffrement utilisée pour brouiller la base " -"de données des paramètres locaux. Cette option peut également être définie " -"avec la variable d'environnement {0}. Utilisez l'option - {1} pour " -"désactiver le brouillage de la base de données." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Dossier de stockage temporaire" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2618,15 +3173,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2635,25 +3227,21 @@ msgstr "" "Impossible de trouver une date valide compte tenu de la date de début {0}, " "de l'intervalle de répétition {1} et des jours autorisés {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Le serveur a démarré et écoute sur {0}, port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Impossible de créer le certificat SSL en utilisant les paramètres fournis. " -"Détails de l'erreur : {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" -"Impossible d'ouvrir un socket pour l'écoute, les ports essayés sont : {0}" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2667,22 +3255,22 @@ msgstr "" "Échec du chargement du type de processus {0} assembly {1}, message d'erreur " ": {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2691,11 +3279,11 @@ msgstr "" "ne donne aucune compression et un réglage de 9 donne une compression " "maximale." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2703,75 +3291,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Fichier non trouvé : {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "* Expérimental * : archive 7z supportant LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Expérimental - archive 7z" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archive non ouverte pour l'écriture" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archive non ouverte pour la lecture" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Le fichier donné ne fait pas partie de cette archive" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Le nombre de threads utilisés dans la compression LZMA 2. Définit par défaut" -" au nombre de cœurs de processeur." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Nombre de passes utilisées en compression" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Cette option contrôle l'algorithme de compression utilisé. Si vous activez " -"cette option, 7z utilisera l'algorithme rapide, qui produit un peu moins de " -"compression." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "sauvegarde" @@ -3223,48 +3773,66 @@ msgstr "Limiter la taille des fichiers qui sont sauvegardés" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Priorité du thread" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limiter la taille des fichiers des volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3277,11 +3845,11 @@ msgstr "" "existant est lu, le nom du fichier est utilisé pour sélectionner le module " "de compression." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Sélectionner quel module de compression utiliser" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3294,27 +3862,27 @@ msgstr "" "existant est lu, le nom du fichier est utilisé pour sélectionner le module " "de chiffrement." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Sélectionnez le module à utiliser pour le chiffrement" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3343,34 +3911,36 @@ msgstr "" "d'administrateur. Sous Linux, cela utilise LVM (Logical Volume Management) " "et nécessite des privilèges root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Le chemin où les volumes prêts sont placés jusqu'au téléversement" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Le nombre de volumes à créer à l'avance" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3379,45 +3949,45 @@ msgstr "" "téléchargements simultanés autorisés. Mettre à zéro pour désactiver la " "limite." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Le nombre de téléversements simultanés autorisés" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Consigner les informations internes dans un fichier" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Niveau de détail du journal" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3426,11 +3996,11 @@ msgstr "" "automatiquement. Activez cette option pour empêcher la création automatique " "de dossier." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3445,14 +4015,14 @@ msgstr "" "par un point-virgule, et la plupart des formes de GUID sont autorisées, y " "compris avec et sans accolades." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Une liste de guids d'écrivains VSS séparés par des points-virgules à exclure" " (Windows uniquement)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3476,11 +4046,22 @@ msgstr "" "est uniquement prise en charge sous Windows et nécessite des privilèges " "d'administrateur." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3493,30 +4074,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Vérifier les téléversements en listant le contenu" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Téléverser des fichiers de manière synchrone" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3524,11 +4106,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Ne pas réutiliser les connexions" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3538,34 +4120,34 @@ msgstr "" "signale que le nombre de tentatives. Activez cette option pour afficher les " "messages d'erreur lorsqu'une nouvelle tentative est effectuée." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" "Afficher les messages d'erreur lorsqu'une nouvelle tentative est effectuée" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Téléverse des fichiers de sauvegarde vides" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3574,22 +4156,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Seuil d'avertissement concernant un quota disponible faible" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3601,11 +4183,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Gestion de symlink" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3622,11 +4204,11 @@ msgstr "" "chemin unique. L'option \"{2}\" ignorera tous les liens physiques avec plus " "d'un lien." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Manipulation de Hardlink" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3634,11 +4216,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Exclure les fichiers par attribut" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3651,41 +4233,51 @@ msgstr "" "instantané. Cette solution de contournement peut accélérer l'accès aux " "fichiers sur Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mapper des instantanés sur un lecteur (Windows uniquement)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nom de la sauvegarde" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3697,11 +4289,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Gérer les extensions de fichiers non compressibles" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3714,71 +4306,63 @@ msgstr "" "importante lors du stockage des listes de fichiers. Notez que la valeur ne " "peut pas être modifiée après la création des fichiers distants." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Taille de bloc utilisée dans le hachage" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Liste des fichiers à scanner pour voir s'ils ont changé" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Chemin vers l'état de la base de donnée locale" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Liste des fichiers supprimés" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Réduire l'empreinte mémoire en désactivant les recherches en mémoire" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Stocker un cache de bloc en mémoire" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Ne pas interroger le back-end au démarrage" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3793,11 +4377,11 @@ msgstr "" "que les fichiers d'index plus grands occupent plus d'espace à distance et " "peuvent ne jamais être utilisés." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3810,43 +4394,43 @@ msgstr "" "récupérée. Cette valeur est un pourcentage utilisé sur chaque volume et le " "stockage total." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Le maximum d'espace perdu en pourcentage" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "L'algorithme de hachage utilisé sur les blocs" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "L'algorithme de hachage utilisé sur les fichiers" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3859,11 +4443,11 @@ msgstr "" "pour désactiver ce compactage automatique et ne compacter que lors de " "l'exécution de la commande compact." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Désactiver le compactage automatique" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3875,11 +4459,11 @@ msgstr "" "Cela garantit que les gros volumes qui risquent de perdre de l'espace de " "quelques octets ne sont pas téléchargés et réécrits." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Seuil de taille d'un volume" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3889,11 +4473,11 @@ msgstr "" "peut forcer le groupement des petits fichiers. Les petits volumes seront " "toujours concaténés lorsqu'ils pourront remplir un volume entier." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Nombre maximum de petits volumes" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3903,42 +4487,42 @@ msgstr "" "afin de trouver des blocs existants. Cette opération est assez lente mais " "peut limiter la taille des téléchargements." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Utiliser les fichiers locaux lors de la restauration" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Garder un nombre de versions" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Utilisez cette option pour définir la durée durant laquelle les sauvegardes " "seront gardées" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Garder toutes les versions dans une fourchette de temps" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3959,33 +4543,33 @@ msgstr "" "option prend également en charge l'utilisation du spécificateur \"U\" pour " "indiquer un intervalle de temps illimité." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Réduire le nombre de versions en supprimant les anciennes sauvegardes " "intermédiaires" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Utilisez cette option pour continuer même si certaines entrées source sont " "manquantes." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignorer les éléments source manquants" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Écrase les fichiers lors de la réstauration" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3994,11 +4578,11 @@ msgstr "" "l'exécution d'une option. En général, cette option produira une ligne pour " "chaque fichier traité." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Produire plus d'informations d'avancement" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -4007,11 +4591,11 @@ msgstr "" "générée à la suite de l'opération, y compris l'ensemble des noms de " "fichiers." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Produire des résultats complets" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -4023,11 +4607,11 @@ msgstr "" "la taille et les hachages SHA256 de tous les fichiers distants et peut être " "utilisé pour vérifier l'intégrité des fichiers." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Déterminez si les fichiers de vérification sont téléchargés" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4037,11 +4621,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Le nombre d'échantillons à tester après une sauvegarde" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4051,11 +4635,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Le pourcentage d'échantillons à tester après une sauvegarde" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4067,41 +4651,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Taille du tampon de lecture du fichier" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Autoriser le changement de phrase de passe" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Afficher uniquement les index de fichiers" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4112,11 +4696,11 @@ msgstr "" "métadonnées accélère les opérations de sauvegarde et de restauration, mais " "n'affecte pas beaucoup la taille des fichiers." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4125,11 +4709,11 @@ msgstr "" "empêcher d'accéder à vos fichiers. Utilisez cette option pour restaurer " "également les autorisations." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restaurer les autorisations de fichiers" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4139,11 +4723,11 @@ msgstr "" "est vérifié pour vérifier que la restauration a réussi. Utilisez cette " "option pour désactiver et donc éviter d'attendre la vérification." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Ignorer la vérification du fichier restauré" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4153,28 +4737,28 @@ msgstr "" "la quantité de données téléchargées. Utilisez cette option pour ignorer " "cette optimisation et n'utiliser que les données distantes." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "N'utilise pas de données locales" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4183,11 +4767,11 @@ msgstr "" "l'empreinte des blocs lus à partir d'un volume avant d'appliquer les " "correctifs aux fichiers restaurés avec les données." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Vérifie les empreintes des blocs" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4201,11 +4785,11 @@ msgstr "" "données résultante est interrogeable, mais ne peut pas être utilisée pour " "restaurer des données." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Répare la base de données avec les chemins d'accès" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4213,11 +4797,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Forcer les paramètres régionaux" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4227,24 +4811,24 @@ msgstr "" " \"Aujourd'hui\" ou \"Jeudi dernier\". En réglant cette option, seules les " "dates réelles sont affichées, par exemple \"12 novembre 2018, 08:01\"." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Gérer la communication de fichiers avec le backend à l'aide de tuyaux " "filetés" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4254,22 +4838,22 @@ msgstr "" "Définir cette valeur sur zéro ou moins équilibrera dynamiquement le nombre " "de threads actifs pour s'adapter au matériel." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Nombre limite de threads simultanés" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Utilisez cette option pour définir le nombre de processus effectuant le " "hachage des données." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Indiquez le nombre de processus de hachage simultanés" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4277,11 +4861,11 @@ msgstr "" "Utilisez cette option pour définir le nombre de processus effectuant la " "compression des données de sortie." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Spécifiez le nombre de processus de compression simultanés" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4291,11 +4875,11 @@ msgstr "" " liste de fichiers correspondant à la dernière sauvegarde effectuée et au " "contenu téléchargé lors de la session de sauvegarde incomplète sera générée." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4303,11 +4887,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4315,11 +4899,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4327,11 +4911,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Autoriser la suppression de tous les ensembles de fichiers" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4348,11 +4932,11 @@ msgstr "" "données. Définir cela sur true permettra à Duplicati d'exécuter les " "opérations VACUUM à sa discrétion." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4360,11 +4944,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Désactiver le scanner à lecture anticipée" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4375,11 +4959,11 @@ msgstr "" "désactivez les contrôles, veillez à exécuter des commandes de contrôle " "régulières pour vous assurer que tout fonctionne comme prévu." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Désactiver les contrôles de cohérence des index" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4387,15 +4971,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Désactiver la sauvegarde sur batterie" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Niveau d'information du fichier journal" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4411,42 +4995,42 @@ msgstr "" "par '-'. Les expressions régulières sont prises en charge dans les " "accolades. Exemple: \"+ Path * {0} + * Mail * {0} - [. * DNS]\"" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Niveau d'information de la console" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4459,11 +5043,11 @@ msgstr "" " de placer ce fichier dans des dossiers qui ne devraient pas être " "sauvegardés." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Liste des noms de fichiers qui excluent les dossiers" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4471,11 +5055,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4483,11 +5067,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4500,11 +5084,11 @@ msgstr "" "les requêtes de base de données et n'oubliez pas de définir - {0} = {2} ou -" " {1} = {2} pour signaler les données de journal supplémentaires." -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4513,11 +5097,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4525,11 +5109,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4537,11 +5121,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4550,54 +5283,54 @@ msgstr "" "La bibliothèque de chiffrement ne prend pas en charge les transformations " "réutilisables pour l'algorithme de hachage {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" "La bibliothèque de chiffrement ne supporte pas l'algorithme de hachage {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "La phrase de passe ne peut pas être modifiée pour une sauvegarde existante" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Échec de la création d'un instantané: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Échec de la suppression du fichier {0}, teste si le fichier existe" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Récupération d'un problème lié à la tentative de suppression d'un fichier " "non existant {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" "Échec de la récupération du fichier d'erreur lors de la suppression du " "fichier {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "L'opération de suppression a échoué pour {0} avec FileNotFound, listage du " "contenu en cours" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Le fichier {0} a été supprimé" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Le fichier {0} n'as pas été supprimé" @@ -4661,29 +5394,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurer les requêtes http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accepter n'importe quel certificat de serveur" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Acceptez éventuellement un certificat SSL connu" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4783,7 +5493,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configurer le module Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4791,125 +5513,125 @@ msgstr "" "Ce module fonctionne en interne pour analyser les paramètres source pour " "sauvegarder les bases de données Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configurer le module Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Script de lancement" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Exécuter un script à la sortie" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Le script \"{0}\" est retourné avec le code de sortie {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Le script \"{0}\" a généré le code de sortie {1} {2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Exécuter un script requis au démarrage" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Erreur lors de l'exécution du script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "L'exécution du script \"{0}\" a expiré" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Exécuter un script au démarrage" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Le script \"{0}\" a signalé des messages d'erreur: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Ce module peut envoyer des emails une fois l'opération terminée" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Envoyer email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4922,21 +5644,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Corps du message" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Mot de passe SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4948,11 +5670,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email destinataire (s)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4962,25 +5684,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Expéditeur" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Messages à envoyer" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4988,11 +5710,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "Url SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -5001,60 +5723,60 @@ msgstr "" "Ce paramètre fournit l'objet du courrier électronique. Les valeurs sont " "remplacées comme décrit dans la description de - {0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Sujet de l'email" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Nom d'utilisateur SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Communication SMTP entière: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Échec de l'envoi du courrier électronique avec le serveur: {0}, message: " "{1}, réessayez avec {2}." -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email envoyé avec succès à l'aide du serveur: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Ce module prend en charge l'envoi de rapports d'état via des messages XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Module de rapport XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "Adresse électronique du destinataire XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5067,35 +5789,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Le modèle de message" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Le nom d'utilisateur XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Le mot de passe XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5104,111 +5826,111 @@ msgstr "" "Vous pouvez en spécifier un de \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Vous pouvez fournir plusieurs options avec un séparateur de virgule, par exemple \"{0}, {1}\". La valeur spéciale \"{4}\" est un raccourci pour \"{0}, {1}, {2}, {3}\" et toutes les opérations de sauvegarde enverront un message." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Envoyer des messages pour toutes les opérations" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" "Un dépassement de délai s'est produit lors de la connexion au serveur jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Ce module prend en charge l'envoi de rapports d'état via des messages HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Module de report HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Le nom du paramètre pour envoyer le message en tant que" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Paramètres supplémentaires à ajouter au message http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5216,46 +5938,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Échec de l'envoi du message: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Journal message filter" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5264,7 +5986,7 @@ msgstr "" "inclure dans le rapport. Des valeurs nulles ou négatives signifient " "illimitées." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5273,16 +5995,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Le format n'est pas pris en charge: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Taille invalide : {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Le validateur de certificat SSL a été appelé dans un ordre incorrect" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5291,7 +6013,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5300,71 +6022,71 @@ msgstr "" "Échec lors de la validation du hachage du certificat, message d'erreur: {0}," " nom de l'erreur SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Le dossier temporaire n'existe pas : {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Échec de l'analyse du segment: {0}, entier non valide" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Spécificateur non valide: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Données non analysées: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "l'Uri est invalide : {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "L'Uri manque un nom d'hôte: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} octets" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "La chaîne \"{0}\" n'a pas pu être analysée dans une date" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Impossible de lire et d'écrire sur le même flux" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5606,11 +6328,11 @@ msgstr "Désactiver les sorties console" msgid "This link may provide additional information: {0}" msgstr "Ce lien peut fournir des informations supplémentaires: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Active les mises à jour automatiques" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-fr_CA.mo b/Localizations/duplicati/localization-fr_CA.mo index 28255e880..7cf2bb254 100644 Binary files a/Localizations/duplicati/localization-fr_CA.mo and b/Localizations/duplicati/localization-fr_CA.mo differ diff --git a/Localizations/duplicati/localization-fr_CA.po b/Localizations/duplicati/localization-fr_CA.po index 8a303b776..174964bc4 100644 --- a/Localizations/duplicati/localization-fr_CA.po +++ b/Localizations/duplicati/localization-fr_CA.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Francois Lafleur , 2024\n" "Language-Team: French (Canada) (https://app.transifex.com/duplicati/teams/67655/fr_CA/)\n" @@ -47,8 +47,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -162,78 +162,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Echec dans l'appel de GnuPG, l'application ne s'arrêtera pas" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "alias" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valeur par défaut" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[PÉRIMÉ]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valeurs" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booléen" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumération" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Drapeaux" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Entier" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Chemin" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Taille" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Texte" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Intervalle de temps" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Inconnu" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Le répertoire ne peut être créé car il existe déjà" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Le dossier requis n'existe pas" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Annulé" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Le fichier demandé n'existe pas" @@ -366,9 +382,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -386,9 +401,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -401,9 +415,8 @@ msgstr "" "Le nom d'utilisateur utilisé pour se connecter au serveur. Il peut également" " être fourni comme une variable d'environnement \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -495,41 +508,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternatif" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -540,50 +543,205 @@ msgstr "" "Le mot de passe utilisé pour se connecter au serveur. Il peut également être" " fourni comme une variable d'environnement \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Pour se protéger des problèmes réseaux, chaque téléversement sera " -"automatiquement vérifié. Utilisez cette option pour désactiver cette " -"vérification afin que le téléchargement soit plus rapide mais moins fiable." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Désactiver la vérification de téléversement" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Le dossier {0} n'a pas été trouvé, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Le dossier {0} introuvable. Message : {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Le fichier {0} a été téléversé, mais la taille renvoyée était {1} alors " +"qu'elle était supposée être {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Pour protéger contre les échecs de réseau ou serveur, chaque téléversement " +"tentera d'être vérifié. Utiliser cette option pour désactiver cette " +"vérification pour rendre le téléversement plus rapide, mais moins fiable." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Désactiver la vérification de téléversement" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Si ce drapeau est sélectionné, les données de type FTP seront changées pour " +"l'option sélectionné." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurer les types de connexion de donnée FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Si ce drapeau est sélectionné, le mode de chiffrement FTP sera changé pour " +"l'option sélectionnée." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurer le mode de chiffrement FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Ce drapeau contrôle la police SSL utilisée quand le chiffrement est activé." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Configure la police SSL à utiliser quand le chiffrement est activé" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -889,6 +1047,11 @@ msgstr "" msgid "Specify storage class" msgstr "Définissez une classe de stockage." +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -921,120 +1084,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternatif" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Le dossier {0} introuvable. Message : {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Le fichier {0} a été téléversé, mais la taille renvoyée était {1} alors " -"qu'elle était supposée être {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Pour protéger contre les échecs de réseau ou serveur, chaque téléversement " -"tentera d'être vérifié. Utiliser cette option pour désactiver cette " -"vérification pour rendre le téléversement plus rapide, mais moins fiable." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Si ce drapeau est sélectionné, les données de type FTP seront changées pour " -"l'option sélectionné." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurer les types de connexion de donnée FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Si ce drapeau est sélectionné, le mode de chiffrement FTP sera changé pour " -"l'option sélectionnée." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurer le mode de chiffrement FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Ce drapeau contrôle la police SSL utilisée quand le chiffrement est activé." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Configure la police SSL à utiliser quand le chiffrement est activé" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Erreur sur la suppression du fichier: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Erreur sur la lecture du fichier: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Erreur sur l'enregistrement du fichier: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Module pour générer un couple de clé SSH privée/publique" @@ -1535,7 +1584,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1543,11 +1596,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1557,11 +1610,11 @@ msgstr "" "spécifié, le lecteur OneDrive ou OneDrive for Business par défaut sera " "utilisé via '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ID facultatif du lecteur" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1569,29 +1622,29 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID du site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Aucun ID de site n'a été fourni" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Identifiants de site en conflit utilisés : donnés {0} mais trouvés {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1603,41 +1656,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Groupe Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID du groupe" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Adresse e-mail du groupe" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Aucun groupe n'a été trouvé avec l'adresse e-mail donnée : {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Plusieurs groupes ont été trouvés avec l'adresse email donnée : {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1656,7 +1709,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1667,7 +1720,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1922,43 +1975,45 @@ msgstr "Mot de passe non renseigné" msgid "No username given" msgstr "Utilisateur non renseigné" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2097,6 +2152,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2131,6 +2191,12 @@ msgstr "" "Le serveur a renvoyé le code d'erreur {0} ({1}), indiquant que le serveur ne" " supporte pas les connexion WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Le dossier {0} n'a pas été trouvé, message: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2159,6 +2225,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Vider la réponse PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accepter n'importe quel certificat de serveur" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Acceptez éventuellement un certificat SSL connu" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2283,9 +2413,410 @@ msgstr "Code d'erreur inattendu : {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Une autre instance est en cours et a été avertie" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2305,13 +2836,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Arguments de ligne de commande supportés:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2340,11 +2869,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Les filtres ne peuvent pas être précisés dans la ligne de commande si les " -"filtres sont aussi présents dans le fichier de paramètre. Utilisez les " -"options spéciales --{0}, --{1} ou --{2} pour préciser les filtres dans le " -"fichier de paramètre. Chaque filtre doit être préfixé avec soit a + ou a - " -"et les filtres multiples doivent être attachés avec {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2382,7 +2906,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2390,8 +2914,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Version non prise en charge de SQLite détectée ({0}), doit être {1} ou " -"supérieure" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2404,42 +2926,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Le port sur lequel le serveur Web écoute. Plusieurs valeurs, séparées par " -"une virgule, peuvent être fournies." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Le certificat et le fichier de clé dans PKCS # 12 formatent l'utilisation du" -" serveur Web pour SSL. Seules les clés RSA / DSA sont prises en charge" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Le mot de passe pour déchiffrer le certificat PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"L'interface sur laquelle le serveur web écoute. Les valeurs spéciales \"*\" " -"et \"any\" signifient n'importe quelle interface. La valeur spéciale " -"\"loopback\" signifie l'adaptateur de bouclage." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Le mot de passe requis pour accéder au serveur Web. Cette option est " -"enregistrée de sorte que vous n'avez pas besoin de le définir à chaque " -"exécution. La définition d'une valeur vide désactive le mot de passe." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2449,135 +2972,166 @@ msgstr "" "d'hôte est \"*\", tous les noms d'hôte sont autorisés et la vérification du " "nom d'hôte est désactivée." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Définissez l'heure après laquelle les données de journal seront purgées de " "la base de données." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Nettoyer les anciennes données de journal" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati a besoin de stocker une petite base de données avec tous les " -"paramètres. Utilisez cette option pour choisir où les paramètres sont " -"stockés. Cette option peut également être définie avec la variable " -"d'environnement {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Cette option définit la clé de chiffrement utilisée pour brouiller la base " -"de données des paramètres locaux. Cette option peut également être définie " -"avec la variable d'environnement {0}. Utilisez l'option - {1} pour " -"désactiver le brouillage de la base de données." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Dossier de stockage temporaire" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2585,15 +3139,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2602,24 +3193,21 @@ msgstr "" "Impossible de trouver une date valide, compte tenu de la date de début {0}, " "de l'intervalle de répétition {1} et des jours autorisés {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Le serveur a démarré et écoute sur {0}, le port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Impossible de créer le certificat SSL en utilisant les paramètres fournis. " -"Détails de l'exception: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Impossible d'ouvrir une socket pour l'écoute, les ports essayés: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2633,22 +3221,22 @@ msgstr "" "Échec du chargement du type de processus {0} assembly {1}, message d'erreur " ": {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2657,11 +3245,11 @@ msgstr "" "ne donne aucune compression et un réglage de 9 donne une compression " "maximale." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2669,75 +3257,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Fichier non trouvé : {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "* Expérimental * : archive 7z supportant LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Expérimental - archive 7z" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archive non ouverte pour l'écriture" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archive non ouverte pour la lecture" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Le fichier donné ne fait pas partie de cette archive" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Le nombre de threads utilisés dans la compression LZMA 2. Définit par défaut" -" au nombre de cœurs de processeur." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Nombre de passes utilisées en compression" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Cette option contrôle l'algorithme de compression utilisé. Si vous activez " -"cette option, 7z utilisera l'algorithme rapide, qui produit un peu moins de " -"compression." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "sauvegarde" @@ -3188,48 +3738,66 @@ msgstr "Limiter la taille des fichiers qui sont sauvegardés" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Priorité du thread" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limiter la taille des fichiers des volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3242,11 +3810,11 @@ msgstr "" "existant est lu, le nom du fichier est utilisé pour sélectionner le module " "de compression." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Sélectionner quel module de compression utiliser" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3259,27 +3827,27 @@ msgstr "" "existant est lu, le nom du fichier est utilisé pour sélectionner le module " "de chiffrement." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Sélectionner quel module de chiffrement utiliser" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3308,78 +3876,80 @@ msgstr "" "d'administrateur. Sous Linux, cela utilise LVM (Logical Volume Management) " "et nécessite des privilèges root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Le chemin où les volumes prêts sont placés jusqu'au téléversement" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Le nombre de volumes à créer à l'avance" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Consigner les informations internes dans un fichier" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Niveau de détail du journal" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3388,11 +3958,11 @@ msgstr "" "automatiquement. Activez cette option pour empêcher la création automatique " "de dossier." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3407,14 +3977,14 @@ msgstr "" "par un point-virgule, et la plupart des formes de GUID sont autorisées, y " "compris avec et sans accolades." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Une liste de guids d'écrivains VSS séparés par des points-virgules à exclure" " (Windows uniquement)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3438,11 +4008,22 @@ msgstr "" "est uniquement prise en charge sous Windows et nécessite des privilèges " "d'administrateur." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3455,30 +4036,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Vérifie les téléchargements en répertoriant le contenu" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Téléverser des fichiers de manière synchrone" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3486,11 +4068,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Ne pas réutiliser les connexions" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3500,34 +4082,34 @@ msgstr "" "signale que le nombre de tentatives. Activez cette option pour afficher les " "messages d'erreur lorsqu'une nouvelle tentative est effectuée." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" "Afficher les messages d'erreur lorsqu'une nouvelle tentative est effectuée" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Téléverse des fichiers de sauvegarde vides" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3536,22 +4118,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Seuil d'avertissement concernant un quota disponible faible" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3563,11 +4145,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Gestion de symlink" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3584,11 +4166,11 @@ msgstr "" "chemin unique. L'option \"{2}\" ignorera tous les liens physiques avec plus " "d'un lien." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Manipulation de Hardlink" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3596,11 +4178,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Exclure les fichiers par attribut" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3613,41 +4195,51 @@ msgstr "" "instantané. Cette solution de contournement peut accélérer l'accès aux " "fichiers sur Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mapper des instantanés sur un lecteur (Windows uniquement)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nom de la sauvegarde" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3659,11 +4251,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Gérer les extensions de fichiers non compressibles" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3676,71 +4268,63 @@ msgstr "" "importante lors du stockage des listes de fichiers. Notez que la valeur ne " "peut pas être modifiée après la création des fichiers distants." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Taille de bloc utilisée dans le hachage" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Liste des fichiers à scanner pour voir s'ils ont changé" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Chemin vers l'état de la base de donnée locale" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Liste des fichiers supprimés" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Réduire l'empreinte mémoire en désactivant les recherches en mémoire" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Stocker un cache de bloc en mémoire" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Ne pas interroger le back-end au démarrage" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3755,11 +4339,11 @@ msgstr "" "que les fichiers d'index plus grands occupent plus d'espace à distance et " "peuvent ne jamais être utilisés." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3772,43 +4356,43 @@ msgstr "" "récupérée. Cette valeur est un pourcentage utilisé sur chaque volume et le " "stockage total." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Le maximum d'espace perdu en pourcentage" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "L'algorithme de hachage utilisé sur les blocs" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "L'algorithme de hachage utilisé sur les fichiers" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3821,11 +4405,11 @@ msgstr "" "pour désactiver ce compactage automatique et ne compacter que lors de " "l'exécution de la commande compact." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Désactiver le compactage automatique" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3837,11 +4421,11 @@ msgstr "" "Cela garantit que les gros volumes qui risquent de perdre de l'espace de " "quelques octets ne sont pas téléchargés et réécrits." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Seuil de taille d'un volume" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3851,11 +4435,11 @@ msgstr "" "peut forcer le groupement des petits fichiers. Les petits volumes seront " "toujours concaténés lorsqu'ils pourront remplir un volume entier." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Nombre maximum de petits volumes" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3865,42 +4449,42 @@ msgstr "" "afin de trouver des blocs existants. Cette opération est assez lente mais " "peut limiter la taille des téléchargements." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Utiliser les fichiers locaux lors de la restauration" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Garder un nombre de versions" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Utilisez cette option pour définir la durée durant laquelle les sauvegardes " "seront gardées" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Garder toutes les versions dans une fourchette de temps" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3921,33 +4505,33 @@ msgstr "" "option prend également en charge l'utilisation du spécificateur \"U\" pour " "indiquer un intervalle de temps illimité." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Réduire le nombre de versions en supprimant les anciennes sauvegardes " "intermédiaires" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Utilisez cette option pour continuer même si certaines entrées source sont " "manquantes." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignorer les éléments source manquants" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Écrase les fichiers lors de la réstauration" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3956,11 +4540,11 @@ msgstr "" "l'exécution d'une option. En général, cette option produira une ligne pour " "chaque fichier traité." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Produire plus d'informations d'avancement" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3969,11 +4553,11 @@ msgstr "" "générée à la suite de l'opération, y compris l'ensemble des noms de " "fichiers." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Produire des résultats complets" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3985,11 +4569,11 @@ msgstr "" "la taille et les hachages SHA256 de tous les fichiers distants et peut être " "utilisé pour vérifier l'intégrité des fichiers." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Déterminez si les fichiers de vérification sont téléchargés" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3999,11 +4583,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Le nombre d'échantillons à tester après une sauvegarde" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4013,11 +4597,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4029,41 +4613,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Taille du tampon de lecture du fichier" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Autoriser le changement de mot de passe" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Afficher uniquement les index de fichiers" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4074,11 +4658,11 @@ msgstr "" "métadonnées accélère les opérations de sauvegarde et de restauration, mais " "n'affecte pas beaucoup la taille des fichiers." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4087,11 +4671,11 @@ msgstr "" "empêcher d'accéder à vos fichiers. Utilisez cette option pour restaurer " "également les autorisations." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restaurer les autorisations de fichiers" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4101,11 +4685,11 @@ msgstr "" "est vérifié pour vérifier que la restauration a réussi. Utilisez cette " "option pour désactiver et donc éviter d'attendre la vérification." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Ignorer la vérification du fichier restauré" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4115,28 +4699,28 @@ msgstr "" "la quantité de données téléchargées. Utilisez cette option pour ignorer " "cette optimisation et n'utiliser que les données distantes." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "N'utilise pas de données locales" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4145,11 +4729,11 @@ msgstr "" "l'empreinte des blocs lus à partir d'un volume avant d'appliquer les " "correctifs aux fichiers restaurés avec les données." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Vérifie les empreintes des blocs" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4163,11 +4747,11 @@ msgstr "" "données résultante est interrogeable, mais ne peut pas être utilisée pour " "restaurer des données." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Répare la base de données avec les chemins d'accès" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4175,35 +4759,35 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Forcer les paramètres régionaux" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Gérer la communication de fichiers avec le backend à l'aide de tuyaux " "filetés" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4213,22 +4797,22 @@ msgstr "" "Définir cette valeur sur zéro ou moins équilibrera dynamiquement le nombre " "de threads actifs pour s'adapter au matériel." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Nombre limite de threads simultanés" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Utilisez cette option pour définir le nombre de processus effectuant le " "hachage des données." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Indiquez le nombre de processus de hachage simultanés" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4236,11 +4820,11 @@ msgstr "" "Utilisez cette option pour définir le nombre de processus effectuant la " "compression des données de sortie." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Spécifiez le nombre de processus de compression simultanés" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4250,11 +4834,11 @@ msgstr "" " liste de fichiers correspondant à la dernière sauvegarde effectuée et au " "contenu téléchargé lors de la session de sauvegarde incomplète sera générée." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4262,11 +4846,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4274,11 +4858,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4286,11 +4870,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Autoriser la suppression de tous les ensembles de fichiers" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4307,11 +4891,11 @@ msgstr "" "données. Définir cela sur true permettra à Duplicati d'exécuter les " "opérations VACUUM à sa discrétion." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4319,11 +4903,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Désactiver le scanner à lecture anticipée" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4334,11 +4918,11 @@ msgstr "" "désactivez les contrôles, veillez à exécuter des commandes de contrôle " "régulières pour vous assurer que tout fonctionne comme prévu." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Désactiver les contrôles de cohérence des index" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4346,15 +4930,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Désactiver la sauvegarde sur batterie" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Niveau d'information du fichier journal" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4370,42 +4954,42 @@ msgstr "" "par '-'. Les expressions régulières sont prises en charge dans les " "accolades. Exemple: \"+ Path * {0} + * Mail * {0} - [. * DNS]\"" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Niveau d'information de la console" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4418,11 +5002,11 @@ msgstr "" " de placer ce fichier dans des dossiers qui ne devraient pas être " "sauvegardés." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Liste des noms de fichiers qui excluent les dossiers" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4430,11 +5014,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4442,11 +5026,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4459,11 +5043,11 @@ msgstr "" "les requêtes de base de données et n'oubliez pas de définir - {0} = {2} ou -" " {1} = {2} pour signaler les données de journal supplémentaires." -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4472,11 +5056,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4484,11 +5068,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4496,11 +5080,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4509,53 +5242,53 @@ msgstr "" "La crypto-bibliothèque ne prend pas en charge les transformations " "réutilisables pour l'algorithme de hachage {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "La crypto-bibliothèque ne supporte pas l'algorithme de hachage {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "La phrase de passe ne peut pas être modifiée pour une sauvegarde existante" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Échec de la création d'un instantané: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Échec de la suppression du fichier {0}, teste si le fichier existe" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Récupération d'un problème lié à la tentative de suppression d'un fichier " "non existant {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" "Échec de la récupération du fichier d'erreur lors de la suppression du " "fichier {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "L'opération de suppression a échoué pour {0} avec FileNotFound, listage du " "contenu en cours" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4614,29 +5347,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurer les requêtes http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accepter n'importe quel certificat de serveur" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Acceptez éventuellement un certificat SSL connu" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4736,7 +5446,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configurer le module Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4744,125 +5466,125 @@ msgstr "" "Ce module fonctionne en interne pour analyser les paramètres source pour " "sauvegarder les bases de données Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configurer le module Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Script de lancement" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Exécuter un script à la sortie" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Le script \"{0}\" est retourné avec le code de sortie {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Le script \"{0}\" a généré le code de sortie {1} {2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Exécuter un script requis au démarrage" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Erreur lors de l'exécution du script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "L'exécution du script \"{0}\" a expiré" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Exécuter un script au démarrage" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Le script \"{0}\" a signalé des messages d'erreur: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Ce module peut envoyer des emails une fois l'opération terminée" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Envoyer email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4875,21 +5597,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Corps du message" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Mot de passe SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4901,11 +5623,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email destinataire (s)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4915,25 +5637,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Expéditeur" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Messages à envoyer" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4941,11 +5663,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "Url SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4954,60 +5676,60 @@ msgstr "" "Ce paramètre fournit l'objet du courrier électronique. Les valeurs sont " "remplacées comme décrit dans la description de - {0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Sujet de l'email" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Nom d'utilisateur SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Communication SMTP entière: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Échec de l'envoi du courrier électronique avec le serveur: {0}, message: " "{1}, réessayez avec {2}." -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email envoyé avec succès à l'aide du serveur: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Ce module prend en charge l'envoi de rapports d'état via des messages XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Module de rapport XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "Adresse électronique du destinataire XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5020,35 +5742,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Le modèle de message" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Le nom d'utilisateur XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Le mot de passe XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5057,111 +5779,111 @@ msgstr "" "Vous pouvez en spécifier un de \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Vous pouvez fournir plusieurs options avec un séparateur de virgule, par exemple \"{0}, {1}\". La valeur spéciale \"{4}\" est un raccourci pour \"{0}, {1}, {2}, {3}\" et toutes les opérations de sauvegarde enverront un message." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Envoyer des messages pour toutes les opérations" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" "Un dépassement de délai s'est produit lors de la connexion au serveur jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Ce module prend en charge l'envoi de rapports d'état via des messages HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Module de report HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Le nom du paramètre pour envoyer le message en tant que" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Paramètres supplémentaires à ajouter au message http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5169,46 +5891,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Échec de l'envoi du message: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Journal message filter" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5217,7 +5939,7 @@ msgstr "" "inclure dans le rapport. Des valeurs nulles ou négatives signifient " "illimitées." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5226,16 +5948,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Le format n'est pas pris en charge: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Taille invalide : {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Le validateur de certificat SSL a été appelé dans un ordre incorrect" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5244,7 +5966,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5253,71 +5975,71 @@ msgstr "" "Échec lors de la validation du hachage du certificat, message d'erreur: {0}," " nom de l'erreur SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Le dossier temporaire n'existe pas : {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Échec de l'analyse du segment: {0}, entier non valide" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Spécificateur non valide: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Données non analysées: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "l'Uri est invalide : {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "L'Uri manque un nom d'hôte: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} octets" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "La chaîne \"{0}\" n'a pas pu être analysée dans une date" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Impossible de lire et d'écrire sur le même flux" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5559,11 +6281,11 @@ msgstr "Désactiver les sorties console" msgid "This link may provide additional information: {0}" msgstr "Ce lien peut fournir des informations supplémentaires: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Active les mises à jour automatiques" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-hu.mo b/Localizations/duplicati/localization-hu.mo index f102cab8c..c88c40463 100644 Binary files a/Localizations/duplicati/localization-hu.mo and b/Localizations/duplicati/localization-hu.mo differ diff --git a/Localizations/duplicati/localization-hu.po b/Localizations/duplicati/localization-hu.po index 702c1403a..1b6d79a70 100644 --- a/Localizations/duplicati/localization-hu.po +++ b/Localizations/duplicati/localization-hu.po @@ -5,18 +5,18 @@ # # Translators: # Kiss István , 2017 +# Falu , 2024 # Faludi Zoltán, 2024 # Dávid Harmath , 2024 -# Falu , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Falu , 2024\n" +"Last-Translator: Dávid Harmath , 2024\n" "Language-Team: Hungarian (https://app.transifex.com/duplicati/teams/67655/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -49,8 +49,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -161,78 +161,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "A GnuPG meghívása közben a program nem áll le" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "álnevek" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "alapértelmezett érték" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[ELAVULT]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "értékek" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Logikai" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Felsorolás" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Zászlók" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Egész szám" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Útvonal" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Méret" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Időtartam" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Ismeretlen" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "A mappát nem lehet létrehozni, mert már létezik" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "A kért mappa nem létezik" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Törölve" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "A kért fájl nem létezik" @@ -357,9 +373,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -377,9 +392,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -392,9 +406,8 @@ msgstr "" "A kiszolgálóhoz való csatlakozáshoz használt felhasználónév. Ezt " ""AUTH_USERNAME" környezeti változóként is eljuttathatjuk." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -485,41 +498,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternatív FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -530,47 +533,195 @@ msgstr "" "A kiszolgálóhoz való csatlakozáshoz használt jelszó. Ezt " ""AUTH_PASSWORD" környezeti változóként is eljuttathatjuk." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "A {0} mappa nem található, üzenet: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "A {0} mappa nem található. Üzenet: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -866,6 +1017,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -898,110 +1054,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternatív FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "A {0} mappa nem található. Üzenet: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Fájl törlési hiba: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Fájl olvasási hiba {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Fájl írási hiba: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1476,7 +1528,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1484,22 +1540,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1507,28 +1563,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1540,42 +1596,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1592,7 +1648,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1603,7 +1659,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1848,43 +1904,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2000,6 +2058,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2032,6 +2095,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "A {0} mappa nem található, üzenet: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2057,6 +2126,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2177,6 +2310,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2197,8 +2731,6 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Támogatott parancssori argumentumok:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format @@ -2269,7 +2801,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2292,57 +2824,68 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Állítsa be azt az időtartamot, amely után a naplóadatok törlődnek az " "adatbázisból." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Tisztítsa meg a régi naplóadatokat" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2350,11 +2893,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2362,86 +2905,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Ideiglenes tároló mappa" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2449,34 +3031,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2491,32 +3109,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2524,69 +3142,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "A {0} fájl nem található" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "A {0} fájl nem található" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -2970,48 +3555,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3019,11 +3622,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3031,27 +3634,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3066,88 +3669,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3156,12 +3761,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3174,11 +3779,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3191,30 +3807,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3222,44 +3839,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3268,22 +3885,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3295,11 +3912,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3309,11 +3926,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3321,11 +3938,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3333,41 +3950,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3379,11 +4006,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3391,71 +4018,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3464,11 +4083,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3476,43 +4095,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3520,11 +4139,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3532,62 +4151,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3599,49 +4218,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3649,11 +4268,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3663,11 +4282,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3677,11 +4296,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3693,111 +4312,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3805,11 +4424,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3817,74 +4436,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3892,11 +4511,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3904,11 +4523,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3916,11 +4535,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3930,11 +4549,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3942,22 +4561,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3965,15 +4584,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3983,42 +4602,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4026,11 +4645,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4038,11 +4657,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4050,11 +4669,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4063,11 +4682,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4076,11 +4695,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4088,11 +4707,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4100,57 +4719,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4204,29 +4972,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4312,131 +5057,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4449,21 +5206,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4471,11 +5228,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4485,25 +5242,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4511,68 +5268,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4585,144 +5342,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4730,52 +5487,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4784,16 +5541,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4802,78 +5559,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} byte" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5088,11 +5845,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-it.mo b/Localizations/duplicati/localization-it.mo index 4e775e760..dcf8339d4 100644 Binary files a/Localizations/duplicati/localization-it.mo and b/Localizations/duplicati/localization-it.mo differ diff --git a/Localizations/duplicati/localization-it.po b/Localizations/duplicati/localization-it.po index 81cbe331a..d362f5717 100644 --- a/Localizations/duplicati/localization-it.po +++ b/Localizations/duplicati/localization-it.po @@ -6,19 +6,20 @@ # Translators: # Andrea Ricci , 2016 # albanobattistella , 2020 -# Francesco Infantini , 2024 # Antonio Mazzarino , 2024 # Andrea De Lunardi , 2024 # Folgore101 , 2024 +# Francesco Infantini , 2024 +# Antonio Galiero, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Folgore101 , 2024\n" +"Last-Translator: Antonio Galiero, 2024\n" "Language-Team: Italian (https://app.transifex.com/duplicati/teams/67655/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -53,11 +54,11 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "Imposta il livello di thread utilizzato per la crittografia" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." -msgstr "" +msgstr "L'opzione--{0} non è più usata ed è stata deprecata" #: Library/Encryption/Strings.cs:37 #, csharp-format @@ -74,6 +75,12 @@ msgid "" "program is available via the PATH environment variable. It is possible to " "supply the path to GPG using the option --{0}." msgstr "" +"Il modulo di crittografia GPG utilizza il programma GNU Privacy Guard per " +"crittografare e decrittografare i file. Richiede che l'eseguibile gpg sia " +"disponibile sul sistema. In Windows si presume che si trovi nella cartella " +"di installazione predefinita tra i file di programma, in Linux e OSX si " +"presume che il programma sia disponibile tramite la variabile d'ambiente " +"PATH. È possibile fornire il percorso di GPG utilizzando l'opzione --{0}." #: Library/Encryption/Strings.cs:42 msgid "GNU Privacy Guard, external" @@ -136,7 +143,7 @@ msgstr "Usa GPG Armor" #: Library/Encryption/Strings.cs:52 msgid "Override the GPG command supplied for decryption." -msgstr "" +msgstr "Sovrascrivi il comando GPG fornito per la decrittazione" #: Library/Encryption/Strings.cs:53 msgid "The GPG decryption command" @@ -148,6 +155,8 @@ msgid "" "Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." msgstr "" +"Sovrascrive il comando di crittografia GPG predefinito “{0}”. L'uso normale " +"è quello di richiedere la crittografia asimmetrica con l'impostazione {1}." #: Library/Encryption/Strings.cs:55 msgid "The GPG encryption command" @@ -166,78 +175,96 @@ msgstr "Errore durante la chiamata di GnuPG, programma non svuota l'uscita" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Errore durante la chiamata di GnuPG, programma non terminto" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "La chiave deve essere almeno 8 caratteri" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "La chiave non deve essere vuota" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "Rifiuto di crittografare con una chiave da una blacklist" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "pseudonimo" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valore predefinito" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DEPRECATO]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valori" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booleano" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumerazione" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flag" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Numero intero" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Percorso" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Dimensione" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Stringa" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Arco di tempo" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Sconosciuto" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "La cartella non può essere creata perché esiste già" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "La cartella richiesta non esiste" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancellato" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +"La chiave di crittografia utilizzata per crittografare le impostazioni di " +"destinazione non corrisponde alla chiave corrente." + +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "Chiave di crittografia mancante" #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Il file richiesto non esiste" @@ -370,16 +397,15 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 #: Library/Backend/SharePoint/Strings.cs:29 #: Library/Backend/WEBDAV/Strings.cs:29 msgid "Supply the password used to connect to the server" -msgstr "" +msgstr "Fornisci la password usata per connettersi al server" #: Library/Backend/OpenStack/Strings.cs:32 msgid "The domain name of the user used to connect to the server." @@ -387,11 +413,10 @@ msgstr "Il nome di dominio utilizzato dell'utente per connettersi al server." #: Library/Backend/OpenStack/Strings.cs:33 msgid "Supply the domain used to connect to the server" -msgstr "" +msgstr "Fornisci il dominio usato per connettersi al server" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -404,16 +429,15 @@ msgstr "" "Il nome utente usato per connettersi al server. Questo può anche essere " "fornito come variabile d'ambiente \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 #: Library/Backend/SharePoint/Strings.cs:31 #: Library/Backend/WEBDAV/Strings.cs:31 msgid "Supply the username used to connect to the server" -msgstr "" +msgstr "Fornisci lo username utilizzato per connettersi al server" #: Library/Backend/OpenStack/Strings.cs:36 msgid "" @@ -439,7 +463,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:39 msgid "Supply the API key used to connect to the server" -msgstr "" +msgstr "Fornisci la chiave API usata per connettersi al server" #: Library/Backend/OpenStack/Strings.cs:40 #, csharp-format @@ -453,7 +477,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:41 msgid "Supply the authentication URL" -msgstr "" +msgstr "Fornisci la URL di autenticazione" #: Library/Backend/OpenStack/Strings.cs:42 msgid "The keystone API version to use. Valid values are 'v2' and 'v3'." @@ -476,7 +500,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:45 msgid "Supply the region used for creating a container" -msgstr "" +msgstr "Fornisci la regione usata per creare il container" #: Library/Backend/OpenStack/OpenStackConfig.cs:67 msgid "OpenStack configuration module" @@ -484,7 +508,7 @@ msgstr "Modulo di configurazione OpenStack" #: Library/Backend/OpenStack/OpenStackConfig.cs:69 msgid "Expose OpenStack configuration as a web module" -msgstr "" +msgstr "Esponi la configurazione OpenStack come un modulo web" #: Library/Backend/OpenStack/OpenStackConfig.cs:77 #: Library/Backend/GoogleServices/GCSConfig.cs:81 @@ -496,43 +520,36 @@ msgstr "La configurazione da ottenere" #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 msgid "Provide different config values" -msgstr "" +msgstr "Fornisce valori della configurazione diversi" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" +"Il backend non può leggere e scrivere dati su un FTP backend. I formati " +"permessi sono \"ftp://hostname/cartella\" e " +"\"ftp://username:password@hostname/cartella\"." -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternativo" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -543,51 +560,215 @@ msgstr "" "La password usata per connettersi al server. Questa può anche essere fornita" " come variabile d'ambiente \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." -msgstr "" -"Per la protezione contro i guasti di rete, si tenterà di verificare ogni " -"caricamento. Usa questa opzione per disattivare questa verifica per rendere " -"il caricamento più veloce ma meno affidabile." +msgid "Log FTP dialog to terminal console" +msgstr "Registra la finestra di dialogo FTP sulla console del terminale" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Disattiva verifica caricamento" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "La cartella {0} non è stata trovata, messaggio: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "Log FTP PRIVATE info to console" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "La cartella {0} non è stata trovata. Messaggio: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" msgstr "" +"Il file {0} è stato caricato ma la dimensione trovata è {1} mentre ci si " +"aspettava che fosse {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Per proteggere da errori di rete o dei server, si cercherà di verificare " +"ogni caricamento. Usa quest'opzione per disattivare questa verifica e " +"rendere il caricamento più veloce ma meno affidabile." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Disattiva verifica caricamento" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Se questo flag è impostato, il tipo di connessione dati FTP sarà modificato " +"dall'opzione selezionata." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurare il tipo di connessione dati FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Se questo flag è abilitato, la modalità di crittografia FTP sarà modificata " +"dall'opzione selezionata." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configura la modalità di crittografia FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Questo flag controlla il criterio SSL da utilizzare quando la crittografia è" +" abilitata." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Configura il criterio SSL da utilizzare quando la crittografia è abilitata" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Alcuni server FTP hanno bisogno di un piccolo ritardo prima di segnalare la " +"corretta dimensione del file. Il ritardo richiesto dipende dalla topologia " +"della rete. Se si riscontrano errori relativi alla dimensione del " +"caricamento che non corrisponde, prova ad aggiungere qualche secondo di " +"ritardo." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Aggiunge un ritardo dopo aver caricato un file" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "Errore nel cancellare il file : {0}, errore: {1}" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "Errore di lettura file : {0}, errore: {1}" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "Errore nella scrittura file: {0}, errore: {1}" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" +"Questa opzione permette di comunicare usando Secure Socket Layer (SSL) over " +"ftp (ftps)." + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "Il file {0} non è stato trovato. Messaggio: {1}" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "Considera il percorso dell'url come assoluto" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "Considera il percorso dell'url come relativo" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "Usa CWD invece dei path assoluti" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" +msgstr "Errore creazione cartella {0}, hai dato la cartella: {1}" #: Library/Backend/GoogleServices/GCSConfig.cs:71 msgid "Google Cloud Storage configuration module" @@ -595,7 +776,7 @@ msgstr "Modulo di configurazione Google Cloud Storage" #: Library/Backend/GoogleServices/GCSConfig.cs:73 msgid "Expose Google Cloud Storage configuration as a web module" -msgstr "" +msgstr "Esponi la configurazione Google Cloud Storage come modulo web" #: Library/Backend/GoogleServices/Strings.cs:27 msgid "" @@ -611,12 +792,12 @@ msgstr "Google Cloud Storage" #: Library/Backend/OAuthHelper/Strings.cs:26 #, csharp-format msgid "You need an AuthID. You can get it from: {0}" -msgstr "" +msgstr "Hai bisogno di un AuthID. Puoi ottenerlo da: {0}" #: Library/Backend/GoogleServices/Strings.cs:30 #, csharp-format msgid "You must supply a project ID with --{0} for creating a bucket." -msgstr "" +msgstr "Devi fornire un ID progetto con --{0} per creare un bucket." #: Library/Backend/GoogleServices/Strings.cs:31 #: Library/Backend/GoogleServices/Strings.cs:47 @@ -648,7 +829,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:35 msgid "Specify location option for creating a bucket" -msgstr "" +msgstr "Specificare l'opzione di posizione per la creazione di un bucket" #: Library/Backend/GoogleServices/Strings.cs:36 #, csharp-format @@ -661,7 +842,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:38 msgid "Specify storage class for creating a bucket" -msgstr "" +msgstr "Specifica la storage class per creare il bucket" #: Library/Backend/GoogleServices/Strings.cs:39 msgid "" @@ -672,7 +853,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:40 msgid "Specify project for creating a bucket" -msgstr "" +msgstr "Specifica il progetto per creare il bucket" #: Library/Backend/GoogleServices/Strings.cs:44 msgid "" @@ -687,7 +868,7 @@ msgstr "Google Drive" #: Library/Backend/GoogleServices/Strings.cs:46 #, csharp-format msgid "There is more than one item named \"{0}\" in the folder \"{1}\"." -msgstr "" +msgstr "C'è più di un oggetto con il nome {0}\" nella cartella \"{1}\"." #: Library/Backend/GoogleServices/Strings.cs:49 msgid "" @@ -704,6 +885,8 @@ msgid "" "This backend can read and write data to CloudFiles. Allowed format is " "\"cloudfiles://container/folder\"." msgstr "" +"Il backend può leggere e scrivere i dati su CloudFiles. Il formato " +"consentito è \"cloudfiles://container/folder\"." #: Library/Backend/CloudFiles/Strings.cs:25 msgid "Rackspace CloudFiles" @@ -723,11 +906,11 @@ msgstr "Fornire un altro URL di autenticazione" #: Library/Backend/CloudFiles/Strings.cs:32 msgid "The API Access Key used to authenticate with CloudFiles." -msgstr "" +msgstr "La chiave d'accesso API usate per autenticarsi con CloudFiles" #: Library/Backend/CloudFiles/Strings.cs:33 msgid "Supply the access key used to connect to the server" -msgstr "" +msgstr "Fornisci la chiave di accesso usata per connettersi al server" #: Library/Backend/CloudFiles/Strings.cs:34 #, csharp-format @@ -743,11 +926,11 @@ msgstr "Usa un account del Regno Unito" #: Library/Backend/CloudFiles/Strings.cs:36 msgid "The username used to authenticate with CloudFiles." -msgstr "" +msgstr "Lo username utilizzato per autenticarsi con CloudFiles" #: Library/Backend/CloudFiles/Strings.cs:37 msgid "Supply the username used to authenticate with CloudFiles" -msgstr "" +msgstr "Fornisci lo username usato per autenticarti con CloudFiles" #: Library/Backend/CloudFiles/Strings.cs:38 msgid "MD5 Hash (ETag) verification failed" @@ -772,7 +955,7 @@ msgstr "CloudFiles ID unente non fornito" #: Library/Backend/CloudFiles/Strings.cs:43 msgid "Unexpected CloudFiles response. Perhaps the API has changed?" -msgstr "" +msgstr "Risposta Inaspettata di CloudFiles, forse l'API è cambiata?" #: Library/Backend/S3/S3Config.cs:75 msgid "S3 configuration module" @@ -780,7 +963,7 @@ msgstr "Modulo di configurazione S3" #: Library/Backend/S3/S3Config.cs:77 msgid "Expose S3 configuration as a web module" -msgstr "" +msgstr "Espone la configurazione S3 come modulo web" #: Library/Backend/S3/Strings.cs:26 msgid "" @@ -801,7 +984,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:29 msgid "AWS Secret Access Key" -msgstr "" +msgstr "Chiave di accesso segreta AWS" #: Library/Backend/S3/Strings.cs:30 #, csharp-format @@ -846,7 +1029,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:43 msgid "Specify an alternate S3 server name" -msgstr "" +msgstr "Specifica un nome del server S3 alternativo" #: Library/Backend/S3/Strings.cs:44 msgid "" @@ -858,7 +1041,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:45 msgid "Specify the S3 client library to use" -msgstr "" +msgstr "Specifica la libreria client S3 da usare" #: Library/Backend/S3/Strings.cs:46 msgid "" @@ -897,6 +1080,11 @@ msgstr "" msgid "Specify storage class" msgstr "Specifica classe archiviazione" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "Client S3 sconosciuto" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "Modulo di supporto IAM S3" @@ -911,7 +1099,7 @@ msgstr "L'operazione da eseguire" #: Library/Backend/S3/S3IAM.cs:81 msgid "Select the operation to perform" -msgstr "" +msgstr "Seleziona l'operazione da eseguire" #: Library/Backend/S3/S3IAM.cs:82 msgid "The username" @@ -929,127 +1117,6 @@ msgstr "La password" msgid "The Amazon Secret Key" msgstr "La chiave segreta di Amazon" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternativo" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "Registra la finestra di dialogo FTP sulla console del terminale" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "Log FTP PRIVATE info to console" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "La cartella {0} non è stata trovata. Messaggio: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Il file {0} è stato caricato ma la dimensione trovata è {1} mentre ci si " -"aspettava che fosse {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Per proteggere da errori di rete o dei server, si cercherà di verificare " -"ogni caricamento. Usa quest'opzione per disattivare questa verifica e " -"rendere il caricamento più veloce ma meno affidabile." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Se questo flag è impostato, il tipo di connessione dati FTP sarà modificato " -"dall'opzione selezionata." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurare il tipo di connessione dati FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Se questo flag è abilitato, la modalità di crittografia FTP sarà modificata " -"dall'opzione selezionata." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configura la modalità di crittografia FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Questo flag controlla il criterio SSL da utilizzare quando la crittografia è" -" abilitata." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Configura il criterio SSL da utilizzare quando la crittografia è abilitata" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Alcuni server FTP hanno bisogno di un piccolo ritardo prima di segnalare la " -"corretta dimensione del file. Il ritardo richiesto dipende dalla topologia " -"della rete. Se si riscontrano errori relativi alla dimensione del " -"caricamento che non corrisponde, prova ad aggiungere qualche secondo di " -"ritardo." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Aggiunge un ritardo dopo aver caricato un file" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Errore durante la cancellazione del file: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Errore durante la lettura del file: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr " Errore durante la scrittura del file: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modulo per generare le chiavi private/pubbliche SSH" @@ -1060,7 +1127,7 @@ msgstr "Generatore Chiavi SSH" #: Library/Backend/SSHv2/Strings.cs:28 msgid "A username to append to the public key." -msgstr "" +msgstr "Un nome utente da aggiungere alla chiave pubblica." #: Library/Backend/SSHv2/Strings.cs:29 msgid "Public key username" @@ -1068,7 +1135,7 @@ msgstr "Nome utente chiave pubblica" #: Library/Backend/SSHv2/Strings.cs:30 msgid "Determines the type of key to generate." -msgstr "" +msgstr "Determina il tipo di chiave da generare." #: Library/Backend/SSHv2/Strings.cs:31 msgid "The key type" @@ -1076,7 +1143,7 @@ msgstr "Il tipo di chiave" #: Library/Backend/SSHv2/Strings.cs:32 msgid "The length of the key in bits." -msgstr "" +msgstr "La lunghezza della chiave in bits." #: Library/Backend/SSHv2/Strings.cs:33 msgid "The key length" @@ -1092,7 +1159,7 @@ msgstr "Carica Chiave SSH" #: Library/Backend/SSHv2/Strings.cs:39 msgid "The SSH connection URL used to establish the connection." -msgstr "" +msgstr "La URL di connessione SSH usata per stabilire una connessione." #: Library/Backend/SSHv2/Strings.cs:40 msgid "The SSH connection URL" @@ -1103,6 +1170,8 @@ msgid "" "The SSH public key must be a valid SSH string, which is appended to the " ".ssh/authorized_keys file." msgstr "" +"La chiave pubblica SSH deve essere una stringa SSH valida, che viene " +"aggiunto al file .ssh/authorized_keys. " #: Library/Backend/SSHv2/Strings.cs:42 msgid "The SSH public key to append" @@ -1142,7 +1211,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:55 msgid "Disable fingerprint validation" -msgstr "" +msgstr "Disattiva la validazione delle impronte digitali" #: Library/Backend/SSHv2/Strings.cs:56 msgid "" @@ -1153,7 +1222,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:57 Library/Backend/SSHv2/Strings.cs:59 msgid "Use a SSH private key to authenticate" -msgstr "" +msgstr "Usa la chiave privata SSH per autenticarti" #: Library/Backend/SSHv2/Strings.cs:58 #, csharp-format @@ -1183,7 +1252,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:63 msgid "Set a keepalive value" -msgstr "" +msgstr "Imposta un valore keepalive" #: Library/Backend/SSHv2/Strings.cs:64 #, csharp-format @@ -1268,7 +1337,7 @@ msgstr "Repository remoto" #: Library/Backend/Rclone/Strings.cs:32 msgid "Path on the Remote repository." -msgstr "" +msgstr "Percorso sul repository remoto." #: Library/Backend/Rclone/Strings.cs:33 msgid "Remote path" @@ -1280,7 +1349,7 @@ msgstr "Le opzioni saranno trasferite su Rclone." #: Library/Backend/Rclone/Strings.cs:35 msgid "Rclone options" -msgstr "" +msgstr "opzioni Rclone" #: Library/Backend/Rclone/Strings.cs:36 msgid "" @@ -1397,7 +1466,7 @@ msgstr "" #: Library/Backend/File/Strings.cs:41 msgid "Disable length verification" -msgstr "" +msgstr "Disattiva verifica lunghezza" #: Library/Backend/Backblaze/Strings.cs:25 msgid "" @@ -1496,7 +1565,7 @@ msgstr "Percorso backup" #: Library/Backend/Sia/Strings.cs:30 msgid "Supply a password for Sia server." -msgstr "" +msgstr "Fornisci una password per il Sia server" #: Library/Backend/Sia/Strings.cs:31 msgid "Sia password" @@ -1504,11 +1573,11 @@ msgstr "Password Sia" #: Library/Backend/Sia/Strings.cs:32 msgid "The minimum value for redundancy is 1.0." -msgstr "" +msgstr "Il valore minimo per la ridondanza è 1.0." #: Library/Backend/Sia/Strings.cs:33 msgid "Set the minimum redundancy" -msgstr "" +msgstr "Imposta la ridondanza minima" #: Library/Backend/OneDrive/Strings.cs:28 #, csharp-format @@ -1554,12 +1623,18 @@ msgstr "Ritardo in millisecondi tra gli errori del frammento" #: Library/Backend/OneDrive/Strings.cs:35 msgid "Use this option to set HttpClient class to perform HTTP requests." msgstr "" +"Usa questa opzione per configurare la classe HttpClient per eseguire " +"richieste HTTP" #: Library/Backend/OneDrive/Strings.cs:36 msgid "Whether the HttpClient class should be used" msgstr "Se la classe HttpClient deve essere utilizzata" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "L'opzione --use-http-client è deprecata e non ha effetti." + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1567,11 +1642,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "Microsoft OneDrive" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1581,11 +1656,11 @@ msgstr "" "sarà utilizzata l'unità predefinita OneDrive o OneDrive for Business tramite" " '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ID opzionale dell'unità" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1593,28 +1668,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID del sito" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Non è stato fornito alcun ID del sito" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "Conflitto ID utilizzati per il sito: dato {0} ma trovato {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1626,41 +1701,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Gruppo Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID del gruppo" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Indirizzo email del gruppo" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 +msgid "No group ID or group email address was provided." +msgstr "Nessun gruppo ID o gruppo di indirizzi mail è stato fornito." + +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Nessun gruppo trovato con l'indirizzo email indicato: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Sono stati trovati più gruppi con l'indirizzo email specificato: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "Conflitto ID utilizzati per il gruppo: dato {0} ma trovato {1}" @@ -1671,16 +1746,16 @@ msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:8 msgid "Aliyun OSS (Object Storage Service)" -msgstr "" +msgstr "Aliyun OSS (Object Storage Service)" #: Library/Backend/AliyunOSS/Strings.cs:9 msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" -msgstr "" +msgstr "ID chiave di accesso" #: Library/Backend/AliyunOSS/Strings.cs:11 msgid "" @@ -1689,7 +1764,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "Chiave di accesso segreta" @@ -1706,7 +1781,7 @@ msgstr "Nome bucket" #: Library/Backend/AliyunOSS/Strings.cs:15 msgid "Region indicates the physical location of the OSS data center." -msgstr "" +msgstr "La regione indica la posizione fisica del data center OSS" #: Library/Backend/AliyunOSS/Strings.cs:16 msgid "Region" @@ -1738,7 +1813,7 @@ msgstr "" #: Library/Backend/AzureBlob/Strings.cs:28 msgid "The name of the storage container" -msgstr "" +msgstr "Il nome dello storage container" #: Library/Backend/AzureBlob/Strings.cs:29 msgid "No Azure storage account name given" @@ -1813,7 +1888,7 @@ msgstr "Chiave segreta" #: Library/Backend/TencentCOS/Strings.cs:35 msgid "Bucket name, format: BucketName-APPID" -msgstr "" +msgstr "Nome bucket formato: NomeBucket-APPID" #: Library/Backend/TencentCOS/Strings.cs:37 msgid "" @@ -1848,11 +1923,13 @@ msgstr "Jottacloud" #: Library/Backend/Jottacloud/Strings.cs:29 msgid "No username found" -msgstr "" +msgstr "Nessun username trovato" #: Library/Backend/Jottacloud/Strings.cs:30 msgid "No path given. Files cannot be uploaded to the root folder" msgstr "" +"Nessun percorso inserito. I files non possono essere caricati della cartella" +" principale" #: Library/Backend/Jottacloud/Strings.cs:31 msgid "Illegal mount point given." @@ -1949,43 +2026,45 @@ msgstr "Nessuna password inserita" msgid "No username given" msgstr "Nessun nome utente inserito" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." -msgstr "" - -#: Library/Backend/Idrivee2/Strings.cs:25 -msgid "IDrive e2" -msgstr "" - #: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." +msgstr "" + +#: Library/Backend/Idrivee2/Strings.cs:27 +msgid "IDrive e2" +msgstr "IDrive e2" + +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2059,7 +2138,7 @@ msgstr "" #: Library/Backend/SharePoint/Strings.cs:41 msgid "Set timeout for SharePoint web operations" -msgstr "" +msgstr "Imposta un timeout per le operazioni web SharePoint" #: Library/Backend/SharePoint/Strings.cs:43 msgid "" @@ -2121,6 +2200,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2155,6 +2239,12 @@ msgstr "" "Il server ha restituito il codice di errore {0} ({1}), indicando che il " "server non supporta le connessioni WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "La cartella {0} non è stata trovata, messaggio: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2172,6 +2262,8 @@ msgid "" "Use this option to communicate using Secure Socket Layer (SSL) over http " "(https)." msgstr "" +"Usa questa opzione per comunicare usando Secure Socket Layer (SSL) over http" +" (https)." #: Library/Backend/WEBDAV/Strings.cs:41 msgid "" @@ -2183,6 +2275,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Dump della risposta PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accetta qualsiasi certificato del server" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Accetta opzionalmente un certificato SSL noto" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "pCloud(API Nativa)" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "Fornisci il token oAuth usato per connettersi al server" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "Operazione {0} fallita con un codice di errore inaspettato: {1}" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2217,7 +2373,7 @@ msgstr "" #: Library/Backend/Storj/Strings.cs:29 msgid "Connection-test failed." -msgstr "" +msgstr "Test di connessione fallito." #: Library/Backend/Storj/Strings.cs:30 msgid "" @@ -2307,6 +2463,407 @@ msgstr "Codice di errore imprevisto: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "Secrets da variabili di ambiente" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "Secrets da un file" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "passphrase di decrittazione" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "Secrets dal AWS Secrets Manager" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "Secrets dal Vault HashiCorp " + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "Token del Vault" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "protocollo del server Vault" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "Un'altra istanza è in esecuzione ed è stato notificato" @@ -2405,7 +2962,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2432,18 +2989,27 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Il certificato e il file chiave in PKCS #12 formato utilizzato del webserver" -" per SSL. Sono supportate solo le chiavi RSA/DSA." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Password per decriptare il file del certificato PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." @@ -2452,7 +3018,7 @@ msgstr "" "\"any\" significano qualsiasi interfaccia. Il valore speciale \"loopback\" " "significa la scheda loopback." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " @@ -2462,7 +3028,7 @@ msgstr "" "in modo che non sia necessario impostarla su ogni volta. L'impostazione di " "un valore vuoto disattiva la password." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2472,28 +3038,28 @@ msgstr "" "qualsiasi dei nomi host è \"*\", tutti i nomi host sono consentiti e il " "controllo del nome host è disabilitato." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Imposta l'ora dopo la quale i dati del registro saranno eliminati dal " "database." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Pulisci i vecchi dati del registro" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2505,11 +3071,11 @@ msgstr "" "archiviate le impostazioni. Questa opzione può essere impostata anche con la" " variabile d'ambiente {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2521,86 +3087,125 @@ msgstr "" "con la variabile d'ambiente {0}. Usa l'opzione --{1} per disabilitare la " "codifica del database." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Cartella archiviazione temporanea" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2608,15 +3213,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2625,21 +3267,18 @@ msgstr "" "Impossibile trovare una data valida, stabilita la data d'inizio {0}, " "l'intervallo di ripetizione {1} e i giorni consentiti {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "Server avviato e in ascolto su {0}, porta {1}" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -"Impossibile creare il certificato SSL usando i parametri forniti. Dettaglio " -"eccezione: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "Impossibile aprire un socket per l'ascolto, porte provate: {0}" @@ -2656,22 +3295,22 @@ msgstr "" "Impossibile caricare processo tipo {0} assembly {1}, messaggio di errore: " "{2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2680,11 +3319,11 @@ msgstr "" " zero non si ha nessuna compressione e impostando a 9 si ha la compressione " "massima." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2692,75 +3331,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "File non trovato: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Sperimentale*: Archivio 7z con supporto LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Sperimentale - Archivio 7z" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archivio non aperto per la scrittura" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "L'archivio non è stato aperto per la lettura" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Il file indicato non fa parte di questo archivio" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Il numero di thread utilizzati nella compressione LZMA 2. L'impostazione " -"predefinita è numero di core del processore." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Numero di thread utilizzati nella compressione" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Questa opzione controlla l'algoritmo di compressione utilizzato. " -"L'attivazione di questa opzione farà sì che 7z utilizzi l'algoritmo fast, " -"che produce una compressione leggermente inferiore." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "Backup" @@ -3205,48 +3806,66 @@ msgstr "Limita le dimensioni dei file sottoposti a backup" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Priorità thread" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limita le dimensioni dei volumi" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3259,11 +3878,11 @@ msgstr "" "file esistente, il nome del file è utilizzato per selezionare il modulo di " "compressione." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Seleziona il modulo da utilizzare per la compressione" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3276,27 +3895,27 @@ msgstr "" "esistente, il nome del file è utilizzato per selezionare il modulo di " "crittografia." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Selezionare il modulo da utilizzare per la crittografia" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3324,34 +3943,36 @@ msgstr "" "Linux questa funzione utilizza Logical Volume Management (LVM) e richiede i " "privilegi di root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Il percorso in cui sono collocati i volumi pronti fino al caricamento" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Il numero di volumi da creare prima del tempo" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3359,45 +3980,45 @@ msgstr "" "Quando si eseguono caricamenti asincroni, è consentito il numero massimo di " "caricamenti simultanei. Impostare su zero per disabilitare il limite." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Il numero di caricamenti simultanei consentiti" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Registra le informazioni interne in un file" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Livello registro informazioni" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3406,11 +4027,11 @@ msgstr "" "automaticamente. Attiva questa opzione per impedire la creazione automatica " "delle cartelle." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3425,14 +4046,14 @@ msgstr "" "e la maggior parte delle forme GUID sono ammesse, incluse con e senza " "parentesi graffe." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Un elenco separato da punti e virgola di GUID di scrittori VSS da escludere " "(solo Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3455,11 +4076,22 @@ msgstr "" "backup se l'utilizzo di USN non riesce. Questa funzionalità è supportata " "solo in Windows e richiede i privilegi dell'amministratore." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3472,30 +4104,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Verificare i caricamenti elencando i contenuti" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Caricare i file in modo sincrono" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3503,11 +4136,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Non riutilizzare le connessioni" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3517,33 +4150,33 @@ msgstr "" "riporterà solo il numero di tentativi. Abilita questa opzione per " "visualizzare i messaggi di errore quando è eseguito un tentativo." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Mostra messaggi di errore quando è eseguito un tentativo" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Carica file di backup vuoti" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3552,22 +4185,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Soglia di avviso su quota bassa" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3579,11 +4212,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Gestione collegamento simbolico" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3598,11 +4231,11 @@ msgstr "" "informazioni hardlink e tratterà ogni hardlink come un percorso univoco. " "L'opzione \"{2}\" ignorerà tutti i hardlink con più di un collegamento." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Gestione hardlink" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3610,11 +4243,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Escludi file per attributo" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3626,41 +4259,51 @@ msgstr "" "temporanee utilizzate per accedere al contenuto di una istantanea. Questa " "soluzione può velocizzare l'accesso ai file su Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mappa istantanee su un'unità (solo Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nome del backup" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3672,11 +4315,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Gestisci le estensioni di file non comprimibili" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3690,72 +4333,64 @@ msgstr "" "di file. Nota che il valore non può essere modificato dopo la creazione di " "file remoti." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Dimensione del blocco usato nell'hash" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Elenco di file da esaminare per le modifiche" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Percorso dello stato locale del database" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Elenco dei file cancellati" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Riduci lo spazio di memoria occupata disabilitando le ricerche in memoria" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Archiviare un blocco cache in memoria" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Non eseguire query sul backend all'avvio" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3769,11 +4404,11 @@ msgstr "" "senza il database. Il compromesso è che i file indice più grandi occupano " "più spazio remoto e che non possono mai essere utilizzati." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3786,43 +4421,43 @@ msgstr "" "recuperato. Questo valore è una percentuale utilizzata per ogni volume e per" " l'archiviazione totale." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Lo spazio massimo sprecato in percentuale" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "L'algoritmo hash usato sui blocchi" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "L'algoritmo hash utilizzato sui file" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3835,11 +4470,11 @@ msgstr "" "compressione automatica e compatta solo quando si esegue il comando " "comprimi." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Disattiva compressione automatica" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3852,11 +4487,11 @@ msgstr "" "possono avere alcuni byte di spazio sprecato, non siano scaricati e " "riscritti." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Soglia dimensione volume" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3867,11 +4502,11 @@ msgstr "" " I piccoli volumi saranno sempre uniti quando possono riempire un intero " "volume." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Numero massimo dei piccoli volumi" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3881,41 +4516,41 @@ msgstr "" "trovare blocchi esistenti. Questa è un'operazione abbastanza lenta, ma può " "limitare la dimensione dei file scaricati." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Utilizza i dati dei file locali durante il ripristino" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Mantieni un numero di versioni" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Usa questa opzione per impostare il periodo in cui sono conservati i backup." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Mantieni tutte le versioni all'interno di un periodo" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3935,30 +4570,30 @@ msgstr "" "questo.\" Questa opzione supporta anche l'uso dell'identificatore \"U\" per " "indicare un intervallo di tempo illimitato." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Riduci il numero di versioni eliminando i vecchi backup intermedi" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Usa questa opzione per continuare, anche se alcune voci sorgenti mancano." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignora elementi sorgente mancanti" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Sovrascrivi i file durante il ripristino" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3967,11 +4602,11 @@ msgstr "" "durante l'esecuzione di un'opzione. Generalmente questa opzione produrrà una" " linea per ogni file elaborato." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Fornisci ulteriori informazioni sull'avanzamento" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3979,11 +4614,11 @@ msgstr "" "Usa questa opzione per aumentare la quantità di dati generati in uscita come" " risultato dell'operazione, includendo tutti i nomi dei file." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Fornisci risultati completi" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3995,11 +4630,11 @@ msgstr "" "gli hash SHA256 di tutti i file remoti e può essere usato per verificare " "l'integrità dei file." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Determina se i file di verifica sono caricati" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4009,11 +4644,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Il numero di campioni da testare dopo un backup" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4023,11 +4658,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "La percentuale di campioni da testare dopo un backup" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4039,41 +4674,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Dimensione del buffer di lettura del file" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Consenti la modifica della passphrase" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Elenca solo gruppi di file" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4084,11 +4719,11 @@ msgstr "" "accelererà le operazioni di backup e ripristino, ma non influisce molto " "sulla dimensione del file." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4097,11 +4732,11 @@ msgstr "" "quanto potrebbero impedire l'accesso ai file. Usa questa opzione per " "ripristinare anche le autorizzazioni." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Ripristina le autorizzazioni sui file" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4112,11 +4747,11 @@ msgstr "" "correttamente. Usa questa opzione per disabilitare il controllo ed evitare " "di aspettare la verifica." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Salta il controllo del file ripristinati" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4126,28 +4761,28 @@ msgstr "" "al minimo la quantità di dati scaricati. Utilizza questa opzione per " "ignorare questa ottimizzazione e usare solo i dati remoti." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Non usare dati locali" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4156,11 +4791,11 @@ msgstr "" "blocchi letti da un volume prima di sistemare i file ripristinati con i " "dati." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Controllo hash blocco" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4173,11 +4808,11 @@ msgstr "" "ricostruire tutte le informazioni. Il database risultante può essere " "cercato, ma non può essere utilizzato per ripristinare i dati." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Ripara database con percorsi" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4185,11 +4820,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Forza le impostazioni locali" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4200,22 +4835,22 @@ msgstr "" "opzione, vengono visualizzate solo le date effettive, ad esempio \"12 nov " "2018, 8:01\"." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Gestire la comunicazione file con backend usando threaded pipe" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4226,22 +4861,22 @@ msgstr "" "bilancia dinamicamente il numero di thread attivi per adattarsi " "all'hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Limita il numero di thread simultanei" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Utilizza questa opzione per impostare il numero di processi che eseguono " "l'hash dei dati." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Specificare il numero di processi hash simultanei" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4249,11 +4884,11 @@ msgstr "" "Utilizza questa opzione per impostare il numero di processi che eseguono la " "compressione dei dati di uscita." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Specifica il numero di processi di compressione simultanei" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4263,11 +4898,11 @@ msgstr "" "genererà un file elenco che è l'unione dell'ultimo backup completato e del " "contenuto caricato nella sessione di backup incompleta." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4275,11 +4910,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4287,11 +4922,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4299,11 +4934,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Consenti rimozione di tutti i gruppi di file" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4320,11 +4955,11 @@ msgstr "" "L'impostazione a true consentirà a Duplicati di eseguire operazioni VACUUM a" " sua discrezione." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4332,11 +4967,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Disabilita lo scanner di lettura in anticipo" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4347,11 +4982,11 @@ msgstr "" "assicurati di eseguire comandi di controllo regolari per assicurarti che " "tutto funzioni come previsto." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Disabilita i controlli di coerenza dell'elenco file" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4359,15 +4994,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Disabilita il backup quando si utilizza la batteria" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Livello informazioni registrane nel file" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4383,42 +5018,42 @@ msgstr "" "regolari sono supportate all'interno di parentesi graffe. Esempio: " "\"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Livello informazioni console" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4430,11 +5065,11 @@ msgstr "" " sarebbe quello di avere un file chiamato per esempio \".nobackup\" e " "posizionarlo nelle cartelle che non dovrebbero essere sottoposte a backup." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Elenco di nomi dei file che escludono cartelle" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4442,11 +5077,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4454,11 +5089,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4471,11 +5106,11 @@ msgstr "" "per registrare tutte le query del database e ricorda di impostare --{0}={2} " "o --{1}={2} per segnalare i dati aggiuntivi nel log" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4484,11 +5119,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4496,11 +5131,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4508,11 +5143,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4521,50 +5305,50 @@ msgstr "" "La libreria di crittografia non supporta le trasformazioni riutilizzabili " "per l'algoritmo hash {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "La libreria di crittografia non supporta l'algoritmo hash {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "La passphrase non può essere modificata per un backup esistente" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Impossibile creare un'istantanea: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Impossibile cancellare il file {0}, verifica se il file esiste" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Recuperato dal problema con il tentativo di cancellare file non esistenti " "{0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Impossibile recuperare da errore cancellazione file {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Operazione di cancellazione non riuscita per {0} con FileNonTrovato, " "contenuto dell'elenco" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "L'elenco indica che il file {0} è stato eliminato correttamente" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "L'elenco conferma che il file {0} non è stato eliminato" @@ -4628,29 +5412,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configura richieste http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accetta qualsiasi certificato del server" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Accetta opzionalmente un certificato SSL noto" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4750,7 +5511,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configura modulo Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4758,125 +5531,125 @@ msgstr "" "Questo modulo funziona internamente per analizzare i parametri sorgente per " "il backup di database Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configura modulo Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Esegui script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Esegui uno script all'uscita" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Lo script \"{0}\" ha restituito il codice di uscita {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Lo script \"{0}\" ha restituito con il codice di uscita {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Esegui lo script richiesto all'avvio" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Errore durante l'esecuzione dello script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Esecuzione dello script \"{0}\" fuori tempo" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Esegui uno script all'avvio" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Lo script \"{0}\" ha segnalato i messaggi di errore: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" -msgstr "" +msgstr "imposta il timeout dello script" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" -msgstr "" +msgstr "Abilita argomenti script" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Questo modulo può inviare email al termine di un'operazione" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Invia mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4889,21 +5662,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Il testo del messaggio" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Password SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4914,11 +5687,11 @@ msgstr "" "Esempio con 3 destinatari:\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email destinatario(i)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4928,25 +5701,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Email mittente" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "I messaggi da inviare" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4954,11 +5727,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "URL SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4967,60 +5740,60 @@ msgstr "" "Questa impostazione fornisce l'oggetto dell'email. I valori sono sostituiti " "come illustrato nella descrizione di --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Il soggetto dell'email" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Nome utente SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Completa comunicazione SMTP: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Invio email con server fallita: {0}, messaggio: {1}, sto riprovando con {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email inviata con successo usando il server: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Questo modulo fornisce il supporto per l'invio di rapporti di stato tramite " "messaggi XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Modulo rapporto XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP email destinatario" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5033,35 +5806,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Il modello di messaggio" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Il nome utente XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "La password XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5070,111 +5843,111 @@ msgstr "" "Puoi specificare uno di \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Puoi fornire più opzioni con una virgola come separatore, es. \"{0},{1}\". Il valore speciale \"{4}\" è una scorciatoia per \"{0},{1},{2},{3}\" e invierà una messaggio per tutte le operazioni di backup." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Invia messaggi per tutte le operazioni" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Si è verificato un timeout durante l'accesso al server Jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" -msgstr "" +msgstr "modulo di report Telegram" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" -msgstr "" +msgstr "Chiave API Telegram" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Questo modulo fornisce il supporto per l'invio di rapporti di stato tramite " "messaggi HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Modulo rapporto HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" -msgstr "" +msgstr "url HTTP per il report" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Il nome del parametro per inviare il messaggio come" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Parametri aggiuntivi da aggiungere al messaggio http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5182,46 +5955,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Impossibile inviare il messaggio: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" -msgstr "" +msgstr "Definisci il livello dei log per i messaggi" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtro messaggi di log" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5229,27 +6002,27 @@ msgstr "" "Utilizza questa opzione per impostare il numero massimo di righe nel log da " "includere nella segnalazione. Valori zero o negativi significa illimitate." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" -msgstr "" +msgstr "Limita le righe del log" #: Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs:39 #, csharp-format msgid "The format is not supported: {0}" msgstr "Il formato non è supportato: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Valore dimensione non valido: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" "Il convalidatore del certificato SSL è stato chiamato in un ordine non " "corretto" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5258,7 +6031,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5267,71 +6040,71 @@ msgstr "" "Errore durante la convalida dell'hash del certificato, messaggio errore: " "{0}, nome errore SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "La cartella temporanea non esiste: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Errore nell'analizzare il segmento: {0}, numero intero non valido" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Identificatore non valido: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Dati non analizzati: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Uri non valido: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Nell'Uri manca il nome dell'host: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "La stringa \"{0}\" non può essere analizzata in una data" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Impossibile leggere e scrivere sullo stesso flusso di dati" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5570,11 +6343,11 @@ msgstr "Disabilita la console di output" msgid "This link may provide additional information: {0}" msgstr "Questo link potrebbe fornire ulteriori informazioni: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Attiva/disattiva aggiornamenti automatici" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-ja_JP.mo b/Localizations/duplicati/localization-ja_JP.mo index f34a9e801..9a4efda5c 100644 Binary files a/Localizations/duplicati/localization-ja_JP.mo and b/Localizations/duplicati/localization-ja_JP.mo differ diff --git a/Localizations/duplicati/localization-ja_JP.po b/Localizations/duplicati/localization-ja_JP.po index 9b9d69801..4a1096ac2 100644 --- a/Localizations/duplicati/localization-ja_JP.po +++ b/Localizations/duplicati/localization-ja_JP.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Suguru Hirahara, 2024\n" "Language-Team: Japanese (Japan) (https://app.transifex.com/duplicati/teams/67655/ja_JP/)\n" @@ -47,11 +47,11 @@ msgstr "AES暗号の操作で許可するスレッドの水準を設定できま msgid "Set thread level utilized for crypting" msgstr "暗号化に使用するスレッドの水準の設定" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." -msgstr "--{0}のオプションはもう使用されておらず、非推奨となりました。" +msgstr "--{0}のオプションは既に使用されておらず、非推奨となりました。" #: Library/Encryption/Strings.cs:37 #, csharp-format @@ -143,7 +143,7 @@ msgstr "GPGの復号用のコマンド" msgid "" "Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." -msgstr "" +msgstr "既定のGPG暗号化のコマンド「{0}」を上書きします。通常は、{1}の設定で非対称暗号化を要求する際に使用されます。" #: Library/Encryption/Strings.cs:55 msgid "The GPG encryption command" @@ -162,78 +162,94 @@ msgstr "GnuPGを実行できませんでした。出力は行われません" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "GnuPGを実行できませんでした。プログラムは終了しません" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "エイリアス" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "既定値" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[非推奨]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "値" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "ブール値" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "列挙" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "フラグ" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "整数" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "パス" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "サイズ" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "文字列" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "時間の幅" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "不明" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "既に存在するフォルダーは作成できません" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "要求されたフォルダーは存在しません" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "キャンセル済" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "要求されたファイルは存在しません" @@ -356,16 +372,15 @@ msgid "" msgstr "" "サーバーの接続に使用するパスワード。環境変数「AUTH_PASSWORD」でも指定できます。パスワードを指定する際には、--{0}もまた設定する必要があります。" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 #: Library/Backend/SharePoint/Strings.cs:29 #: Library/Backend/WEBDAV/Strings.cs:29 msgid "Supply the password used to connect to the server" -msgstr "" +msgstr "サーバーの接続に使用するパスワードを指定" #: Library/Backend/OpenStack/Strings.cs:32 msgid "The domain name of the user used to connect to the server." @@ -373,11 +388,10 @@ msgstr "サーバーの接続に使用するユーザーのドメイン名。" #: Library/Backend/OpenStack/Strings.cs:33 msgid "Supply the domain used to connect to the server" -msgstr "" +msgstr "サーバーの接続に使用するドメインを指定" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -388,9 +402,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "サーバーの接続に使用するユーザー名。環境変数「AUTH_USERNAME」でも指定できます。" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -431,7 +444,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:41 msgid "Supply the authentication URL" -msgstr "" +msgstr "認証用URLを指定" #: Library/Backend/OpenStack/Strings.cs:42 msgid "The keystone API version to use. Valid values are 'v2' and 'v3'." @@ -471,9 +484,9 @@ msgstr "取得する設定" #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 msgid "Provide different config values" -msgstr "" +msgstr "異なる設定値を指定" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " @@ -481,11 +494,150 @@ msgid "" msgstr "" "このバックエンドでは、FTPをベースとしたバックエンドとの間でデータの読み書きを実行できます。許可されている形式は、「ftp://ホスト名/フォルダー」と「ftp://ユーザー名:パスワード@ホスト名/フォルダー」となります。" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "代替FTP" + +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 +#: Library/Backend/SharePoint/Strings.cs:28 +#: Library/Backend/WEBDAV/Strings.cs:28 +msgid "" +"The password used to connect to the server. This may also be supplied as the" +" environment variable \"AUTH_PASSWORD\"." +msgstr "サーバーの接続に使用するパスワード。環境変数「AUTH_PASSWORD」でも指定できます。" + +#: Library/Backend/FTP/Strings.cs:38 +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." +msgstr "このオプションを有効にすると、デバッグ用に、FTPダイアログのログを端末のコンソールに出力します。" + +#: Library/Backend/FTP/Strings.cs:39 +msgid "Log FTP dialog to terminal console" +msgstr "FTPダイアログのログを端末のコンソールに出力" + +#: Library/Backend/FTP/Strings.cs:40 +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" +"このオプションを有効にすると、デバッグ用に、FTPのログイン情報(ユーザー名とパスワード)のログをコンソールに出力します(インターネットに投稿しないでください!)" + +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "FTPのログイン情報を端末のコンソールに出力" + +#: Library/Backend/FTP/Strings.cs:42 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "フォルダー「 {0} 」が見つかりません。メッセージ:{1}" + +#: Library/Backend/FTP/Strings.cs:43 +#, csharp-format +msgid "" +"The file {0} was uploaded but not found afterwards. The file listing " +"returned {1}" +msgstr "ファイル {0} をアップロードしましたが、見つかりませんでした。ファイルのリスト作成は{1}を返しました。" + +#: Library/Backend/FTP/Strings.cs:44 +#, csharp-format +msgid "" +"The file {0} was uploaded but the returned size was {1} and it was expected " +"to be {2}" +msgstr "ファイル {0} をアップロードしましたが、サイズ {1} は正しいサイズ {2} と等しくありません。" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"ネットワークまたはサーバーの不具合に対処するために、全てのアップロードに関して検証が試みられます。このオプションを使うと検証が無効になり、アップロードの速度を向上できますが、アップロードの信頼性は減少します。" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "アップロードの検証を無効にする" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "FTPのデータ通信モードを、設定したオプションに変更します。" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "FTPのデータ通信の種類を設定" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "FTPの暗号化モードを、設定したオプションに変更します。" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "FTPの暗号化モードを設定" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "暗号化が有効になっている場合に使用するSSLのポリシーを管理します。" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "暗号化が有効な場合に使用するSSLポリシーを設定" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"一部のFTPサーバーでは、ファイルの正確なサイズを報告するのにわずかな遅延が必要とされます。必要とされる遅延は、ネットワークのトポロジーに依存します。アップロードのサイズの不一致に関するエラーが報告される場合は、数秒の遅延を追加してみてください。" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "ファイルのアップロード後に遅延を追加" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "このオプションを有効にすると、FTP通信にSecure Socket Layer(SSL)を使用します(ftps)。" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 #, csharp-format msgid "" "Activate this option to make the FTP connection in active mode. Even if the " @@ -494,11 +646,11 @@ msgstr "" "このオプションを有効にすると、FTP接続はアクティブモードで行われます。オプション --{0} " "が同時に設定されている場合でも、接続はアクティブモードで行われます。" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 msgid "Toggle the FTP connections method" msgstr "" -#: Library/Backend/FTP/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:62 #, csharp-format msgid "" "Activate this option to make the FTP connection in passive mode, which works" @@ -508,62 +660,63 @@ msgstr "" "このオプションを有効にすると、FTP接続はパッシブモードで行われます。一部のファイヤーウォールではそれにより動作が改善する場合があります。オプション " "--{0} が同時に設定されている場合、このオプションは無視されます。" -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 -#: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 -#: Library/Backend/SharePoint/Strings.cs:28 -#: Library/Backend/WEBDAV/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:64 msgid "" -"The password used to connect to the server. This may also be supplied as the" -" environment variable \"AUTH_PASSWORD\"." -msgstr "サーバーの接続に使用するパスワード。環境変数「AUTH_PASSWORD」でも指定できます。" - -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "このオプションを有効にすると、FTP通信にSecure Socket Layer(SSL)を使用します(ftps)。" - -#: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." msgstr "" -#: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." msgstr "" -"ネットワークの不具合からデータを保護するために、全てのアップロードに関して検証が試みられます。このオプションを使うと検証が無効になり、アップロードの速度を向上できます。ただし、アップロードの信頼性は減少します。" -#: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "アップロードの検証を無効にする" +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:67 #, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "フォルダー {0} は見つかりませんでした。メッセージ: {1}" +msgid "The file {0} was not found. Message: {1}" +msgstr "" -#: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 -#, csharp-format +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 msgid "" -"The file {0} was uploaded but not found afterwards. The file listing " -"returned {1}" -msgstr "ファイル {0} をアップロードしましたが、見つかりませんでした。ファイルのリスト作成は{1}を返しました。" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" -#: Library/Backend/FTP/Strings.cs:43 -#, csharp-format +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." -msgstr "ファイル {0} をアップロードしましたが、サイズ {1} は正しいサイズ {2} と等しくありません。" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" +msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 msgid "Google Cloud Storage configuration module" @@ -589,7 +742,7 @@ msgstr "Google クラウドストレージ" #: Library/Backend/OAuthHelper/Strings.cs:26 #, csharp-format msgid "You need an AuthID. You can get it from: {0}" -msgstr "認証IDが必要です。{0}で取得できます。" +msgstr "認証IDが必要です。{0}で取得してください。" #: Library/Backend/GoogleServices/Strings.cs:30 #, csharp-format @@ -817,7 +970,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:40 msgid "Specify S3 location constraints" -msgstr "" +msgstr "S3の場所に関する制約を指定" #: Library/Backend/S3/Strings.cs:41 #, csharp-format @@ -877,13 +1030,18 @@ msgstr "保存領域のクラスを指定できます。オプションが使用 msgid "Specify storage class" msgstr "保存領域のクラスを指定" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "S3 IAMのサポートモジュール" #: Library/Backend/S3/S3IAM.cs:73 msgid "Expose S3 IAM manipulation as a web module" -msgstr "" +msgstr "S3のIAMの変更をウェブモジュールとして公開" #: Library/Backend/S3/S3IAM.cs:81 msgid "The operation to perform" @@ -909,114 +1067,6 @@ msgstr "パスワード" msgid "The Amazon Secret Key" msgstr "Amazonの秘密鍵" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" -"このバックエンドでは、代替のFTPクライアントを使用して、FTPをベースとしたバックエンドとの間でデータの読み書きを実行できます。サポートされている形式は、「aftp://ホスト名/フォルダー」と「aftp://ユーザー名:パスワード@ホスト名/フォルダー」となります。" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "代替FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "このオプションを有効にすると、デバッグ用に、FTPダイアログのログを端末のコンソールに出力します。" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "FTPダイアログのログを端末のコンソールに出力" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" -"このオプションを有効にすると、デバッグ用に、FTPのログイン情報(ユーザー名とパスワード)のログをコンソールに出力します(インターネットに投稿しないでください!)" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "FTPのログイン情報を端末のコンソールに出力" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "フォルダー「 {0} 」が見つかりません。メッセージ:{1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "ファイル {0} をアップロードしましたが、サイズ {1} は正しいサイズ {2} と等しくありません。" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"ネットワークまたはサーバーの不具合に対処するために、全てのアップロードに関して検証が試みられます。このオプションを使うと検証が無効になり、アップロードの速度を向上できますが、アップロードの信頼性は減少します。" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "FTPのデータ通信モードを、設定したオプションに変更します。" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "FTPのデータ通信の種類を設定" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "FTPの暗号化モードを、設定したオプションに変更します。" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "FTPの暗号化モードを設定" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "暗号化が有効になっている場合に使用するSSLのポリシーを管理します。" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "暗号化が有効な場合に使用するSSLポリシーを設定" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"一部のFTPサーバーでは、ファイルの正確なサイズを報告するのにわずかな遅延が必要とされます。必要とされる遅延は、ネットワークのトポロジーに依存します。アップロードのサイズの不一致に関するエラーが報告される場合は、数秒の遅延を追加してみてください。" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "ファイルのアップロード後に遅延を追加" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "ファイル {0} の削除時にエラーが発生しました" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "ファイル {0} の読み込み中にエラーが発生しました" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "ファイル {0} の書き込み中にエラーが発生しました" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "SSHの公開鍵と秘密鍵を生成するためのモジュール" @@ -1120,7 +1170,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:57 Library/Backend/SSHv2/Strings.cs:59 msgid "Use a SSH private key to authenticate" -msgstr "" +msgstr "SSHの秘密鍵で認証" #: Library/Backend/SSHv2/Strings.cs:58 #, csharp-format @@ -1153,7 +1203,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:63 msgid "Set a keepalive value" -msgstr "" +msgstr "keepaliveの値を設定" #: Library/Backend/SSHv2/Strings.cs:64 #, csharp-format @@ -1474,7 +1524,7 @@ msgstr "最小の冗長性を設定" #: Library/Backend/OneDrive/Strings.cs:28 #, csharp-format msgid "No Auth-ID was provided - you can get one from {0}" -msgstr "認証IDが指定されていません。{0}で取得できます。" +msgstr "認証IDが指定されていません。{0}で取得してください。" #: Library/Backend/OneDrive/Strings.cs:29 msgid "" @@ -1517,19 +1567,25 @@ msgstr "このオプションを有効にすると、HTTPリクエストを行 msgid "Whether the HttpClient class should be used" msgstr "HttpClientクラスの使用を選択" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" " Microsoft Graph API. Usage of this backend requires that you agree to the " "terms in {0} ({1}) and {2} ({3})." msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive バージョン2" +"Microsoft Graph APIでファイルをMicrosoft OneDriveまたはMicrosoft OneDrive for " +"Businessに保存します。このバックエンドを使用するには{0}({1})と{2}({3})の利用規約に同意する必要があります。" #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1538,11 +1594,11 @@ msgstr "" "データを保存するドライブのID。ドライブが指定されていない場合は、「{0}」を介して、OneDriveまたはOneDrive for " "Businessの既定のドライブを使用します。" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ドライブのID(オプション)" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1550,28 +1606,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "データを保存するサイトのID。" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "サイトのID" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "サイトのIDが指定されていません" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "サイトIDが衝突しています。{0}が指定されましたが、{1}が見つかりました。" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1582,42 +1638,44 @@ msgid "" "specify the SharePoint site's ID via --{0}). Usage of this backend requires " "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" +"Microsoft Graph APIでファイルをMicrosoft Office365 " +"Groupに保存します。許可されている形式は、「sharepoint://tenant.sharepoint.com/{{ウェブへのパス}}//{{ドキュメント}}/サブフォルダー」(パスに二重スラッシュ「//」を使うと、ルートのドキュメントフォルダーを示すことができます)と、「sharepoint://サブフォルダー」となります。後者の場合は、SharePointのサイトIDを--{0}で併せて指定する必要があります。このバックエンドを使用するには{1}({2})と{3}({4})の利用規約に同意する必要があります。" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365グループ" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "データを保存するグループのID。" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "グループのID" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "データを保存するグループの電子メールアドレス。" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "グループの電子メールアドレス" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "グループのIDまたは電子メールアドレスが指定されていません。" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "指定した電子メールアドレスが見つかるグループはありませんでした:{0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "複数のグループが指定した電子メールアドレスで見つかりました:{0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "使用されているグループIDが矛盾しています。{0}が指定されましたが、見つかったIDは{1}です。" @@ -1635,7 +1693,7 @@ msgid "Access Key ID is used to identify the user." msgstr "アクセスキーのIDはユーザーの特定に使用されます。" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "アクセスキーのID" @@ -1646,7 +1704,7 @@ msgid "" msgstr "アクセスキーのシークレットは、ユーザーが署名の文字列を暗号化し、OSSがその文字列を検証する際に使用されます。" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "アクセスキーのシークレット" @@ -1837,7 +1895,7 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:34 msgid "Supply the backup device to use" -msgstr "" +msgstr "使用するバックアップ用デバイスを指定" #: Library/Backend/Jottacloud/Strings.cs:35 #, csharp-format @@ -1905,43 +1963,45 @@ msgstr "パスワードが入力されていません" msgid "No username given" msgstr "ユーザー名が入力されていません" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." -msgstr "このバックエンドでは、IDrive e2との間でデータの読み書きを実行できます。" +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." +msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "IDrive e2" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "アクセスキーのシークレットは、IDrive e2のアカウントにログインしてから取得できます。オプション --{0} でも指定できます。" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "アクセスキーのIDは、IDrive e2のアカウントにログインしてから取得できます。オプション --{0} でも指定できます。" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "「バケット名または完全なパス」は、バックアップ先のバケットの名称、または、バケット内のフォルダーの完全なパスを指します。" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "バケット名または完全なパス" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "アクセスキーのシークレットが指定されていません" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "アクセスキーのIDが指定されていません" @@ -2066,6 +2126,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2100,6 +2165,12 @@ msgid "" "does not support WebDAV connections" msgstr "サーバーがエラーコード {0}({1})を返しました。サーバーはWebDAVの接続をサポートしていないようです。" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "フォルダー {0} は見つかりませんでした。メッセージ: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2128,6 +2199,72 @@ msgstr "デバッグを行う際に、PROPFINDのレスポンスで上書きさ msgid "Dump the PROPFIND response" msgstr "PROPFINDのレスポンスを出力" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" +"このオプションを有効にすると、エラーの有無にかかわらず、いかなるサーバー証明書も受け入れます。可能な場合は--{0}のオプションを代わりに使用してください。" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "任意のサーバー証明書を許可" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" +"サーバーの証明書が不正であると報告された場合(例えば、自己署名証明書が使われている場合)に、証明書のハッシュ値(SHA1)を指定して証明書を承認することができます。ハッシュ値はスペースやコロンを入れずに、hexフォーマットで入力してください。コンマで区切り、複数のハッシュ値を入力できます。" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "既知のSSL証明書を許可(オプション)" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2254,9 +2391,410 @@ msgstr "予期されていないエラーコード:{0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "現在OAuthのサービスは割り当て量を超えています。数時間後に再度試してください" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "他のインスタンスが存在しています。通知しました" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2276,13 +2814,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"サポートするコマンドラインの引数:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2298,6 +2834,11 @@ msgid "" "parameter file. Each filter must be prefixed with either a + or a -, and " "multiple filters must be joined with {5} " msgstr "" +"コマンドラインのクライアントに指定されたオプションを保存できます。ファイルはプレーンテキストファイルでなければならず、UTF-8エンコーディングが推奨されます。ファイルのそれぞれの行は「オプション=値」の形式で記入してください。特殊オプション" +" --{0} と --{1} " +"で、ローカルのパスとバックアップ先のURIをそれぞれ上書きできます。このファイルで指定されるオプションは、コマンドラインで指定されるオプションに優先します。ファイルとコマンドラインの両方でフィルターを設定することはできません。特殊オプション" +" --{2}、--{3}、または --{4} を使用すると、パラメーターのファイルでフィルターを設定できます。各フィルターの先頭には+または-" +"が付いていなければならず、複数のフィルターがある場合は、{5}で結合する必要があります。" #: Library/RestAPI/Strings.cs:18 CommandLine/CLI/Strings.cs:45 msgid "Path to a file with parameters" @@ -2311,8 +2852,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"パラメーターのファイルにフィルターが指定されている場合、フィルターをコマンドラインで指定することはできません。--{0}、--{1}、または--{2}の特殊オプションを使用すると、パラメーターのファイルでフィルターを設定できます。各フィルターの先頭には+または-" -"が付いていなければならず、複数のフィルターがある場合は{3}で結合する必要があります。" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2321,7 +2860,7 @@ msgstr "パラメーターのファイル「{0}」を読み込めません。理 #: Library/RestAPI/Strings.cs:22 msgid "Output log information to the file given" -msgstr "" +msgstr "指定されたファイルにログの情報を出力" #: Library/RestAPI/Strings.cs:23 msgid "Determine the amount of information written in the log file" @@ -2350,14 +2889,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "サポートされていないSQLiteのバージョンが検知されました({0})。{1}以上のバージョンを使用してください" +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2369,87 +2908,94 @@ msgstr "" msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." -msgstr "ウェブサーバーがリクエストを受け付けるポート番号。複数の値をコンマで区切って指定できます。" +msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." -msgstr "ウェブサーバーがSSL通信に使用するPKCS #12形式の証明書と鍵のファイル。RSAまたはDSAの鍵のみをサポートします。" +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." +msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "PKCS #12 ファイルの証明書を復号するためのパスワード" +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"ウェブサーバーにアクセスする際に必要となるパスワード。このオプションは保存されるため毎回入力する必要はありません。値を入力しない場合、パスワードは無効となります。" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "許可するホスト名。セミコロンで分けて入力。ホスト名に「*」を設定した場合、全てのホスト名が許可され、ホスト名の確認は行いません。" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "データベースからログデータを削除するまでの時間を設定できます。" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "古いログデータを消去" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicatiは全ての設定に関してデータベースを保存する必要があります。このオプションで、設定を保存する場所を指定できます。このオプションは環境変数" -" {0} でも設定できます。" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" -msgstr "" +msgstr "設定を保存するフォルダーを設定" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"このオプションは、ローカルの設定のデータベースを暗号化するための鍵を設定します。このオプションは環境変数の{0}でも設定できます。オプションの--{1}でデータベースの暗号化を無効にできます。" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" -msgstr "" +msgstr "データベースの暗号鍵を設定" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " @@ -2457,113 +3003,189 @@ msgid "" msgstr "" "一時的な保存領域として使うフォルダーを指定できます。既定ではシステムの一時フォルダーを使用します。SQLiteも一時ファイルをここで設定したフォルダーに保存します。" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "一時的な保存フォルダー" -#: Library/RestAPI/Strings.cs:48 -msgid "Reset the JWT configuration, invalidating any issued login tokens" -msgstr "" - -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - #: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +msgid "Reset the JWT configuration, invalidating any issued login tokens" +msgstr "JWTの設定をリセットします。発行済のログイントークンはすべて無効になります。" + +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" -msgstr "" +msgid "Disable the visual captcha" +msgstr "画像のキャプチャを無効にする" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 -msgid "The Windows event log is not supported on this platform" +msgid "Use this option to disable database encryption of sensitive fields" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "Windowsイベントログに記録" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 +msgid "The Windows event log is not supported on this platform" +msgstr "Windowsのイベントログはこのプラットフォームではサポートされていません" + +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" +msgstr "サーバーがクラッシュしました:{0}" + +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" msgstr "" -#: Library/RestAPI/Strings.cs:61 -msgid "Require database encryption" +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" msgstr "" -#: Library/RestAPI/Strings.cs:62 +#: Library/RestAPI/Strings.cs:71 msgid "" "Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" +"sensitive fields and not rely on the serial number." msgstr "" -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:72 +msgid "Require database encryption" +msgstr "データベースの暗号化が必要です" + +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " "environment variable {0} or disable database encryption with the option " "--{1}" msgstr "" - -#: Library/RestAPI/Strings.cs:64 -#, csharp-format -msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " -"disable database encryption with the option --{1}" -msgstr "" - -#: Library/RestAPI/Strings.cs:68 -#, csharp-format -msgid "" -"Unable to find a valid date, given the start date {0}, the repetition " -"interval {1} and the allowed days {2}" -msgstr "" - -#: Library/RestAPI/Strings.cs:73 -#, csharp-format -msgid "Server has started and is listening on {0}, port {1}" -msgstr "サーバーが起動しました。{0}のポート{1}でリクエストを待ち受けています" +"データベースの暗号化用の鍵が必要です。環境変数の{0}で暗号化用の鍵を指定するか、--{1}のオプションでデータベースの暗号化を無効にしてください。" #: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" -msgstr "指定されたパラメーターでSSL証明書を作成できません。例外に関する詳細:{0}" +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" #: Library/RestAPI/Strings.cs:75 #, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " +"disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 +#, csharp-format +msgid "" +"Unable to find a valid date, given the start date {0}, the repetition " +"interval {1} and the allowed days {2}" +msgstr "正しい日付が見つかりません。開始日に{0}、繰り返しの間隔に{1}、許容日に{2}が指定されています。" + +#: Library/RestAPI/Strings.cs:88 +#, csharp-format +msgid "Server has started and is listening on {0}, port {1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 +#, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "待ち受け用のソケットを設定できません。試したポート番号:{0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2575,33 +3197,33 @@ msgstr "アセンブリー {0} を読み込めませんでした。エラーメ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "プロセスの種類 {0} アセンブリー {1} を読み込めませんでした。エラーメッセージ:{2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" "このモジュールは業界標準のZIP圧縮をサポートします。このモジュールで作成したファイルは、標準に準拠するZIPアプリケーションで読み込むことができます。" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "ZIP圧縮" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "代わりに--{0}のオプションを使用してください。" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "使用する圧縮レベルを操作できます。0に設定すると圧縮は行わず、9に設定すると圧縮を最大限に行います。" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" -msgstr "" +msgstr "ZIP圧縮のレベルを設定" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2609,70 +3231,37 @@ msgid "" "ignored." msgstr "LZMAなど別の圧縮方法を設定できます。Deflate以外の値を設定するとオプション --{0} は無視されます。" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" -msgstr "" +msgstr "ZIP圧縮の方法を設定" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "ZIP64のフォーマットには4GiB以上のファイルが必要となります。このオプションで、フォーマットのサポートを切り替えられます。" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "ファイルが見つかりません:{0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*実験的*:LZMA2をサポートする7zアーカイブ。" - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "実験的 - 7zアーカイブ" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "アーカイブを開いて書き込めません" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "アーカイブを開いて読み込めません" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "指定したファイルはこのアーカイブに含まれていません" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "LZMA 2による圧縮に使用するスレッドの数。既定ではプロセッサーのコアの数が指定されます。" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "圧縮に使用するスレッド数" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "使用する圧縮アルゴリズムを制御します。このオプションを有効にすると、7zは、圧縮率がわずかに下がる高速アルゴリズムを使用します。" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "バックアップ" @@ -2733,7 +3322,7 @@ msgstr "オプション --{0} は非推奨となりました:{1}" #, csharp-format msgid "" "The option --{0} exists more than once. Please report this to the developers" -msgstr "オプション--{0}が2つ以上存在しています。この問題を開発者に報告してください" +msgstr "オプション --{0} が2つ以上存在しています。この問題を開発者に報告してください" #: Library/Main/Strings.cs:32 msgid "No source folders specified for backup" @@ -2759,7 +3348,7 @@ msgstr "バックアップ元のフォルダー {0} へのアクセスが承認 msgid "" "The value \"{1}\" supplied to --{0} does not parse into a valid boolean. " "This will be treated as if it was set to \"true\"" -msgstr "--{0}に指定された値「{1}」は、有効なブール値に変換できません。ここでは、この値は「true」に設定されていると仮定して扱われます。" +msgstr "--{0}に指定された値「{1}」を有効なブール値に変換できません。ここでは、この値が「true」に設定されていると仮定して扱います。" #: Library/Main/Strings.cs:36 #, csharp-format @@ -2910,7 +3499,7 @@ msgstr "バックアップまたは復元中にシステムが非アクティブ #: Library/Main/Strings.cs:65 msgid "Toggle system sleep mode" -msgstr "" +msgstr "システムのスリープモードを切り替える" #: Library/Main/Strings.cs:66 msgid "" @@ -3083,15 +3672,21 @@ msgstr "バックアップするファイルのサイズを制限" #: Library/Main/Strings.cs:98 msgid "" -"Select another thread priority for the process. Use this to set Duplicati to" -" be more or less CPU intensive." +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" msgstr "" #: Library/Main/Strings.cs:99 +msgid "" +"Select another thread priority for the process. Use this to set Duplicati to" +" be more or less CPU intensive." +msgstr "プロセスに関するスレッドの優先度を指定し、DuplicatiのCPUの使用量を調節できます。" + +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "スレッドの優先度" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " @@ -3099,22 +3694,34 @@ msgid "" msgstr "" "dblockファイルの最大のサイズを変更できます。バックエンドが各ファイルのサイズを制限している場合、サイズの変更が役立つ可能性があります。" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "ボリュームのサイズを制限" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " @@ -3122,11 +3729,11 @@ msgid "" msgstr "" "このオプションを有効にすると、マニフェストファイルの内容は読み込まれず、ファイルのハッシュ値も検証されません。緊急時の復旧にのみ使用してください。" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" -msgstr "" +msgstr "マニフェストファイルの検証を無効にする" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3135,11 +3742,11 @@ msgid "" msgstr "" "Duplicatiは外部の圧縮用モジュールの使用をサポートしています。このオプションで、圧縮に使用するモジュールを選択できます。これは新しいボリュームを作成する際にのみ適用されます。既存のファイルを読み込む際には、ファイル名を使用して圧縮用モジュールが選択されます。" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "圧縮に使用するモジュールを選択" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3148,27 +3755,27 @@ msgid "" msgstr "" "Duplicatiは外部の暗号化モジュールの使用をサポートしています。このオプションで、暗号化に使用するモジュールを選択できます。これは新しいボリュームを作成する際にのみ適用されます。既存のファイルを読み込む際には、ファイル名を使用して暗号化モジュールが選択されます。" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "暗号化に使用するモジュールを選択してください" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "無効にするモジュールを指定できます。複数ある場合はコンマで区切ってください。" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "読み込むモジュールを指定できます。複数ある場合はコンマで区切ってください。" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3184,11 +3791,11 @@ msgid "" msgstr "" "この設定で、スナップショットを使用するかどうかを制御できます。スナップショットを使うと、Duplicatiは他のプログラムによりロックされているファイルをバックアップすることができます。この設定を「off」にした場合、Duplicatiはディスクのスナップショットの作成を試みません。「auto」にした場合、Duplicatiはスナップショットの作成を試み、それが許可されていなかったり失敗したりした場合は、通知を行わずにスナップショットの作成の試みを終了します。「on」に設定した場合は、スナップショットの作成を試み、失敗した場合は警告メッセージをログに出力します。「required」を設定すると、スナップショットを作成できなかった場合、Duplicatiはバックアップを中断します。Windowsでは、スナップショットの作成は「ボリュームシャドウコピーサービス」(VSS)により行い、管理者権限が必要となります。Linuxでは、スナップショットの作成は論理ボリューム管理(LVM)により行い、ルート権限が必要となります。" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " @@ -3196,79 +3803,80 @@ msgid "" msgstr "" "既定では、事前に作成したボリュームが一時フォルダーに保存されます。このオプションで、一時的なボリュームを保存するフォルダーを設定できます。なお、オプションの名称とは異なり、これは同期アップロードでも機能します。" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "アップロードの準備ができたボリュームを保管しておく場所のパス" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -"非同期アップロードを実行する際、Duplicatiはアップロードするボリュームを作成します。このオプションで、待機中のアップロードの数を制限し、Duplicatiがボリュームを作りすぎないように設定できます。0に設定すると、制限は無効となります。" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "アップロードに先立って作成するボリュームの数" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "非同期アップロードを実行する際に許可する、並行アップロードの最大数を設定できます。0に設定すると、最大数の制限を無効にできます。" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "許可する並行アップロードの数" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "このオプションを有効にすると、エラーメッセージの一部がより詳細に出力されるため、トラブルシューティングが容易になります。" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "内部情報をファイルに記録" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." -msgstr "" +msgstr "--{0}のオプションで指定したファイルに書き込む情報の量を指定できます。" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "ログに記録する情報のレベル" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "代わりに--{0}と--{1}のオプションを使用してください。" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" "バックアップの作成先のフォルダーが存在しない場合、Duplicatiは自動的にフォルダーを作成します。このオプションを有効にすると、フォルダーは自動的に作成されなくなります。" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3279,12 +3887,12 @@ msgstr "" "正しく機能しないライターをスナップショットから除外できます。これはvshadow.exeの-" "wxフラグと同等の設定ですが、このオプションには、ライターのクラスのGUIDのみを設定できます。コンポーネントの名称や、インスタンスのGUIDは設定できません。複数のGUIDを指定する場合はセミコロンで区切ってください。中括弧のあるGUIDを含めて、ほとんどの形式のGUIDを指定できます。" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "除外するVSSライターのGUIDをセミコロンで分けた一覧(Windowsのみ)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3298,11 +3906,22 @@ msgid "" msgstr "" "この設定で、NTFSのUSN番号を使用するかどうかを制御できます。USNを使うと、Duplicatiはファイルとフォルダーの一覧をより迅速に取得できます。この設定を「off」にした場合、DuplicatiはUSNの使用を試みません。「auto」にした場合、DuplicatiはUSNの使用を試み、それが許可されていなかったり失敗したりした場合は、通知を行わずにUSNを使用する試みを終了します。「on」に設定した場合は、USNの使用を試み、失敗した場合は警告メッセージをログに出力します。「required」を設定すると、USNを使用できなかった場合、Duplicatiはバックアップを中断します。この機能はWindowsでのみサポートされており、管理者権限が必要となります。" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" +msgstr "NTFSのUpdate Sequence Numbersの使用を制御" + +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3317,31 +3936,31 @@ msgstr "" "タイムスタンプを検証する際、わずかな時差でバックアップの予期しない更新が生じないよう、Duplicatiは時間を調整します。--{0} " "のオプションで1週間のバックアップを保管するように設定し、また、毎週同じ時間にバックアップを作成する場合、バックアップを行う時間がずれて、ちょうど1週間が経過してしまい、その結果Duplicatiはバックアップを予定より早く削除してしまうことがありえます。これを防ぐために、Duplicatiは1%の誤差(最大で1時間)を考慮に入れて、タイムスタンプの検証を行います。このオプションを有効にすると、誤差の考慮を行わず、時間を厳密に確認します。" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "このオプションを有効にすると、ファイルのリストを作成してアップロードを検証します。" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "ファイルのリスト作成でアップロードを検証" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -"Duplicatiは、ディスクのスキャンとボリュームの作成と並行してファイルをアップロードすることで、バックアップの高速化を図ります。このオプションを有効にすると、Duplicatiは各ボリュームの作成が完了するまで待機します。" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "複数のファイルを並行してアップロード" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3350,11 +3969,11 @@ msgid "" msgstr "" "Duplicatiは、ログインを1度だけ行うことによって処理を高速化するために、1個の接続で複数の操作を実行します。このオプションを有効にすると、それぞれの操作を個別の接続で実行するように設定できます。" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "接続を再利用しない" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3362,11 +3981,11 @@ msgid "" msgstr "" "エラーが発生した場合、Duplicatiはエラーを表示せず、再試行した回数だけを報告します。このオプションを有効にすると、再試行が行われた際にエラーメッセージを表示させることができます。" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "再試行が行われた際にエラーメッセージを表示" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " @@ -3374,11 +3993,11 @@ msgid "" msgstr "" "変更されたファイルが無い場合、Duplicatiはバックアップのセットをアップロードしません。バックアップのデータを使って、バックアップが実行されたことを検証する場合、このオプションで、バックアップのセットが空だったとしても、Duplicatiにバックアップのセットをアップロードするように設定できます。" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "空のバックアップファイルをアップロード" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " @@ -3386,11 +4005,11 @@ msgid "" msgstr "" "(このバックアップで)バックエンドが使用する保存領域の使用量に対する制限を設定できます。これは利用可能な場合、バックエンドの完全な割り当て量に追加されます。注意:バックアップは、割り当て量を超えた場合でも続行されます。これは警告とエラーのメッセージを出力するにとどまります。" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "保存領域の使用を制限" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3398,23 +4017,24 @@ msgid "" "size. If the backend does not report the quota information, this value will " "be ignored." msgstr "" +"バックエンドに割り当てられた容量の残りが少なくなってきた際に、どの程度まで割り当て量が減少したら警告を行うか設定できます。量はパーセントで指定してください。バックアップの全容量のうち、利用可能な割り当て量がここで指定した値よりも少なくなった場合に警告を行います。バックエンドが割り当て量に関する情報を報告しない場合、この値は無視されます。" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "割り当て量の残りについて警告する際の値" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "バックエンドにより報告される割り当て量を無効にします。--{0}のオプションを使うと、手動で割り当て量を設定できます。" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "バックエンドの割り当て量を無効にする" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3427,11 +4047,11 @@ msgid "" msgstr "" "シンボリックリンクの扱い方を設定できます。「{0}」のオプションを設定すると、名前とリンク先を含めてシンボリックリンクを記録し、復元の際にはこれをリンクとして再度作成します。「{1}」のオプションでは、シンボリックリンクを考慮せず、シンボリックリンクの情報を保存しません。「{2}」のオプションを設定すると、シンボリックリンクのリンク先にあるファイルをバックアップし、シンボリックリンクの名前で、通常のファイルとして復元します。Duplicatiの初期のバージョンではこのオプションがサポートされておらず、{2}が指定されたものとして動作していました。" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "シンボリックリンクの扱い方" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3442,11 +4062,11 @@ msgid "" msgstr "" "ハードリンクの扱い方を設定できます(LinuxまたはmacOSでのみ機能)。「{0}」のオプションを設定すると、ハードリンクのパスを複数回保存するのを防ぐため、それぞれのハードリンクに関してIDを記録します。「{1}」のオプションでは、ハードリンクの情報を考慮せず、それぞれのハードリンクを異なるパスとして扱います。「{2}」のオプションを設定すると、1個のリンクを除いて、全てのハードリンクが無視されます。" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "ハードリンクの扱い方" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3454,11 +4074,11 @@ msgid "" "are: {0}." msgstr "特定の属性をもつファイルを除外できます。複数の属性を指定する場合は、属性の名称をコンマで区切ってください。除外できる属性は {0} です。" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "属性でファイルを除外" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3469,42 +4089,52 @@ msgstr "" "DefineDosDeviceを使ったSUBSTと類似)、スナップショットの内容にアクセスするのに使用する一時的なドライブを作成します。マッピングを行うと、Windows" " XPでファイルにアクセスする速度を向上することができます。" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "スナップショットをドライブにマッピング(Windowsのみ)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "バックアップに与えられる表示名。電子メールを送信したり、スクリプトを実行したりする際に、バックアップを一意に特定するのに使用できます。" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "バックアップの名称" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "バックアップの一意の識別子。メールを送信したりスクリプトを実行したりする際に、バックアップを特定するのに使うことができます。" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "バックアップのID" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" "バックアップを実行しているコンピューターの一意の識別子。メールを送信したりスクリプトを実行したりする際に、コンピューターを特定するのに使うことができます。" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "コンピューターのID" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3518,11 +4148,11 @@ msgstr "" "ここで指定したテキストファイルの各行にファイルの拡張子を指定すると、指定された拡張子をもつファイルに関しては圧縮を行わず、単純にアーカイブとしてのみ保存します。ピリオド(.)で始まらない行は考慮されません。また、拡張子の後には半角スペースを追加してください。既定のファイルには設定例が含まれます。既定のファイルは" " {0} にあります。" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "圧縮を行わないファイルの拡張子を設定" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3531,11 +4161,11 @@ msgid "" msgstr "" "ファイルをどの程度分割するかを指定できます。大きなサイズを指定すると、ファイルの変更時のオーバーヘッドが大きくなり、小さいサイズを指定すると、ファイルの一覧を保存する際のオーバーヘッドが大きくなります。リモートのファイルが作成された後、この値は変更できませんのでご注意ください。" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "ハッシュ化に使用するブロックのサイズ" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " @@ -3543,49 +4173,41 @@ msgid "" msgstr "" "変更が検知されたファイルのみにスキャンを限定できます。これは通常、ファイルの変更を監視するファイルシステムの監視機能と併用する場合にのみ機能します。" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "変更を確認するファイルのリスト" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "リモートのファイルデータベースのローカルのキャッシュを含むファイルのパス。" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "ローカルの状態のデータベースへのパス" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "削除したファイルの一覧を指定できます。--{0}が併せて設定されていない限り、このオプションは無視されます。" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "削除されたファイルの一覧" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "パスと更新日時のタイムスタンプをメモリーに保存しないように設定して、メモリーの使用量を減らすことができます。" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "メモリー内のルックアップを無効にすることで、メモリーの使用量を削減" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "メモリーを追加で使用し、速度を高めることができます。" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "メモリー内のブロックのキャッシュを保存" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" @@ -3593,11 +4215,11 @@ msgid "" msgstr "" "このオプションを有効にすると、起動時にローカルのデータベースとリモートのファイル一覧は比較されません。ファイルの一覧表示が適切に機能しない場合、このオプションが役立つ可能性があります。" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "起動時にバックエンドのクエリーを行わない" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3607,11 +4229,11 @@ msgid "" msgstr "" "インデックスファイルを使うと、ローカルのデータベースが存在しない場合に、dblockファイルをダウンロードする必要性を制限することができます。インデックスファイルに多くの情報が記録されるほど、操作はデータベースを使わず、より一層高速に実行されます。ただし、インデックスファイルのサイズが大きくなると、リモートの保存領域の使用量が増えてしまうため、注意して使用してください。" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3620,21 +4242,21 @@ msgid "" msgstr "" "ファイルが変更されるにつれて、リモートのバックアップ先にある一部のファイルは不要になることがあります。このオプションで、バックアップ先で余分に使用されている保存領域を、再度使用可能に設定するまで、どの程度まで許容するか調節できます。数値には、各ボリュームと保存領域全体で使用される割合を百分率(パーセント)で指定してください。" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "余分に使用されている保存領域を許容する程度を百分率(パーセント)で指定" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "このオプションを有効にすると、実際にファイルを変更することなく、異なる設定を試して、その結果を確認することができます。" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" @@ -3642,11 +4264,11 @@ msgid "" msgstr "" "これは非常に高度な設定です!このオプションで、性能または保存領域のサイズの観点から、ハッシュのサイズが異なるブロック用ハッシュアルゴリズムを選択できます。" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "ブロックに使用するハッシュ化アルゴリズム" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" @@ -3654,11 +4276,11 @@ msgid "" msgstr "" "これは非常に高度な設定です!このオプションで、性能または保存領域のサイズの観点から、ハッシュのサイズが異なるファイル用ハッシュアルゴリズムを選択できます。" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "ファイルに使用するハッシュ化アルゴリズム" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3667,11 +4289,11 @@ msgid "" msgstr "" "バックアップ中に小さいサイズのファイルが大量に検知された場合、または、バックアップを削除した後で余分に使用されている保存領域が検知された場合には、リモートのデータを圧縮します。このオプションを有効にすると、自動圧縮を無効にし、圧縮用のコマンドを実行した場合にのみ圧縮を行うよう設定できます。" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "自動圧縮を無効にする" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3680,11 +4302,11 @@ msgid "" msgstr "" "圧縮を行うかどうかを判断するためにボリュームのサイズを調べる際、既定ではボリュームのサイズの20%が、誤差の許容範囲として設定されます。許容範囲を定めることで、数バイトの誤差を含んでいる可能性がある大きなボリュームをダウンロードして再度書き込みを行うことのないよう設定することができます。" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "ボリュームのサイズの閾値" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3692,11 +4314,11 @@ msgid "" msgstr "" "リモートの保存領域を小さいサイズのファイルで満たさないよう、この値を設定すると、小さいファイルを強制的にまとめることができます。小さいボリュームは、ボリューム全体を満たせる場合は常に結合されます。" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "小サイズのボリュームの最大数" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3704,11 +4326,11 @@ msgid "" msgstr "" "このオプションを有効にすると、このコンピューター上にある他のファイルから、既存のブロックを検索します。これには時間がかかりますが、ダウンロードのサイズを制限できます。" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "復元時にローカルのファイルデータを使用" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " @@ -3716,29 +4338,29 @@ msgid "" msgstr "" "内容を一覧表示したり、ファイルを復元したりする場合、ローカルのデータベースをスキップすることができます。スキップすると、処理は通常遅くなりますが、リモートに保存している実際の内容を検証することができます。" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" -msgstr "" +msgstr "ローカルのデータベースを無効にする" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "維持するバージョンの数を設定できます。-1を指定すると全てのバージョンを維持します。" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "維持するバージョン数" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "バックアップを保存する期間を設定できます。" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "特定の期間の全てのバックアップを維持" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3750,51 +4372,51 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "古い中間のバックアップを削除してバージョンの数を減らす" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "このオプションを有効にすると、バックアップ元のエントリーが欠けている場合でも続行できます。" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "欠けているバックアップ元の要素を無視" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" "このオプションを有効にすると、ファイルを復元する際に、復元先にあるファイルを上書きします。このオプションを無効にすると、タイムスタンプと数字をファイル名に付けて、ファイルを復元します。" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "復元時にファイルを上書き" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" "このオプションを有効にすると、進捗状況に関して出力するデータの量を増やすことができます。通常、処理した各ファイルについて、情報を1行ずつ出力します。" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "進捗状況に関するより詳細な情報を出力" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "このオプションを有効にすると、全てのファイル名を含めて、操作により生成される出力の量を増やすことができます。" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "完全な結果を出力" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3803,11 +4425,11 @@ msgid "" msgstr "" "このオプションを有効にすると、リモートの保存領域の変更後に検証用のファイルをアップロードします。ファイルは暗号化されておらず、全てのリモートのファイルのSHA256によるハッシュ値を含んでおり、ファイルの整合性の検証に利用できます。" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "検証ファイルをアップロードするか否かを決定" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3818,11 +4440,11 @@ msgid "" msgstr "" "バックアップが完了した後で、一部のファイル(dblock、dindex、dlist)がリモートのバックエンドから検証用に選択されます。このオプションで、検証するファイル数を変更できます。--{0}のオプションが同時に指定されている場合は、テストするサンプル数の指定の方が優先されます。この値を0に設定するか、あるいは--{1}のオプションが設定されている場合は、リモートのファイルは一切検証しません。" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "バックアップ後にテストするサンプルの数" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3833,11 +4455,11 @@ msgid "" msgstr "" "バックアップが完了した後で、一部のファイル(dblock、dindex、dlist)がリモートのバックエンドから検証用に選択されます。このオプションで、検証するファイルの割合(0から100まで)を指定できます。--{0}のオプションが同時に指定されている場合は、テストするサンプル数の指定の方が優先されます。--{1}のオプションが設定されている場合は、リモートのファイルは一切検証しません。" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "バックアップ後にテストするサンプルの割合" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3850,42 +4472,42 @@ msgid "" msgstr "" "バックアップが完了した後で、一部のファイル(dblock、dindex、dlist)がリモートのバックエンドから検証用に選択されます。このオプションを有効にすると、単純にハッシュ値を検証する代わりに、ファイルを復号してそれぞれのボリュームの内容を検査し、完全な検証を実行します。オプションの--{0}が設定されている場合は、リモートのファイルは一切検証しません。検証が直接実行される場合は、このオプションは自動的に設定されます。ListAndIndexesはTrueと同様に機能しますが、dlistとインデックスのボリュームのみを対象とします。" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "このサイズを使用して、ファイルを処理する前に読み込むバイト数を調整できます。" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "ファイルの読み込みバッファーのサイズ" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "このオプションを有効にすると、パスフレーズを変更できます。なお、このオプションはバックアップまたは修復の操作に関しては許可されていません。" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "パスフレーズの変更を許可" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" "このオプションを有効にすると、処理速度を改善すべく、ファイルのセットのリスト作成だけを行い、ファイル名やその他のメタデータのスキャンは行いません。" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "ファイルのセットのみのリストを作成" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -3893,21 +4515,21 @@ msgid "" msgstr "" "このオプションを有効にすると、ファイルのタイムスタンプなどのメタデータの保存を無効にできます。メタデータの保存を無効にすると、バックアップと復元の処理速度は向上しますが、ファイルのサイズにはあまり影響しません。" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" -msgstr "" +msgstr "メタデータを保存しない" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "ファイルにアクセスできなくなる可能性があるため、既定では権限は復元されません。このオプションを有効にすると、権限も復元できます。" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "ファイルの権限を復元" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -3915,11 +4537,11 @@ msgid "" msgstr "" "ファイルを復元した後、復元が正常に行われたことを検証するために、全てのファイルのハッシュ値が確認されます。このオプションを有効にすると、ハッシュ値の確認と、復元の検証を無効にできます。" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "復元したファイルの確認をスキップ" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -3927,39 +4549,39 @@ msgid "" msgstr "" "Duplicatiは、ダウンロードするデータ量を最小にするため、バックアップ元のファイルのデータを使用するよう試みます。このオプションを有効にすると、リモートのデータのみを使用するように設定できます。" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "ローカルのデータを使用しない" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "既定でローカルのブロックを復元に使用しないようになりました。ローカルのブロックを使用する場合は、--{0}のオプションを設定してください。" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "このオプションを有効にすると、復元を実行する際、リモートの保存領域にあるファイルに加えて、ディスクにあるブロックも使用します。" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "既存のデータを復元に使用" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" "このオプションを有効にすると、復元したファイルをデータで修復する前に、ボリュームから読み込んだブロックのハッシュ値を確認することで、検証を強化することができます。" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "ブロックのハッシュ値を確認" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3968,11 +4590,11 @@ msgid "" msgstr "" "このオプションを有効にすると、パスの情報しか含まないデータベースを検索可能なものとしてローカルで作成します。これは、全ての情報を再構築する必要がない場合に、ファイルの場所を特定するためのデータベースを迅速に作成するために使用できます。作成したデータベースを使ってファイルの検索を行うことはできますが、ファイルを実際に復元することはできません。" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "データベースをパスで修復" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3981,11 +4603,11 @@ msgid "" msgstr "" "既定では、システムのロケールの設定が使用されますが、別の言語でメッセージを受信する際など、別のロケールを使用したい場合は、このオプションでロケールを設定してください。空白にするとロケールは設定されません。" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "ロケールの設定を強制" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -3994,11 +4616,11 @@ msgstr "" "既定では、日付は「今日」や「先週の木曜日」など、カレンダーの形式で表示されます。このオプションを設定すると、例えば「Nov 12, 2018, 8:01" " AM」のように、実際の日時のみが表示されます。" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " @@ -4006,41 +4628,41 @@ msgid "" msgstr "" "このオプションを有効にすると、アップロードとダウンロードのマルチスレッド化は無効となります。使用しているハードウェアと、バックエンドの転送速度に応じて、バックエンドの処理速度を大幅に向上させられる場合があります。" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "スレッド化したパイプを使用してバックエンドとのファイルの通信を扱う" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "スレッドの最大使用数を設定できます。0以下に設定すると、アクティブなスレッドの数を、ハードウェアに適した数に動的に設定できます。" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "並行するスレッドの数を制限" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "データのハッシュ化を行うプロセスの数を設定できます。" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "並行するハッシュ化のプロセスの数を指定" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "出力データの圧縮を行うプロセスの数を設定できます。" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "並行する圧縮のプロセスの数を指定" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4048,11 +4670,11 @@ msgid "" msgstr "" "Duplicatiは、前回のバックアップが完了しなかったことを検知した場合、直近に完了したバックアップと、未完了のバックアップのセッションでアップロードされたファイルを統合したファイルリストを生成します。" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" -msgstr "" +msgstr "統合されたファイルリストを無効にする" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4061,11 +4683,11 @@ msgid "" msgstr "" "このオプションを有効にすると、変更を確認するのにファイルをスキャンするかどうかを決める際に、メタデータやファイルのサイズをチェックしなくなります。大量のファイルがあり、変更されていないファイルのスキャンに長い時間を要している場合、このオプションを有効にしてください。" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" -msgstr "" +msgstr "ファイルの最終更新日時のみを確認" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4074,11 +4696,11 @@ msgid "" msgstr "" "バックアップの部分を新しいフォルダーに復元する場合には、空のフォルダーの階層が生成されないよう、出来る限り最短のパスが使用されます。このオプションを有効にすると、パスの圧縮をスキップして、上位の空のフォルダーも含めた、元々のフォルダーの全体の構造を保存します。" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4087,11 +4709,11 @@ msgid "" msgstr "" "既定では、最後のファイルのセットは削除できません。これは設定ミスで全てのリモートのデータが削除されてしまうことに対する予防のためです。このオプションを有効にすると、保護は無効となり、全てのファイルセットを削除できるようになります。" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "全てのファイルのセットの削除を許可" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4102,11 +4724,11 @@ msgid "" msgstr "" "ローカルのデータベースを変更する操作の中には、未使用のエントリーを残してしまうものがあります。これらのエントリーは、VACUUMを実行するまで、ハードディスクからは削除されません。VACUUMを実行すると、長期的にはディスクの保存領域を節約できますが、データベースにある全ての正常なエントリーのコピーを一時的に作成する必要があります。このオプションをtrueに設定すると、DuplicatiがVACUUMを自らの判断で行えるようになります。" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "スペースの節約のためローカルのデータベースを自動的に再構築することを許可" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4115,11 +4737,11 @@ msgid "" msgstr "" "このオプションが有効となっている間、バックアップ元のファイルのサイズを計算するスキャナー機能は無効となり、代わりにデータベースからサイズを読み込みます。このオプションで、ディスクへのアクセスを減らし、バックアップの速度を向上させられますが、バックアップの進捗状況に関する報告の正確性は損なわれます。" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Read-aheadスキャナーを無効にする" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4127,11 +4749,11 @@ msgid "" msgstr "" "多くのファイルセットがあるバックアップでは、検証作業がバックアップに掛かる時間の大部分を占めることがあります。この確認を無効にした場合は、定期的に確認コマンドを実行して、全てが問題なく機能していることをか確認してください。" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "ファイルの一覧の一貫性の確認を無効にする" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4139,15 +4761,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "バッテリーで動作している際にバックアップを無効にする" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "ログファイルに記録する情報の水準" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4158,23 +4780,23 @@ msgid "" msgstr "" "メッセージを取り除いたり含んだりするフィルターを設定できます。ログの水準は考慮されません。「{0}」で区切ると、複数のフィルターを設定できます。フィルターはログのタグについて設定され、半角ハイフンで始まるものについては除外されます。中括弧で正規表現をサポートします。例:「+Path*{0}+*Mail*{0}-[.*DNS]」" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "コンソールに表示する情報の水準" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" @@ -4182,19 +4804,19 @@ msgid "" msgstr "" "現在のプロセスに、最も低い入出力の優先度を設定するよう、オペレーティングシステムに指示。各操作の実行速度は遅くなる場合がありますが、同時に実行している操作に干渉する程度は低くなります。" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" -msgstr "" +msgstr "プロセスの入出力の優先度を「低」に設定" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "このオプションを有効にすると、全ての空のフォルダーをバックアップから削除できます。" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" -msgstr "" +msgstr "空のフォルダーを除外" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4203,11 +4825,11 @@ msgid "" msgstr "" "ここで指定したファイル名(またはファイル名の一覧)に該当するファイルを含むフォルダーは、バックアップから除外されます。典型的な使い方としては、例えばここに「.nobackup」と記入し、同じ名前のファイルをフォルダーに設置して、そのフォルダーをバックアップに含めないようにすることなどがあります。" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "フォルダーを除外するファイル名の一覧" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4216,11 +4838,11 @@ msgid "" msgstr "" "シンボリックリンクのメタデータが適用される場合、通常は、シンボリックリンク自体ではなく、そのリンク先を変更することになります。そのため、メタデータはシンボリックリンクには適用されません。このオプションを有効にすると、メタデータをシンボリックリンクにも適用されるよう設定することができます。" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "メタデータをシンボリックリンクに適用" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4229,11 +4851,11 @@ msgid "" msgstr "" "ユニットテストモードで実行している間、自動修正は行われず、入力したデータは常に完璧な状態であると想定されます。このオプションは普段のバックアップではなく、潜在的な問題を発見するためのテストにのみ使用してください。" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "ユニットテストモードを有効にする" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4244,11 +4866,11 @@ msgstr "" "バックアップの性能を改善するために、既定では、頻繁に行われるデータベースのクエリーに関するログは記録されません。このオプションを有効にすると、データベースの全てのクエリーに関するログを記録します。その際には、追加のログデータを報告するために、--{0}={2}" " または --{1}={2} を忘れずに設定してください" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4258,11 +4880,11 @@ msgid "" msgstr "" "dblockのファイルがバックアップ先で見つからない場合、ローカルのバックアップ元のデータを使って、その再構築を試みることができます。ローカルのデータが既に変更されてしまっている可能性があるため、全ての必要なデータを取得できず、処理が遅くなる場合があります。このオプションを有効にすると、欠けているdblockのファイルを再構築するよう試みることができます。" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "dblockのファイルが見つからない場合、これを再構築" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4271,11 +4893,11 @@ msgid "" msgstr "" "最後に圧縮を行ってから、どの程度の時間が経過した後で、バックアップのタスク後の圧縮を自動的に行うかを設定できます。自動圧縮には時間がかかる場合があり、毎回のバックアップ後に行うのは望ましくない可能性があります。" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "自動圧縮を行う最短の間隔" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4284,57 +4906,206 @@ msgid "" msgstr "" "最後にVACUUMを行ってから、どの程度の時間が経過した後で、バックアップのタスク後のVACUUMを自動的に行うかを設定できます。自動的なVACUUMには時間がかかる場合があり、毎回のバックアップ後に行うのは望ましくない可能性があります。" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "自動的にデータベースのVACUUMを行う最短の間隔" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "この暗号化ライブラリーは、ハッシュ化アルゴリズムの{0}の再利用可能な変換をサポートしていません" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "この暗号化ライブラリーは、ハッシュ化アルゴリズムの{0}をサポートしていません" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "既存のバックアップのパスフレーズは変更できません" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "スナップショットを作成できませんでした: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "ファイル {0} を削除できませんでした。ファイルが存在するかテストしています" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "存在しないファイル {0} を削除しようとするエラーから回復しました" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "ファイル {0} を削除する際に発生したエラーから回復できませんでした" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "ファイル {0} は見つからなかったため削除できませんでした。コンテンツのリストを作成しています" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "リスト作成の結果、ファイル {0} は削除されたことが分かりました" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "リスト作成の結果、ファイル {0} が削除されなかったことが分かりました" @@ -4390,31 +5161,6 @@ msgstr "このモジュールは、HTTPのリクエストが行われる方法 msgid "Configure http requests" msgstr "HTTPのリクエストを設定" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" -"このオプションを有効にすると、エラーの有無にかかわらず、いかなるサーバー証明書も受け入れます。可能な場合は--{0}のオプションを代わりに使用してください。" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "任意のサーバー証明書を許可" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" -"サーバーの証明書が不正であると報告された場合(例えば、自己署名証明書が使われている場合)に、証明書のハッシュ値(SHA1)を指定して証明書を承認することができます。ハッシュ値はスペースやコロンを入れずに、hexフォーマットで入力してください。コンマで区切り、複数のハッシュ値を入力できます。" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "既知のSSL証明書を許可(オプション)" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4475,7 +5221,7 @@ msgstr "" #: Library/Modules/Builtin/Strings.cs:55 msgid "Set the default operation timeout" -msgstr "" +msgstr "操作に関する既定のタイムアウトの値を設定" #: Library/Modules/Builtin/Strings.cs:56 msgid "" @@ -4511,105 +5257,118 @@ msgstr "このモジュールは、Hyper-V仮想マシンをバックアップ msgid "Configure Hyper-V module" msgstr "Hyper-Vモジュールを設定" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "このモジュールは、Microsoft SQL Serverのデータベースをバックアップするために、ソースパラメーターを解析します" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Microsoft SQLサーバーモジュールを設定" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" -msgstr "" +msgstr "バックアップの操作を開始する前と、バックアップの完了後に実行するスクリプトを設定できます" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "スクリプトを実行" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." -msgstr "" +msgstr "バックアップの操作を行った後に実行するスクリプトを設定できます。スクリプトは、標準出力に書き込まれたバックアップの結果を受け取ります。" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "スクリプトを終了時に実行" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "スクリプトの「{0}」は終了コード {1} を返しました" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "スクリプトの「{0}」は終了コード {1}{2} を返しました" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "必要なスクリプトを開始時に実行" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "結果を出力する形式を選択できます。利用可能な形式は{0}です。" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "スクリプト「{0}」を実行する際にエラーが発生しました:{1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "スクリプト「{0}」の実行がタイムアウトしました" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" +"バックアップの操作を行う前に実行するスクリプトを設定できます。スクリプトが完了するかタイムアウトになるまで、バックアップの操作は実行されません。" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "スクリプトを開始時に実行" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "スクリプト「{0}」はエラーメッセージを返しました:{1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" -msgstr "" +msgstr "スクリプトのタイムアウトを設定" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " @@ -4617,26 +5376,26 @@ msgid "" msgstr "" "スクリプトの引数を有効にします。このオプションを有効にすると、スクリプトの引数は、コマンドラインの文字列として扱われます。引数を分けるには、一重引用符または二重引用符を使用してください。" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "スクリプトの引数を有効にする" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "このモジュールを使うと、操作が完了した後で電子メールを送信できます" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "メールを送信" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4658,21 +5417,21 @@ msgstr "" "\n" "コマンドラインの全オプションも%value%で報告されます(例:%volsize%)。不明または未設定の値は取り除かれます。" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "メッセージの本文" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "SMTPサーバーの認証が必要な場合に使用するパスワードを設定。" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTPのパスワード" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4684,11 +5443,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "電子メールの受信者" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4704,11 +5463,11 @@ msgstr "" "メールの送信者 \n" "メールの送信者 " -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "電子メールの送信者" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" @@ -4717,14 +5476,14 @@ msgstr "" "「{0}」、「{1}」、「{2}」、「{3}」のどれかを指定できます。\n" "「{0},{1}」のようにコンマで分けて複数のオプションを指定できます。「{4}」は「{0},{1},{2},{3}」の省略形で、バックアップに関する全ての操作について電子メールを送信します。" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "送信するメッセージ" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4737,68 +5496,68 @@ msgstr "" "SMTP over SSLを有効にする場合は smtps://example.com の形式でURLを指定してください。SMTP STARTTLSを有効にする場合は smtp://example.com:25/?starttls=when-available または smtp://example.com:25/?starttls=always の形式で指定してください。\n" "ポートが指定されていない場合は、SSLを有効にしない場合は25番ポート、SSL接続の場合は465番ポートを使用します。また、STARTTLSの使用を無効にする場合は smtp://example.com:25/?starttls=never の形式でURLを指定してください。" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTPのURL" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "電子メールの題名を指定できます。使用できる変数については --{0} の概要を確認してください。" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "メールの題名" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "SMTPサーバーの認証が必要な場合に使用するユーザー名を設定。" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTPのユーザー名" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "SMTPによる通信の全体:{0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "以下のサーバーで電子メールを送信できませんでした:{0}(メッセージ:{1})。{2}で再試行します。" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "以下のサーバーで電子メールを送信しました:{0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "このモジュールを使用すると、状態に関する報告をXMPPによるメッセージで送信できるようになります" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPPのレポートモジュール" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "メッセージの送信先となるユーザーを設定。コンマで区切って複数のユーザーを指定できます。" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPPのメッセージの受信者" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4820,35 +5579,35 @@ msgstr "" "\n" "コマンドラインの全オプションも%value%で報告されます(例:%volsize%)。不明または未設定の値は取り除かれます。" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "メッセージのひな型" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "メッセージの送信を行うアカウントのユーザー名を設定(ホスト名も含む)。例:「account@jabber.org/Home」" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPPのユーザー名" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "メッセージの送信を行うアカウントのパスワードを設定。" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPPのパスワード" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4857,109 +5616,109 @@ msgstr "" "「{0}」、「{1}」、「{2}」、「{3}」のどれかを指定できます。\n" "「{0},{1}」のようにコンマで分けて複数のオプションを指定できます。「{4}」は「{0},{1},{2},{3}」の省略形で、バックアップに関する全ての操作についてメッセージを送信します。" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "既定では、メッセージはバックアップの実行後にしか送信されません。このオプションを有効にすると、全ての操作についてメッセージを送信します。" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "全ての操作に関してメッセージを送信" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Jabberサーバーのログイン中にタイムアウトになりました" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" -msgstr "" +msgstr "このモジュールを使用すると、状態に関する報告をTelegramのメッセージで送信できるようになります" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." +msgstr "チャンネルのIDを設定。" + +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." -msgstr "" +msgstr "メッセージを送信するボットのAPIキーを設定。" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" -msgstr "" +msgstr "Telegramのサーバーへの送信中にタイムアウトになりました" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "このモジュールを使用すると、状態に関する報告をHTTPによるメッセージで送信できるようになります" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTPのレポートモジュール" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "HTTPのレポート用URLを設定。" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "HTTPのレポートのURL" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "メッセージを送信する際のパラメーターの名称を設定。" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "メッセージを送信する際のパラメーターの名称" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "HTTPのメッセージに追加するパラメーターを設定。例:「parameter1=value1¶meter2=value2」" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "HTTPのメッセージに追加するパラメーター" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "レポートを送信する際に使用する既定のHTTPリクエストのメソッドを変更できます。" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4968,11 +5727,11 @@ msgid "" msgstr "" "フォームにエンコードしたデータを送信する際のHTTPのレポート用URLを設定。セミコロンで区切り、複数のURLを入力できます。全てのURLは同一のデータを受信します。この設定は、フォーマットと、リクエストメソッドの設定を無視します。" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "フォーム形式のデータを送信する際のHTTPのレポート用URL" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " @@ -4980,41 +5739,41 @@ msgid "" msgstr "" "JSON形式のデータを送信する際のHTTPのレポート用URLを設定。セミコロンで区切り、複数のURLを入力できます。全てのURLは同一のデータを受信します。この設定は、フォーマットと、リクエストメソッドの設定を無視します。" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "JSON形式のデータを送信する際のHTTPのレポート用URL" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "メッセージを送信できませんでした: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "レポートに含むメッセージに関するログの水準を設定できます。" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" -msgstr "" +msgstr "メッセージに記録するログの水準を規定" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "レポートに含むオプションを定めるフィルターの表現を設定できます。" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "ログメッセージのフィルター" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "レポートに含むログの最大行数を設定できます。0または負の値を設定すると、最大行数は無制限になります。" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5023,16 +5782,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "このフォーマットはサポートされていません:{0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "不正なサイズが指定されました:{0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "SSL証明書の検証機能が不正な順序で呼び出されました" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5043,78 +5802,78 @@ msgstr "" "サーバーの証明書でエラー {0} が発生しました。ハッシュ値は{1}です。{2}エラーを無視して証明書を信頼する場合は、コマンドラインのオプション " "--{3}={1} を設定してください。{2}証明書は、これをOSの信頼プールにインポートすることもできます。" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "証明書のハッシュを検証できませんでした。エラーのメッセージは「{0}」、SSLのエラーの名称は「{1}」です。" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "一時フォルダーが存在しません:{0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "セグメントを解析できませんでした:{0}、不正な整数" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "不正な値が指定されました:{0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "未解析のデータ:{0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "URIが不正です: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "URIにホスト名がありません:{0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0}バイト" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N}ギガバイト" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N}キロバイト" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N}メガバイト" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N}テラバイト" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "文字列「{0}」を日付に変換できませんでした" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "同一のストリームに読み込みと書き込みを行うことはできません" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5342,11 +6101,11 @@ msgstr "コンソールの出力を無効にする" msgid "This link may provide additional information: {0}" msgstr "このリンクから追加の情報を確認できる可能性があります:{0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "自動アップデートを切り替える" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-ko.mo b/Localizations/duplicati/localization-ko.mo index 20faab210..7532bc77d 100644 Binary files a/Localizations/duplicati/localization-ko.mo and b/Localizations/duplicati/localization-ko.mo differ diff --git a/Localizations/duplicati/localization-ko.po b/Localizations/duplicati/localization-ko.po index 5071d08cb..b3ff66354 100644 --- a/Localizations/duplicati/localization-ko.po +++ b/Localizations/duplicati/localization-ko.po @@ -4,20 +4,20 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Soon Keun Kim , 2017 # Hyun Chang Lee, 2022 # arn junmo , 2024 -# Soon Keun Kim , 2024 -# Myung Joon Shin , 2024 # joyfuI , 2024 +# Myung Joon Shin , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: joyfuI , 2024\n" +"Last-Translator: Myung Joon Shin , 2024\n" "Language-Team: Korean (https://app.transifex.com/duplicati/teams/67655/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -48,8 +48,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -155,78 +155,94 @@ msgstr "GnuPG를 호출하는 동안 오류가 발생해도 프로그램은 출 msgid "Failure while invoking GnuPG, program won't terminate" msgstr "GnuPG를 호출하는 동안 오류가 발생해도 프로그램이 종료되지 않습니다." -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "별칭" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "기본값" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[더 이상 사용되지 않음]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "문자열" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "폴더가 이미 존재하므로 폴더를 만들 수 없습니다" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "요청한 폴더가 없습니다" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "취소됨" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "요청한 파일이 없습니다" @@ -347,9 +363,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -366,9 +381,8 @@ msgstr "서버에 연결하는 데 사용되는 사용자의 도메인 이름입 msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -379,9 +393,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "서버에 연결하는 데 사용되는 사용자 이름입니다. 환경 변수 \"AUTH_USERNAME\"로 제공될 수도 있습니다." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -463,41 +476,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "대체 FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -506,47 +509,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "서버에 연결하는 데 사용되는 암호입니다. 환경 변수 \"AUTH_PASSWORD\"로 제공될 수도 있습니다." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "업로드 검증 비활성화" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "{0} 폴더를 찾을 수 없음. 메시지: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "{0} 폴더를 찾을 수 없음. 메시지: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "업로드 검증 비활성화" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "이 플래그를 설정하면 FTP 데이터 연결 유형이 선택한 옵션으로 변경됩니다." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "FTP 데이터 연결 유형 구성" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "이 플래그가 설정되면 FTP 암호화 모드가 선택된 옵션으로 변경됩니다." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "FTP 암호화 모드 구성" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "암호화가 활성화되었을 때 사용할 SSL 정책 구성" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -842,6 +993,11 @@ msgstr "스토리지 클래스를 지정하려면이 옵션을 사용하십시 msgid "Specify storage class" msgstr "스토리지 클래스 지정" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -874,110 +1030,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "대체 FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "{0} 폴더를 찾을 수 없음. 메시지: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "이 플래그를 설정하면 FTP 데이터 연결 유형이 선택한 옵션으로 변경됩니다." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "FTP 데이터 연결 유형 구성" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "이 플래그가 설정되면 FTP 암호화 모드가 선택된 옵션으로 변경됩니다." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "FTP 암호화 모드 구성" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "암호화가 활성화되었을 때 사용할 SSL 정책 구성" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "파일 삭제 오류 : {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "파일 읽기 오류 : {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "파일 쓰기 오류 : {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "SSH 개인 / 공개 키 생성을위한 모듈" @@ -1459,7 +1511,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1467,11 +1523,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1480,11 +1536,11 @@ msgstr "" "데이터를 저장할 드라이브의 ID입니다. 드라이브를 지정하지 않으면 기본 OneDrive 또는 비즈니스 용 OneDrive 드라이브는 " "'{0}'을 (를) 통해 사용됩니다." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "드라이브 옵셔널 ID" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1492,28 +1548,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "사이트 ID" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "사이트 ID가 제공되지 않았습니다" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1525,41 +1581,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Group" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "그룹 ID" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "그룹 이메일 주소" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "지정된 이메일 주소를 가진 그룹을 찾을 수 없습니다 : {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "이메일 주소가 {0} 인 여러 그룹을 찾았습니다." -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1577,7 +1633,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1588,7 +1644,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1840,43 +1896,45 @@ msgstr "암호가 없습니다." msgid "No username given" msgstr "사용자 이름이 없습니다." -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1999,6 +2057,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2031,6 +2094,12 @@ msgid "" "does not support WebDAV connections" msgstr "서버가 WebDAV 연결을 지원하지 않음을 나타내는 오류 코드 {0} ({1})을 리턴했습니다" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "{0} 폴더를 찾을 수 없음. 메시지: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2056,6 +2125,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "모든 서버 인증서 수락" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2178,9 +2311,410 @@ msgstr "예기치 않은 오류 코드: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "다른 인스턴스가 실행 중이며 알림을 받았습니다." +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2200,13 +2734,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"지원되는 명령 줄 인수 :\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2235,9 +2767,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"필터가 파라미터 파일내에도 있을 경우 필터를 명령줄에 지정할 수 없습니다. 필터를 지정하려면 파라미터 파일내에 특수 옵션 --{0} 또는" -" --{1} , --{2}을 사용하세요. 각 필터는 반드시 a + 또는 a - 로 시작되어야 하며 복수개의 필터는 {3}와 함께 " -"사용되어야 합니다." #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2275,14 +2804,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "지원되지 않는 SQLite 버전 ({0})이 감지되었습니다. {1} 이상이어야합니다." +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2294,37 +2823,44 @@ msgstr "" msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." -msgstr "웹 서버가 수신하는 포트입니다. 쉼표를 사용하여 여러 값으로 제공될 수 있습니다." +msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." -msgstr "웹 서버가 SSL에 사용하는 PKCS # 12 형식의 인증서 및 키 파일입니다. RSA / DSA 키만 지원됩니다." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." +msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "인증서 PKCS #12 파일의 복호화를 위한 암호입니다." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"웹 서버가 수신하는 인터페이스입니다. 특수 값 \"*\"및 \"any\"는 모든 인터페이스를 의미합니다. 특수한 값 " -"\"loopback\"은 루프백 어댑터를 의미합니다." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"웹 서버에 액세스하는 데 필요한 암호입니다. 이 옵션은 저장되므로 각 실행에 설정할 필요가 없습니다. 빈 값을 설정하면 암호가 " -"비활성화됩니다." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2333,26 +2869,26 @@ msgstr "" "허용되는 호스트 이름은 세미콜론으로 구분됩니다. 호스트 이름이 \"*\"인 경우 모든 호스트 이름이 허용되며 호스트 이름 확인이 " "비활성화됩니다." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "데이터베이스에서 로그 데이터가 제거되는 시간을 설정하십시오." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "오래된 로그 데이터 정리" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2360,100 +2896,137 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"이 옵션은 로컬 설정 데이터베이스를 스크램블하는 데 사용되는 암호화 키를 설정합니다. 이 옵션은 환경 변수 {0}으로도 설정할 수 " -"있습니다. -{1} 옵션을 사용하여 데이터베이스 스크램블링을 비활성화하십시오." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "임시 저장 폴더" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2461,34 +3034,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" -msgstr "제공된 매개 변수를 사용하여 SSL 인증서를 만들 수 없습니다. 예외 세부 정보: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2503,32 +3112,32 @@ msgstr "{0} 어셈블리를 로드하지 못함. 오류 메시지: {1}" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "프로세스 유형 {0} 어셈블리 {1}을 로드하지 못했습니다. 오류 메시지 : {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "이 옵션은 사용할 압축 수준을 설정합니다. 0으로 설정하면 압축이 없고 9로 설정하면 최대 압축이됩니다." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2536,70 +3145,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "파일을 찾을 수 없습니다: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "* 실험 * : LZMA2를 지원하는 7z 아카이브." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "실험 -7z 아카이브" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "주어진 파일이 이 아카이브의 일부가 아닙니다" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "LZMA 2 압축에 사용 된 스레드 수입니다. 기본값은 프로세서 코어 수입니다." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "압축에 사용 된 스레드 수" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "" @@ -2992,48 +3568,66 @@ msgstr "백업되는 파일 크기 제한" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "스레드 우선 순위" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "볼륨의 크기를 제한" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3041,11 +3635,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "압축에 사용할 모듈을 선택하십시오" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3053,27 +3647,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3088,88 +3682,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3178,12 +3774,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3196,11 +3792,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3213,30 +3820,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3244,44 +3852,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3290,22 +3898,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3317,11 +3925,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3331,11 +3939,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "하드링크 처리" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3343,11 +3951,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3355,41 +3963,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "백업 이름" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3401,11 +4019,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3413,73 +4031,65 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "해싱에 사용되는 블록 크기" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "변경 사항을 검사 할 파일 목록" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "로컬 상태 데이터베이스의 경로" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" "삭제 된 파일 목록\n" " " -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "시작할 때 백엔드를 쿼리하지 마십시오" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3488,11 +4098,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3500,43 +4110,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "블록에 사용되는 해시 알고리즘" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "파일에 사용 된 해시 알고리즘입니다" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3544,11 +4154,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "자동 압축 비활성화" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3556,62 +4166,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "작은 볼륨의 최대 수" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "복원시 로컬 파일 데이터 사용" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "여러 버전 유지" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "백업이 유지되는 시간을 설정하려면 이 옵션을 사용하십시오." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "모든 버전을 기간 내에 유지" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3623,39 +4233,39 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "오래된 중간 백업을 삭제하여 버전 수를 줄입니다." -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "일부 소스 항목이 누락 된 경우에도 이 옵션을 사용하여 계속하십시오." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "누락 된 소스 요소 무시" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "복원시 파일 덮어 쓰기" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "더 많은 진행 정보 출력합니다" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3663,11 +4273,11 @@ msgstr "" "모든 파일 이름을 포함하여 작업의 결과로 생성 된 출력량을 늘리려면\n" "이 옵션을 사용하십시오." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "전체 결과 출력" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3675,11 +4285,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "확인 파일이 업로드되었는지 확인" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3689,11 +4299,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "백업 후 테스트 할 샘플 수" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3703,11 +4313,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "백업 후 테스트 할 샘플의 비율" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3719,111 +4329,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "파일 읽기 버퍼의 크기" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "암호 변경 허용" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "파일세트만 나열" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "파일 권한 복원" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "복원 된 파일 검사 건너 뛰기" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "로컬 데이터를 사용하지 마십시오" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "블록 해시 확인" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3831,11 +4441,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "경로가 있는 데이터베이스 복구" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3843,74 +4453,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "데이터 해싱을 수행하는 프로세스 수를 설정하려면 이 옵션을 사용하십시오." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "출력 데이터 압축을 수행하는 프로세스 수를 설정하려면 이 옵션을 사용하십시오." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3918,11 +4528,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3930,11 +4540,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3942,11 +4552,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "모든 파일 세트 제거 허용" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3956,11 +4566,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3968,22 +4578,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "미리 읽기 스캐너 비활성화" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3991,15 +4601,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "배터리 사용시 백업 비활성화" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "로그 파일 정보 수준" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4009,42 +4619,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4052,11 +4662,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "폴더를 제외한 파일 이름 목록" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4064,11 +4674,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4076,11 +4686,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4089,11 +4699,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4102,11 +4712,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4114,11 +4724,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4126,57 +4736,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "기존 백업에 대해 암호를 변경할 수 없습니다" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4230,29 +4989,6 @@ msgstr "" msgid "Configure http requests" msgstr "http 요청 구성" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "모든 서버 인증서 수락" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4338,131 +5074,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Hyper-V 모듈 구성" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Microsoft SQL Server 모듈 구성" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "스크립트 실행" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "종료시 스크립트 실행" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "시작할 때 필요한 스크립트를 실행" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "시작할 때 스크립트 실행" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "메일 보내기" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4475,21 +5223,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "메시지 본문" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP 암호" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4497,11 +5245,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4511,25 +5259,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "이메일 발신자" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "보낼 메시지" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4537,68 +5285,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP 보고서 모듈" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4611,144 +5359,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "메시지 템플릿" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP 사용자 이름" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP 암호" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "모든 작업을 이메일로 보냅니다" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP 보고서 모듈" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4756,52 +5504,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4810,16 +5558,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4828,78 +5576,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "같은 스트림에서 읽고 쓸 수 없습니다" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5116,11 +5864,11 @@ msgstr "콘솔 출력 비활성화" msgid "This link may provide additional information: {0}" msgstr "이 링크에서 추가 정보를 제공할 수 있습니다: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "자동 업데이트 켜기/끄기" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-nl_NL.mo b/Localizations/duplicati/localization-nl_NL.mo index 632b54079..b31fe115d 100644 Binary files a/Localizations/duplicati/localization-nl_NL.mo and b/Localizations/duplicati/localization-nl_NL.mo differ diff --git a/Localizations/duplicati/localization-nl_NL.po b/Localizations/duplicati/localization-nl_NL.po index fef3004fb..23c69cc22 100644 --- a/Localizations/duplicati/localization-nl_NL.po +++ b/Localizations/duplicati/localization-nl_NL.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Kees Zaaijer, 2024\n" "Language-Team: Dutch (Netherlands) (https://app.transifex.com/duplicati/teams/67655/nl_NL/)\n" @@ -48,8 +48,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "Stel het threadniveau in dat wordt gebruikt voor codering." -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "De optie --{0} is niet langer in gebruik en is verouderd." @@ -138,7 +138,7 @@ msgstr "Gebruik GPG Armor" #: Library/Encryption/Strings.cs:52 msgid "Override the GPG command supplied for decryption." -msgstr "" +msgstr "Overschrijf de GPG-opdracht die voor decodering is opgegeven." #: Library/Encryption/Strings.cs:53 msgid "The GPG decryption command" @@ -150,6 +150,8 @@ msgid "" "Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." msgstr "" +"Overschrijf de standaard GPG-coderingsopdracht \"{0}\". Normaal gesproken " +"wordt asymmetrische codering aangevraagd met de instelling {1}." #: Library/Encryption/Strings.cs:55 msgid "The GPG encryption command" @@ -169,78 +171,96 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Fout bij het uitvoeren van GnuPG, programma wil niet beëindigen" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "Sleutel moet minstens 8 tekens lang zijn." + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "Sleutel mag niet leeg zijn" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "Encryptie met een op een zwarte lijst geplaatste sleutel gwweigerd" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliassen" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "standaard waarde" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[VEROUDERD]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "waarden" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booleaans" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Opsomming" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Vlaggen" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Integer" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Pad" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Grootte" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Tijdspanne" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Onbekend" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "De map kan niet worden aangemaakt omdat die reeds bestaat" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "De opgevraagde map bestaat niet" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Afgebroken" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +"Coderingssleutel die gebruikt wordt voor het coderen van de doelinstellingen" +" komt niet overeen met de huidige sleutel." + +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "Encryptiesleutel ontbreekt." #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Het opgevraagde bestand bestaat niet" @@ -374,9 +394,8 @@ msgstr "" " worden opgegeven met de omgevingsvariabele \"AUTH_PASSWORD\". Als het " "wachtwoord is opgegeven, moet --{0} eveneens worden ingesteld." -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -384,6 +403,8 @@ msgstr "" #: Library/Backend/WEBDAV/Strings.cs:29 msgid "Supply the password used to connect to the server" msgstr "" +"Geef het wachtwoord op dat wordt gebruikt om verbinding te maken met de " +"server" #: Library/Backend/OpenStack/Strings.cs:32 msgid "The domain name of the user used to connect to the server." @@ -393,11 +414,10 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:33 msgid "Supply the domain used to connect to the server" -msgstr "" +msgstr "Geef het domein op dat wordt gebruikt om te verbinden met de server" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -411,9 +431,8 @@ msgstr "" "Dit kan eveneens worden opgegeven in een omgevingsvariabele " "\"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -421,6 +440,8 @@ msgstr "" #: Library/Backend/WEBDAV/Strings.cs:31 msgid "Supply the username used to connect to the server" msgstr "" +"Geef de gebruikersnaam op die wordt gebruikt om verbinding te maken met de " +"server" #: Library/Backend/OpenStack/Strings.cs:36 msgid "" @@ -435,6 +456,8 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:37 msgid "Supply the Tenant Name used to connect to the server" msgstr "" +"Geef de Tenant-naam op die gebruikt wordt om verbinding te maken met de " +"server" #: Library/Backend/OpenStack/Strings.cs:38 msgid "" @@ -447,6 +470,8 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:39 msgid "Supply the API key used to connect to the server" msgstr "" +"Geef de API sleutel op die gebruikt wordt om verbinding te maken met de " +"server" #: Library/Backend/OpenStack/Strings.cs:40 #, csharp-format @@ -460,7 +485,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:41 msgid "Supply the authentication URL" -msgstr "" +msgstr "Geef de authenticatie-URL op" #: Library/Backend/OpenStack/Strings.cs:42 msgid "The keystone API version to use. Valid values are 'v2' and 'v3'." @@ -486,6 +511,7 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:45 msgid "Supply the region used for creating a container" msgstr "" +"Geef de regio op die gebruikt wordt voor het aanmaken van een container" #: Library/Backend/OpenStack/OpenStackConfig.cs:67 msgid "OpenStack configuration module" @@ -493,7 +519,7 @@ msgstr "OpenStack configuratiemodule" #: Library/Backend/OpenStack/OpenStackConfig.cs:69 msgid "Expose OpenStack configuration as a web module" -msgstr "" +msgstr "Toon OpenStack-configuratie als een webmodule" #: Library/Backend/OpenStack/OpenStackConfig.cs:77 #: Library/Backend/GoogleServices/GCSConfig.cs:81 @@ -505,9 +531,9 @@ msgstr "De configuratie die moet worden opgehaald" #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 msgid "Provide different config values" -msgstr "" +msgstr "Geef verschillende configuratiewaarden op" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " @@ -517,40 +543,27 @@ msgstr "" "backend. Toegestane formaten zijn \"ftp://hostname/folder\" en " "\"ftp://username:password@hostname/folder\"." -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" +"Deze backend kan gegevens lezen en schrijven naar een FTP-gebaseerde " +"backend. Toegestane formaten zijn \"aftp://hostname/folder\" en " +"\"aftp://username:password@hostname/folder\"." + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" -"Activeer deze optie om een FTP-verbinding tot stand te brengen in actieve " -"modus. Zelfs als de optie --{0} ook is ingesteld, zal de verbinding tot " -"stand gebracht worden in actieve modus." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternatieve FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" -"Activeer deze optie om een FTP-verbinding tot stand te brengen in passieve " -"modus, wat beter werkt met sommige firewalls. Als de optie --{0} ook is " -"ingesteld, wordt deze optie genegeerd." - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -561,41 +574,36 @@ msgstr "" "Het wachtwoord dat wordt gebruikt om verbinding te maken met de server. Dit " "kan eveneens worden opgegeven in een omgevingsvariabele \"AUTH_PASSWORD\"" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" -"Gebruik deze optie om te communiceren door middel van Secure Sockets Layer " -"(SSL) over ftp (ftps)." - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" +"Gebruik deze optie om FTP-dialogen te loggen naar het terminal-venster voor " +"foutopsporingsdoeleinden." #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." -msgstr "" -"Om bescherming te bieden tegen netwerkstoringen, zal voor iedere upload " -"geprobeerd worden te verifiëren. Gebruik deze optie om deze verificatie uit " -"te schakelen, zodat uploads sneller verlopen, maar minder betrouwbaar zijn." +msgid "Log FTP dialog to terminal console" +msgstr "Log FTP dialoogvenster naar terminalconsole" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Upload verificatie uitschakelen" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" +"Gebruik deze optie om FTP PRIVATE-info (username, password) te loggen naar " +"de console voor foutopsporingsdoeldinden (PLAATS DEZE NIET OP HET INTERNET!)" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "De map {0} is niet gevonden, melding: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "Log FTP PERSOONLIJKE info naar de console" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "De map {0} is niet gevonden. Melding: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " @@ -604,14 +612,191 @@ msgstr "" "Het bestand {0} werd geüpload maar kon nadien niet worden gevonden. De " "bestandenlijst gaf {1}" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Het bestand {0} is geüpload maar de teruggegeven grootte is {1}, terwijl {2}" +" werd verwacht" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Om bescherming te bieden tegen netwerk- of serverstoringen, zal voor iedere " +"upload geprobeerd worden te verifiëren. Gebruik deze optie om deze " +"verificatie uit te schakelen, zodat uploads sneller verlopen, maar minder " +"betrouwbaar zijn." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Upload verificatie uitschakelen" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Als deze vlag is ingesteld, zal het FTP gegevensverbindingtype worden " +"veranderd in de geselecteerde optie." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Stel het FTP gegevensverbindingtype in." + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Als deze vlag is ingesteld, zal de FTP encryptiemodus worden veranderd in de" +" geselecteerde optie." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Stel de FTP encryptiemodus in" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Deze vlag bepaalt het SSL beleid dat gebruikt wordt als encryptie is " +"ingeschakeld." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Stel het SSL beleid in dat gebruikt moet worden als encryptie is " +"ingeschakeld." + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Sommige FTP servers hebben een kleine vertraging nodig voordat de juiste " +"bestandsgrootte wordt gerapporteerd. De benodigde vertraging hangt af van de" +" netwerktopologie. Als fouten worden weergegeven die verband houden met een " +"niet overeenkomende upload-grootte, probeer dan een aantal seconden " +"vertraging toe te voegen" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Voeg een vertraging toe na het uploaden van een bestand" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "Fout bij het verwijderen van bestand: {0}, fout: {1}" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "Fout bij het lezen van bestand: {0}, fout: {1}" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "Fout bij het schrijven naar bestand: {0}, fout: {1}" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" +"Gebruik deze optie om te communiceren door middel van Secure Sockets Layer " +"(SSL) over ftp (ftps)." + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "Laat Duplicati een SSL (ftps) verbinding gebruiken" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" +"Activeer deze optie om een FTP-verbinding tot stand te brengen in actieve " +"modus. Zelfs als de optie --{0} ook is ingesteld, zal de verbinding tot " +"stand gebracht worden in actieve modus." + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "Schakel tussen de FTP verbindingmethodes" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" +"Activeer deze optie om een FTP-verbinding tot stand te brengen in passieve " +"modus, wat beter werkt met sommige firewalls. Als de optie --{0} ook is " +"ingesteld, wordt deze optie genegeerd." + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" -"Het bestand {0} werd geüpload maar de teruggegeven grootte was {1} en er " -"werd verwacht dat het {2} zou zijn." #: Library/Backend/GoogleServices/GCSConfig.cs:71 msgid "Google Cloud Storage configuration module" @@ -619,7 +804,7 @@ msgstr "Google Cloud Storage configuratiemodule" #: Library/Backend/GoogleServices/GCSConfig.cs:73 msgid "Expose Google Cloud Storage configuration as a web module" -msgstr "" +msgstr "Toon de Google Cloud Storage-configuratie als een webmodule" #: Library/Backend/GoogleServices/Strings.cs:27 msgid "" @@ -675,7 +860,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:35 msgid "Specify location option for creating a bucket" -msgstr "" +msgstr "Geef de locatie-optie op voor het aanmaken van een bucket" #: Library/Backend/GoogleServices/Strings.cs:36 #, csharp-format @@ -688,7 +873,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:38 msgid "Specify storage class for creating a bucket" -msgstr "" +msgstr "Geef opslagklasse op voor het aanmaken van een bucket" #: Library/Backend/GoogleServices/Strings.cs:39 msgid "" @@ -702,7 +887,7 @@ msgstr "" #: Library/Backend/GoogleServices/Strings.cs:40 msgid "Specify project for creating a bucket" -msgstr "" +msgstr "Geef project op voor het aanmaken van een bucket" #: Library/Backend/GoogleServices/Strings.cs:44 msgid "" @@ -768,6 +953,8 @@ msgstr "" #: Library/Backend/CloudFiles/Strings.cs:33 msgid "Supply the access key used to connect to the server" msgstr "" +"Geef de toegangssleutel op die gebruikt wordt om verbinding te maken met de " +"server" #: Library/Backend/CloudFiles/Strings.cs:34 #, csharp-format @@ -792,6 +979,8 @@ msgstr "" #: Library/Backend/CloudFiles/Strings.cs:37 msgid "Supply the username used to authenticate with CloudFiles" msgstr "" +"Geef de gebruikersnaam op die gebruikt wordt om te authentiseren met " +"CloudFiles" #: Library/Backend/CloudFiles/Strings.cs:38 msgid "MD5 Hash (ETag) verification failed" @@ -824,7 +1013,7 @@ msgstr "S3 configuratiemodule" #: Library/Backend/S3/S3Config.cs:77 msgid "Expose S3 configuration as a web module" -msgstr "" +msgstr "Toon de S3 configuratie als een webmodule" #: Library/Backend/S3/Strings.cs:26 msgid "" @@ -883,7 +1072,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:40 msgid "Specify S3 location constraints" -msgstr "" +msgstr "Geef S3 locatiebeperkingen op" #: Library/Backend/S3/Strings.cs:41 #, csharp-format @@ -896,7 +1085,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:43 msgid "Specify an alternate S3 server name" -msgstr "" +msgstr "Geef een alternatieve S3 servernaam op" #: Library/Backend/S3/Strings.cs:44 msgid "" @@ -908,7 +1097,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:45 msgid "Specify the S3 client library to use" -msgstr "" +msgstr "Geef de S3 client-bibliotheek op die gebruikt moet worden" #: Library/Backend/S3/Strings.cs:46 msgid "" @@ -923,7 +1112,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:47 Library/Backend/WEBDAV/Strings.cs:40 #: Library/Backend/TahoeLAFS/Strings.cs:27 msgid "Instruct Duplicati to use an SSL (https) connection" -msgstr "" +msgstr "Geef Duplicati de opdracht om een SSL (https) verbinding te gebruiken" #: Library/Backend/S3/Strings.cs:48 msgid "" @@ -949,13 +1138,18 @@ msgstr "" msgid "Specify storage class" msgstr "Geef een opslag-klasse op" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "S3 IAM ondersteuningsmodule" #: Library/Backend/S3/S3IAM.cs:73 msgid "Expose S3 IAM manipulation as a web module" -msgstr "" +msgstr "Toon S3 IAM manipulatie als een webmodule" #: Library/Backend/S3/S3IAM.cs:81 msgid "The operation to perform" @@ -963,7 +1157,7 @@ msgstr "De uit te voeren bewerking" #: Library/Backend/S3/S3IAM.cs:81 msgid "Select the operation to perform" -msgstr "" +msgstr "Selecteer de uit te voeren bewerking" #: Library/Backend/S3/S3IAM.cs:82 msgid "The username" @@ -981,136 +1175,6 @@ msgstr "Het wachtwoord" msgid "The Amazon Secret Key" msgstr "De Amazon Geheime Sleutel" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" -"Deze backend kan gegevens lezen en schrijven naar een FTP-gebaseerde backend" -" met een alternatieve FTP client. Toegestane indelingen zijn " -"\"aftp://hostname/folder\" en \"aftp://username:password@hostname/folder\"." - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternatieve FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" -"Gebruik deze optie om FTP-dialogen te loggen naar het terminal-venster voor " -"foutopsporingsdoeleinden." - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "Log FTP dialoogvenster naar terminalconsole" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" -"Gebruik deze optie om FTP PRIVATE-info (username, password) te loggen naar " -"de console voor foutopsporingsdoeldinden (PLAATS DEZE NIET OP HET INTERNET!)" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "Log FTP PERSOONLIJKE info naar de console" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "De map {0} is niet gevonden. Melding: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Het bestand {0} is geüpload maar de teruggegeven grootte is {1}, terwijl {2}" -" werd verwacht" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Om bescherming te bieden tegen netwerk- of serverstoringen, zal voor iedere " -"upload geprobeerd worden te verifiëren. Gebruik deze optie om deze " -"verificatie uit te schakelen, zodat uploads sneller verlopen, maar minder " -"betrouwbaar zijn." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Als deze vlag is ingesteld, zal het FTP gegevensverbindingtype worden " -"veranderd in de geselecteerde optie." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Stel het FTP gegevensverbindingtype in." - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Als deze vlag is ingesteld, zal de FTP encryptiemodus worden veranderd in de" -" geselecteerde optie." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Stel de FTP encryptiemodus in" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Deze vlag bepaalt het SSL beleid dat gebruikt wordt als encryptie is " -"ingeschakeld." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Stel het SSL beleid in dat gebruikt moet worden als encryptie is " -"ingeschakeld." - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Sommige FTP servers hebben een kleine vertraging nodig voordat de juiste " -"bestandsgrootte wordt gerapporteerd. De benodigde vertraging hangt af van de" -" netwerktopologie. Als fouten worden weergegeven die verband houden met een " -"niet overeenkomende upload-grootte, probeer dan een aantal seconden " -"vertraging toe te voegen" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Voeg een vertraging toe na het uploaden van een bestand" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Fout bij het verwijderen van bestand: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Fout bij het lezen van bestand: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Fout bij het schrijven naar bestand: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Module voor het genereren van SSH persoonlijke/openbare sleutels" @@ -1197,6 +1261,8 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:53 msgid "Supply server fingerprint used for validation of server identity" msgstr "" +"Geef server-vingerafdruk op die gebruikt wordt voor validatie van " +"serveridentiteit" #: Library/Backend/SSHv2/Strings.cs:54 msgid "" @@ -1210,7 +1276,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:55 msgid "Disable fingerprint validation" -msgstr "" +msgstr "Schakel vingerafdruk-validatie uit" #: Library/Backend/SSHv2/Strings.cs:56 msgid "" @@ -1218,10 +1284,14 @@ msgid "" "supplied is used to decrypt it. If the keyfile is specified, the password is" " not used to authenticate." msgstr "" +"Verwijs naar een geldig OpenSSH-sleutelbestand. Als het bestand is " +"gecodeerd, wordt het opgegeven wachtwoord gebruikt om het te decoderen. Als " +"deze optie wordt opgegeven, wordt het wachtwoord niet gebruikt voor " +"authenticatie." #: Library/Backend/SSHv2/Strings.cs:57 Library/Backend/SSHv2/Strings.cs:59 msgid "Use a SSH private key to authenticate" -msgstr "" +msgstr "Gebruik een SSH sleutelbestand om te authentiseren" #: Library/Backend/SSHv2/Strings.cs:58 #, csharp-format @@ -1246,7 +1316,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:61 msgid "Set the operation timeout value" -msgstr "" +msgstr "Stel de time-out waarde van de bewerking in" #: Library/Backend/SSHv2/Strings.cs:62 msgid "" @@ -1263,7 +1333,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:63 msgid "Set a keepalive value" -msgstr "" +msgstr "Stel de keepalive-waarde in" #: Library/Backend/SSHv2/Strings.cs:64 #, csharp-format @@ -1690,19 +1760,26 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Of de klasse HttpClient moet worden gebruikt" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" " Microsoft Graph API. Usage of this backend requires that you agree to the " "terms in {0} ({1}) and {2} ({3})." msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" +"Sla bestanden op in Microsoft OneDrive of Microsoft OneDrive voor Business " +"via de Microsoft Graph API. Gebruik van deze backend vereist u akkoord gaat " +"met de voorwaarden in {0} ({1}) en {2} ({3})." #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "Microsoft OneDrive" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1712,40 +1789,43 @@ msgstr "" " de standaard drive van OneDrive of OneDrive for Business worden gebruikt " "via '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Optionele ID van de drive" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " "Usage of this backend requires that you agree to the terms in {0} ({1}) and " "{2} ({3})." msgstr "" +"Sla bestanden op in een Microsoft SharePoint site via de Microsoft Graph " +"API. Gebruik van deze backend vereist dat u akkoord gaat met de voorwaarden " +"in {0} ({1}) en {2} ({3})." -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "ID dan de site waarin gegevens worden opgeslagen." -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID van de site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Er is geen site ID opgegeven" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "Tegenstrijdige site-ID's gebruikt: opgegeven {0} maar gevonden {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1756,42 +1836,50 @@ msgid "" "specify the SharePoint site's ID via --{0}). Usage of this backend requires " "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" +"Sla bestanden op in een Microsoft Office 365 Groep via de Microsoft Graph " +"API. Toegestane formaten zijn " +"\"sharepoint://tenant.sharepoint.com/{{PathToWeb}}//{{Documents}}/subfolder\"" +" (waarbij \"//\" optioneel kan worden gebruikt om de hoofdmap van het " +"document aan te geven) en \"sharepoint://subfolder\" (in welk geval u ook " +"expliciet de ID van de SharePoint-site moet opgeven via --{0}). Gebruik van " +"deze backend vereist dat u akkoord gaat met de voorwaarden in {1} ({2}) and " +"{3} ({4})." -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Groep" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "ID van de groep waarin gegevens worden opgeslagen." -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID van de groep" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "E-mailadres van de groep waarin gegevens worden opgeslagen." -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "E-mailadres van de groep" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "Geen groep-ID of groep-e-mailadres opgegeven." -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Er zijn geen groepen gevonden met het opgegeven e-mailadres: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Meerdere groepen zijn gevonden met het opgegeven e-mailadres: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "Tegenstrijdige groep ID's gebruikt: opgegeven {0} maar gevonden {1}" @@ -1809,7 +1897,7 @@ msgid "Access Key ID is used to identify the user." msgstr "Toegangssleutel-ID wordt gebruikt om de gebruiker te identificeren." #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "Toegangscode ID" @@ -1823,7 +1911,7 @@ msgstr "" "handtekening-tekenreeksen te verifiëren." #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "Toegangssleutel Geheim" @@ -1973,7 +2061,7 @@ msgstr "" #: Library/Backend/TencentCOS/Strings.cs:38 msgid "Specify COS location constraints" -msgstr "" +msgstr "Specificeer COS-locatiebeperkingen" #: Library/Backend/TencentCOS/Strings.cs:39 msgid "" @@ -2028,7 +2116,7 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:34 msgid "Supply the backup device to use" -msgstr "" +msgstr "Geef het te gebruiken back-up apparaat op" #: Library/Backend/Jottacloud/Strings.cs:35 #, csharp-format @@ -2047,7 +2135,7 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:36 msgid "Supply the mount point to use on the server" -msgstr "" +msgstr "Geef het koppelpunt op dat gebruikt moet worden op de server." #: Library/Backend/Jottacloud/Strings.cs:37 msgid "" @@ -2111,15 +2199,17 @@ msgstr "Geen wachtwoord opgegeven" msgid "No username given" msgstr "Geen gebruikersnaam opgegeven" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." -msgstr "Deze backend kan gegevens lezen en schrijven naar IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." +msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "IDrive e2" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." @@ -2128,7 +2218,7 @@ msgstr "" "Toegangssleutel-geheim kan verkregen worden na inloggen op uw IDrive " "e2-account. Deze kan ook worden opgegeven met de optie --{0}." -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " @@ -2137,7 +2227,7 @@ msgstr "" "Toegangssleutel-ID kan verkregen worden na inloggen op uw IDrive e2-account." " Deze kan ook worden opgegeven met de optie --{0}." -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." @@ -2145,15 +2235,15 @@ msgstr "" "De \"bucketnaam of volledig pad\" is de naam van de doelbucket of het " "volledige pad van een map in de bucket." -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "De \"bucketnaam of volledig pad\"" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "Geen Toegangssleutelgeheim opgegeven" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "Geen Toegangssleutel-ID opgegeven" @@ -2306,6 +2396,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2351,6 +2446,12 @@ msgstr "" "De server stuurde foutcode {0} ({1}) terug, wat aangeeft dat de server geen " "ondersteuning biedt voor WebDAV verbindingen" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "De map {0} is niet gevonden, melding: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2383,6 +2484,77 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Dump de PROPFIND respons" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" +"Gebruik deze optie om ieder servercertificaat te accepteren, ongeacht welke " +"fouten het heeft. Gebruik in plaats hiervan --{0} wanneer mogelijk." + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Accepteer ieder servercertificaat" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" +"Als uw servercertificaat wordt gerapporteerd als onveilig (bijv. bij zelf-" +"ondertekende certificaten), kan de certificaat-hash (SHA1) opgegeven worden " +"om het toch goed te keuren. De hash-waarde moet in hexadecimale indeling " +"worden ingevoerd, zonder spaties of dubbele punten. U kunt meerdere hashes " +"invoeren, gescheiden door komma's." + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Accepteer optioneel een bekend SSL certificaat" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2405,7 +2577,7 @@ msgstr "Storj DCS configuratiemodule" #: Library/Backend/Storj/StorjConfig.cs:46 msgid "Expose Storj DCS configuration as a web module" -msgstr "" +msgstr "Toon Storj DCS configuratie als een webmodule" #: Library/Backend/Storj/Strings.cs:27 msgid "This backend can read and write data to the Storj DCS." @@ -2524,6 +2696,407 @@ msgstr "" "De OAuth service is momenteel over quota. Probeer het opnieuw over een paar " "uur" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "Een andere instance is in uitvoering, en was aangekondigd" @@ -2539,7 +3112,7 @@ msgstr "" #: Library/RestAPI/Strings.cs:12 msgid "Display this help" -msgstr "" +msgstr "Geef deze helptekst weer" #: Library/RestAPI/Strings.cs:13 msgid "" @@ -2568,6 +3141,18 @@ msgid "" "parameter file. Each filter must be prefixed with either a + or a -, and " "multiple filters must be joined with {5} " msgstr "" +"Gebruik deze optie om sommige of alle opties op te slaan die worden " +"opgegeven aan de opdrachtregel-client. Het bestand moet een alleen-" +"tekstbestand zijn, UTF-8 codering heeft de voorkeur. Iedere regel in het " +"bestand moet het formaat --optie=waarde hebben. De speciale opties --{0} en " +"--{1} kunnen worden gebruikt om respectievelijk het lokale pad en de URI van" +" het externe doel te overschrijven. De opties in dit bestand hebben voorrang" +" boven de opties die vanaf de opdrachtregel worden opgegeven. Filters kunnen" +" niet zowel in het bestand als vanaf de opdrachtregel worden opgegeven. " +"Gebruik daarvoor in de plaats de speciale --{2}, --{3}, of --{4} opties om " +"filters op te geven binnen het parameter-bestand. Ieder filter moet worden " +"voorafgegaan door een + of een -, en meerdere filters moeten worden " +"samengevoegd met {5}" #: Library/RestAPI/Strings.cs:18 CommandLine/CLI/Strings.cs:45 msgid "Path to a file with parameters" @@ -2594,17 +3179,20 @@ msgstr "Parameters bestand \"{0}\" kan niet gelezen worden, reden: {1}" #: Library/RestAPI/Strings.cs:22 msgid "Output log information to the file given" -msgstr "" +msgstr "Voer log-informatie uit naar een opgegeven bestand" #: Library/RestAPI/Strings.cs:23 msgid "Determine the amount of information written in the log file" msgstr "" +"Bepaal de hoeveelheid informatie die wordt geschreven naar het logbestand" #: Library/RestAPI/Strings.cs:24 msgid "" "Activate portable mode where the database is placed below the program " "executable" msgstr "" +"Activeer portable mode waarbij de database wordt opgeslagen onder het " +"uitvoerbaar bestand van het programma" #: Library/RestAPI/Strings.cs:25 #, csharp-format @@ -2614,7 +3202,7 @@ msgstr "Een ernstige fout trad op in Duplicati: {0}" #: Library/RestAPI/Strings.cs:26 #, csharp-format msgid "An error occurred on server tear down: {0}" -msgstr "" +msgstr "Er is een fout opgetreden bij het verwijderen van de server: {0}" #: Library/RestAPI/Strings.cs:27 #, csharp-format @@ -2622,10 +3210,12 @@ msgid "" "Unable to start up. Perhaps another process is already running?\n" "Error message: {0}" msgstr "" +"Opstarten niet mogelijk. Er loopt wellicht al een ander proces?\n" +"Foutmelding: {0}" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" -msgstr "" +msgstr "Schakel database-versleuteling uit" #: Library/RestAPI/Strings.cs:30 #, csharp-format @@ -2639,6 +3229,8 @@ msgid "" "The path to the folder where the static files for the webserver is present. " "The folder must be located beneath the installation folder." msgstr "" +"Het pad naar de map waar statische bestanden voor de webserver zich " +"bevinden. De map moet zich bevinden onder de installatiemap." #: Library/RestAPI/Strings.cs:32 msgid "" @@ -2650,19 +3242,29 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Het certificaat en het sleutelbestand in PKCS #12 indeling die de webserver " -"gebruikt voor SSL. Alleen RSA/DSA sleutels worden ondersteund." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" -"Het wachtwoord voor het ontsleutelen van het certificaat PKCS #12 bestand." #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" +"Het certificaat en het sleutelbestand in PKCS #12 indeling die de webserver " +"gebruikt voor SSL." + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." @@ -2671,7 +3273,7 @@ msgstr "" "\"any\" betekent iedere willekeurige interface. De speciale waarde " "\"loopback\" betekent de loopback adapter." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " @@ -2681,7 +3283,7 @@ msgstr "" " opgeslagen, dus het is niet nodig dit bij iedere uitvoering in te stellen. " "Het instellen van een lege waarde schakelt het wachtwoord uit." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2691,26 +3293,29 @@ msgstr "" "van de hostnamen \"*\" is, zijn alle hostnamen toegestaan en is controle van" " de hostnaam uitgeschakeld." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" +"Indien actief als server, moet de service deamon controleren dat het proces " +"reageert. Als deze optie wordt ingeschakeld, leest de server stdin en " +"schrijft een antwoord op iedere gelezen regel." -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" -msgstr "" +msgstr "Schakel de ping-pong responder in" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Stel de tijd in waarna log-gegevens worden gewist uit de database." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Schoon oude log-gegevens op" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2721,11 +3326,11 @@ msgstr "" "deze optie om te kiezen waar de instellingen worden opgeslagen. Deze optie " "kan ook worden ingesteld met de omgevingsvariabele {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" -msgstr "" +msgstr "Stel de map in waar instellingen worden opgeslagen" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2737,11 +3342,11 @@ msgstr "" "ingesteld met de omgevingsvariabele {0}. Gebruik de optie --{1} om het " "versleutelen van de database uit te schakelen." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" -msgstr "" +msgstr "Stel de database-encryptiesleutel in" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " @@ -2752,91 +3357,173 @@ msgstr "" " ook tijdelijke bestanden van SQLite worden in deze tijdelijke map " "geplaatst." -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Tijdelijke opslagmap" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" +"Stel de JWT-configuratie opnieuw in, waardoor alle uitgegeven inlogtokens " +"ongeldig worden" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" -msgstr "" +msgid "Disable the visual captcha" +msgstr "Schakel de visuele captcha uit" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 -msgid "The Windows event log is not supported on this platform" -msgstr "" - -#: Library/RestAPI/Strings.cs:58 -#, csharp-format -msgid "Server has started and is listening on port {0}" +msgid "Use this option to disable database encryption of sensitive fields" msgstr "" +"Gebruik deze optie om database-codering van gevoelige velden uit te " +"schakelen" #: Library/RestAPI/Strings.cs:59 -#, csharp-format -msgid "Use the following link to sign in: {0}" +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." msgstr "" #: Library/RestAPI/Strings.cs:60 -#, csharp-format -msgid "The server crashed: {0}" -msgstr "" +msgid "Log to the Windows event log" +msgstr "Schrijf logboekvermeldingen naar het Windows-gebeurtenislogboek" #: Library/RestAPI/Strings.cs:61 -msgid "Require database encryption" +msgid "Use this option to set the log level for the Windows event log." msgstr "" #: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" +msgid "Set the log level for the Windows event log" +msgstr "Stel het log-niveau in voor het Windows gebeurtenislogboek" #: Library/RestAPI/Strings.cs:63 #, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 +msgid "The Windows event log is not supported on this platform" +msgstr "Het Windows gebeurtenislogboek wordt niet ondersteund op dit platform" + +#: Library/RestAPI/Strings.cs:66 +#, csharp-format +msgid "Server has started and is listening on port {0}" +msgstr "Server is gestart en luistert op poort {0}" + +#: Library/RestAPI/Strings.cs:67 +#, csharp-format +msgid "Use the following link to sign in: {0}" +msgstr "Gebruik de volgende link om in te loggen: {0}" + +#: Library/RestAPI/Strings.cs:68 +#, csharp-format +msgid "The server crashed: {0}" +msgstr "De server is gecrasht: {0}" + +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 +msgid "Require database encryption" +msgstr "Vereis databasecodering" + +#: Library/RestAPI/Strings.cs:73 +#, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " "environment variable {0} or disable database encryption with the option " "--{1}" msgstr "" +"Database-coderingssleutel is vereist. Geef een coderingssleutel op via de " +"omgevingsvariabele {0} of schakel database-codering uit met de optie --{1}" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2845,21 +3532,18 @@ msgstr "" "Kan geen geldige datum vinden, rekening houdend met de start-datum {0}, de " "herhalingsinterval {1} en de toegestane dagen {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "Server is gestart en luistert op {0}, poort {1}" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -"SSL certificaat kan niet aangemaakt worden met de opgegeven parameters. " -"Uitzondering detail: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "Kan geen socket openen om te luisteren, geprobeerd op poorten: {0}" @@ -2875,7 +3559,7 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Het laden van proces type {0} assembly {1} is mislukt, foutmelding: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." @@ -2884,16 +3568,16 @@ msgstr "" "deze module zijn aangemaakt kunnen gelezen worden met iedere standaard Zip-" "toepassing." -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "Zip-compressie" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "Gebruik in plaats hiervan de optie --{0}." -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2902,11 +3586,11 @@ msgstr "" "van 0 geeft geen compressie, en een instelling van 9 geeft de maximale " "compressie." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" -msgstr "" +msgstr "Stel het Zip-compressieniveau in" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2917,11 +3601,11 @@ msgstr "" "zoals LZMA. Let op: het gebruik van een andere waarde dan Deflate zal ervoor" " zorgen dat de --{0} optie wordt genegeerd." -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" -msgstr "" +msgstr "Stel de ZIP-compressiemethode in" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." @@ -2929,65 +3613,27 @@ msgstr "" "Het ZIP64-formaat is vereist voor bestanden groter dan 4GiB. Gebruik deze " "vlag om het te wisselen." -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" +msgstr "Schakel ZIP64-ondersteuning in en uit" + +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Bestand niet gevonden: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Experimenteel*: 7z Archief met LZMA2 ondersteuning." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimenteel - 7z Archief " - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archief niet geopend voor schrijven" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archief niet geopend voor lezen" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Het opgegeven bestand is geen onderdeel van dit archief" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Het aantal threads dat gebruikt wordt voor LZMA 2 compressie. Standaard " -"wordt het aantal processorkernen gebruikt." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Aantal threads dat gebruikt wordt in compressie" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Deze optie beheert het compressie-algoritme dat gebruikt wordt. Als deze " -"optie wordt aangezet, zal 7z het snelle algoritme gebruiken, wat enigszins " -"minder compressie oplevert." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "back-up" @@ -3263,7 +3909,7 @@ msgstr "" #: Library/Main/Strings.cs:65 msgid "Toggle system sleep mode" -msgstr "" +msgstr "Schakel systeem slaapmodus in en uit" #: Library/Main/Strings.cs:66 msgid "" @@ -3470,15 +4116,23 @@ msgstr "Beperk de grootte van bestanden die meegenomen worden in de back-up" #: Library/Main/Strings.cs:98 msgid "" -"Select another thread priority for the process. Use this to set Duplicati to" -" be more or less CPU intensive." +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" msgstr "" #: Library/Main/Strings.cs:99 +msgid "" +"Select another thread priority for the process. Use this to set Duplicati to" +" be more or less CPU intensive." +msgstr "" +"Selecteer een andere thread prioriteit voor het proces. Gebruik dit om " +"Duplicati meer of minder CPU-intensief te maken." + +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Thread prioriteit" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " @@ -3488,22 +4142,37 @@ msgstr "" "Het veranderen van de grootte kan nuttig zijn als de backend een limiet " "heeft voor de grootte van elk individueel bestand." -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Beperk de grootte van de volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" - -#: Library/Main/Strings.cs:103 -msgid "Disable use of the streaming transfer method" -msgstr "" +"Gebruik deze optie om de streaming interface te blokkeren, wat wil zeggen " +"dat voortgangsbalken van een overdracht niet worden weergegeven, en " +"instellingen voor bandbreedtegebruik worden genegeerd." #: Library/Main/Strings.cs:104 +msgid "Disable use of the streaming transfer method" +msgstr "Schakel het gebruik van de streaming overdrachtsmethode uit" + +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " @@ -3513,11 +4182,11 @@ msgstr "" " niet wordt gelezen. Dit betekent ook dat bestandshashes niet worden " "gecontroleerd. Gebruik alleen voor noodherstel." -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" -msgstr "" +msgstr "Schakel manifests-verificatie uit" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3530,11 +4199,11 @@ msgstr "" "bestand wordt gelezen, de bestandsnaam wordt gebruikt om de compressiemodule" " op te geven." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Geef aan welke module voor compressie gebruikt moet worden" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3547,29 +4216,29 @@ msgstr "" "wordt gelezen, de bestandsnaam wordt gebruikt om de encryptiemodule op te " "geven." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Geef aan welke module gebruikt moet worden voor codering" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" "Geef één of meer modulenamen op, gescheiden door komma's om ze te ontladen." -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" -msgstr "" +msgstr "Schakel één of meer modules uit" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" "Geef één of meer modulenamen op, gescheiden door komma's om ze te laden." -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" -msgstr "" +msgstr "Schakel één of meer modules in" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3597,11 +4266,11 @@ msgstr "" "(VSS) gebruikt en vereist beheerdersrechten. In Linux wordt hiervoor Logical" " Volume Management (LVM) gebruikt en vereist root permissies. " -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" -msgstr "" +msgstr "Bepaal het gebruik van schijf-momentopnames" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " @@ -3612,27 +4281,25 @@ msgstr "" " plaatsen van tijdelijke volumes. Ondanks de naam werkt dit ook voor " "synchrone uitvoeringen." -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Het pad waar klaargezette volumes staan totdat ze geüpload zijn" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -"Bij het uitvoeren van asynchrone uploads zal Duplicati volumes aanmaken die " -"kunnen worden geüpload. Om te voorkomen dat Duplicati teveel volumes " -"genereert, beperkt deze optie het aantal in behandeling zijnde uploads. Stel" -" in op nul om de limiet uit te schakelen." -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Het aantal volumes dat van tevoren aangemaakt mag worden" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3640,11 +4307,11 @@ msgstr "" "Als asynchrone uploads worden uitgevoerd, het maximum aantal toegestane " "gelijktijdige uploads. Stel in op nul om de beperking uit te schakelen." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Het aantal toegestane gelijktijdige uploads" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." @@ -3652,35 +4319,37 @@ msgstr "" "Activeer deze optie om sommige foutmeldingen uitgebreider te laten " "weergegeven, waardoor u mogelijk een specifiek probleem kunt opsporen." -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" -msgstr "" +msgstr "Schakel debug-uitvoer in" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." -msgstr "" +msgstr "Schrijf logboekinformatie naar het opgegeven bestand." -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Log interne informatie naar een bestand" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" +"Geef de hoeveelheid logboekinformatie op die naar het bestand moet worden " +"geschreven dat is opgegeven met de optie --{0}." -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Log informatie niveau" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "Gebruik in plaats hiervan de opties --{0} en --{1}." -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3689,11 +4358,11 @@ msgstr "" "aangemaakt worden. Activeer deze optie om het automatisch aanmaken van " "mappen te voorkomen." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" -msgstr "" +msgstr "Schakel het automatisch aanmaken van mappen uit" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3708,14 +4377,14 @@ msgstr "" " en de meeste vormen van GUID's zijn toegestaan, inclusief met en zonder " "accolades." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Een door puntkomma's gescheiden lijst met guid's van VSS writers die " "uitgesloten moeten worden (alleen Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3738,11 +4407,22 @@ msgstr "" " als het gebruik van USN mislukt. Deze functie wordt alleen ondersteund door" " Windows en vereist beheerdersrechten." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" +msgstr "Bepaal het gebruik van NTFS Update Sequence Numbers" + +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3764,36 +4444,33 @@ msgstr "" " 1 uur). Gebruik deze optie om deze tolerantie uit te schakelen en strikte " "tijdcontrole te gebruiken." -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" -msgstr "" +msgstr "Deactiveer tolerantie bij het vergelijken van tijden" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" "Gebruik deze optie om uploads te controleren door het opvragen van de " "inhoud." -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Controleer uploads door het opvragen van de inhoud" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -"Duplicati zal bestanden uploaden tijdens het scannen van de schijf en het " -"samenstellen van volumes, waardoor de back-up gewoonlijk sneller zal " -"verlopen. Gebruik deze optie om dit gedrag uit te schakelen, zodat Duplicati" -" voor ieder volume zal wachten tot het voltooid is." -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Upload bestanden synchroon" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3805,11 +4482,11 @@ msgstr "" "dus versnelt. Gebruik deze optie om ervoor te zorgen dat elke bewerking " "uitgevoerd wordt op een afzonderlijke verbinding." -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Hergebruik geen verbindingen" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3820,11 +4497,11 @@ msgstr "" " in om foutmeldingen weer te geven zodra een bewerking opnieuw wordt " "uitgevoerd." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Toon foutmeldingen zodra een bewerking opnieuw wordt uitgevoerd" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " @@ -3835,11 +4512,11 @@ msgstr "" "back-up was uitgevoerd, zorgt deze optie ervoor dat Duplicati altijd een " "back-up set uploadt, zelfs als die leeg is." -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Upload lege back-upbestanden" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " @@ -3851,11 +4528,11 @@ msgstr "" "voorgezet als het quotum overschreden is. Hierdoor ontstaan alleen " "waarschuwings- en foutberichten." -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "Beperk opslaggebruik" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3863,12 +4540,17 @@ msgid "" "size. If the backend does not report the quota information, this value will " "be ignored." msgstr "" +"Stel een drempel in voor wanneer te waarschuwen over backend quota die bijna" +" overschreden wordt. Dit wordt opgegeven als een percentage, en een " +"waarschuwing wordt gegenereerd als de hoeveelheid beschikbare quota minder " +"is dan dit percentage van de totale back-upgrootte. Als de backend geen " +"quota-informatie rapporteert, zal deze waarde worden genegeerd." -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Drempelwaarde voor waarschuwing voor lage quota" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " @@ -3878,11 +4560,11 @@ msgstr "" "--{0} kan gebruikt blijven worden voor het instellen van een handmatig " "quotum" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "Backend-quotum uitschakelen" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3902,11 +4584,11 @@ msgstr "" "naam. Eerdere versies van Duplicati ondersteunden deze optie niet en " "gedroegen zich alsof optie \"{2}\" was opgegeven." -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Symlink afhandeling" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3922,11 +4604,11 @@ msgstr "" "hardlink behandelen als een uniek pad. De optie \"{2}\" zal alle hardlinks " "negeren met meer dan één link." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Hardlink afhandeling" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3937,11 +4619,11 @@ msgstr "" "Gebruik een door komma's gescheiden lijst met attribuutnamen om er meer dan " "één op te geven. Mogelijke waarden zijn: {0}." -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Sluit bestanden uit op basis van attribuut" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3954,11 +4636,11 @@ msgstr "" "tot de inhoud van een momentopname. Deze workaround kan bestandstoegang " "versnellen onder Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Wijs momentopnames toe aan een schijf (alleen Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." @@ -3967,11 +4649,11 @@ msgstr "" "om de back-up te identificeren bij het verzenden van email of het uitvoeren " "van scripts." -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Naam van de back-up" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." @@ -3980,11 +4662,11 @@ msgstr "" " identificeren van de back-up bij het verzenden van mail of het uitvoeren " "van scripts." -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "Back-up ID" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." @@ -3993,11 +4675,21 @@ msgstr "" "Dit kan gebruikt worden voor het identificeren van de machine bij het " "verzenden van mail of het uitvoeren van scripts." -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "Machine ID" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -4017,11 +4709,11 @@ msgstr "" "standaard bestand wordt meegeleverd, dat eveneens dient als voorbeeld. Het " "standaard bestand is opgeslagen in {0}." -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Beheer niet-comprimeerbare bestandsextensies" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -4034,11 +4726,11 @@ msgstr "" "bestandslijsten. Merk op dat de waarde niet kan worden veranderd nadat " "externe bestanden zijn aangemaakt." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Blokgrootte gebruikt in hashing" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " @@ -4048,22 +4740,22 @@ msgstr "" "bekend is dat ze veranderd zijn. Dit is gewoonlijk alleen geactiveerd in " "combinatie met een bestandssysteem-bewaker die bestandswijzigingen bijhoudt." -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lijst met bestanden om na te kijken op wijzigingen" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" "Pad naar het bestand dat de lokale cache bevat van de externe " "bestandsdatabase." -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Pad naar de lokale status database" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " @@ -4072,11 +4764,11 @@ msgstr "" "Gebruik deze optie om een lijst met verwijderde bestanden op te geven. Deze " "optie zal worden genegeerd tenzij de optie --{0} eveneens is ingesteld." -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lijst met verwijderde bestanden" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." @@ -4084,22 +4776,12 @@ msgstr "" "Gebruik deze optie om het geheugengebruik te verminderen door paden en " "tijdstempels van wijzigingen niet in het geheugen te houden." -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Verminder geheugengebruik door zoekacties in het geheugen uit te schakelen" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" -"Gebruik deze optie om de snelheid te verhogen ten koste van een hoger " -"geheugengebruik." - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Sla een blok cache op die zich in het geheugen bevindt" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" @@ -4110,11 +4792,11 @@ msgstr "" "om correct te werken in situaties waar het opvragen van bestandenlijsten " "niet meer werkt of niet beschikbaar is." -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Vraag geen gegevens van de backend op bij het opstarten" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -4129,11 +4811,11 @@ msgstr "" "indexbestanden meer ruimte aan de externe zijde innemen die wellicht nooit " "gebruikt wordt." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" -msgstr "" +msgstr "Bepaal het gebruik van indexbestanden" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -4145,11 +4827,11 @@ msgstr "" "ruimte de doellocatie kan bevatten voordat het weer opgeëist wordt. Deze " "waarde is een percentage gebruikt op ieder volume en de totale opslag." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "De maximum hoeveelheid onnodige ruimte in procenten" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." @@ -4157,11 +4839,11 @@ msgstr "" "Gebruik deze optie om te experimenteren met verschillende instellingen om te" " zien wat de uitkomst is zonder daadwerkelijk bestanden te wijzigen." -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" -msgstr "" +msgstr "Voer geen enkele wijziging uit" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" @@ -4171,11 +4853,11 @@ msgstr "" "algoritme te selecteren met een kleinere of grotere hash-grootte, voor " "prestatie- of opslag-gerelateerde redenen." -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Het hash-algoritme dat gebruikt wordt voor blokken" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" @@ -4185,11 +4867,11 @@ msgstr "" "algoritme te selecteren met een kleinere of grotere hash-grootte, voor " "prestatie- of opslag-gerelateerde redenen." -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Het hash-algoritme dat gebruikt wordt voor bestanden" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -4202,11 +4884,11 @@ msgstr "" "automatische opruimacties uit te schakelen en alleen op te ruimen als het " "opruimcommando wordt uitgevoerd." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Schakel automatisch opruimen uit" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -4219,11 +4901,11 @@ msgstr "" "die een klein aantal bytes onnodige ruimte bevatten niet worden gedownload " "en opnieuw weggeschreven." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Volumegrootte drempelwaarde" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -4233,11 +4915,11 @@ msgstr "" "deze waarde het groeperen van kleine bestanden forceren. De kleine volumes " "zullen altijd gecombineerd worden als ze een volledig volume kunnen vullen." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maximum aantal kleine volumes" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -4247,11 +4929,11 @@ msgstr "" "het vinden van bestaande blokken. Dit is een vrij trage bewerking maar het " "kan de grootte van downloads beperken." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Gebruik lokale bestandsdata bij het herstellen" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " @@ -4262,11 +4944,11 @@ msgstr "" "maar kan gebruikt worden om de daadwerkelijke inhoud van externe opslag te " "controleren." -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" -msgstr "" +msgstr "Schakel de lokale database uit" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." @@ -4274,21 +4956,21 @@ msgstr "" "Gebruik deze optie om het aantal versies in te stellen dat behouden moet " "worden, geef -1 op om alle versies te behouden." -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Behoud een bepaald aantal versies" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Gebruik deze optie om de tijdspanne in te stellen waarbinnen back-ups " "behouden moeten worden." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Behoud alle versies binnen een bepaalde tijdspanne" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -4309,22 +4991,22 @@ msgstr "" "Deze optie ondersteunt eveneens de aanduiding \"U\" om een onbeperkt " "tijdsinterval aan te geven." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Verminder het aantal versies door oude tussenliggende back-ups te " "verwijderen" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Gebruik deze optie om door te gaan als een aantal bronelementen ontbreken." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Negeer ontbrekende bronelementen" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." @@ -4333,11 +5015,11 @@ msgstr "" " deze optie niet is ingesteld, zullen de bestanden worden hersteld met een " "tijdstempel en een nummer eraan toegevoegd." -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Overschrijf bestanden bij het herstellen" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -4346,11 +5028,11 @@ msgstr "" "wordt als een optie wordt uitgevoerd. In het algemeen zal deze optie een " "regel aanmaken voor ieder verwerkt bestand." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Voer meer voortgangsinformatie uit" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -4358,11 +5040,11 @@ msgstr "" "Gebruik deze optie om de hoeveelheid uitvoer te vergroten die gegenereerd " "wordt als het resultaat van een bewerking, inclusief alle bestandsnamen." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Uitvoer volledige resultaten" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -4374,11 +5056,11 @@ msgstr "" "SHA256 hashes van alle externe bestanden en kan gebruikt worden om de " "integriteit van de bestanden te controleren." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Bepaal of controlebestanden geüpload zijn" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4394,11 +5076,11 @@ msgstr "" "twee opties. Als deze waarde wordt ingesteld op 0 of de optie --{1} is " "ingesteld, dan worden geen externe bestanden gecontroleerd." -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Het aantal samples die getest moeten worden na een back-up" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4414,11 +5096,11 @@ msgstr "" "bestanden het maximum dat wordt geïmpliceerd door te twee opties. Als de " "optie --{1} is opgegeven, worden geen externe bestanden gecontroleerd." -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Het percentage van te controleren bestanden na een back-upbewerking" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4439,11 +5121,11 @@ msgstr "" "ListAndIndexes staat op True, maar alleen dlist- en indexvolumes worden " "verwerkt." -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" -msgstr "" +msgstr "Activeer diepgaande controle van bestanden" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." @@ -4451,11 +5133,11 @@ msgstr "" "Gebruik deze grootte om te bepalen hoeveel bytes uit een bestand worden " "gelezen voordat deze worden bewerkt." -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Groote van de bestands leesbuffer" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." @@ -4463,11 +5145,11 @@ msgstr "" "Gebruik deze optie om toe te staan dat een wachtwoordzin veranderd wordt. " "Let op: deze optie is niet toegestaan voor een back-up- of herstelbewerking." -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Sta toe dat een wachtwoordzin veranderd wordt" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." @@ -4476,11 +5158,11 @@ msgstr "" "voorkomen dat bestanden andere metadata doorkruisen wat het proces " "vertraagt." -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Geef enkel bestandsverzamelingen weer" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4491,11 +5173,11 @@ msgstr "" "en herstelbewerkingen versnellen, maar heeft niet veel effect op de " "bestandsgrootte." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" -msgstr "" +msgstr "Sla geen metadata op" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4504,11 +5186,11 @@ msgstr "" "zou kunnen hebben tot uw bestanden. Gebruik deze optie om ook " "bestandspermissies te herstellen." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Herstel bestandspermissies" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4519,11 +5201,11 @@ msgstr "" "succesvol was. Gebruik deze optie om de controle uit te schakelen en het " "wachten op het controleproces te vermijden." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Sla het controleren van herstelde bestanden over" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4534,11 +5216,11 @@ msgstr "" "optie op deze optimalisatie over te slaan en alleen externe gegevens te " "gebruiken." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Gebruik geen lokale gegevens" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " @@ -4548,7 +5230,7 @@ msgstr "" "herstelbewerking. Om gebruik te maken van lokale gegevensblokken, stel de " "optie --{0} in." -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." @@ -4558,11 +5240,11 @@ msgstr "" "herstelbewerkingen, in plaats van alleen bestanden vanuit de externe opslag " "te gebruiken." -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "Gebruik bestaande gegevens voor herstellen" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4571,11 +5253,11 @@ msgstr "" "controleren die gelezen worden van een volume voordat herstelde bestanden " "worden bijgewerkt met de gegevens." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Controleer blok hashes" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4588,11 +5270,11 @@ msgstr "" "informatie te reconstrueren. De resulterende database kan worden doorzocht, " "maar kan niet worden gebruikt om er gegevens mee te herstellen." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Repareer database met paden" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4605,11 +5287,11 @@ msgstr "" "deze instelling om de plaatsinstelling te in te stellen. Geef een lege regel" " op om te kiezen voor \"Invariant Culture\"." -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "forceer de plaatsinstelling" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4620,11 +5302,12 @@ msgstr "" "worden alleen de werkelijke datums weergegeven, bijvoorbeeld \"12 november " "2018, 8:01 AM\"." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" +"Dwing de weergave van de werkelijke datum af in plaats van de kalenderdatum" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " @@ -4634,12 +5317,12 @@ msgstr "" "schakelen. Dat kan backend bewerkingen aanzienlijk versnellen afhankelijk " "van de hardware die gebruikt wordt en de doorvoersnelheid van de backend." -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Handel bestandscommunicatie met de backend af door middel van threaded pipes" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4649,22 +5332,22 @@ msgstr "" " te stellen. Als u deze waarde instelt op nul of lager, wordt het aantal " "actieve threads dynamisch afgestemd op de hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Beperk het aantal gelijktijdige threads" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Gebruik deze optie om het aantal processen in te stellen dat hashing van " "gegevens uitvoert." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Geef het aantal gelijktijdige hash-processen op" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4672,11 +5355,11 @@ msgstr "" "Gebruik deze optie om het aantal processen in te stellen die de compressie " "van uitvoergegevens uitvoert." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Geef het aantal gelijktijdige compressieprocessen op" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4686,11 +5369,11 @@ msgstr "" "bestandslijst samenstellen die een samenvoeging is van de laatste afgeronde " "back-up en de inhoud die werd geüpload tijdens de incomplete back-up sessie." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" -msgstr "" +msgstr "Schakel synthetische bestandenlijst uit" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4702,11 +5385,11 @@ msgstr "" "u een groot aantal bestanden hebt en opmerkt dat het scannen een lange tijd " "duurt met ongewijzigde bestanden." -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" -msgstr "" +msgstr "Controleer alleen tijdstip laatst bewerkt van bestanden" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4719,11 +5402,11 @@ msgstr "" "originele mapstructuur in zijn geheel behouden blijft, inclusief hoger " "gelegen lege mappen." -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" -msgstr "" +msgstr "Schakel pad-compressie uit bij een herstelbewerking" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4736,11 +5419,11 @@ msgstr "" "deze beveiliging uit te schakelen, zodat alle bestandsverzamelingen kunnen " "worden verwijderd." -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Sta verwijderen van alle bestandsverzamelingen toe" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4756,13 +5439,13 @@ msgstr "" "velden in de database. Door dit aan te zetten zal Duplicati VACUUM " "bewerkingen naar eigen goeddunken uitvoeren." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" "Sta het automatisch opnieuw opbouwen van de lokale database toe om ruimte te" " besparen." -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4775,11 +5458,11 @@ msgstr "" "kan het back-up proces versnellen door het verminderen van schijftoegang, " "maar zak een minder accurate voortgangsindicator tot gevolg hebben." -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Schakel de vooruit lees-scanner uit" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4790,27 +5473,32 @@ msgstr "" "controlecommando's uit als deze automatische controles worden uitgeschakeld," " om u ervan te verzekeren dat alles naar behoren werkt." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Consistentiecontroles voor bestandslijsten uitschakelen" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " "run). If the detected power source is mains (e.g., AC) or unknown, then " "scheduled backups will proceed as normal." msgstr "" +"Gebruik deze optie om een geplande back-up uit te schakelen als wordt " +"gedetecteerd dat het systeem werkt op batterijstroom (handmatige back-ups of" +" back-ups vanaf de opdrachtregel zullen wel blijven werken). Als netstroom " +"(bijv. wisselstroom) de gedetecteerde stroombron is, of bij een onbekende " +"stroombron, zullen de geplande back-ups zoals gebruikelijk doorgaan." -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Schakel de back-up uit als op batterijstroom wordt gewerkt" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Logbestand informatieniveau" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4822,23 +5510,24 @@ msgstr "" "Deze optie staat filters toe die meldingen opnemen of uitsluiten, ongeacht het logniveau hiervan. Meerdere filters worden ondersteund door ze te scheiden met {0}. Filters worden vergeleken met de log-tag en verondersteld inclusief te zijn, tenzij ze beginnen met '-'. Reguliere expressies worden ondersteund binnen teksthaken.\n" "Voorbeeld: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" -msgstr "" +msgstr "Pas filters toe op de gegevens in het logbestand" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" +"Geef de hoeveelheid log-informatie op die naar de console gestuurd wordt." -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Console informatie-niveau" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" -msgstr "" +msgstr "Pas filters toe op de console log-gegevens" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" @@ -4849,20 +5538,20 @@ msgstr "" "bewerkingen langzamer uitgevoerd kunnen worden maar andere bewerkingen die " "op hetzelfde moment uitgevoerd worden minder verstoord zullen worden." -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" -msgstr "" +msgstr "Stel het proces in voor laag IO gebruik" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" "Gebruik deze optie om alle lege mappen te verwijderen van een back-up." -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" -msgstr "" +msgstr "Sluit lege mappen uit" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4875,11 +5564,11 @@ msgstr "" "\".nobackup\" te hebben en dit bestand te plaatsen in mappen waarvan geen " "back-up zou moeten worden gemaakt." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Lijst met bestandsnamen die mappen uitsluit" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4892,11 +5581,11 @@ msgstr "" "gebruikt om dit toch te doen, zodat metadata ook op symlinks wordt " "toegepast." -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "Pas metadata toe op symlinks" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4909,11 +5598,11 @@ msgstr "" "back-ups, maar is vereist voor testdoeleinden om mogelijke problemen aan het" " licht te brengen." -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "Activeer unittest-modus" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4926,11 +5615,11 @@ msgstr "" " in om alle databasequery's vast te leggen, en vergeet niet om ofwel " "--{0}={2} of --{1}={2} in te stellen om de extra loggegevens te rapporteren." -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" -msgstr "" +msgstr "Activeer logboekregistratie van alle databasequery's" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4945,11 +5634,11 @@ msgstr "" "proces kan traag zijn. Gebruik deze optie om te proberen ontbrekende dblock-" "bestanden opnieuw samen te stellen." -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "Stel dblock-bestanden opnieuw samen wanneer deze ontbreken" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4962,11 +5651,11 @@ msgstr "" "mogelijk is het niet wenselijk om dit na elke afzonderlijke back-uptaak uit" " te voeren." -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "Minimale tijd tussen automatische opruimacties" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4979,11 +5668,160 @@ msgstr "" "mogelijk is het niet wenselijk om dit na elke afzonderlijke back-uptaak uit " "te voeren." -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "Minimale tijd tussen automatische vacuum-taken" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4992,51 +5830,51 @@ msgstr "" "De cryptobibliotheek ondersteunt geen herbruikbare transformaties voor het " "hash algoritme {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "De cryptobibliotheek ondersteunt het hash algoritme {0} niet" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "De wachtwoordzin kan niet veranderd worden voor een al bestaande back-up" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Het maken van een momentopname is mislukt: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Verwijderen bestand {0} mislukt, testen of bestand bestaat" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Hersteld van problemen bij een poging om niet-bestaand bestand te " "verwijderen {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Herstellen van fout bij verwijderen bestand mislukt: {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Verwijder-bewerking mislukt voor {0} met BestandNietGevonden, inhoud " "weergeven" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Opgevraagde lijst geeft aan dat bestand {0} correct was verwijderd" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Opgevraagde lijst bevestigt dat bestand {0} niet was verwijderd" @@ -5101,36 +5939,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configureer http aanvragen" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" -"Gebruik deze optie om ieder servercertificaat te accepteren, ongeacht welke " -"fouten het heeft. Gebruik in plaats hiervan --{0} wanneer mogelijk." - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Accepteer ieder servercertificaat" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" -"Als uw servercertificaat wordt gerapporteerd als onveilig (bijv. bij zelf-" -"ondertekende certificaten), kan de certificaat-hash (SHA1) opgegeven worden " -"om het toch goed te keuren. De hash-waarde moet in hexadecimale indeling " -"worden ingevoerd, zonder spaties of dubbele punten. U kunt meerdere hashes " -"invoeren, gescheiden door komma's." - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Accepteer optioneel een bekend SSL certificaat" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -5184,7 +5992,7 @@ msgstr "" #: Library/Modules/Builtin/Strings.cs:53 msgid "Set allowed SSL versions" -msgstr "" +msgstr "Stel toegestane SSL versies in" #: Library/Modules/Builtin/Strings.cs:54 msgid "" @@ -5197,7 +6005,7 @@ msgstr "" #: Library/Modules/Builtin/Strings.cs:55 msgid "Set the default operation timeout" -msgstr "" +msgstr "Stel de standaard bewerkingstime-out in." #: Library/Modules/Builtin/Strings.cs:56 msgid "" @@ -5212,7 +6020,7 @@ msgstr "" #: Library/Modules/Builtin/Strings.cs:57 msgid "Set readwrite" -msgstr "" +msgstr "Stel lezen-schrijven in" #: Library/Modules/Builtin/Strings.cs:58 #, csharp-format @@ -5226,7 +6034,7 @@ msgstr "" #: Library/Modules/Builtin/Strings.cs:59 msgid "Set HTTP buffering" -msgstr "" +msgstr "Stel HTTP-buffering in" #: Library/Modules/Builtin/Strings.cs:63 msgid "" @@ -5240,7 +6048,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configureer Hyper-V module" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -5248,51 +6068,59 @@ msgstr "" "Deze module werkt intern om bronparameters te verwerken om back-ups te maken" " van Microsoft SQL Server databases" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configureer Microsoft SQL Server module" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" +"Voer een script uit voordat een bewerking wordt gestart, en opnieuw na " +"voltooiing" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Voer script uit" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" +"Voer een script uit na het uitvoeren van een bewerking. Het script zal de " +"resultaten van de bewerking ontvangen die geschreven zijn naar stdout." -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Voer een script uit bij beëindiging" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Het script \"{0}\" gaf de exit code {1} terug" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Het script \"{0}\" gaf afsluitcode {1}{2} terug" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" +"Voer een script uit voordat een bewerking wordt gestart. De bewerking zal " +"onderbroken worden totdat het script is afgerond of er een time-out heeft " +"plaatsgevonden. Als het script een waarde anders dan 0 teruggeeft of er een " +"time-out plaatsvindt, zal de bewerking worden afgebroken." -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Voer een vereist script uit bij opstarten" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " @@ -5301,48 +6129,55 @@ msgstr "" "Gebruik deze optie om het uitvoerformaat voor resultaten te selecteren. " "Beschikbare formaten: {0}" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" -msgstr "" +msgstr "Selecteer het uitvoerformaat voor de resultaten" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Fout tijdens uitvoeren script: \"{0}\":{1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Time-out bij uitvoeren van script \"{0}\"" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" +"Voer een script uit voordat een bewerking wordt uitgevoerd. De bewerking zal" +" onderbroken worden totdat het script is afgerond of er een time-out heeft " +"plaatsgevonden." -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Voer een script uit bij opstarten" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Het script \"{0}\" rapporteerde foutmeldingen: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" +"Stel in hoeveel tijd een script maximaal in uitvoering mag zijn. Als het " +"script niet is afgerond binnen deze tijd, zal het verder gaan maar de " +"bewerking zal ook verder gaan, en uitvoer van het script zal niet worden " +"verwerkt." -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" -msgstr "" +msgstr "Stel de script time-out in" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " @@ -5353,26 +6188,28 @@ msgstr "" "tekenreeksen. Gebruik enkele of dubbele aanhalingstekens om meerdere " "argumenten te scheiden." -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "Script argumenten inschakelen" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Deze module kan een email verzenden nadat een bewerking voltooid is" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Stuur email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" +"De doel-mailserver kan niet gevonden worden door MX-lookup. Gebruik de optie" +" {0} om aan te geven welke SMTP-server gebruikt moet worden." -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -5394,11 +6231,11 @@ msgstr "" "\n" "Alle opdrachtregel-opties worden eveneens gerapporteerd binnen %value%, bijvoorbeeld %volsize%. Onbekende/niet ingestelde waarden worden verwijderd." -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "De berichttekst" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." @@ -5406,11 +6243,11 @@ msgstr "" "Gebruik deze optie om het wachtwoord in te stellen dat gebruikt wordt om te " "authentiseren bij de SMTP server, indien vereist." -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP wachtwoord" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -5422,11 +6259,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email ontvanger(s)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -5442,11 +6279,11 @@ msgstr "" "Mail Sender \n" "Mail Sender " -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Email afzender" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" @@ -5458,14 +6295,14 @@ msgstr "" " korte schrijfwijze voor \"{0},{1},{2},{3}\" en zal ervoor zorgen dat voor " "alle back-up bewerkingen een email wordt verstuurd." -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "De berichten die verzonden moeten worden" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -5476,11 +6313,11 @@ msgstr "" "Als geen server is opgegeven, zal een DNS-lookup worden uitgevoerd om het MX-record van de eerste geadresseerde te vinden, en alle SMTP-servers worden geprobeerd op volgorde van prioriteit totdat het bericht is verzonden.\n" "Gebruik om SMTP over SSL in te schakelen het formaat smtps://example.com. Gebruik om SMTP STARTTLS in te schakelen het formaat smtp://example.com:25/?starttls=when-available of smtp://example.com:25/?starttls=always. Als geen poort wordt opgegeven, wordt poort 25 gebruikt voor niet-SSL en poort 465 gebruikt voor SSL verbindingen. Gebruik om het niet gebruiken van STARTTLS af te dwingen het formaat smtp://example.com:25/?starttls=never." -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -5489,11 +6326,11 @@ msgstr "" "Deze instelling geeft het email onderwerp op. Waarden worden vervangen zoals" " beschreven in de beschrijving voor --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Het email onderwerp" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." @@ -5501,39 +6338,39 @@ msgstr "" "Gebruik deze optie om de gebruikersnaam in te stellen die gebruikt wordt om " "te authentiseren bij de SMTP server, indien vereist." -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP gebruikersnaam" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Complete SMTP communicatie: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Verzenden van email mislukt met server: {0}, bericht {1}, opnieuw proberen " "met {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email succesvol verzonden via server: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Deze module geeft ondersteuning voor het versturen van statusrapporten via " "XMPP berichten" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP rapportagemoduke" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." @@ -5542,13 +6379,13 @@ msgstr "" "verzonden moeten worden. Meerdere gebruikers kunnen gescheiden door komma's " "worden opgegeven." -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP ontvanger email" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5570,13 +6407,13 @@ msgstr "" "\n" "Alle opdrachtregel-opties worden eveneens gerapporteerd binnen %value%, bijvoorbeeld %volsize%. Onbekende/niet ingestelde waarden worden verwijderd." -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Het bericht-sjabloon" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" @@ -5585,11 +6422,11 @@ msgstr "" "waarmee het bericht verstuurd wordt, inclusief de hostnaam. Voorbeeld: " "\"account@jabber.org/Home\"" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "De XMPP gebruikersnaam" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." @@ -5597,13 +6434,13 @@ msgstr "" "Gebruik deze optie om een wachtwoord in te stellen van het account waarmee " "het bericht verstuurd wordt." -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Het XMPP wachtwoord" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5612,9 +6449,9 @@ msgstr "" "Eén van deze opties kan worden opgegeven: \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Meerdere opties kunnen worden opgegeven met een komma als scheidingsteken, bijvoorbeeld \"{0},{1}\". De speciale waarde \"{4}\" is een korte schrijfwijze voor \"{0},{1},{2},{3}\" en zal ervoor zorgen dat voor alle back-up bewerkingen een bericht wordt verstuurd." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." @@ -5622,86 +6459,92 @@ msgstr "" "Standaard worden berichten alleen verstuurd na een back-upbewerking. Gebruik" " deze optie om berichten te versturen voor alle bewerkingen." -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Verstuur berichten voor alle bewerkingen" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Time-out opgetreden tijdens inloggen bij jabber server" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" - -#: Library/Modules/Builtin/Strings.cs:169 -msgid "Telegram report module" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:170 -msgid "Use this option to set the channel ID." -msgstr "" +"Deze module biedt ondersteuning voor het versturen van statusrapporten via " +"Telegram-berichten" #: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" -msgstr "" +msgid "Telegram report module" +msgstr "Telegram rapportagemodule" -#: Library/Modules/Builtin/Strings.cs:182 -msgid "" -"Use this option to set a bot ID for the bot that will send the message." +#: Library/Modules/Builtin/Strings.cs:172 +msgid "Use this option to set the channel ID." +msgstr "Gebruik deze optie om het kanaal-ID in te stellen" + +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" #: Library/Modules/Builtin/Strings.cs:184 -msgid "The Telegram bot ID" +msgid "" +"Use this option to set a bot ID for the bot that will send the message." msgstr "" +"Gebruik deze optie om een bot-ID in te stellen van de bot waarmee het " +"bericht verstuurd wordt." -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:186 +msgid "The Telegram bot ID" +msgstr "Het Telegram bot-ID" + +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" +"Gebruik deze optie om een API-sleutel in te stellen van de bot waarmee het " +"bericht verstuurd wordt." -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" -msgstr "" +msgstr "De Telegram API-sleutel" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" -msgstr "" +msgstr "Time-out opgetreden tijdens verzenden naar Telegram-server" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Deze module biedt ondersteuning voor het versturen van statusrapporten via " "HTTP-berichten" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP rapportagemodule" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "Gebruik deze optie om een HTTP rapportage-URL in te stellen." -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "HTTP rapportage-URL" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" "Gebruik deze optie om de naam van de parameter in te stellen namens wie het " "bericht verstuurd moet worden." -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "De naam van de parameter namens wie het bericht verstuurd moet worden" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" @@ -5710,22 +6553,22 @@ msgstr "" "bericht moeten worden toegevoegd, bijvoorbeeld " "\"parameter1=waarde1¶meter2=waarde2\"" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Extra parameters die aan het http bericht moeten worden toegevoegd" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" "Gebruik deze optie om het standaard HTTP-woord te veranderen dat wordt " "gebruikt om een rapport in te dienen." -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" -msgstr "" +msgstr "Stel het te gebruiken HTTP-woord in" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5737,11 +6580,11 @@ msgstr "" "gescheiden door puntkomma's. Alle URL's ontvangen dezelfde gegevens. Houd er" " rekening mee dat deze optie de opmaak- en verb-instellingen negeert." -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "HTTP rapportage-URL's voor het verzenden van formuliergegevens." -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " @@ -5752,27 +6595,27 @@ msgstr "" "puntkomma's. Alle URL's ontvangen dezelfde gegevens. Houd er rekening mee " "dat deze optie de opmaak- en verb-instellingen negeert." -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "HTTP rapportage-URL's voor het verzenden van JSON-gegevens" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Verzenden van bericht mislukt: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" "Gebruik deze optie om een logniveau in te stellen voor berichten die moeten " "worden opgenomen in het rapport." -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" -msgstr "" +msgstr "Definieer een logniveau voor berichten" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." @@ -5780,11 +6623,11 @@ msgstr "" "Gebruik deze optie om een filteruitdrukking in te stellen die definieert " "welke opties worden opgenomen in het rapport." -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Logbericht filter" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5793,26 +6636,26 @@ msgstr "" " die worden opgenomen in het rapport. Nul of een negatieve waarde betekent " "onbeperkt." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" -msgstr "" +msgstr "Beperk logboekregels" #: Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs:39 #, csharp-format msgid "The format is not supported: {0}" msgstr "Het formaat wordt niet ondersteund: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Ongeldige afmetingswaarde: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" "De SSL certificaat validator werd aangeroepen in een verkeerde volgorde" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5825,7 +6668,7 @@ msgstr "" "servercertificaat toch te vertrouwen.{2} U kunt ook proberen het " "servercertificaat te importeren in de trust pool van uw besturingssysteem." -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5834,71 +6677,71 @@ msgstr "" "Valideren van de certificaat-hash mislukt, foutmelding: {0}, SSL foutnaam: " "{1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Tijdelijke map bestaat niet: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Uitvoeren van het segment mislukt: {0}, ongeldige integer" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Ongeldige specifier: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Onverwerkte gegevens: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "De Uri is ongeldig: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "De hostname ontbreekt in de Uri: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "De string \"{0}\" kon niet ontleed worden in een datum" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Kan niet lezen en schrijven in dezelfde stream" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -6172,11 +7015,11 @@ msgstr "Uitvoer naar het scherm uitschakelen" msgid "This link may provide additional information: {0}" msgstr "Deze link kan aanvullende informatie weergeven: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Automatische updates inschakelen" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-pl.mo b/Localizations/duplicati/localization-pl.mo index 463b3cfc4..0b1432a84 100644 Binary files a/Localizations/duplicati/localization-pl.mo and b/Localizations/duplicati/localization-pl.mo differ diff --git a/Localizations/duplicati/localization-pl.po b/Localizations/duplicati/localization-pl.po index a17a3f9ca..6481a1941 100644 --- a/Localizations/duplicati/localization-pl.po +++ b/Localizations/duplicati/localization-pl.po @@ -6,19 +6,20 @@ # Translators: # Jerzy Wartałowicz , 2017 # Mikolaj Zajac , 2017 +# marekjedrzejewski , 2017 # Slawomir Ciunczyk , 2024 -# Waldemar Stoczkowski, 2024 -# marekjedrzejewski , 2024 # Mariusz Wierzbicki , 2024 +# Waldemar Stoczkowski, 2024 +# Jakub Dębski, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Mariusz Wierzbicki , 2024\n" +"Last-Translator: Jakub Dębski, 2024\n" "Language-Team: Polish (https://app.transifex.com/duplicati/teams/67655/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -46,16 +47,17 @@ msgstr "Nie można użyć pustego hasła szyfrowania" msgid "" "Use this option to set the thread level allowed for AES crypt operations." msgstr "" +"Użyj tej opcji, aby ustawić poziom wątków dozwolony dla operacji AES crypt." #: Library/Encryption/Strings.cs:32 msgid "Set thread level utilized for crypting" -msgstr "" +msgstr "Ustaw poziom wątków używany do szyfrowania" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." -msgstr "" +msgstr "Opcja --{0} nie jest już używana i została uznana za przestarzałą." #: Library/Encryption/Strings.cs:37 #, csharp-format @@ -72,6 +74,12 @@ msgid "" "program is available via the PATH environment variable. It is possible to " "supply the path to GPG using the option --{0}." msgstr "" +"Moduł szyfrujący GPG wykorzystuje program GNU Privacy Guard do szyfrowania i" +" odszyfrowywania plików. Wymaga, aby plik wykonywalny gpg był dostępny w " +"systemie. W systemie Windows zakłada się, że znajduje się on w domyślnym " +"folderze instalacyjnym w Program Files, w systemie Linux i OSX zakłada się, " +"że program jest dostępny za pośrednictwem zmiennej środowiskowej PATH. " +"Możliwe jest podanie ścieżki do GPG za pomocą opcji --{0}" #: Library/Encryption/Strings.cs:42 msgid "GNU Privacy Guard, external" @@ -111,6 +119,8 @@ msgid "" "The path to the GNU Privacy Guard program. If not supplied, Duplicati will " "search for \"gpg2\" and \"gpg\" on the system." msgstr "" +"Ścieżka do programu GNU Privacy Guard. Jeśli nie zostanie podana, Duplicati " +"spróbuje znaleźć \"gpg2\" i \"gpg\" w systemie." #: Library/Encryption/Strings.cs:49 msgid "The path to GnuPG" @@ -142,6 +152,8 @@ msgid "" "Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." msgstr "" +"Zastąp domyślne polecenie szyfrowania GPG \"{0}\". Normalnym zastosowaniem " +"jest żądanie szyfrowania asymetrycznego z ustawieniem {1}." #: Library/Encryption/Strings.cs:55 msgid "The GPG encryption command" @@ -161,78 +173,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Błąd podczas wywoływania GnuPG, program się nie kończy" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliasy" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "wartość domyślna" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DEPRECATED]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "wartości" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boole'owski" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Wyliczanie" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flagi" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Liczba całkowita" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Ścieżka" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Rozmiar" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Łańcuch" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Przedział czasowy" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Nieznany" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Nie można utworzyć folderu, ponieważ już istnieje" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Żądany folder nie istnieje" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Anulowano" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Żądany plik nie istnieje" @@ -361,9 +389,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -380,9 +407,8 @@ msgstr "Nazwa domeny użytkownika używana do łączenia się z serwerem." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -395,9 +421,8 @@ msgstr "" "Nazwa użytkownika używana do łączenia się z serwerem. Może to być również " "podane jako zmienna środowiskowa „AUTH_USERNAME”." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -444,11 +469,11 @@ msgstr "" #: Library/Backend/OpenStack/Strings.cs:41 msgid "Supply the authentication URL" -msgstr "" +msgstr "Niestandardowy URL uwierzytelniania" #: Library/Backend/OpenStack/Strings.cs:42 msgid "The keystone API version to use. Valid values are 'v2' and 'v3'." -msgstr "" +msgstr "Wersja Keystone API do użycia. Prawidłowe wartości to „v2” i „v3”." #: Library/Backend/OpenStack/Strings.cs:43 msgid "The keystone API version to use" @@ -471,7 +496,7 @@ msgstr "" #: Library/Backend/OpenStack/OpenStackConfig.cs:67 msgid "OpenStack configuration module" -msgstr "" +msgstr "Moduł konfiguracji OpenStack" #: Library/Backend/OpenStack/OpenStackConfig.cs:69 msgid "Expose OpenStack configuration as a web module" @@ -487,43 +512,36 @@ msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 msgid "Provide different config values" -msgstr "" +msgstr "Podaj różne wartości konfiguracji" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" +"Ten backend może odczytywać i zapisywać dane do backendu opartego na FTP. " +"Dozwolone formaty to \"ftp://nazwa_hosta/folder\" i " +"\"ftp://nazwa_użytkownika:hasło@nazwa_hosta/folder\"" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternatywny FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -534,50 +552,212 @@ msgstr "" "Hasło używane do łączenia się z serwerem. Może to być również podane jako " "zmienna środowiskowa „AUTH_PASSWORD”." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Aby chronić się przed awariami sieci, każda próba przesłania zostanie " -"zweryfikowana. Użyj tej opcji, aby wyłączyć tę weryfikację, aby przesyłanie " -"było szybsze, ale mniej niezawodne." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Wyłącz weryfikację przesyłania" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Folder {0} nie zostal znaleziony, wiadomość: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Katalog {0} nie został znaleziony. Komunikat: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Plik {0} został przesłany, ale zwrócony rozmiar to {1} i oczekiwano, że " +"będzie to {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Aby zabezpieczyć się przed awariami sieci lub serwera, każdy przesyłany plik" +" będzie poddawany próbie weryfikacji. Użyj tej opcji, aby wyłączyć tę " +"weryfikację, aby przesyłanie było szybsze, ale mniej niezawodne." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Wyłącz weryfikację przesyłania" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Jeśli ta flaga jest ustawiona, typ połączenia danych FTP zostanie zmieniony " +"na wybraną opcję." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Skonfiguruj typ połączenia FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Jeśli ta flaga jest ustawiona, tryb szyfrowania FTP zostanie zmieniony na " +"wybraną opcję." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Konfigurowanie trybu szyfrowania FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Ta flaga określa zasady SSL, które mają być używane, gdy szyfrowanie jest " +"włączone." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Skonfiguruj zasady SSL, które mają być używane, gdy włączone jest " +"szyfrowanie" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Niektóre serwery FTP wymagają niewielkiego opóźnienia przed zgłoszeniem " +"prawidłowego rozmiaru pliku. Wymagane opóźnienie zależy od topologii sieci. " +"Jeśli wystąpią błędy związane z niedopasowaniem rozmiaru przesyłanych " +"plików, spróbuj dodać kilka sekund opóźnienia." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Dodaj opóźnienie po przesłaniu pliku" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -734,7 +914,7 @@ msgstr "Użyj konta UK" #: Library/Backend/CloudFiles/Strings.cs:36 msgid "The username used to authenticate with CloudFiles." -msgstr "" +msgstr "Nazwa użytkownika używana do autoryzacji z serwerem CloudFiles" #: Library/Backend/CloudFiles/Strings.cs:37 msgid "Supply the username used to authenticate with CloudFiles" @@ -792,7 +972,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:29 msgid "AWS Secret Access Key" -msgstr "" +msgstr "Klucz Dostępu AWS" #: Library/Backend/S3/Strings.cs:30 #, csharp-format @@ -883,6 +1063,11 @@ msgstr "" msgid "Specify storage class" msgstr "Określ klasę magazynu" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -915,127 +1100,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternatywny FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Katalog {0} nie został znaleziony. Komunikat: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Plik {0} został przesłany, ale zwrócony rozmiar to {1} i oczekiwano, że " -"będzie to {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Aby zabezpieczyć się przed awariami sieci lub serwera, każdy przesyłany plik" -" będzie poddawany próbie weryfikacji. Użyj tej opcji, aby wyłączyć tę " -"weryfikację, aby przesyłanie było szybsze, ale mniej niezawodne." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Jeśli ta flaga jest ustawiona, typ połączenia danych FTP zostanie zmieniony " -"na wybraną opcję." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Skonfiguruj typ połączenia FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Jeśli ta flaga jest ustawiona, tryb szyfrowania FTP zostanie zmieniony na " -"wybraną opcję." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Konfigurowanie trybu szyfrowania FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Ta flaga określa zasady SSL, które mają być używane, gdy szyfrowanie jest " -"włączone." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Skonfiguruj zasady SSL, które mają być używane, gdy włączone jest " -"szyfrowanie" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Niektóre serwery FTP wymagają niewielkiego opóźnienia przed zgłoszeniem " -"prawidłowego rozmiaru pliku. Wymagane opóźnienie zależy od topologii sieci. " -"Jeśli wystąpią błędy związane z niedopasowaniem rozmiaru przesyłanych " -"plików, spróbuj dodać kilka sekund opóźnienia." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Dodaj opóźnienie po przesłaniu pliku" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Błąd podczas usuwania pliku: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Błąd odczytu pliku: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Błąd zapisu pliku: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Moduł do generowania kluczy prywatnych/publicznych SSH" @@ -1545,7 +1609,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Czy należy użyć klasy HttpClient" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1553,11 +1621,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1567,11 +1635,11 @@ msgstr "" "dysku, domyślny dysk OneDrive lub OneDrive dla Firm będzie używany za pomocą" " '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Opcjonalny identyfikator dysku" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1579,28 +1647,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "Identyfikator strony" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Nie podano identyfikatora strony" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "Użyto sprzeczne identyfikatory stron: podano {0}, ale znaleziono {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1612,41 +1680,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Grupa Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "Identyfikator grupy" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Adres e-mail grupy" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Nie znaleziono grup o podanym adresie e-mail: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Znaleziono wiele grup z podanym adresem e-mail: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1666,7 +1734,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1677,7 +1745,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1836,7 +1904,7 @@ msgstr "Jottacloud" #: Library/Backend/Jottacloud/Strings.cs:29 msgid "No username found" -msgstr "" +msgstr "Nie znaleziono nazwy użytkownika" #: Library/Backend/Jottacloud/Strings.cs:30 msgid "No path given. Files cannot be uploaded to the root folder" @@ -1895,7 +1963,7 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:38 msgid "Number of threads for restore operations" -msgstr "" +msgstr "Liczba wątków dla operacji przywracania" #: Library/Backend/Jottacloud/Strings.cs:39 msgid "" @@ -1907,13 +1975,15 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:40 msgid "The chunk size for simultaneous downloading" -msgstr "" +msgstr "Rozmiar fragmentu do jednoczesnego pobierania" #: Library/Backend/Mega/Strings.cs:25 msgid "" "This backend can read and write data to Mega.co.nz. Allowed format is " "\"mega://folder/subfolder\"." msgstr "" +"Ten backend może odczytywać i zapisywać dane na Mega.co.nz. Dozwolony format" +" to: \"mega://folder/subfolder\"." #: Library/Backend/Mega/Strings.cs:26 msgid "mega.nz" @@ -1924,10 +1994,12 @@ msgid "" "For accounts with two-factor authentication enabled, set the shared secret " "used to generate the two-factor TOTP codes." msgstr "" +"Dla kont z włączoną weryfikacją dwuetapową ustaw udostępniony klucz tajny " +"używany do generacji dwuetapowych kodów TOTP." #: Library/Backend/Mega/Strings.cs:32 msgid "The shared secret used to generate two-factor TOTP codes" -msgstr "" +msgstr "Udostępniony klucz tajny używany do generacji dwuetapowych kodów TOTP" #: Library/Backend/Mega/Strings.cs:33 msgid "No password given" @@ -1937,43 +2009,45 @@ msgstr "Nie podano hasła" msgid "No username given" msgstr "Nie podano nazwy użytkownika" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" -msgstr "" +msgstr "Nie podano Tajnego Klucza Dostępu" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2110,6 +2184,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2144,6 +2223,12 @@ msgstr "" "Serwer zwrócił kod błędu {0} ({1}), oznaczający, że serwer nie wspiera " "połączeń WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Folder {0} nie zostal znaleziony, wiadomość: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2172,6 +2257,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Zrzuć odpowiedź PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Zaakceptuj dowolny certyfikat serwera" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Opcjonalnie zaakceptuj znany certyfikat SSL" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2296,9 +2445,410 @@ msgstr "Nieoczekiwany kod błędu: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Inny wątek jest uruchomiony i został powiadomiony" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2318,13 +2868,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Obsługiwane argumenty wiersza poleceń:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2353,10 +2901,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Nie można ustawić filtrów w wierszu poleceń, jeśli są one także obecne w " -"pliku parametrów. Użyj specjalnych opcji --{0}, --{1}, lub --{2} aby ustawić" -" filtry wewnątrz pliku parametrów. Każdy filtr musi być poprzedzony przez + " -"lub -, a wiele filtrów musi być połączone za pomocą {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2394,7 +2938,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2402,7 +2946,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Wykryto nieobsługiwaną wersję SQLite ({0}), wymagane jest {1} lub wyższe" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2415,42 +2958,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Port serwera web nasłuchuje. Można wprowadzić wiele wartości jednocześnie " -"oddzielając je przecinkiem." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Serwer web użyje certyfikat i plik klucza w formacie PKCS #12 do SSL. Tylko " -"klucze RSA/DSA są obsługiwane." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Hasła do odszyfrowywania pliku certyfikatu PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "Serwer web użyje certyfikat i plik klucza w formacie PKCS #12 do SSL." + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Interfejs serwera web nasłuchuje. Specjalne wartości \"*\" i \"any\" oznacza" -" dowolny interfejs. Specjalna wartość \"loopback\" oznacza kartę sprzężenia " -"zwrotnego." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Do uzyskania dostępu do serwera www wymagane jest hasło. Opcja jest " -"zapisana, więc nie trzeba ustawiać jej przy każdym uruchomieniu. Pusta " -"wartość dezaktywuje hasło." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2460,131 +3004,164 @@ msgstr "" "hostów to „*”, wszystkie nazwy hostów są dozwolone, a sprawdzanie nazwy " "hosta jest wyłączone." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Ustaw czas, po którym dane dziennika zostaną usunięte z bazy danych." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Wyczyść stare dane dziennika" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati musi zachować małą bazę danych z wszystkimi ustawieniami. Użyj tej" -" opcji, aby wybrać, gdzie będą przechowywane ustawienia. Ta opcja może być " -"również ustawiona za pomocą zmiennej środowiskowej {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Ta opcja ustawia klucz używany do szyfrowania bazy danych ustawień " -"lokalnych. Ta opcja może być również ustawiona za pomocą zmiennej " -"środowiskowej {0}. Użyj opcji --{1} aby wyłączyć szyfrowanie bazy danych." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Folder Tymczasowy" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2592,15 +3169,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2609,25 +3223,21 @@ msgstr "" "Nie odnaleziono prawidłowej daty, podaj datę rozpoczęcia {0}, interwał " "powtórzeń {1} i dozwolonych dni {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Serwer został uruchomiony i nasłuchuje {0}, port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Nie udało się utworzyć certyfikatu SSL używając podanych parametrów. " -"Szczegóły wyjątku: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" -"Nie udało się otworzyć nasłuchiwania z gniazda, wypróbowane porty: {0}" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2641,22 +3251,22 @@ msgstr "" "Nie udało się załadować zestawu typu {0} procesu {1}, komunikat o błędzie: " "{2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2664,11 +3274,11 @@ msgstr "" "Ta opcja określa poziom kompresji. Ustawienie wartości zero oznacza brak " "kompresji, a ustawienie 9 oznacza maksymalną kompresję." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2676,75 +3286,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Nie znaleziono pliku: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Eksperymentalne*: Archiwum 7z z obsługą LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Eksperymentalne - Archiwum 7z" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Archiwum nie otwarte do zapisu" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Archiwum nie otwarte do odczytu" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Dany plik nie jest częścią tego archiwum" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Liczba wątków używanych w kompresji LZMA 2. Domyślnie liczba rdzeni " -"procesora." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Liczba wątków używanych w kompresji" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Ta opcja określa używany algorytm kompresji. Włączenie tej opcji spowoduje, " -"że 7z użyje szybkiego algorytmu, który uzyskuje nieco mniejszy stopień " -"kompresji." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "kopia" @@ -3187,48 +3759,66 @@ msgstr "Ogranicz rozmiar plików kopii zapasowej" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Priorytet wątku" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limit rozmiaru wolumenów" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3240,11 +3830,11 @@ msgstr "" "nowych woluminów, podczas odczytu istniejącego pliku nazwa pliku służy do " "wyboru modułu kompresji." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Wybierz moduł do kompresji" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3256,27 +3846,27 @@ msgstr "" "nowych woluminów, podczas odczytu istniejącego pliku nazwa pliku jest " "używana do wyboru modułu szyfrowania." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Wybierz moduł do szyfrowania" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3305,35 +3895,37 @@ msgstr "" "wykorzystuje zarządzanie woluminami logicznymi (LVM) i wymaga uprawnień " "administratora." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" "Ścieżka, w której umieszczane są gotowe woluminy do momentu przesłania" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Liczba woluminów do utworzenia z wyprzedzeniem" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3341,45 +3933,45 @@ msgstr "" "W przypadku przekazywania asynchronicznego maksymalna dozwolona liczba " "jednoczesnych operacji przekazywania. Ustaw na zero, aby wyłączyć limit." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Dozwolona liczba jednoczesnych operacji przesyłania" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Rejestrowanie informacji wewnętrznych w pliku" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Poziom informacji dziennika" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3388,11 +3980,11 @@ msgstr "" "automatycznie. Aktywuj tę opcję, aby zapobiec automatycznemu tworzeniu " "folderów." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3407,14 +3999,14 @@ msgstr "" "GUID musi być oddzielonych średnikami, a większość form identyfikatorów GUID" " jest dozwolona, w tym z nawiasami klamrowymi i bez." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Rozdzielana średnikami lista identyfikatorów guids obiektów zapisywania " "usługi VSS, które mają zostać wykluczone (tylko system Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3437,11 +4029,22 @@ msgstr "" "powiedzie się. Ta funkcja jest obsługiwana tylko w systemie Windows i wymaga" " uprawnień administratora." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3454,30 +4057,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Weryfikowanie przesłanych treści przez wyświetlanie zawartości listy" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Wysyłaj pliki synchronicznie" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3485,11 +4089,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Nie używaj ponownie połączeń" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3499,33 +4103,33 @@ msgstr "" " liczbę ponownych prób. Włącz tę opcję, aby komunikaty o błędach były " "wyświetlane po wykonaniu ponownej próby." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Pokaż komunikaty o błędach po wykonaniu ponownej próby" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Prześlij puste pliki kopii zapasowej" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3534,22 +4138,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Próg ostrzeżenia o niskim przydziale" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3561,11 +4165,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Obsługa dowiązania symbolicznego" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3581,11 +4185,11 @@ msgstr "" "traktować każde dowiązanie twarde jako unikalną ścieżkę. Opcja \"{2}\" " "zignoruje wszystkie dowiązania twarde z więcej niż jednym łączem." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Obsługa dowiązań twardych" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3593,11 +4197,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Wyklucz pliki według atrybutu" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3609,41 +4213,51 @@ msgstr "" "które są następnie używane do uzyskiwania dostępu do zawartości migawki. To " "obejście może przyspieszyć dostęp do plików w systemie Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mapuj migawki na dysk (tylko Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nazwa kopii zapasowej" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3655,11 +4269,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Zarządzaj nieskompresowanymi rozszerzeniami plików" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3671,71 +4285,63 @@ msgstr "" "spowoduje duży narzut na przechowywanie list plików. Należy zauważyć, że " "wartości nie można zmienić po utworzeniu plików zdalnych." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Rozmiar bloku dla sum kontrolnych" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista plików do sprawdzenia pod kątem zmian" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Ścieżka do lokalnego stanu bazy danych" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista usuniętych plików" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Zmniejsz zużycie pamięci, wyłączając wyszukiwanie w pamięci" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Przechowywanie pamięci podręcznej bloku w pamięci" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Nie wysyłaj zapytań podczas uruchamiania" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3749,11 +4355,11 @@ msgstr "" "Kompromis polega na tym, że większe pliki indeksów zajmują więcej " "przestrzeni zdalnej i mogą nigdy nie być używane." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3765,43 +4371,43 @@ msgstr "" "przestrzeni może zawierać miejsce docelowe przed odzyskaniem. Ta wartość " "jest procentem wykorzystanym na każdym woluminie i całkowitej pamięci." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Maksymalna zmarnowana przestrzeń w procentach" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Algorytm haszujący zastosowany do bloków" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Algorytm haszujący zastosowany do plików" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3814,11 +4420,11 @@ msgstr "" "takie automatyczne kompaktowanie i kompaktować tylko podczas uruchamiania " "polecenia kompaktowania." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Wyłącz automatyczne kompaktowanie" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3830,11 +4436,11 @@ msgstr "" "to, że duże woluminy, które mogą mieć kilka bajtów zmarnowane miejsce nie są" " pobierane i przepisywane." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Próg rozmiaru woluminu" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3844,11 +4450,11 @@ msgstr "" "wymusić grupowanie małych plików. Małe objętości będą zawsze łączone, jeśli " "mogą wypełnić całą objętość." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maksymalna liczba małych woluminów" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3858,42 +4464,42 @@ msgstr "" "istniejące bloki. Jest to dość powolna operacja, ale może ograniczyć rozmiar" " pobieranych plików." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Użyj lokalnych danych z pliku podczas przywracania" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Zachowaj kilka wersji" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Użyj tej opcji, aby ustawić przedział czasu, w którym przechowywane są kopie" " zapasowe." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Przechowuj wszystkie wersje w określonym przedziale czasowym" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3914,31 +4520,31 @@ msgstr "" "kopię zapasową ”. Ta opcja obsługuje również użycie specyfikatora 'U' do " "wskazania nieograniczonego przedziału czasu." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Zmniejsz liczbę wersji, usuwając stare pośrednie kopie zapasowe" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Użyj tej opcji, aby kontynuować, nawet jeśli brakuje niektórych wpisów " "źródłowych." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Zignoruj ​​brakujące elementy źródłowe" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Nadpisz pliki podczas przywracania" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3947,11 +4553,11 @@ msgstr "" "uruchamiania opcji. Ogólnie ta opcja wygeneruje linię dla każdego " "przetwarzanego pliku." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Wyświetl więcej informacji o postępie" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3959,11 +4565,11 @@ msgstr "" "Użyj tej opcji, aby zwiększyć ilość danych wyjściowych generowanych w wyniku" " operacji, w tym wszystkie nazwy plików." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Wyprowadzaj pełne wyniki" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3975,11 +4581,11 @@ msgstr "" "wszystkich zdalnych plików i może być używany do weryfikacji integralności " "plików." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Sprawdź, czy pliki weryfikacyjne zostały przesłane" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3989,11 +4595,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Liczba próbek do przetestowania po wykonaniu kopii zapasowej" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4003,11 +4609,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Procent próbek do przetestowania po utworzeniu kopii zapasowej" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4019,41 +4625,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Rozmiar bufora odczytu pliku" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Pozwól na zmianę hasła szyfrowania" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Wyświetlaj tylko zestawy plików" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4064,11 +4670,11 @@ msgstr "" "tworzenia kopii zapasowych i przywracania, ale nie ma dużego wpływu na " "rozmiar pliku." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4076,11 +4682,11 @@ msgstr "" "Domyślnie uprawnienia nie są przywracane, ponieważ mogą uniemożliwić dostęp " "do plików. Użyj tej opcji, aby przywrócić również uprawnienia." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Przywróć uprawnienia plików" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4090,11 +4696,11 @@ msgstr "" " w celu sprawdzenia, czy przywracanie powiodło się. Użyj tej opcji, aby " "wyłączyć sprawdzanie i uniknąć czekania na weryfikację." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Pomiń sprawdzanie przywróconego pliku" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4104,28 +4710,28 @@ msgstr "" " pobieranych danych. Użyj tej opcji, aby pominąć tę optymalizację i używać " "tylko danych zdalnych." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Nie używaj danych lokalnych" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4133,11 +4739,11 @@ msgstr "" "Użyj tej opcji, aby zwiększyć weryfikację poprzez sprawdzenie skrótu bloków " "odczytanych z woluminu przed dodaniem danych do przywracanych plików." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Sprawdź haszowane bloki" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4151,11 +4757,11 @@ msgstr "" "Wynikową bazę danych można przeszukiwać, ale nie można jej używać do " "przywracania danych." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Napraw bazę danych ze ścieżkami" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4163,11 +4769,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Wymuś ustawienie regionalne" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4177,22 +4783,22 @@ msgstr "" "„Ostatni czwartek”. Po ustawieniu tej opcji wyświetlane są tylko rzeczywiste" " daty, na przykład „12 listopada 2018, 8:01”." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Zarządzaj komunikacją plików z serwerem za pomocą wątków potokowych." -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4202,22 +4808,22 @@ msgstr "" "tej wartości na zero lub mniej spowoduje dynamiczne zrównoważenie liczby " "aktywnych wątków w celu dopasowania do sprzętu." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Ogranicz liczbę jednoczesnych wątków" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Użyj tej opcji, aby ustawić liczbę procesów, które wykonują haszowanie " "danych." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Określanie liczby jednoczesnych procesów pobierania odcisków palców" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4225,11 +4831,11 @@ msgstr "" "Użyj tej opcji, aby ustawić liczbę procesów, które wykonują kompresję danych" " wyjściowych." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Określ liczbę jednoczesnych procesów kompresji" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4240,11 +4846,11 @@ msgstr "" "zapasowej i zawartości przesłanej w niekompletnej sesji tworzenia kopii " "zapasowej." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4252,11 +4858,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4264,11 +4870,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4276,11 +4882,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Zezwól na usunięcie wszystkich zestawów plików" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4296,11 +4902,11 @@ msgstr "" " danych. Ustawienie tego na true pozwoli Duplicati na wykonywanie operacji " "VACUUM według własnego uznania." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4308,11 +4914,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Wyłącz skaner z wyprzedzeniem" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4323,11 +4929,11 @@ msgstr "" "sprawdzanie, upewnij się, że uruchamiasz regularne polecenia sprawdzania, " "aby upewnić się, że wszystko działa zgodnie z oczekiwaniami." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Wyłącz sprawdzanie spójności listy plików" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4335,15 +4941,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Wyłącz kopię zapasową przy zasilaniu bateryjnym" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Poziom informacji o pliku dziennika" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4359,42 +4965,42 @@ msgstr "" "regularne są obsługiwane w nawiasach klamrowych. Przykład: " "\"+Path*{0}+*Mail*{0}-[.*DNS]\"" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Poziom informacyjny konsoli" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4406,11 +5012,11 @@ msgstr "" " posiadanie pliku o nazwie „.nobackup” i umieszczenie tego pliku w " "folderach, których nie należy tworzyć kopii zapasowej." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Lista nazw plików wykluczających foldery" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4418,11 +5024,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4430,11 +5036,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4447,11 +5053,11 @@ msgstr "" "wszystkie zapytania do bazy danych i pamiętaj, aby ustawić --{0}={2} lub " "--{1}={2}, aby raportować dodatkowe dane dziennika" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4460,11 +5066,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4472,11 +5078,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4484,11 +5090,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4497,48 +5252,48 @@ msgstr "" "Biblioteka kryptograficzna nie obsługuje przekształceń wielokrotnego użytku " "dla algorytmu mieszającego {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "Biblioteka kryptograficzna nie obsługuje algorytmu hash {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "Hasło szyfrowania nie może być zmienione dla istniejącej kopii zapasowej" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Nie udało się utworzyć zrzutu: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Nie udało się usunąć pliku {0}, testowanie, czy plik istnieje" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "Rozwiązanie problemu z próbą usunięcia nieistniejącego pliku {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Nie można odzyskać po usunięciu pliku z błędem {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Operacja usuwania nie powiodła się dla {0} z FileNotFound, lista zawartości" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Lista wskazuje, że plik {0} został poprawnie usunięty" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Lista potwierdza, że ​​plik {0} nie został usunięty." @@ -4601,29 +5356,6 @@ msgstr "" msgid "Configure http requests" msgstr "Skonfiguruj żądania http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Zaakceptuj dowolny certyfikat serwera" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Opcjonalnie zaakceptuj znany certyfikat SSL" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4722,7 +5454,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Skonfiguruj moduł Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4730,125 +5474,125 @@ msgstr "" "Ten moduł działa wewnętrznie w celu przeanalizowania parametrów źródłowych w" " celu tworzenia kopii zapasowych baz danych programu Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Konfigurowanie modułu programu Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Wykonaj skrypt" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Wykonaj skrypt na koniec" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Skrypt \"{0}\" zwrócił kod zakończenia {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Skrypt \"{0}\" zwrócił kod zakończenia {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Wykonaj skrypt podczas startu" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Błąd podczas wykonywania skryptu \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Przekroczono limit czasu wykonania skryptu \"{0}\"" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Uruchom skrypt podczas uruchamiania" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Skrypt \"{0}\" zgłosił komunikaty o błędach: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Ten moduł może wysyłać wiadomości e-mail po zakończeniu operacji" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Wyślij email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4861,21 +5605,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Treść wiadomości" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Hasło SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4887,11 +5631,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Adresat(ci) wiadomości e-mail" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4901,25 +5645,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Nadawca wiadomości e-mail" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Wiadomości do wysłania" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4927,11 +5671,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP Url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4940,61 +5684,61 @@ msgstr "" "To ustawienie dostarcza temat wiadomości e-mail. Wartości są zastępowane " "zgodnie z opisem w opisie --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Temat wiadomości" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Nazwa użytkownika SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Całość komunikacji SMTP: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Nie można wysłać wiadomości e-mail z serwera: {0}, wiadomość: {1}, " "ponawianie próby z {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-mail wysłany pomyślnie za pomocą serwera: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Ten moduł zapewnia obsługę wysyłania raportów o stanie za pośrednictwem " "komunikatów XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Moduł raportowania XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "E-mail odbiorcy XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5007,35 +5751,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Szablon wiadomości" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Nazwa użytkownika XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Hasło XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5044,111 +5788,111 @@ msgstr "" "Możesz określić jeden z \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Możesz podać wiele opcji z separatorem przecinków, np. \"{0},{1}\". Specjalna wartość \"{4}\" jest skrótem dla \"{0},{1},{2},{3}\" i spowoduje, że wszystkie operacje tworzenia kopii zapasowych wyślą komunikat." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Wysyłaj wiadomości dla wszystkich operacji" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Limit czasu upłynął podczas logowania do serwera Jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Ten moduł zapewnia obsługę wysyłania raportów o stanie za pośrednictwem " "wiadomości HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Moduł raportów HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Nazwa parametru, który ma wysłać wiadomość jako" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Dodatkowe parametry do dodania do wiadomości http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5156,46 +5900,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Nie udało się wysłać wiadomości: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtr komunikatów dziennika" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5204,7 +5948,7 @@ msgstr "" "uwzględnienia w raporcie. Wartości zerowe lub ujemne oznaczają " "nieograniczone." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5213,18 +5957,18 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Format nie jest obsługiwany: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Nieprawidłowa wartość rozmiaru: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" "Walidator poprawności certyfikatu SSL został wywołany w nieprawidłowej " "kolejności" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5233,7 +5977,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5242,72 +5986,72 @@ msgstr "" "Niepowodzenie podczas sprawdzania poprawności sygnatury certyfikatu, " "komunikat o błędzie: {0}, nazwa błędu SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Katalog tymczasowy nie istnieje: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" "Nie udało się przeanalizować segmentu: {0}, Nieprawidłowa liczba całkowita" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Nieprawidłowy specyfikator: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Dane nieanalizowane: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "URI jest nieprawidłowy: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "W URI brakuje nazwy hosta: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bajtów" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Ciąg \"{0}\" nie może zostać przekształcony na datę" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Nie można odczytywać i zapisywać do tego samego strumienia" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5545,11 +6289,11 @@ msgstr "Dezaktywuj wyjście na konsoli" msgid "This link may provide additional information: {0}" msgstr "Ten link może dostarczyć dodatkowych informacji: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Przełącz automatyczne aktualizacje" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-pt.mo b/Localizations/duplicati/localization-pt.mo index f7dbadcf5..c8e3593be 100644 Binary files a/Localizations/duplicati/localization-pt.mo and b/Localizations/duplicati/localization-pt.mo differ diff --git a/Localizations/duplicati/localization-pt.po b/Localizations/duplicati/localization-pt.po index cf0875010..924ea303f 100644 --- a/Localizations/duplicati/localization-pt.po +++ b/Localizations/duplicati/localization-pt.po @@ -4,20 +4,20 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Rui , 2019 # Paulo Constança , 2019 # Tomás F. , 2021 -# Rui , 2024 -# Peter J. Mello , 2024 # Sérgio Marques , 2024 +# Peter J. Mello , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Sérgio Marques , 2024\n" +"Last-Translator: Peter J. Mello , 2024\n" "Language-Team: Portuguese (https://app.transifex.com/duplicati/teams/67655/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -50,8 +50,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -155,78 +155,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "pseudônimos" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valor padrão" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DESCONTINUADO]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valores" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Booleano" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeração" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Marcas" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Inteiro" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Caminho" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Tamanho" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Cadeia" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Intervalo de tempo" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Desconhecido" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "A pasta não pode ser criada porque já existe" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "A pasta especificada não existe" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancelado" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "O ficheiro requisitado não existe" @@ -343,9 +359,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -362,9 +377,8 @@ msgstr "O nome de domínio do utilizador usado para se conectar ao servidor." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -375,9 +389,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -457,41 +470,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternativo" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -500,47 +503,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Desativar verificação de carregamento" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "A pasta {0} não foi encontrada, mensagem: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Desativar verificação de carregamento" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurar o tipo de conexão de dados FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurar o modo de encriptação do FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Adicionar um atraso após o carregamento de um ficheiro" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -836,6 +987,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -868,110 +1024,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternativo" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurar o tipo de conexão de dados FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurar o modo de encriptação do FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Adicionar um atraso após o carregamento de um ficheiro" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Erro ao eliminar o ficheiro: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Erro ao ler o ficheiro: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Erro ao escrever o ficheiro: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1447,7 +1499,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1455,22 +1511,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1478,28 +1534,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID do site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Não indicou a ID do site" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1511,42 +1567,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Grupo Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID do grupo" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Endereço de e-mail do grupo" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1563,7 +1619,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1574,7 +1630,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1819,43 +1875,45 @@ msgstr "Palavra-passe não fornecida" msgid "No username given" msgstr "Utilizador não fornecido" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1971,6 +2029,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2003,6 +2066,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "A pasta {0} não foi encontrada, mensagem: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2028,6 +2097,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2148,9 +2281,410 @@ msgstr "Código do erro inesperado: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Outra instância está em execução e foi notificada" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2170,13 +2704,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Argumentos suportados na linha de comandos:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2205,11 +2737,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Os filtros não podem ser definidos na linha de comando se os filtros também " -"estiverem no ficheiro de parâmetros. As opções especiais --{0}, --{1}, ou " -"--{2} devem ser utilizadas para se referir aos filtros específicos listados " -"no ficheiro. Cada filtro deve ser prefixado com um '+' ou um '-', e vários " -"filtros devem ser unidos com {3}." #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2247,7 +2774,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2255,7 +2782,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Versão não suportada do SQLite detectado ({0}), deve ser {1} ou superior." #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2268,43 +2794,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"A porta que o servidor web utiliza. Podem ser especificadas várias portas, " -"separadas por vírgulas." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Os ficheiros PKCS #12 de certificado e chave que o servidor web utiliza para" -" SSL. Apenas as chaves RSA e DSA são suportadas." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" -"A palavra-passe para desencriptação do ficheiro do certificado PKCS #12." #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"A ligação de rede que o servidor web utilizar. Os valores especiais '*' e " -"'any' significa todas as interfaces disponíveis, e 'loopback' corresponde ao" -" adaptador virtual acessível apenas por utilizadores locais." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"A palavra-passe necessária para aceder à interface web. Esta é gravada para " -"não precisar de a fornecer sempre, ao deixar aberta desactiva a palavra-" -"passe." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2313,134 +2839,166 @@ msgstr "" "Os hostnames que são autorizados a ligar, separados por ponto-e-vírgula. Um " "'*' permite a ligação as todos e desactiva esta protecção." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Definir o tempo depois do que os dados de registo serão eliminados da base " "de dados." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Apagar dados de registo antigos" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"O Duplicati precisa de guardar uma pequena base de dados com todas as " -"configurações. Usa esta opção para escolher onde as definições são " -"guardadas. Também pode ser definida com a variável de ambiente {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Esta opção define a chave de encriptação utilizada para encriptar a base de " -"dados de definições locais. Também pode ser definida com a variável de " -"ambiente {0}. Utilizar a opção de linha de comando --{1} para desativar a " -"encriptação da base de dados." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Pasta temporária de armazenamento" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2448,15 +3006,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2465,24 +3060,21 @@ msgstr "" "Não foi possível encontrar uma data válida, utilizando a data de início {0}," " o intervalo de repetição {1} e os dias permitidos {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "O servidor foi iniciado e está a escutar em {0}, porta {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Não foi possível criar certificado SSL utilizando os parâmetros fornecidos. " -"Detalhe de excepção: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Não foi possível abrir uma porta para escutar, tentou utilizar: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2494,32 +3086,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2527,69 +3119,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "Ficheiro não encontrado: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "Ficheiro não encontrado: {0}" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -2973,48 +3532,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Prioridade" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limitar o tamanho dos volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3022,11 +3599,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3034,27 +3611,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3069,88 +3646,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3159,12 +3738,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3177,11 +3756,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3194,30 +3784,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3225,44 +3816,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Não reutilizar ligações" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3271,22 +3862,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3298,11 +3889,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Gestão de ligações simbólicas" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3312,11 +3903,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3324,11 +3915,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3336,41 +3927,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nome da cópia de segurança" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3382,11 +3983,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3394,71 +3995,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista de ficheiros eliminados" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3467,11 +4060,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3479,43 +4072,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3523,11 +4116,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3535,62 +4128,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3602,49 +4195,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3656,11 +4249,11 @@ msgstr "" "hashes SHA256 de todos os ficheiros remotos e pode ser usado para verificar " "a integridade dos ficheiros." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3670,11 +4263,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3684,11 +4277,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3700,111 +4293,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Permitir alteração da palavra-passe" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Não utilizar dados locais" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3812,11 +4405,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3824,74 +4417,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3899,11 +4492,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3911,11 +4504,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3923,11 +4516,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3937,11 +4530,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3949,22 +4542,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3972,15 +4565,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3990,42 +4583,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4033,11 +4626,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4045,11 +4638,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4057,11 +4650,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4070,11 +4663,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4083,11 +4676,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4095,11 +4688,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4107,57 +4700,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4211,29 +4953,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurar pedidos HTTP" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4319,131 +5038,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4456,21 +5187,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Texto da mensagem" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Palavra-passe SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4478,11 +5209,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4492,25 +5223,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Mensagem a enviar" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4518,68 +5249,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Assunto do e-mail" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4592,144 +5323,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4737,52 +5468,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4791,16 +5522,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4809,78 +5540,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5095,11 +5826,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Comutar atualizações automáticas" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-pt_BR.mo b/Localizations/duplicati/localization-pt_BR.mo index 0b3a4486f..0b7e6df99 100644 Binary files a/Localizations/duplicati/localization-pt_BR.mo and b/Localizations/duplicati/localization-pt_BR.mo differ diff --git a/Localizations/duplicati/localization-pt_BR.po b/Localizations/duplicati/localization-pt_BR.po index 5f0ebaf71..e05c94d15 100644 --- a/Localizations/duplicati/localization-pt_BR.po +++ b/Localizations/duplicati/localization-pt_BR.po @@ -8,6 +8,7 @@ # wviana , 2016 # canove , 2017 # Felipe Rodrigues da Silva , 2017 +# Luiz Cezar Philippi Junior , 2017 # Mateus Bueno , 2017 # Lincoln Nogueira , 2017 # 121c0555e4161ef983afa1da05d1de8a, 2017 @@ -21,20 +22,18 @@ # Rafael Martins , 2021 # Ricardo Malta , 2024 # Madson Coelho , 2024 -# Ednilsom Montanhole , 2024 # Danilo Silva, 2024 -# Luiz Cezar Philippi Junior , 2024 -# Tácio Andrade , 2024 # Paulo Calixto , 2024 +# Tácio Andrade , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Paulo Calixto , 2024\n" +"Last-Translator: Tácio Andrade , 2024\n" "Language-Team: Portuguese (Brazil) (https://app.transifex.com/duplicati/teams/67655/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -67,8 +66,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -178,78 +177,94 @@ msgstr "Falha ao invocar GnuPG, o programa não descarregará a saída" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Falha ao invocar o GnuPG, o programa não terminará" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliases" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valor padrão" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[DEPRECIADO]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valores" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boleano" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumeração" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Sinalizadores" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Inteiro" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Caminho" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Tamanho" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Timespan" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Desconhecido" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "A pasta não pode ser criada por que ela já existe" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "A pasta solicitada não existe" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Cancelado" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "O arquivo solicitado não existe" @@ -380,9 +395,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -399,9 +413,8 @@ msgstr "O nome de domínio do usuário usado para se conectar ao servidor." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -414,9 +427,8 @@ msgstr "" "O nome de usuário usado para se conectar ao servidor. Isto também pode ser " "fornecido como a variável de ambiente \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -508,41 +520,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP Alternativo" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -553,50 +555,212 @@ msgstr "" "Senha usada para se conectar ao servidor. Isto também pode ser fornecido " "como a variável de ambiente \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Para proteger contra falhas de rede, cada upload será verificado. Use esta " -"opção para desativar essa verificação e fazer um upload mais rápido, mas " -"menos confiável." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Desativar verificação de upload" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"O arquivo {0} foi carregado, mas o tamanho devolvido foi {1} e esperava-se " +"que fosse {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Para proteger contra falhas de rede ou servidor, cada upload será " +"verificado. Use esta opção para desativar esta verificação para tornar o " +"upload mais rápido, mas menos confiável." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Desativar verificação de upload" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Se esta opção estiver definida, o tipo de conexão de dados FTP será alterado" +" para a opção selecionada." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurar o tipo de conexão de dados FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Se esta opção estiver definida, o modo de criptografia FTP será alterado " +"para a opção selecionada." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurar o modo de criptografia de FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Esta opção controle a política SSL que será usada quando a criptografia " +"estiver habilitada." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Configurar a política de SSL a ser usada quando a criptografia estiver " +"ativada" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Alguns servidores FTP precisam de um pequeno delay antes de reportar o " +"tamanho correto do arquivo. O delay requerido depende da topologia da rede. " +"Caso apresente algum erro de tamanho do upload não combina, tente adicionar " +"um dalay de alguns segundos." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Adicione um atraso após o upload de um arquivo" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -905,6 +1069,11 @@ msgstr "" msgid "Specify storage class" msgstr "Especificar o tipo de armazenamento" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -937,127 +1106,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP Alternativo" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"O arquivo {0} foi carregado, mas o tamanho devolvido foi {1} e esperava-se " -"que fosse {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Para proteger contra falhas de rede ou servidor, cada upload será " -"verificado. Use esta opção para desativar esta verificação para tornar o " -"upload mais rápido, mas menos confiável." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Se esta opção estiver definida, o tipo de conexão de dados FTP será alterado" -" para a opção selecionada." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurar o tipo de conexão de dados FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Se esta opção estiver definida, o modo de criptografia FTP será alterado " -"para a opção selecionada." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurar o modo de criptografia de FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Esta opção controle a política SSL que será usada quando a criptografia " -"estiver habilitada." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Configurar a política de SSL a ser usada quando a criptografia estiver " -"ativada" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Alguns servidores FTP precisam de um pequeno delay antes de reportar o " -"tamanho correto do arquivo. O delay requerido depende da topologia da rede. " -"Caso apresente algum erro de tamanho do upload não combina, tente adicionar " -"um dalay de alguns segundos." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Adicione um atraso após o upload de um arquivo" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Erro na exclusão do arquivo: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Erro ao ler o arquivo: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Erro ao escrever o arquivo: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Módulo para a geração de chaves SSH pública e privada" @@ -1566,7 +1614,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Se a classe HttpClient deve ser usada" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1574,11 +1626,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1588,11 +1640,11 @@ msgstr "" "unidade padrão do OneDrive ou do OneDrive for Business será usada por meio " "de '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "ID opcional da unidade" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1600,28 +1652,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID do site" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Nenhum ID do site foi fornecido" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "IDs de sites conflitantes usados: dados {0} mas encontrados {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1633,42 +1685,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Grupo do Microsoft Office 365" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID do grupo" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Endereço de email do grupo" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Nenhum grupo foi encontrado com o endereço de e-mail fornecido: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "" "Vários grupos foram encontrados com o endereço de e-mail fornecido: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "IDs de grupo conflitantes usados: dado {0} mas encontrado {1}" @@ -1686,7 +1738,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1697,7 +1749,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1956,43 +2008,45 @@ msgstr "Nenhuma senha informada" msgid "No username given" msgstr "Nenhum nome de usuário informado" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2127,6 +2181,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2161,6 +2220,12 @@ msgstr "" "O servidor retornou o código de erro {0} ({1}), indicando que o servidor não" " suporta conexões WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "A pasta {0} não foi encontrada. Mensagem: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2189,6 +2254,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Despeja a resposta PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Aceita vários servidores de certificados" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Opcionalmente, aceitar um certificado SSL conhecido" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2313,9 +2442,410 @@ msgstr "Código de erro inesperado: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Outra instância está em execução e foi notificada" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2335,13 +2865,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Argumentos de linha de comando suportados:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2370,11 +2898,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Os filtros não podem ser especificados na linha de comando se os filtros " -"também estiverem presentes no arquivo de parâmetro. Use as opções especiais " -"--{0}, --{1} ou --{2} para especificar filtros dentro do arquivo de " -"parâmetro. Cada filtro deve ser prefixado com um + ou um -, e vários filtros" -" devem ser associados com {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2412,7 +2935,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2420,7 +2943,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Versão do SQLite detectada não suportada ({0}), deve ser {1} ou superior" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2433,42 +2955,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"A porta do servidor web escuta em. Múltiplos valores podem ser suportados " -"entre vírgulas" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"O certificado e o arquivo de chave no formato PKCS #12 do servidor web para " -"SSL. Somente as chaves RSA / DSA são suportadas." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "A senha para descriptografar o arquivo com certificado PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"A interface na qual o servidor web escuta. Os valores especiais \"*\" e " -"\"qualquer\" significam qualquer interface. O valor especial \"loopback\" " -"significa o adaptador de loopback." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"A senha é necessária para acessar o servidor web. Esta opção é salva para " -"que não seja necessário defini-la em cada execução. Definir um valor vazio " -"desabilita a senha." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2478,135 +3001,166 @@ msgstr "" " dos nomes de host for \"*\", todos os nomes de host serão permitidos e a " "verificação do nome do host será desativada." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Defina o tempo após o qual os dados do registro serão purgados do banco de " "dados." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Limpar log de dados antigos" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati precisa armazenar um pequeno banco de dados com todas as " -"configurações. Use esta opção para escolher onde as configurações estão " -"armazenadas. Esta opção também pode ser definida com a variável de ambiente " -"{0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Esta opção define a chave de criptografia usada para codificar o banco de " -"dados de configurações locais. Esta opção também pode ser definida com a " -"variável de ambiente {0}. Use a opção --{1} para desativar a codificação do " -"banco de dados." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Pasta de armazenamento temporário" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2614,15 +3168,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2631,24 +3222,21 @@ msgstr "" "Não é possível encontrar uma data válida, dada a data de início {0}, o " "intervalo de repetição {1} e os dias permitidos {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Servidor foi iniciado e está ouvindo em {0}, porta {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Não foi possível criar o certificado SSL usando os parâmetros fornecidos. " -"Detalhe da exceção: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Impossível abrir um socket para comunicação, tentar portas: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2661,22 +3249,22 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Falha ao carregar o tipo de processo {0} assembly {1}, mensagem de erro: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2684,11 +3272,11 @@ msgstr "" "Esta opção controla o nível de compressão utilizado. Um ajuste de zero não " "dá nenhuma compressão, e um ajuste de 9 dá a compressão máxima." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2696,75 +3284,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Arquivo não encontrado: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "* Experimental *: 7z Arquivo com suporte LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Experimental - 7z Arquivo" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Arquivo não aberto para escrita." - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Arquivo não aberto para leitura." - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "O conteúdo não faz parte deste arquivo." - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"O número de threads utilizados na compressão LZMA 2. Padrão é o número de " -"núcleos do processador." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Número de tarefas utilizadas na compressão" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Esta opção controla o algoritmo de compressão utilizado. Habilitar esta " -"opção fará com que o 7z use o algoritmo rápido, que produz uma compressão " -"ligeiramente menor." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "cópia" @@ -3202,48 +3752,66 @@ msgstr "Limite o tamanho dos arquivos que estão sendo feito backup" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Tarefa prioritária" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limite de tamanho para os volumes" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3255,11 +3823,11 @@ msgstr "" "criar novos volumes, ao ler um arquivo existente, o nome do arquivo é usado " "para selecionar o módulo de compactação." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Selecione qual o módulo utilizado para a compressão" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3271,27 +3839,27 @@ msgstr "" "criar novos volumes, ao ler um arquivo existente, o nome do arquivo é usado " "para selecionar o módulo de criptografia." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Selecione qual o módulo utilizado para encriptação" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3318,34 +3886,36 @@ msgstr "" "Copy Services (VSS) e requer privilégios administrativos. No Linux, usa " "Logical Volume Management (LVM) e requer privilégios de root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "O caminho onde os volumes prontos são colocados até serem carregados" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "O número de volumes criadas antecipadamente" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3353,45 +3923,45 @@ msgstr "" "Ao realizar uploads assíncronos, o número máximo de uploads simultâneos " "permitidos. Defina como zero para desativar o limite." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Número de uploads simultâneos permitidos" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Registrar informações internas em um arquivo" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Nível de informação de log" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3400,11 +3970,11 @@ msgstr "" "la automaticamente. Ative esta opção para evitar a criação automática de " "pastas." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3418,14 +3988,14 @@ msgstr "" "instância. Múltiplos GUIDs devem ser separados com um ponto-e-vírgula e a " "maioria das formas de GUIDs são permitidas, inclusive com e sem chaves." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Uma lista separada por ponto-e-vírgula de guids de escritores VSS para " "excluir (apenas Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3447,11 +4017,22 @@ msgstr "" "fará o Duplicati abortar o backup se o uso USN falhar. Esse recurso é " "suportado apenas no Windows e requer privilégios administrativos." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3464,30 +4045,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Verifique envio por conteúdo listado" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Envio de arquivos sincronizadamente" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3495,11 +4077,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Não reutilize conexões" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3509,33 +4091,33 @@ msgstr "" "denunciará o número de tentativas. Ative esta opção para exibir as mensagens" " de erro quando uma repetição é executada." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Mostrar mensagens de erro quando uma nova tentativa for executada" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Envio de cópia de arquivos vazio" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3544,22 +4126,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Limite de aviso sobre quota baixa." -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3571,11 +4153,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Manipulação de link simbólico" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3590,11 +4172,11 @@ msgstr "" "informações do hardlink e tratará cada hardlink como um caminho exclusivo. A" " opção \"{2}\" ignorará todos os hardlinks com mais de um link." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Manipulação de Hardlink" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3602,11 +4184,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Exclusão de arquivos por atributo" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3618,41 +4200,51 @@ msgstr "" "temporárias que serão usadas para acessar o conteúdo de um snapshot. Esta " "solução alternativa pode acelerar o acesso a arquivos no Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mapa de snapshot em uma unidade de disco (apenas no Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Nome para a cópia" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3664,11 +4256,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Gerenciar extensões de arquivo não compressíveis" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3681,71 +4273,63 @@ msgstr "" "listas de arquivos. Observe que o valor não pode ser alterado após a criação" " de arquivos remotos." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Tamanho do bloco usado no hash" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista de arquivos para examinar as alterações" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Caminho para o banco de dados local" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista de arquivos excluídos" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Reduzir a pegada de memória desativando pesquisas em memória" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Armazene um cache de bloco na memória" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Não faça consultas no backend na inicialização" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3759,11 +4343,11 @@ msgstr "" " rápidas podem prosseguir sem o banco de dados. O tradeoff é que os arquivos" " de índice maiores ocupam mais espaço remoto e que nunca podem ser usados." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3775,43 +4359,43 @@ msgstr "" "que o destino pode conter antes de ser recuperado. Esse valor é uma " "porcentagem usada em cada volume e no armazenamento total." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Espaço máximo desperdiçado em percentagem" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "O algoritmo hash usado em blocos" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "O algoritmo hash usado em arquivos" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3823,11 +4407,11 @@ msgstr "" " remotos serão compactados. Use esta opção para desativar essa compactação " "automática e apenas compacta ao executar o comando compacto." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Desabilitar compactação automática" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3839,11 +4423,11 @@ msgstr "" "Isso garante que volumes grandes que podem ter alguns bytes de espaço " "desperdiçado não são baixados e reescritos." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Limite do tamanho do volume" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3854,11 +4438,11 @@ msgstr "" "volumes sempre serão combinados quando eles puderem preencher um volume " "inteiro." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Número máximo para pequenos volumes" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3868,40 +4452,40 @@ msgstr "" "blocos existentes. Esta é uma operação bastante lenta, mas pode limitar o " "tamanho dos downloads." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Use dados de arquivos locais ao restaurar" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Armazenar um número de versões" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "Use esta opção para definir o período em que os backups são mantidos." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Mantenha todas as versões dentro de um período de tempo" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3920,30 +4504,30 @@ msgstr "" "este \". Esta opção também suporta a utilização do especificador \"U\" para " "indicar um intervalo de tempo ilimitado." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Reduza o número de versões ao apagar backups antigos" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Use esta opção para continuar, mesmo que faltem algumas entradas de origem." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignorar elementos de origem faltantes" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Substituir arquivos ao restaurar" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3952,11 +4536,11 @@ msgstr "" "opção. Geralmente, esta opção produzirá uma linha para cada arquivo " "processado." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Exibir mais informações de progresso" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3964,11 +4548,11 @@ msgstr "" "Use esta opção para aumentar a quantidade de saída gerada como resultado da " "operação, incluindo todos os nomes de arquivos." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Mostrar resultados completos" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3980,11 +4564,11 @@ msgstr "" "hashes SHA256 de todos os arquivos remotos e pode ser usado para verificar a" " integridade dos arquivos." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Determinar se os arquivos de verificação estão enviados" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3994,11 +4578,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "O número de amostras a serem testadas após um backup" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4008,11 +4592,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Porcentagem de amostras a serem testadas após um backup" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4024,41 +4608,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Tamanho do buffer de leitura de arquivos" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Permitir que a senha mude" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Listar apenas conjuntos de arquivos" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4069,11 +4653,11 @@ msgstr "" "operações de backup e restauração, mas não afetará muito o tamanho do " "arquivo." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4081,11 +4665,11 @@ msgstr "" "Por padrão, as permissões não são restauradas, pois podem impedir que você " "acesse seus arquivos. Use esta opção para restaurar as permissões também." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restaurar permissões de arquivo" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4095,11 +4679,11 @@ msgstr "" "restaurados é verificado para verificar se a restauração foi bem-sucedida. " "Use esta opção para desativar a verificação e evitar aguardar a verificação." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Ignorar verificação de arquivo restaurado" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4109,28 +4693,28 @@ msgstr "" "quantidade de dados baixados. Use esta opção para ignorar esta otimização e " "usar apenas dados remotos." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Não utilizar dados locais" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4138,11 +4722,11 @@ msgstr "" "Utilize esta opção para incrementar a verificação por checagem de hash dos " "blocos escritos por um volume antes de " -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Verifique os hashes do bloco" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4155,11 +4739,11 @@ msgstr "" "todas as informações. O banco de dados resultante pode ser pesquisado, mas " "não pode ser usado para restaurar dados." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Reparar banco de dados com caminhos" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4167,11 +4751,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Forçar a configuração da localidade" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4181,23 +4765,23 @@ msgstr "" "\"Hoje\" ou \"Última quinta-feira\". Ao definir esta opção, apenas as datas " "reais são exibidas, \"12 de novembro de 2018, 8:01\", por exemplo." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Gerencie a comunicação de arquivos com o backend usando threaded pipes" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4207,22 +4791,22 @@ msgstr "" "valor como zero ou menos equilibrará dinamicamente o número de threads " "ativos para ajustar o hardware." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Limitar o número de threads simultâneas" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Use esta opção para definir o número de processos que executam o hash de " "dados." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Especifique o número de processos de hashing simultâneos" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4230,11 +4814,11 @@ msgstr "" "Use essa opção para definir o número de processos que executam a compactação" " dos dados de saída." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Especifique o número de processos de compactação simultâneos" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4244,11 +4828,11 @@ msgstr "" "uma lista de arquivos que é uma mesclagem do último backup completo e os " "conteúdos que foram enviados na sessão de backup incompleta." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4256,11 +4840,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4268,11 +4852,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4280,11 +4864,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Permitir remover todos os conjuntos de arquivos" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4300,11 +4884,11 @@ msgstr "" "entradas válidas no banco de dados. Definir isso como verdadeiro permitirá " "que o Duplicati execute operações VACUUM a seu critério." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4312,11 +4896,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Desabilitar o scanner read-ahead " -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4327,11 +4911,11 @@ msgstr "" "verificações, certifique-se de executar comandos de verificação regulares " "para garantir que tudo esteja funcionando conforme o esperado." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Desativar verificações de consistência da lista de arquivos" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4339,15 +4923,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Desativar o backup quando estiver usando a bateria" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Nível de informação do arquivo de log" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4363,42 +4947,42 @@ msgstr "" "são suportadas em \"hard braces\". Exemplo: \"+CAMINHO*{0}+*EMAIL* " "{0}-[.*DNS]\"" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Nível de informação do console" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4410,11 +4994,11 @@ msgstr "" "seria ter um arquivo chamado algo como \".nobackup\" e colocar esse arquivo " "em pastas que não devem ser submetidas a backup." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Lista de nomes de arquivos que excluem pastas" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4422,11 +5006,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4434,11 +5018,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4451,11 +5035,11 @@ msgstr "" "as consultas ao banco de dados e lembre-se de definir --{0}={2} ou --{1}={2}" " para relatar os dados de log adicionais" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4464,11 +5048,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4476,11 +5060,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4488,11 +5072,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4501,50 +5234,50 @@ msgstr "" "A biblioteca de criptografia não suporta transformações reutilizáveis ​​para" " o algoritmo hash {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "A biblioteca de criptografia não suporta o algoritmo hash {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "A frase de acesso não pode ser inserida em uma cópia existente" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Falha na criação de um snapshot: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Falha ao excluir o arquivo {0}, testando se o arquivo existe" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Recuperado do problema com a tentativa de excluir o arquivo não existente " "{0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Falha ao recuperar-se do erro ao excluir o arquivo {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "A operação de exclusão falhou para {0} com \"arquivo não encontrado\", " "listando conteúdo" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Listagem indica que o arquivo {0} foi deletado corretamente" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Listagem indica que o arquivo {0} não foi deletado" @@ -4608,29 +5341,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurar requisições http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Aceita vários servidores de certificados" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Opcionalmente, aceitar um certificado SSL conhecido" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4730,7 +5440,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configure o módulo Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4738,125 +5460,125 @@ msgstr "" "Este módulo funciona internamente para analisar parâmetros de origem para " "fazer backup de bancos de dados do Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configurar módulo Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Rodar script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Executar um script na saída" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "O script \"{0}\" retornou com o código de erro {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "O script \"{0}\" retornou com o código de saída {1} {2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Executar um script necessário na inicialização" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Erro durante a execução do script \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "A execução do script \"{0}\" expirou" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Rodar um script na inicialização" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "O script \"{0}\" relatou mensagem de erro: \"{1}\"" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Este modulo pode enviar email após completa uma operação." -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Envio de email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4869,21 +5591,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "O corpo da mensagem" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Senha SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4894,11 +5616,11 @@ msgstr "" "Exemplo para 3 destinatários:\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Email destinatário(s)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4908,25 +5630,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Remetente de email" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "A mensagem para envio" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4934,11 +5656,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP Url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4947,60 +5669,60 @@ msgstr "" "Esta configuração fornece o assunto do e-mail. Os valores são substituídos " "como descrito na descrição para --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "O assunto do email" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Nome do usuário SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Comunicação SMTP completa: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Falha ao envio o email com servidor: {0}. mensagem: {1}. tentando com {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email enviado com sucesso usando o servidor: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Este módulo fornece suporte para enviar relatórios de status através de " "mensagens XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Módulo relatório XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "Destinatário email XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5013,35 +5735,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "O modelo de mensagem" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "O nome de usuário XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "A senha XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5050,111 +5772,111 @@ msgstr "" "Você pode especificar um para \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Você pode fornecer múltiplas opções separadas por aspas, por exemplo \"{0},{1}\". O valor especial \"{4}\" é uma forma abreviada para \"{0},{1},{2},{3}\" e vai causar o envio de uma mensagem em toda a operação de cópia." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Enviar mensagem para todas operações" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "O tempo limite estourou ao efetuar login no servidor jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Este módulo fornece suporte para enviar relatórios de status via mensagens " "HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Módulo relatório HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "O nome do parâmetro para enviar a mensagem como" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Parâmetros extras para adicionar à mensagem http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5162,46 +5884,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Falha ao enviar mensagem: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filtro de mensagens de log" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5209,7 +5931,7 @@ msgstr "" "Use essa opção para definir o número máximo de linhas de log a serem " "incluídas no relatório. Valores zero ou negativos significam ilimitado." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5218,16 +5940,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "O formato não é suportado: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Valor de tamanho inválido: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "O validador do certificado SSL foi chamado em uma ordem incorreta" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5236,7 +5958,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5245,71 +5967,71 @@ msgstr "" "Falha ao validar o hash do certificado, mensagem de erro: {0}, nome do erro " "SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Pasta temporária não existe: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Falha ao analisar o segmento: {0}, número inválido" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Especificador inválido: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Dado não analisado: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "A Uri é inválida: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Na Uri está faltando um nome de host: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "A string \"{0}\" não pôde ser analisada em uma data" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Não é possível ler e gravar no mesmo fluxo" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5546,11 +6268,11 @@ msgstr "Desativar console de saída " msgid "This link may provide additional information: {0}" msgstr "Este link pode fornecer informações adicionais: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Habilitar atualizações automáticas" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-ro.mo b/Localizations/duplicati/localization-ro.mo index 435ea42b2..c27a53d5d 100644 Binary files a/Localizations/duplicati/localization-ro.mo and b/Localizations/duplicati/localization-ro.mo differ diff --git a/Localizations/duplicati/localization-ro.po b/Localizations/duplicati/localization-ro.po index c9b8b36d5..44b6b73bb 100644 --- a/Localizations/duplicati/localization-ro.po +++ b/Localizations/duplicati/localization-ro.po @@ -4,9 +4,9 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Robert Cocirlea , 2017 # George Draghici , 2017 # Daniel Jircă , 2024 -# Robert Cocirlea , 2024 # Leonte Cristian , 2024 # #, fuzzy @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Leonte Cristian , 2024\n" "Language-Team: Romanian (https://app.transifex.com/duplicati/teams/67655/ro/)\n" @@ -49,8 +49,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -160,78 +160,94 @@ msgstr "Defectul în timp ce invocați GnuPG, programul nu va elimina ieșirea" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Eșecul în timp ce invocați GnuPG, programul nu se va termina" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "pseudonime" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "valoare implicită" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[ÎNVECHITĂ]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "valorile" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "boolean" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Enumerare" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Steaguri" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Întreg" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "cale" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "mărimea" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Şir" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Interval de timp" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Necunoscut" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Dosarul nu poate fi creat deoarece există deja" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Dosarul solicitat nu există" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Anulat" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Fișierul solicitat nu există" @@ -359,9 +375,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -378,9 +393,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -393,9 +407,8 @@ msgstr "" "Numele de utilizator utilizat pentru a vă conecta la server. Aceasta poate " "fi furnizată și ca variabila de mediu \"AUTH_USERNAME\"." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -487,41 +500,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "FTP alternativ" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -532,51 +535,208 @@ msgstr "" "Parola utilizată pentru conectarea la server. Aceasta poate fi furnizată și " "ca variabila de mediu \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Pentru a proteja împotriva eșecurilor din rețea, se va încerca să fie " -"verificată fiecare încărcare. Utilizați această opțiune pentru a dezactiva " -"această verificare pentru a face încărcarea mai rapidă, dar mai puțin " -"fiabilă." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Dezactivați verificarea încărcării" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Dosarul {0} nu a fost găsit, mesajul: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Dosarul {0} nu a fost găsit. Mesaj: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Fișierul {0} a fost încărcat, dar dimensiunea returnată a fost {1} și era de" +" așteptat să fie {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Pentru a proteja împotriva eșecurilor de rețea sau de server, se va încerca " +"să fie verificată fiecare încărcare. Utilizați această opțiune pentru a " +"dezactiva această verificare pentru a face încărcarea mai rapidă, dar mai " +"puțin fiabilă." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Dezactivați verificarea încărcării" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Dacă acest flag este setat, tipul conexiunii de date FTP va fi modificat la " +"opțiunea selectată." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Configurați tipul de conexiune de date FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Dacă acest flag este setat, modul de criptare FTP va fi modificat la " +"opțiunea selectată." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Configurați modul de criptare FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Acest steguleț controlează politica SSL care trebuie utilizată atunci când " +"este activată criptarea." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Configurați politica SSL pe care să o utilizați când este activată criptarea" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -883,6 +1043,11 @@ msgstr "" msgid "Specify storage class" msgstr "Specificați clasa de stocare" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -915,123 +1080,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "FTP alternativ" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Dosarul {0} nu a fost găsit. Mesaj: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Fișierul {0} a fost încărcat, dar dimensiunea returnată a fost {1} și era de" -" așteptat să fie {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Pentru a proteja împotriva eșecurilor de rețea sau de server, se va încerca " -"să fie verificată fiecare încărcare. Utilizați această opțiune pentru a " -"dezactiva această verificare pentru a face încărcarea mai rapidă, dar mai " -"puțin fiabilă." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Dacă acest flag este setat, tipul conexiunii de date FTP va fi modificat la " -"opțiunea selectată." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Configurați tipul de conexiune de date FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Dacă acest flag este setat, modul de criptare FTP va fi modificat la " -"opțiunea selectată." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Configurați modul de criptare FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Acest steguleț controlează politica SSL care trebuie utilizată atunci când " -"este activată criptarea." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Configurați politica SSL pe care să o utilizați când este activată criptarea" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Eroare la ștergerea fișierului: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Eroare la citirea fișierului: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Eroare la scrierea fișierului: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul pentru generarea cheilor private / publice SSH" @@ -1520,7 +1568,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1528,22 +1580,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1551,28 +1603,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1584,42 +1636,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1636,7 +1688,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1647,7 +1699,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1902,43 +1954,45 @@ msgstr "Nu a fost dată nici o parolă" msgid "No username given" msgstr "Niciun nume de utilizator dat" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2075,6 +2129,11 @@ msgstr "" msgid "Dropbox" msgstr "dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2109,6 +2168,12 @@ msgstr "" "Serverul a returnat codul de eroare {0} ({1}), indicând faptul că serverul " "nu suportă conexiuni WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Dosarul {0} nu a fost găsit, mesajul: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2137,6 +2202,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Dați răspunsul PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Acceptați orice certificat de server" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Opțional acceptați un certificat SSL cunoscut" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2261,9 +2390,410 @@ msgstr "Codul de eroare neașteptat: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "O altă instanță rulează, și a fost notificată" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2283,13 +2813,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Argumente de de comandă suportate:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2318,11 +2846,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filtrele nu pot fi specificate pe linia de comandă dacă filtrele sunt " -"prezente și în fișierul cu parametri. Utilizați opțiunile speciale - {0}, - " -"{1} sau - {2} pentru a specifica filtrele din interiorul fișierului " -"parametru. Fiecare filtru trebuie să fie prefixat fie cu + sau -, iar mai " -"multe filtre trebuie să fie asociate cu {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2360,7 +2883,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2368,8 +2891,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Versiune neacceptată de SQLite detectată ({0}) trebuie să fie {1} sau mai " -"mare" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2382,64 +2903,70 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Portul pe care serverul web îl ascultă. Mai multe valori pot fi adăugate cu " -"o virgulă între ele." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Parola pentru decriptarea certificatului PKCS # 12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Interfața pe care serverele web o ascultă. Valorile speciale \"*\" și " -"\"orice\" înseamnă orice interfață. Valoarea specială \"loopback\" înseamnă " -"adaptorul loopback." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Setați perioada după care datele din jurnal vor fi epurate din baza de date." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Curăță datele vechi ale jurnalului" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2447,11 +2974,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2459,86 +2986,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Dosarul de stocare temporară" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2546,34 +3112,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2590,22 +3192,22 @@ msgstr "" "Nu a reușit să se încarce tipul de proces {0} ansamblul {1}, mesajul de " "eroare: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2613,11 +3215,11 @@ msgstr "" "Această opțiune controlează nivelul de compresie utilizat. O setare de zero " "nu dă nici o compresie, iar o setare de 9 oferă o comprimare maximă." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2625,74 +3227,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "Fișierul nu a fost găsit: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Arhiva nu a fost deschisă pentru scriere" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Arhiva nu a fost deschisă pentru citire" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Fișierul dat nu face parte din această arhivă" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Numărul de fire utilizate în compresia LZMA 2. Implicit la numărul de nuclee" -" de procesoare." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Numărul de fire utilizate în comprimare" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Această opțiune controlează algoritmul de comprimare utilizat. Activarea " -"acestei opțiuni va determina 7z să utilizeze algoritmul rapid, care produce " -"o comprimare puțin mai mică." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "Fișierul nu a fost găsit: {0}" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -3136,48 +3700,66 @@ msgstr "Limitați dimensiunea fișierelor care au fost salvate" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Prioritate de prioritate" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Limitați dimensiunea volumelor" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3190,11 +3772,11 @@ msgstr "" "existent, numele fișierului este utilizat pentru a selecta modulul de " "comprimare." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Selectați ce modul să utilizați pentru comprimare" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3207,27 +3789,27 @@ msgstr "" "existent, numele fișierului este utilizat pentru a selecta modulul de " "criptare." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Selectați ce modul să utilizați pentru criptare" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3242,78 +3824,80 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Calea în care sunt plasate volumele pregătite până la încărcare" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Numărul de volume pe care trebuie să le creați înainte de timp" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Nivel de informație log" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3321,11 +3905,11 @@ msgstr "" "Dacă Duplicati detectează lipsa dosarului țintă, îl va crea automat. " "Activați această opțiune pentru a împiedica crearea automată a folderelor." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3340,14 +3924,14 @@ msgstr "" "trebuie să fie separate cu punct și virgulă și majoritatea formelor de GUID-" "uri sunt permise, inclusiv cu și fără bretele curbate." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Se exclude o listă de guiduri ale scriitorilor VSS separate prin punct și " "virgulă (numai pentru Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3370,11 +3954,22 @@ msgstr "" "copia de siguranță dacă utilizarea USN nu reușește. Această caracteristică " "este acceptată numai în Windows și necesită privilegii administrative." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3387,30 +3982,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Verificați încărcările prin afișarea conținutului" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Încărcați fișiere sincron" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3418,11 +4014,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Nu reutilizați conexiunile" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3432,33 +4028,33 @@ msgstr "" "numărul de încercări. Activați această opțiune pentru a afișa mesajele de " "eroare atunci când este efectuată o nouă încercare." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Afișați mesajele de eroare când este efectuată o nouă încercare" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Încărcați fișiere de rezervă goale" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3467,22 +4063,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3494,11 +4090,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Manipularea simbolică" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3514,11 +4110,11 @@ msgstr "" "hardlink ca o cale unică. Opțiunea \"{2}\" va ignora toate hardlink-urile cu" " mai mult de un link." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Manipularea hardlinkurilor" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3526,11 +4122,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Excludeți fișierele după atribut" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3543,41 +4139,51 @@ msgstr "" "instantaneu. Această soluție poate accelera accesul la fișiere în Windows " "XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Împărțiți imaginile unei unități (numai pentru Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Numele de rezervă" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3589,11 +4195,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Gestionați extensiile de fișiere care nu pot fi comprimate" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3606,71 +4212,63 @@ msgstr "" "cheltuială la stocarea listelor de fișiere. Rețineți că valoarea nu poate fi" " modificată după crearea fișierelor la distanță." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Dimensiunea blocurilor utilizate în hașcare" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista fișierelor de examinat pentru modificări" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Calea către baza de date locală de stat" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista fișierelor șterse" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Reduceți amprenta de memorie dezactivând căutările în memorie" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Stocați o memorie cache în memorie" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Nu interogați backend la pornire" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3685,11 +4283,11 @@ msgstr "" "mai mari ocupă un spațiu mai îndepărtat și care nu pot fi folosite " "niciodată." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3702,43 +4300,43 @@ msgstr "" "a fi recuperat. Această valoare reprezintă un procentaj utilizat pentru " "fiecare volum și pentru stocarea totală." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Spațiul maxim pierdut în procente" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Algoritmul hash utilizat pe blocuri" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Algoritmul hash utilizat în fișiere" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3751,11 +4349,11 @@ msgstr "" "pentru a dezactiva o astfel de compactare automată și numai compactă atunci " "când executați comanda compactă." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Dezactivați compactarea automată" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3767,11 +4365,11 @@ msgstr "" "dimensiunea volumului. Acest lucru asigură că volume mari care pot avea " "câteva octeți pierduți în spațiu nu sunt descărcate și rescrise." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Volumul pragului de dimensiune" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3781,11 +4379,11 @@ msgstr "" "valoare poate forța gruparea fișierelor mici. Volumele mici vor fi combinate" " întotdeauna când pot umple un întreg volum." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Numărul maxim de volume mici" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3795,42 +4393,42 @@ msgstr "" "pentru a găsi blocurile existente. Aceasta este o operație destul de lentă " "dar poate limita dimensiunea descărcărilor." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Folosiți datele locale ale fișierelor atunci când restaurați" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Păstrați o serie de versiuni" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Utilizați această opțiune pentru a seta intervalul de timp în care sunt " "păstrate copii de siguranță." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Păstrați toate versiunile într-un interval de timp" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3842,31 +4440,31 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Reduceți numărul de versiuni ștergând copiile de rezervă vechi" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Utilizați această opțiune pentru a continua chiar dacă lipsesc unele intrări" " de surse." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Ignorați elementele sursă care lipsesc" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Suprascrieți fișierele atunci când restaurați" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3875,11 +4473,11 @@ msgstr "" "rularea unei opțiuni. În general, această opțiune va produce o linie pentru " "fiecare fișier procesat." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Obțineți mai multe informații despre progres" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3887,11 +4485,11 @@ msgstr "" "Utilizați această opțiune pentru a crește cantitatea de ieșire generată ca " "rezultat al operației, inclusiv toate numele de fișiere." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Rezultatele rezultate complete" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3903,11 +4501,11 @@ msgstr "" "conține mărimea și șahurile SHA256 ale tuturor fișierelor la distanță și " "poate fi folosit pentru a verifica integritatea fișierelor." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Determinați dacă fișierele de verificare sunt încărcate" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3917,11 +4515,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Numărul de mostre pentru a testa după o copie de rezervă" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3931,11 +4529,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3947,41 +4545,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Dimensiunea fișierului de citire a fișierului" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Permiteți modificării expresiei de acces" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Listează numai fișierele" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -3992,11 +4590,11 @@ msgstr "" "accelera operațiile de backup și restaurare, dar nu va afecta mult " "dimensiunea fișierului." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4005,11 +4603,11 @@ msgstr "" " împiedica să accesați fișierele. Utilizați această opțiune pentru a " "restaura și permisiunile." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Restaurați permisiunile fișierului" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4020,11 +4618,11 @@ msgstr "" "Utilizați această opțiune pentru a dezactiva verificarea și pentru a evita " "așteptarea verificării." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Verificați verificarea fișierului restabilit" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4034,28 +4632,28 @@ msgstr "" "minimiza cantitatea de date descărcate. Utilizați această opțiune pentru a " "sări peste această optimizare și utilizați numai date de la distanță." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Nu utilizați date locale" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4064,11 +4662,11 @@ msgstr "" "blocurilor citite dintr-un volum înainte de a patra fișierele restaurate cu " "datele." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Verificați hashes-ul blocului" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4082,11 +4680,11 @@ msgstr "" "Baza de date rezultată poate fi căutată, dar nu poate fi utilizată pentru " "restaurarea datelor cu." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Reparați baza de date cu căi" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4094,64 +4692,64 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Activați setarea locale" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Manipulați comunicarea fișierelor cu backend-ul folosind țevi filetate" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4162,11 +4760,11 @@ msgstr "" "copii de rezervă completate și conținutul încărcat în sesiunea de copiere " "incompletă." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4174,11 +4772,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4186,11 +4784,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4198,11 +4796,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Permiteți eliminarea tuturor fileurilor" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4218,11 +4816,11 @@ msgstr "" "valide din baza de date. Setarea acestui lucru la adevărat va permite " "companiei Duplicați să efectueze operații VACUUM la discreția sa." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4230,22 +4828,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4253,15 +4851,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4271,42 +4869,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4314,11 +4912,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4326,11 +4924,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4338,11 +4936,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4351,11 +4949,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4364,11 +4962,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4376,11 +4974,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4388,11 +4986,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4401,49 +5148,49 @@ msgstr "" "Cryptolibrary nu suporta transformări reutilizabile pentru algoritmul hash " "{0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "Cryptolibrary nu suporta algoritmul hash {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" "Fraza de acces nu poate fi modificată pentru o copie de rezervă existentă" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Nu a reușit să creeze un instantaneu: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Nu sa reușit ștergerea fișierului {0}, testarea dacă există un fișier" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" "Recuperat de la problema încercării de a șterge fișierul inexistent {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Nu sa reușit recuperarea din eroarea de ștergere a fișierului {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Operația de ștergere a eșuat pentru {0} cu FileNotFound, conținând conținut" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4502,29 +5249,6 @@ msgstr "" msgid "Configure http requests" msgstr "Configurați solicitările http" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Acceptați orice certificat de server" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Opțional acceptați un certificat SSL cunoscut" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4624,7 +5348,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configurați modulul Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4632,125 +5368,125 @@ msgstr "" "Acest modul funcționează intern pentru a analiza parametrii sursă pentru a " "salva bazele de date Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Configurați modulul Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Rulați scriptul" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Rulați un script la ieșire" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Scriptul \"{0}\" a revenit cu codul de ieșire {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Rulați un script necesar la pornire" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Eroare la executarea scriptului \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Executarea scriptului \"{0}\" a expirat" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Rulați un script la pornire" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Scriptul \"{0}\" a raportat mesaje de eroare: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Acest modul poate trimite e-mailuri după finalizarea unei operații" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Trimiteți e-mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4763,21 +5499,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Corpul mesajului" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Parola SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4789,11 +5525,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "E-mail destinatar (e)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4803,25 +5539,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Expeditor de e-mail" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Mesajele pe care trebuie să le trimită" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4829,11 +5565,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "Adresa URL SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4842,61 +5578,61 @@ msgstr "" "Această setare furnizează subiectul e-mailului. Valorile sunt înlocuite așa " "cum este descris în descrierea pentru - {0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Subiectul e-mailului" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Numele de utilizator SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Întreaga comunicație SMTP: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Nu a reușit să se trimită e-mail la server: {0}, mesaj: {1}, reîncercare cu " "{2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-mailul a fost trimis cu succes pe server: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Acest modul oferă suport pentru trimiterea rapoartelor de stare prin " "intermediul mesajelor XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Modul de raportare XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "E-mail destinatar XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4909,35 +5645,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Șablonul de mesaj" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Numele de utilizator XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "Parola XMPP" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4946,111 +5682,111 @@ msgstr "" "Puteți specifica unul dintre \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Puteți furniza mai multe opțiuni cu un separator de virgulă, de ex. \"{0}, {1}\". Valoarea specială \"{4}\" este o scurtă durată pentru \"{0}, {1}, {2}, {3}\" și va determina trimiterea unui mesaj pentru toate operațiile de salvare." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Trimiteți mesaje pentru toate operațiile" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "A expirat timp în timp ce vă conectați la serverul jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Acest modul oferă suport pentru trimiterea rapoartelor de stare prin " "intermediul mesajelor HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Modul de raportare HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Numele parametrului pentru a trimite mesajul ca" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Parametri suplimentari adăugați la mesajul http" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5058,52 +5794,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5112,16 +5848,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Valoarea nevalidă a dimensiunii: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Validatorul certificatului SSL a fost sunat într-o ordine incorectă" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5130,7 +5866,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5139,71 +5875,71 @@ msgstr "" "Eroare la validarea hash-ului certificatului, mesaj de eroare: {0}, nume de " "eroare SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Dosarul temporar nu există: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Parola nu a putut fi analizată: {0}, număr întreg nevalid" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Specificator nevalid: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Date nepartiționate: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Uri este nevalid: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Uri lipsește un nume de gazdă: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} octeți" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0: N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0: N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0: N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0: N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Șirul \"{0}\" nu a putut fi analizat într-o dată" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Nu pot citi și scrie pe același flux" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5426,11 +6162,11 @@ msgstr "Dezactivați ieșirea consolei" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Comutați actualizările automate" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-ru.mo b/Localizations/duplicati/localization-ru.mo index cfb6b1c83..9a7a7d0d9 100644 Binary files a/Localizations/duplicati/localization-ru.mo and b/Localizations/duplicati/localization-ru.mo differ diff --git a/Localizations/duplicati/localization-ru.po b/Localizations/duplicati/localization-ru.po index 572754021..0c923cd69 100644 --- a/Localizations/duplicati/localization-ru.po +++ b/Localizations/duplicati/localization-ru.po @@ -5,25 +5,25 @@ # # Translators: # Василий Хворов , 2017 +# Dmitry Kartsyn , 2017 # Igor *** , 2021 # Rondo Van , 2024 # Nikolay Parukhin , 2024 # Captain Quake , 2024 # Alex McArrow , 2024 -# Evgeny Popichev, 2024 -# Dmitry Kartsyn , 2024 # ke, 2024 # Pavel Klevakin , 2024 # Aleksandrs Aleksandrovs , 2024 +# Evgeny Popichev, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Aleksandrs Aleksandrovs , 2024\n" +"Last-Translator: Evgeny Popichev, 2024\n" "Language-Team: Russian (https://app.transifex.com/duplicati/teams/67655/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -56,8 +56,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -167,78 +167,94 @@ msgstr "Ошибка при вызове GnuPG, программа не выда msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Ошибка при вызове GnuPG, программа не завершилась" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "псевдонимы" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "значение по умолчанию" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[УСТАРЕЛО]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "значения" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Булево" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Перечисление" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Флаги" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Целое" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Путь" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Размер" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Строка" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Период времени" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Неизвестный" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Невозможно создать папку, поскольку она уже существует." -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Запрашиваемая папка не существует" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Отменено" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Запрашиваемый файл не существует" @@ -364,9 +380,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -383,9 +398,8 @@ msgstr "Доменное имя пользователя, использующе msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -398,9 +412,8 @@ msgstr "" "Логин для подключения к серверу. Также может передаваться в переменной " "окружения \"AUTH_USERNAME\"" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -491,41 +504,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Альтернативный FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -536,50 +539,203 @@ msgstr "" "Пароль для подключения к серверу. Также может устанавливаться через " "переменную окружения «AUTH_PASSWORD»." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Для защиты от сбоев в сети, каждая попытка выгрузки будет проверена. " -"Используйте этот параметр, чтобы отключить эту проверку и сделать выгрузку " -"более быстрой, но менее надежной." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Не проверять при загрузке" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Папка {0} не найдена: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Папка {0} не найдена: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Файл {0} был загружен, но возвращенный размер был {1}, а ожидалось {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Для защиты от сбоев сети или ошибок сервера, будут предприняты попытки " +"проверки каждой загрузки. Используйте этот параметр, чтобы отключить эту " +"проверку. Загрузка станет быстрее, но менее надежной." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Не проверять при загрузке" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "Если этот параметр задан, тип подключения FTP будет изменен." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Настройка типа соединения FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "Если этот параметр задан, режим шифрования FTP будет изменен." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Настройка режима шифрования FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "Этот флажок определяет политику SSL при использовании шифрования." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Настройка политики SSL для использования при включенном шифровании" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Некоторым FTP-серверам требуется небольшая задержка перед сообщением о " +"правильном размере файла. Требуемая задержка зависит от топологии сети. Если" +" вы столкнулись с ошибками, связанными с несоответствием размера " +"загружаемого фала , попробуйте добавить задержку в несколько секунд." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Добавить задержку после выгрузки файла" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -885,6 +1041,11 @@ msgstr "" msgid "Specify storage class" msgstr "Укажите класс хранилища" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -917,118 +1078,6 @@ msgstr "Пароль" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Альтернативный FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Папка {0} не найдена: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Файл {0} был загружен, но возвращенный размер был {1}, а ожидалось {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Для защиты от сбоев сети или ошибок сервера, будут предприняты попытки " -"проверки каждой загрузки. Используйте этот параметр, чтобы отключить эту " -"проверку. Загрузка станет быстрее, но менее надежной." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "Если этот параметр задан, тип подключения FTP будет изменен." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Настройка типа соединения FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "Если этот параметр задан, режим шифрования FTP будет изменен." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Настройка режима шифрования FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "Этот флажок определяет политику SSL при использовании шифрования." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Настройка политики SSL для использования при включенном шифровании" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Некоторым FTP-серверам требуется небольшая задержка перед сообщением о " -"правильном размере файла. Требуемая задержка зависит от топологии сети. Если" -" вы столкнулись с ошибками, связанными с несоответствием размера " -"загружаемого фала , попробуйте добавить задержку в несколько секунд." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Добавить задержку после выгрузки файла" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Ошибка удаления файла: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Ошибка чтения файла: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Ошибка записи файла: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Модуль для создания частных и открытых ключей SSH" @@ -1529,7 +1578,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Использовать класс HttpClient" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1537,11 +1590,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1551,11 +1604,11 @@ msgstr "" "\"OneDrive\" или \"OneDrive для бизнеса\" по умолчанию будет использоваться " "через «{0}»." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Необязательный идентификатор диска (ID)" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1563,29 +1616,29 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "идентификатор сайта" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Не указан идентификатор сайта" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Использованы конфликтующие идентификаторы сайтов: даны {0}, но найдены {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1597,41 +1650,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Group" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "идентификатор группы" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Электронный адрес группы" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Не найдено ни одной группы с указанным адресом электронной почты: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Найдено несколько групп с указанным адресом электронной почты: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1650,7 +1703,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1661,7 +1714,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1922,43 +1975,45 @@ msgstr "Не указан пароль" msgid "No username given" msgstr "Не указано имя пользователя" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2094,6 +2149,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2135,6 +2195,12 @@ msgstr "" "Сервер вернул код ошибки {0} ({1}), означающий, что сервер не поддерживает " "соединения WebDAV" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Папка {0} не найдена: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2163,6 +2229,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Записать ответ PROPFIND" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Принимать любой сертификат сервера" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Опционально принимать известный сертификат SSL" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2287,9 +2417,410 @@ msgstr "Неизвестная ошибка: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Другой экземпляр запущен и был уведомлен" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2309,13 +2840,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Поддерживаемые аргументы командной строки:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2344,10 +2873,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Фильтры нельзя указывать в командной строке, если фильтры также присутствуют" -" в файле параметров. Используйте специальные параметры --{0}, --{1}, или " -"--{2}, чтобы задать фильтры в файле параметров. Каждому фильтру должен " -"предшествовать + или -. Несколько фильтров должны быть соединены через {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2385,7 +2910,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2393,7 +2918,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Обнаружена неподдерживаемая версия SQLite ({0}), требуется {1} или выше" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2406,42 +2930,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Порт для входящих соединений веб-сервера. Несколько значений может быть " -"задано через запятую." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Ключ и сертификат в формате PKCS #12, которые будут использованы веб-" -"сервером для SSL. Поддерживаются только ключи RSA/DSA." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Пароль для расшифровки файла сертификата PKCS #12." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Интерфейс, который будет слушать веб-сервер. Специальные значения «*» и " -"«any» означают любой интерфейс. Специальное значение «loopback» означает " -"адаптер loopback." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Пароль, необходимый для доступа к веб-серверу. Эта опция сохраняется, " -"поэтому вам не нужно ее устанавливать при каждом запуске. Установка пустого " -"значения отключает пароль." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2451,133 +2976,165 @@ msgstr "" "имен хостов имеет \"*\", все имена хостов разрешены, а проверка имен хостов " "отключена." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Указать время, после которого данные журнала будут удаляться из базы данных." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Очистить старые логи" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Duplicati требуется хранить небольшую базу данных со всеми настройками. " -"Используйте этот параметр, чтобы выбрать, где хранятся настройки. Эту опцию " -"можно также установить с помощью переменной окружения {0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Эта опция устанавливает ключ шифрования, используемый для скремблирования " -"базы данных локальных настроек. Эту опцию можно также установить с помощью " -"переменной окружения {0}. Используйте опцию --{1}, чтобы отключить " -"скремблирование базы данных." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Папка для временного хранения" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2585,15 +3142,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2602,23 +3196,21 @@ msgstr "" "Невозможно найти допустимую дату с учетом даты начала {0}, интервала " "повторения {1} и разрешенных дней {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Сервер запущен и слушает на {0}, порт {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Невозможно создать SSL сертификат с данными параметрами. Детали ошибки: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "Невозможно открыть сокет для входящих соединений, порты: {0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2631,22 +3223,22 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Не удалось загрузить процесс типа {0} сборки {1}, сообщение об ошибке: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2654,11 +3246,11 @@ msgstr "" "Эта опция устанавливает уровень сжатия. 0 означает отсутствие сжатия, 9 – " "максимальное сжатие." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2666,75 +3258,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Файл не найден: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Экспериментально*: Архив 7z с поддержкой сжатия LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Экспериментально - 7z Архив" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Архив не открыт для записи" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Архив не открыт для чтения" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Данный файл не является частью этого архива" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Количество потоков, используемых в сжатии LZMA 2. По умолчанию - количество " -"процессорных ядер." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Количество потоков, используемых в сжатии" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Эта опция управляет используемым алгоритмом сжатия. Включение этой опции " -"приведет к тому, что 7z будет использовать более быстрый алгоритм, который " -"дает немного меньшее сжатие." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "резервная копия" @@ -3176,48 +3730,66 @@ msgstr "Ограничить размер файлов для резервног #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Приоритет потока" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Ограничить размер томов" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3229,11 +3801,11 @@ msgstr "" "для чтения существующих файлов модуль сжатия будет выбран исходя из имени " "файла." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Выберите модуль шифрования" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3245,27 +3817,27 @@ msgstr "" "новых томов, при чтении существующего файла применяемый модуль шифрования " "определяется именем файла." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Выбрать модуль шифрования" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3294,34 +3866,36 @@ msgstr "" "требуются права администратора. В Linux для этого используется управление " "логическими томами (LVM) и требуются привилегии root." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Путь, по которому готовые тома будут храниться до выгрузки" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Количество томов для создания заранее" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3330,45 +3904,45 @@ msgstr "" "количество одновременных загрузок. Установите на ноль, чтобы отключить " "ограничение." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Количество одновременных загрузок" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Записывать внутреннюю информацию в log-файл" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Уровень информации журнала" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3377,11 +3951,11 @@ msgstr "" "создана автоматически. Активируйте эту опцию, чтобы запретить автоматическое" " создание папок." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3392,12 +3966,12 @@ msgstr "" "Используйте этот параметр, чтобы исключить ошибочные записи из моментального снимка. Это эквивалентно флагу -wx средства vshadow.exe, за исключением, что принимаются только GUID класса записи, а не имена компонентов или GUID экземпляров. \n" "Несколько GUID должны разделяться точкой с запятой; разрешено большинство форм GUID, в том числе с фигурными скобками и без них." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "Перечень GUID писателей VSS через точку с запятой (только Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3416,11 +3990,22 @@ msgstr "" "При установке значения «обязательно», Duplicati прервет резервное копирование, если использовать USN не удастся. \n" "Функция поддерживается только в Windows и требует прав администратора" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3442,30 +4027,31 @@ msgstr "" "добавляет 1% допуска (максимум 1 час). Используйте эту опцию, чтобы " "отключить допуск и использовать строгое сравнение времени." -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Подтверждение выгрузки по перечислению содержимого" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Загружать файлы синхронно" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3473,11 +4059,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Не использовать соединения повторно" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3487,33 +4073,33 @@ msgstr "" "сообщая только о количестве повторных попыток. Включите эту опцию, чтобы " "отображать сообщения об ошибках при повторном выполнении." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Показывать сообщение об ошибке при выполнении повторной попытки" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Выгружать пустые файлы резервной копии" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3522,22 +4108,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Порог для предупреждения о низкой квоте" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3549,11 +4135,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Обработка symlink" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3569,11 +4155,11 @@ msgstr "" "каждую ссылку как уникальный путь. Опция «{2}» будет игнорировать все " "жесткие ссылки с более чем одной связью." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Обработка жестких ссылок" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3581,11 +4167,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Исключить файлы по атрибутам" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3598,41 +4184,51 @@ msgstr "" "содержимому моментального снимка. Это обходное решение может ускорить доступ" " к файлам в Windows XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Назначить диск для снимков (только для Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Название резервной копии" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3644,11 +4240,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Управление несжимаемыми расширениями файлов" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3660,71 +4256,63 @@ msgstr "" "значение приведет к большим издержкам при хранении списков файлов. Обратите " "внимание, значение не может быть изменено после создания удаленных файлов." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Размер блока, используемого для хэширования" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Список файлов для проверки на изменения" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Путь к локальной базе данных состояний" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Список удаленных файлов" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "Уменьшить объем памяти, отключив поиск в памяти" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Сохранять кэш блоков в памяти" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Не опрашивать бэкэнд при запуске" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3739,11 +4327,11 @@ msgstr "" "пространство на удаленном севере, однако, возможно, никогда не будут " "использованы." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3756,43 +4344,43 @@ msgstr "" "рекуперации. Это значение является процентным соотношением каждого из томов " "и суммарного размера хранилища." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Максимальное неиспользованное место в процентах" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Алгоритм хэширования блоков" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Алгоритм хэширования файлов" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3805,11 +4393,11 @@ msgstr "" " автоматическое уплотнение и выполнять его только при запуске " "соответствующей команды." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Отключить автоматическое уплотнение" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3821,11 +4409,11 @@ msgstr "" "гарантирует, что большие тома, которые могут иметь несколько байт " "неиспользуемого пространства, не бужут загружены и переписаны." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Предельный размер тома" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3835,11 +4423,11 @@ msgstr "" "принудительно группировать небольшие файлы. Небольшие объемы всегда " "объединяются, когда могут заполнить весь том." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Максимальное количество маленьких томов" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3849,42 +4437,42 @@ msgstr "" "найти существующие блоки. Это довольно медленная операция, но она может " "ограничить размер загрузок." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Использовать данные локальных файлов при восстановлении" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Сохранять определенное количество версий" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Используйте эту опцию, чтобы установить промежуток времени, в течение " "которого хранятся резервные копии." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Сохранять все версии в течение периода времени" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3905,33 +4493,33 @@ msgstr "" " параметр также поддерживает использование спецификатора \"U\" для указания " "неограниченного интервала времени." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" "Уменьшить количество версий путём удаления старых промежуточных резервных " "копий" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Используйте этот параметр, чтобы продолжить, даже если некоторые исходные " "записи отсутствуют." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Пропустить отсутствующие исходные элементы" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Перезаписывать файлы при восстановлении" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3939,11 +4527,11 @@ msgstr "" "Используйте эту опцию для увеличения генерируемого вывода. Обычно эта опция " "выдает по строке для каждого обработанного файла." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Выводить больше информации о прогрессе" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3951,11 +4539,11 @@ msgstr "" "Используйте этот параметр для увеличения объема вывода в результате " "операции, включая все имена файлов." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Вывод всех результатов" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3967,11 +4555,11 @@ msgstr "" "всех файлов удаленного хранилища и может служить для проверки целостности " "этих файлов." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Определить, загружены ли файлы верификации" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3981,11 +4569,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Количество образцов для тестирования после создания резервной копии" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3995,11 +4583,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Процент образцов для тестирования после резервного копирования" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4011,41 +4599,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Объем буфера чтения файлов" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Разрешить изменение кодовой фразы" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Перечислять только наборы файлов" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4056,11 +4644,11 @@ msgstr "" "резервного копирования и восстановления, но не сильно влияет на размер " "файла." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4069,11 +4657,11 @@ msgstr "" "помешать вам получить доступ к этим файлам. Используйте эту опцию, чтобы " "восстанавливать разрешения." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Восстанавливать права доступа файлов" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4083,11 +4671,11 @@ msgstr "" "чтобы убедиться, что восстановление прошло успешно. Используйте этот " "параметр, чтобы отключить проверку и не дожидаться подтверждения." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Пропустить проверку восстановленных файлов" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4097,28 +4685,28 @@ msgstr "" "минимизировать объем загружаемых данных. Используйте эту опцию, чтобы " "пропустить данную оптимизацию и использовать только удаленные данные." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Не использовать локальные данные" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4127,11 +4715,11 @@ msgstr "" "восстановленных файлов, будет проведена сверка хэш блоков, прочитанных с " "тома." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Проверить хэши блоков" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4143,11 +4731,11 @@ msgstr "" "содержимого без необходимости восстановления всей информации. Такая база " "данных может быть использована для поиска, но не для восстановления данных." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Исправить базу данных с путями" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4155,11 +4743,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Принудительно настроить локаль" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4169,23 +4757,23 @@ msgstr "" "или «Последний четверг». При установке этого параметра отображаются только " "фактические даты, например «12 ноября 2018 г., 8:01»." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" "Производить файловое взаимодействие с бэкэндом при помощи потоковых каналов" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4196,22 +4784,22 @@ msgstr "" "динамически балансировать количество активных потоков в соответствии с " "аппаратным обеспечением." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Ограничить количество одновременных потоков" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Используйте этот параметр, чтобы задать количество процессов, выполняющих " "хеширование данных." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Укажите количество одновременных процессов хеширования" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4219,11 +4807,11 @@ msgstr "" "Используйте этот параметр, чтобы задать количество процессов, выполняющих " "сжатие выходных данных." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Укажите количество одновременных процессов сжатия" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4233,11 +4821,11 @@ msgstr "" "будет создан список файлов, являющихся слиянием последней завершенной " "резервной копии и содержимого, выгруженного во незавершенного сеанса." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4245,11 +4833,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4257,11 +4845,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4269,11 +4857,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Разрешить удаление всех наборов файлов" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4289,11 +4877,11 @@ msgstr "" "записей в базе данных. Установка этого значения в true разрешит Duplicati " "выполнять операции VACUUM на своё усмотрение." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4301,11 +4889,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Отключить сканер упреждающего чтения" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4316,11 +4904,11 @@ msgstr "" "отключите проверки, убедитесь, что вы запускаете регулярные команды " "проверки, чтобы убедиться, что все работает должным образом." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Отключить проверку согласованности списка файлов" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4328,15 +4916,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Отключить резервное копирование при питании от батареи" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Уровень информирования для файла журнала(log-файла)" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4352,42 +4940,42 @@ msgstr "" "поддерживаются в жестких фигурных скобках. Пример: " "\"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Уровень информирования консоли" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4399,11 +4987,11 @@ msgstr "" "использованием было бы иметь файл с именем, например «.nobackup», и помещать" " этот файл в папки, для которых не следует создавать резервные копии." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Список имен файлов, исключающих папки" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4411,11 +4999,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4423,11 +5011,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4440,11 +5028,11 @@ msgstr "" "регистрировать все запросы к базе данных, и не забудьте установить либо " "--{0}={2}, либо --{1}={2}, чтобы сообщать дополнительные данные журнала." -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4453,11 +5041,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4465,11 +5053,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4477,11 +5065,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4490,48 +5227,48 @@ msgstr "" "Криптографическая библиотека не поддерживает многоразовые преобразования для" " алгоритма хеширования {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "Криптографическая библиотека не поддерживает алгоритм хэширования {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "Кодовая фраза не может быть изменена для существующей резервной копии" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Не удалось создать снимок: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Не удалось удалить файл {0}, проверка наличия файла" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "Исправлена попытка удаления несуществующего файла {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Не удалось исправить ошибку при удалении файла {0}" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Операция удаления не удалась для {0} с ошибкой FileNotFound, содержимое " "списка" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "В листинге указано, что файл {0} был удален правильно" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Листинг подтверждает, что файл {0} не был удален" @@ -4595,29 +5332,6 @@ msgstr "" msgid "Configure http requests" msgstr "Настройка HTTP запросов" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Принимать любой сертификат сервера" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Опционально принимать известный сертификат SSL" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4717,7 +5431,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Настройка модуля Hyper-V" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4725,125 +5451,125 @@ msgstr "" "Этот модуль служит для анализа исходных параметров резервного копирования " "баз данных Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Настройка модуля Microsoft SQL Server" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Запустить скрипт" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Запустить скрипт при выходе" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Сценарий «{0}» завершён с кодом {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Сценарий \"{0}\" возвратил код выхода {1}{2}." -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Выполнить требуемый сценарий при старте" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Ошибка при выполнении сценария «{0}»: {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Истекло время ожидания исполнения сценария «{0}»" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Запустить скрипт при запуске" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Сценарий «{0}» сообщил об ошибке: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Этот модуль может посылать e-mail по окончании выполнения операции" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Отправка сообщения" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4856,21 +5582,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Тело сообщения" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "Пароль SMTP" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4882,11 +5608,11 @@ msgstr "" "Пример с 3 получателями:\n" "Петр Иванов , Иван Петров , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Получатель(-и) e-mail" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4896,25 +5622,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Отправитель e-mail" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Сообщения для отправки" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4931,11 +5657,11 @@ msgstr "" "Если порт не указан, используется порт 25 для соединений без SSL и 465 для SSL-соединений.\n" "Чтобы запретить использование STARTTLS, используйте smtp://example.com:25/?starttls=never." -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "Адрес сервера SMTP" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4944,61 +5670,61 @@ msgstr "" "Этот параметр определяет тему сообщения электронной почты. Значения " "заменяются так, как указано в описании --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Тема сообщения" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "Имя пользователя SMTP" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Полная связь по протоколу SMTP: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Не удалось отправить электронную почту через сервер: {0}, сообщение: {1}, " "повтор через {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-mail успешно отправлен через сервер: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Этот модуль предоставляет поддержку отправки отчетов о состоянии через " "сообщения XMPP" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "Модуль отчета XMPP" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "Электронная почта получателя XMPP" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -5011,35 +5737,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Шаблон сообщения" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "Имя пользователя XMPP" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP пароль" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5048,111 +5774,111 @@ msgstr "" "Можно указать один из «{0}» «{1}», «{2}», «{3}». \n" "Можно указать несколько вариантов, разделенных запятыми, напр. «{0}, {1}». Специальное значение «{4}» является сокращением для «{0}, {1}, {2}, {3}» и служит для отправки сообщений обо всех операциях резервного копирования." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Посылать e-mail обо всех операциях" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "Вышло время ожидания ответа при входе на сервер Jabber" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Этот модуль предоставляет поддержку отправки отчетов о состоянии через " "сообщения HTTP" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "Модуль отчёта HTTP" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Название параметра отправляемого сообщения" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Дополнительные параметры для http сообщения." -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5160,46 +5886,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Не удалось отправить сообщение: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Фильтр сообщений журнала" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5208,7 +5934,7 @@ msgstr "" "журнала для включения в отчет. Нулевые или отрицательные значения означают " "неограниченное количество." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5217,16 +5943,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Формат не поддерживается: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Неверный размер значения: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Средство проверки SSL-сертификата было вызвано в неправильном порядке" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5235,7 +5961,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5244,71 +5970,71 @@ msgstr "" "Ошибка при проверке хэша сертификата, сообщение об ошибке: {0}, имя ошибки " "SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Временная папка не существует: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Не удалось проанализировать сегмент: {0}, неверное целое число" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Недопустимый спецификатор: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Нераспознанные данные: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Недопустимый Uri: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Uri не содержит имени хоста: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} байт" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} ГБ" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} КБ" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} МБ" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} ТБ" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "Строка «{0}» не может быть преобразована в дату" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Невозможно чтение и запись в одном потоке" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5551,11 +6277,11 @@ msgstr "Подавить вывод на консоль" msgid "This link may provide additional information: {0}" msgstr "Эта ссылка может предоставить дополнительную информацию: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Включить автоматическое обновление" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-sk.mo b/Localizations/duplicati/localization-sk.mo index c247a8e4a..3f9ef5811 100644 Binary files a/Localizations/duplicati/localization-sk.mo and b/Localizations/duplicati/localization-sk.mo differ diff --git a/Localizations/duplicati/localization-sk.po b/Localizations/duplicati/localization-sk.po index d84ee9438..905c53fef 100644 --- a/Localizations/duplicati/localization-sk.po +++ b/Localizations/duplicati/localization-sk.po @@ -4,16 +4,16 @@ # FIRST AUTHOR , YEAR. # # Translators: -# Peter Krajcovic , 2024 +# Peter Krajcovic , 2017 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Peter Krajcovic , 2024\n" +"Last-Translator: Peter Krajcovic , 2017\n" "Language-Team: Slovak (https://app.transifex.com/duplicati/teams/67655/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -44,8 +44,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -147,78 +147,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 -msgid "aliases" +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" msgstr "" -#: Library/Interface/Strings.cs:25 -msgid "default value" +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" msgstr "" #: Library/Interface/Strings.cs:26 -msgid "[DEPRECATED]" +msgid "aliases" msgstr "" #: Library/Interface/Strings.cs:27 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:28 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -335,9 +351,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -354,9 +369,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -367,9 +381,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -449,41 +462,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -494,47 +497,195 @@ msgstr "" "Heslo používané na pripojenie k serveru. Toto môže byť vložené aj ako " "premenná prostredia \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -830,6 +981,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -862,110 +1018,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1440,7 +1492,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1448,22 +1504,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1471,28 +1527,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1504,42 +1560,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1556,7 +1612,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1567,7 +1623,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1812,43 +1868,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1964,6 +2022,11 @@ msgstr "" msgid "Dropbox" msgstr "" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1996,6 +2059,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2021,6 +2090,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2141,6 +2274,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2194,10 +2728,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filtre nie je možné špecifikovať v príkazovom riadku, ak sú zapísané aj v " -"súbore parametrov. Použite špeciálne možnosti - {0}, - {1} alebo - {2} na " -"zadanie filtrov v rámci súboru parametrov. Každý filter musí mať predponu " -"buď a + alebo -, a viaceré filtre musia byť spojené s {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2235,7 +2765,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2258,55 +2788,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2314,11 +2855,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2326,86 +2867,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2413,34 +2993,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2455,32 +3071,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2488,68 +3104,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2934,48 +3517,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2983,11 +3584,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2995,27 +3596,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3030,88 +3631,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3120,12 +3723,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3138,11 +3741,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3155,30 +3769,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3186,44 +3801,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3232,22 +3847,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3259,11 +3874,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3273,11 +3888,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3285,11 +3900,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3297,41 +3912,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3343,11 +3968,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3355,71 +3980,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3428,11 +4045,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3440,43 +4057,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3484,11 +4101,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3496,62 +4113,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3563,49 +4180,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3613,11 +4230,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3627,11 +4244,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3641,11 +4258,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3657,111 +4274,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3769,11 +4386,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3781,74 +4398,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3856,11 +4473,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3868,11 +4485,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3880,11 +4497,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3894,11 +4511,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3906,22 +4523,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3929,15 +4546,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3947,42 +4564,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3990,11 +4607,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4002,11 +4619,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4014,11 +4631,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4027,11 +4644,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4040,11 +4657,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4052,11 +4669,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4064,57 +4681,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4168,29 +4934,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4276,131 +5019,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4413,21 +5168,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4435,11 +5190,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4449,25 +5204,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4475,68 +5230,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4549,144 +5304,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4694,52 +5449,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4748,16 +5503,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4766,78 +5521,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5061,11 +5816,11 @@ msgstr "Zakázať výstup do konzoly" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Zapnúť automatické aktualizácie" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-sk_SK.mo b/Localizations/duplicati/localization-sk_SK.mo index 515780231..ff74876d6 100644 Binary files a/Localizations/duplicati/localization-sk_SK.mo and b/Localizations/duplicati/localization-sk_SK.mo differ diff --git a/Localizations/duplicati/localization-sk_SK.po b/Localizations/duplicati/localization-sk_SK.po index 64cca44a1..052bcc591 100644 --- a/Localizations/duplicati/localization-sk_SK.po +++ b/Localizations/duplicati/localization-sk_SK.po @@ -4,9 +4,9 @@ # FIRST AUTHOR , YEAR. # # Translators: +# Martin Minka, 2017 # Peter Krajcovic , 2017 -# Martin Novara, 2024 -# Martin Minka, 2024 +# Martin Novara, 2022 # Stanislav Prekop , 2024 # Martin Rovňák , 2024 # @@ -15,7 +15,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Martin Rovňák , 2024\n" "Language-Team: Slovak (Slovakia) (https://app.transifex.com/duplicati/teams/67655/sk_SK/)\n" @@ -48,8 +48,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -151,78 +151,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "aliasy" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "predvolená hodnota" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Cesta" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Veľkosť" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Neznámy" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Adresár nemôže byť vytvorený, lebo už existuje" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Zrušené" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -339,9 +355,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -358,9 +373,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -371,9 +385,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -453,41 +466,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternatívne FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -496,47 +499,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Adresár {0} nenájdený, správa: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Adresár {0} nenájdený. Správa: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Nastavenie FTP prístupu" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Nastavenie FTP kryptovania" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Nastavenie SSL politiky ak je šifrovanie povolené " + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -832,6 +983,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -864,110 +1020,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternatívne FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Adresár {0} nenájdený. Správa: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Nastavenie FTP prístupu" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Nastavenie FTP kryptovania" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Nastavenie SSL politiky ak je šifrovanie povolené " - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Chyba pri vymazávaní súboru: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Chyba pri čítaní súboru: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Chyba pri zapisovaní do súboru: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul pre generovanie SSH súkromných/verejných kľúčov" @@ -1442,7 +1494,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1450,22 +1506,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1473,28 +1529,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1506,42 +1562,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1558,7 +1614,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1569,7 +1625,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1814,43 +1870,45 @@ msgstr "Nezadané heslo" msgid "No username given" msgstr "Nezadané užívateľské meno" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1966,6 +2024,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1998,6 +2061,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Adresár {0} nenájdený, správa: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2023,6 +2092,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Akceptovenie všetkých serverových certifikátov" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2143,9 +2276,410 @@ msgstr "Nešpecifikovaný kód chyby : {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Iná inštancia aplikácie je spustená, bola informovaná" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2169,7 +2703,7 @@ msgstr "" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2235,14 +2769,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "Zistená nepodporovaná verzia SQLite ({0}), musí byť {1} alebo vyššia" +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2258,55 +2792,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Vyčistiť staré záznamy" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2314,11 +2859,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2326,86 +2871,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Dočasný adresár" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2413,34 +2997,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2455,32 +3075,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2488,69 +3108,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "Súbor nenájdený: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "Súbor nenájdený: {0}" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -2934,48 +3521,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2983,11 +3588,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2995,27 +3600,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3030,88 +3635,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Log informačná úroveň" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3120,12 +3727,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3138,11 +3745,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3155,30 +3773,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3186,44 +3805,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3232,22 +3851,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3259,11 +3878,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3273,11 +3892,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3285,11 +3904,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Vylúčenie súborov podľa atribútov" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3297,41 +3916,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Názov zálohy" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3343,11 +3972,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3355,71 +3984,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Zoznam zmazaných súborov" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3428,11 +4049,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3440,43 +4061,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3484,11 +4105,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3496,62 +4117,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maximálny počet malých častí" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3563,49 +4184,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Prepísanie súborov pri obnove" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3613,11 +4234,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3627,11 +4248,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3641,11 +4262,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3657,111 +4278,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3769,11 +4390,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3781,74 +4402,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3856,11 +4477,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3868,11 +4489,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3880,11 +4501,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3894,11 +4515,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3906,22 +4527,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3929,15 +4550,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3947,42 +4568,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3990,11 +4611,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4002,11 +4623,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4014,11 +4635,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4027,11 +4648,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4040,11 +4661,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4052,11 +4673,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4064,57 +4685,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4168,29 +4938,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Akceptovenie všetkých serverových certifikátov" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4276,131 +5023,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Configurácia Hyper-V modulu" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Konfigurácia Microsoft SQL Server modulu" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Spustiť skript" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Spustiť skript pri vypnutí" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Spustiť potrebný skript pri štarte" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Spustenie skriptu pri štarte" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Poslať email" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4413,21 +5172,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Správa" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP Heslo" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4435,11 +5194,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Príjemca(i)" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4449,25 +5208,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4475,68 +5234,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP Užívateľské meno" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "Email úspešne odoslaný s použitím servera: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4549,144 +5308,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP užívateľ" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP heslo" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4694,52 +5453,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4748,16 +5507,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4766,78 +5525,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Dočasný adresár neexistuje: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Nedá sa čítať aj zapisovať na tom istom 'streame'" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5055,11 +5814,11 @@ msgstr "Zakázať konzolový výstup" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Povoliť automatické aktualizácie" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-sr_RS.mo b/Localizations/duplicati/localization-sr_RS.mo index 152fce93f..b7196f36f 100644 Binary files a/Localizations/duplicati/localization-sr_RS.mo and b/Localizations/duplicati/localization-sr_RS.mo differ diff --git a/Localizations/duplicati/localization-sr_RS.po b/Localizations/duplicati/localization-sr_RS.po index b77dae015..7c7483c07 100644 --- a/Localizations/duplicati/localization-sr_RS.po +++ b/Localizations/duplicati/localization-sr_RS.po @@ -5,17 +5,17 @@ # # Translators: # Stefan Kostic , 2017 -# Zoran Tasić , 2024 # Milan Marinković , 2024 +# Zoran Tasić , 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Milan Marinković , 2024\n" +"Last-Translator: Zoran Tasić , 2024\n" "Language-Team: Serbian (Serbia) (https://app.transifex.com/duplicati/teams/67655/sr_RS/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -48,8 +48,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -158,78 +158,94 @@ msgstr "Greška prilikom pozivanja GnuPG-a, program neće isprazniti izlaz" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "Neuspeh pri pozivanju GnuPG-a, program se neće prekinuti" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "alijasi" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "podrazumevana vrednost" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[ZASTARELO]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "vrednosti" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Bulova logika" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Nabrajanje" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flegovi" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Ceo broj" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Putanja" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Veličina" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Niz znakova" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Vremenski raspon" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Nepoznat" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Fascikla ne može biti napravljena jer već postoji" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Zahtevana fascikla ne postoji" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Otkazano" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Zahtevana datoteka ne postoji" @@ -359,9 +375,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -378,9 +393,8 @@ msgstr "Ime domena korisnika koji se koristi za povezivanje sa serverom." msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -393,9 +407,8 @@ msgstr "" "Korisničko ime koje se koristi za povezivanje sa serverom. Ovo se takođe " "može dostaviti kao promenljiva okruženja „AUTH_USERNAME“." -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -486,41 +499,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternativni FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -531,50 +534,212 @@ msgstr "" "Lozinka koja se koristi za povezivanje sa serverom. Ovo se takođe može " "dostaviti kao promenljiva okruženja \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" -"Da bismo se zaštitili od kvarova na mreži, svako otpremanje će se pokušati " -"verifikovati. Koristite ovu opciju da onemogućite ovu verifikaciju da " -"otpremanje bude brže, ali manje pouzdano." #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Onemogući verifikaciju otpremanja" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Fascikla {0} nije pronađena, poruka: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "Fascikla {0} nije pronađena. Poruka: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" +"Fajl {0} je otpremljen, ali vraćena veličina je bila {1} a očekivalo se da " +"će biti {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" +"Da bismo se zaštitili od grešaka na mreži ili serveru, svako otpremanje će " +"se pokušati verifikovati. Koristite ovu opciju da onemogućite ovu " +"verifikaciju da otpremanje bude brže, ali manje pouzdano." + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Onemogući verifikaciju otpremanja" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" +"Ako je ova zastavica postavljena, tip FTP data veze će biti promenjen u " +"izabranu opciju." + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Konfigurišite tip FTP veze za prenos podataka" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" +"Ako je ova zastavica postavljena, režim FTP šifrovanja će biti promenjen na" +" izabranu opciju." + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Konfigurišite režim FTP šifrovanja" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Ova oznaka kontroliše SSL politiku koja se koristi kada je šifrovanje " +"omogućeno." + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" +"Konfigurišite SSL politiku koja će se koristiti kada je šifrovanje " +"omogućeno" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"Nekim FTP serverima je potrebno malo kašnjenje pre nego što prijave tačnu " +"veličinu fajla. Potrebno kašnjenje zavisi od topologije mreže. Ako naiđete " +"na greške u vezi sa veličinom otpremanja koja se ne podudara, pokušajte da " +"dodate odlaganje od nekoliko sekundi." + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "Dodajte odlaganje nakon otpremanja fajla" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -880,6 +1045,11 @@ msgstr "" msgid "Specify storage class" msgstr "Navedite klasu skladištenja" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -912,127 +1082,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternativni FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "Fascikla {0} nije pronađena. Poruka: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" -"Fajl {0} je otpremljen, ali vraćena veličina je bila {1} a očekivalo se da " -"će biti {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" -"Da bismo se zaštitili od grešaka na mreži ili serveru, svako otpremanje će " -"se pokušati verifikovati. Koristite ovu opciju da onemogućite ovu " -"verifikaciju da otpremanje bude brže, ali manje pouzdano." - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" -"Ako je ova zastavica postavljena, tip FTP data veze će biti promenjen u " -"izabranu opciju." - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Konfigurišite tip FTP veze za prenos podataka" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" -"Ako je ova zastavica postavljena, režim FTP šifrovanja će biti promenjen na" -" izabranu opciju." - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Konfigurišite režim FTP šifrovanja" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Ova oznaka kontroliše SSL politiku koja se koristi kada je šifrovanje " -"omogućeno." - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" -"Konfigurišite SSL politiku koja će se koristiti kada je šifrovanje " -"omogućeno" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"Nekim FTP serverima je potrebno malo kašnjenje pre nego što prijave tačnu " -"veličinu fajla. Potrebno kašnjenje zavisi od topologije mreže. Ako naiđete " -"na greške u vezi sa veličinom otpremanja koja se ne podudara, pokušajte da " -"dodate odlaganje od nekoliko sekundi." - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "Dodajte odlaganje nakon otpremanja fajla" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Greška prilikom brisanja datoteke: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Greška prilikom čitanja datoteke: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Greška prilikom upisa u datoteku: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul za generisanje SSH privatnih/javnih ključeva" @@ -1539,7 +1588,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "Da li treba koristiti klasu HttpClient" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1547,11 +1600,11 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " @@ -1561,11 +1614,11 @@ msgstr "" "podrazumevani OneDrive ili OneDrive for Business disk će se koristiti preko" " '{0}'." -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "Opcioni ID disk jedinice" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1573,29 +1626,29 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "ID sajta" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "Nije naveden ID sajta" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" "Korišćeni neusaglašeni ID-ovi sajtova: dati su {0}, ali pronađeni {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1607,41 +1660,41 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Group" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "ID grupe" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "Adresa e-pošte grupe" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "Nije pronađena nijedna grupa sa datom adresom e-pošte: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "Pronađeno je više grupa sa datom adresom e-pošte: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "Korišćeni ID-ovi neusaglašenih grupa: dati su {0}, ali pronađeni {1}" @@ -1659,7 +1712,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1670,7 +1723,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1929,43 +1982,45 @@ msgstr "Nije data lozinka" msgid "No username given" msgstr "Nije dato korisničko ime" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -2101,6 +2156,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2135,6 +2195,12 @@ msgstr "" "Server je vratio kod greške {0} ({1}), što ukazuje da server ne podržava " "WebDAV veze" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Fascikla {0} nije pronađena, poruka: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2163,6 +2229,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "Izbacite PROPFIND odgovor" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "Prihvati bilo koji sertifikat sa servera" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "Opciono prihvati poznat SSL sertifikat" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2287,9 +2417,410 @@ msgstr "Neočekivan kod greške: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "Još jedna instanca je pokrenuta i obaveštena je" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2309,13 +2840,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Podržani argumenti komandne linije:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2344,10 +2873,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"Filteri se ne mogu navesti na komandnoj liniji ako su filteri prisutni i u " -"datoteci parametara. Koristite posebne opcije --{0}, --{1} ili --{2} da " -"biste naveli filtere unutar datoteke parametara. Svaki filter mora imati " -"prefiks sa + ili -, a više filtera mora biti spojeno sa {3}" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2385,7 +2910,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2393,7 +2918,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"Nepodržana verzija SQLite-a je otkrivena ({0}), mora biti {1} ili viša" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2406,42 +2930,43 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Port koji osluškuje web server. Moguće je navesti više vrednosti sa zarezom " -"između." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" -"Fajl sertifikata i ključa u PKCS #12 formatu koji veb server koristi za SSL." -" Podržani su samo RSA/DSA ključevi." #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "Lozinka za dešifrovanje datoteke PKCS #12 sertifikata." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -"Interfejs koji veb server sluša. Posebne vrednosti \"*\" i \"any\" " -"označavaju bilo koji interfejs. Posebna vrednost \"loopback\" označava " -"adapter za hvatanje petlje." -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Lozinka potrebna za pristup veb serveru. Ova opcija je sačuvana tako da ne " -"morate da je postavljate pri svakom pokretanju. Postavljanje prazne " -"vrednosti onemogućava lozinku." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " @@ -2451,135 +2976,166 @@ msgstr "" "od imena hosta \"*\", sva imena hostova su dozvoljena i provera imena hosta " "je onemogućena." -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" "Podesite vreme nakon kojeg će podaci dnevnika biti očišćeni iz baze " "podataka." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Očistite stare podatke dnevnika" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." msgstr "" -"Program Duplicati treba da uskladišti malu bazu podataka sa svim " -"podešavanjima. Koristite ovu opciju da izaberete gde će se podešavanja " -"čuvati. Ova opcija se takođe može podesiti pomoću promenljive okruženja " -"{0}." -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." msgstr "" -"Ova opcija postavlja ključ za enkripciju koji se koristi za skremblovanje " -"lokalnih podešavanja baze podataka. Ova opcija se takođe može podesiti " -"pomoću promenljive okruženja {0}. Koristite opciju --{1} da biste " -"onemogućili skremblovanje baze podataka." -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Fascikla privremenog skladišta" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2587,15 +3143,52 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " @@ -2604,25 +3197,21 @@ msgstr "" "Nije moguće pronaći važeći datum, s obzirom na datum početka {0}, " "interval ponavljanja {1} i dozvoljene dane {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Server je pokrenut i osluškuje na {0}, port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Nije moguće napraviti SSL sertifikat pomoću navedenih parametara. Detalji " -"izuzetka: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" -"Nije moguće otvoriti priključak za slušanje, pokušali su portovi: {0}" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2635,22 +3224,22 @@ msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" "Učitavanje tipa procesa {0} sklop {1} nije uspelo, poruka o grešci: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2658,11 +3247,11 @@ msgstr "" "Ova opcija određuje nivo kompresije koji se koristi. Postavljanje nule radi " "bez kompresije, a postavljanje 9 daje najveću moguću kompresiju." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2670,75 +3259,37 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "Fajl nije pronađen: {0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*Eksperimentalno*: 7z arhiva sa podrškom za LZMA2." - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "Eksperimentalno - 7z Arhiva" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "Arhiva nije otvorena za upis" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "Arhiva nije otvorena za čitanje" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Dati fajl nije deo ove arhive" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" -"Broj niti korišćenih u LZMA 2 kompresiji. Podrazumevano je broj procesorskih" -" jezgara." - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "Broj niti korišćen u kompresiji" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" -"Ova opcija kontroliše korišćeni algoritam kompresije. Omogućavanje ove " -"opcije će prouzrokovati da 7z koristi brzi algoritam, koji proizvodi nešto " -"manje kompresije." - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "rezervna kopija" @@ -3175,48 +3726,66 @@ msgstr "Ograničite veličinu fajlova za koje se pravi rezervna kopija" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Prioritet niti" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Ograničite veličinu volumena" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3228,11 +3797,11 @@ msgstr "" "primenjuje samo kada se kreiraju novi volumeni, kada se čita postojeći " "fajl, ime fajla se koristi za izbor modula kompresije." -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Izaberite koji modul želite da koristite za kompresiju" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3244,27 +3813,27 @@ msgstr "" "primenjuje samo kada se kreiraju novi volumeni, kada se čita postojeći " "fajl, ime fajla se koristi za izbor modula za šifrovanje." -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Izaberite koji modul želite da koristite za šifrovanje" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3291,34 +3860,36 @@ msgstr "" "Services (VSS) i zahteva administrativne privilegije. Na Linux-u ovo koristi" " upravljanje logičkim volumenom (LVM) i zahteva root privilegije." -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "Putanja na koju se spremni volumeni postavljaju do učitavanja" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "Broj volumena koje treba kreirati unapred" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." @@ -3326,45 +3897,45 @@ msgstr "" "Prilikom asinhronog otpremanja, dozvoljen je maksimalan broj istovremenih " "otpremanja. Postavite na nulu da biste onemogućili ograničenje." -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "Broj dozvoljenih istovremenih otpremanja" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "Zabeležite interne informacije u fajl" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "Nivo informacija dnevnika" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." @@ -3372,11 +3943,11 @@ msgstr "" "Ako Duplicati otkrije da ciljni folder nedostaje, automatski će ga " "kreirati. Aktivirajte ovu opciju da sprečite automatsko kreiranje fascikli." -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3390,14 +3961,14 @@ msgstr "" "mora biti odvojeno tačkom i zarezom, a većina oblika GUID-a je dozvoljena, " "uključujući sa i bez vitičastih zagrada." -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" "Lista vodiča VSS pisaca odvojenih tačkom i zarezom za izuzimanje (samo za " "Vindouz)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3420,11 +3991,22 @@ msgstr "" "uspe. Ova funkcija je podržana samo na Vindouz-u i zahteva administrativne " "privilegije." -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3437,30 +4019,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "Potvrdite otpremanja navođenjem sadržaja" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "Sinhrono otpremajte fajlove" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3468,11 +4051,11 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "Nemojte ponovo koristiti veze" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " @@ -3482,33 +4065,33 @@ msgstr "" "broj ponovnih pokušaja. Omogućite ovu opciju da bi se poruke o grešci " "prikazivale kada se izvrši ponovni pokušaj." -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "Prikaži poruke o grešci kada se izvrši ponovni pokušaj" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "Otpremite prazne fajlove rezervnih kopija" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3517,22 +4100,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "Prag za upozorenje o niskoj kvoti" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3544,11 +4127,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "Upravljanje simboličkim vezama" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3564,11 +4147,11 @@ msgstr "" "jedinstvenu putanju. Opcija \"{2}\" će ignorisati sve tvrde veze sa više od" " jedne veze." -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "Rukovanje čvrstim vezama" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3576,11 +4159,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "Izuzmi fajlove prema atributima" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3592,41 +4175,51 @@ msgstr "" "disk jedinice koje se zatim koriste za pristup sadržaju snimka. Ovo rešenje " "može da ubrza pristup fajlu u operativnom sistemu Vindouz XP." -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "Mapirajte snimke na disk (samo za Vindouz)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "Naziv rezervne kopije" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3638,11 +4231,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "Upravljajte ekstenzijama fajlova koji nisu komprimovani" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3654,72 +4247,64 @@ msgstr "" "vrednosti će izazvati velike troškove skladištenja lista fajlova. Imajte na" " umu da se vrednost ne može promeniti nakon kreiranja udaljenih fajlova." -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "Veličina bloka korišćena za heširanje" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "Lista fajlova koje treba pregledati za promene" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "Putanja do baze podataka lokalne države" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "Lista obrisanih fajlova" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" "Smanjite memorijskog otiska tako što ćete onemogućiti pretrage u memoriji" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "Sačuvajte blok predmemoriju u memoriju" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "Nemojte postavljati upite pozadini pri pokretanju" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3733,11 +4318,11 @@ msgstr "" "podataka. Kompromis je u tome što veći indeksni fajlovi zauzimaju više " "udaljenog prostora i koji se možda nikada neće koristiti." -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3749,43 +4334,43 @@ msgstr "" " odredište može da sadrži pre nego što bude zauzeto. Ova vrednost je " "procenat koji se koristi za svaki volumen i ukupno skladište." -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "Maksimalni izgubljeni prostor u procentima" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "Heš algoritam korišćen na blokovima" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "Heš algoritam korišćen na fajlovima" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3798,11 +4383,11 @@ msgstr "" "automatsko sažimanje i samo kompaktiranje kada se izvodi komanda za " "sažimanje." -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "Onemogućite automatsko sažimanje" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3814,11 +4399,11 @@ msgstr "" "Ovo osigurava da se veliki volumeni koji mogu imati nekoliko bajtova " "izgubljenog prostora ne preuzimaju i ponovo pišu." -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "Prag veličine volumena" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " @@ -3828,11 +4413,11 @@ msgstr "" "vrednost može prinudno grupisati male fajlove. Male količine će uvek biti " "kombinovane kada mogu da popune ceo volumen." -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "Maksimalan broj malih volumna" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " @@ -3842,42 +4427,42 @@ msgstr "" "biste pronašli postojeće blokove. Ovo je prilično spora operacija, ali može" " ograničiti veličinu preuzimanja." -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Prilikom vraćanja koristite lokalne podatke o fajlu" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "Čuvajte nekoliko verzija" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" "Koristite ovu opciju da postavite vremenski interval iz kojeg će rezervne " "kopije biti zadžane." -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "Zadrži sve verzije iz vremenskog intervala" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3898,30 +4483,30 @@ msgstr "" "podržava korišćenje specifikacije \"U\" za označavanje neograničenog " "vremenskog intervala." -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "Smanjite broj verzija brisanjem starih srednjih rezervnih kopija" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" "Koristite ovu opciju da nastavite čak i ako neki izvorni unosi nedostaju." -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "Zanemarite izvorne elemente koji nedostaju" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "Prepiši fajlove kad vraćaš" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." @@ -3930,11 +4515,11 @@ msgstr "" "pokrene opcija. Generalno, ova opcija će proizvesti liniju za svaku " "obrađeni fajl." -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "Ispiši više informacija o napretku" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." @@ -3942,11 +4527,11 @@ msgstr "" "Koristite ovu opciju da povećate količinu izlaza generisanog kao rezultat " "operacije, uključujući sva imena datoteka." -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "Ispiši pune rezultate" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3957,11 +4542,11 @@ msgstr "" " skladišta. Fajl nije šifrovan i sadrži veličinu i SHA256 hešove svih " "udaljenih fajlova i može se koristiti za proveru integriteta fajlova." -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "Utvrdite da li su fajlovi za verifikaciju otpremljeni" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3971,11 +4556,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "Broj uzoraka za testiranje nakon pravljenja rezervne kopije" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3985,11 +4570,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "Procenat uzoraka za testiranje nakon pravljenja rezervne kopije" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -4001,41 +4586,41 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "Veličina bafera za čitanje fajla" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "Dozvolite da se pristupna fraza promeni" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "Navedite samo setove fajlova" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," @@ -4046,11 +4631,11 @@ msgstr "" "ubrzaće operacije pravljenja rezervnih kopija i vraćanja, ali ne utiče " "mnogo na veličinu fajla." -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." @@ -4058,11 +4643,11 @@ msgstr "" "Podrazumevano se dozvole ne vraćaju jer bi vas mogle sprečiti da pristupite" " vašim fajlovima. Koristite ovu opciju i da vratite dozvole." -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "Vrati dozvole fajla" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" @@ -4072,11 +4657,11 @@ msgstr "" "se potvrdilo da je vraćanje bilo uspešno. Koristite ovu opciju da biste " "onemogućili proveru i izbegli čekanje na verifikaciju." -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "Preskoči proveru vraćenih fajlova" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " @@ -4086,28 +4671,28 @@ msgstr "" "minimizirao količinu preuzetih podataka. Koristite ovu opciju da preskočite " "ovu optimizaciju i koristite samo udaljene podatke." -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "Ne koristi lokalne podatke" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." @@ -4116,11 +4701,11 @@ msgstr "" "blokova pročitanih sa volumena pre nego što zakrpite vraćene fajlove sa " "podacima." -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "Proveri heševe blokova" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -4133,11 +4718,11 @@ msgstr "" " bez potrebe za rekonstruisanjem svih informacija. Dobijena baza podataka " "može se pretraživati, ali se ne može koristiti za vraćanje podataka." -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "Popravite bazu podataka sa putanjama" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -4145,11 +4730,11 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "Prisilno podesite lokalizaciju" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " @@ -4159,22 +4744,22 @@ msgstr "" "\"Danas\" ili \"Prošli četvrtak\". Podešavanjem ove opcije, prikazuju se " "samo stvarni datumi, na primer „12. novembar 2018, 20:01“." -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "Rukujte komunikacijom fajla sa pozadinom koristeći niti" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " @@ -4184,22 +4769,22 @@ msgstr "" "Postavljanje ove vrednosti na nulu ili manje će dinamički izbalansirati " "broj aktivnih niti kako bi odgovarao hardveru." -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "Ograničenje broja istovremenih niti" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" "Koristite ovu opciju da podesite broj procesa koji obavljaju heširanje " "podataka." -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "Navedite broj istovremenih procesa heširanja" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." @@ -4207,11 +4792,11 @@ msgstr "" "Koristite ovu opciju da podesite broj procesa koji vrše kompresiju izlaznih " "podataka." -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "Odredite broj istovremenih procesa kompresije" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " @@ -4222,11 +4807,11 @@ msgstr "" "rezervne kopije i sadržaja koji je učitan u nepotpunoj sesiji pravljenja " "rezervne kopije." -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -4234,11 +4819,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -4246,11 +4831,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -4258,11 +4843,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "Dozvoli uklanjanje svih skupova set fajlova" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4278,11 +4863,11 @@ msgstr "" "Podešavanje ovog na true će omogućiti Duplicati-ju da izvrši VACUUM " "operacije po sopstvenom nahođenju." -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4290,11 +4875,11 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "Onemogućite skener za čitanje unapred" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " @@ -4305,11 +4890,11 @@ msgstr "" "provere, uverite se da pokrećete redovne komande za proveru da biste bili " "sigurni da sve funkcioniše kako se očekuje." -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "Onemogućite proveru doslednosti liste fajlova" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4317,15 +4902,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "Onemogućite rezervnu kopiju kada je napajanje preko baterije" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "Nivo podataka o fajlu dnevnika" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4340,42 +4925,42 @@ msgstr "" "osim ako ne počinju sa '-'. Regularni izrazi su podržani u uglasim " "zagradama. Primer: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "Nivo informacija konzole" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4387,11 +4972,11 @@ msgstr "" "bila da se fajlovi zovu nešto poput „.nobackup“ i da se ovaj fajl smešta u " "fascikle za koje ne treba praviti rezervnu kopiju." -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "Lista imena fajlova koji isključuju fascikle" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4399,11 +4984,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4411,11 +4996,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4428,11 +5013,11 @@ msgstr "" "evidentirali sve upite baze podataka i ne zaboravite da podesite --{0}={2} " "ili --{1}={2} da biste prijavili dodatne podatke evidencije" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4441,11 +5026,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4453,11 +5038,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4465,11 +5050,160 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " @@ -4478,47 +5212,47 @@ msgstr "" "Kriptoteka ne podržava transformacije koje se mogu ponovo koristiti za heš " "algoritam {0}" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "Kriptoteka ne podržava heš algoritam {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "Pristupna fraza se ne može promeniti za postojeću rezervnu kopiju" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "Pravljenje snimka nije uspelo: {0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "Brisanje fajlova {0} nije uspelo, proveravanje da li fajl postoji" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "Oporavljeno od problema sa pokušajem brisanja nepostojećeg fajla {0}" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "Oporavak od greške prilikom brisanja fajla {0} nije uspeo" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" "Operacija brisanja nije uspela za {0} sa FileNotFound, navodeći sadržaj" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "Listing pokazuje da je fajl {0} ispravno izbrisan" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "Listing potvrđuje da fajl {0} nije izbrisan" @@ -4581,29 +5315,6 @@ msgstr "" msgid "Configure http requests" msgstr "Podesi http zahteve" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "Prihvati bilo koji sertifikat sa servera" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "Opciono prihvati poznat SSL sertifikat" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4702,7 +5413,19 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "Konfiguriši Hyper-V modul" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" @@ -4710,125 +5433,125 @@ msgstr "" "Ovaj modul interno radi na raščlanjivanju izvornih parametara za pravljenje " "rezervnih kopija Microsoft SQL Servera baze podataka" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "Podesi Microsoft SQL Server modul" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "Pokreni skriptu" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "Pokreni skriptu pri izlasku" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "Skripta \"{0}\" je završena sa izlaznim kodom {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "Skripta \"{0}\" se vratila sa izlaznim kodom {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "Pokreni zahtevanu skriptu prilikom pokretanja" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "Greška u toku izvršavanja skripte \"{0}\": {1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "Isteklo je vreme za izvršavanje skripte \"{0}\"." -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "Pokreni skriptu prilikom pokretanja" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "Skripta \"{0}\" je prijavila poruke grešaka: {1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "Ovaj modul može da pošalje email nakon što se operacija završi" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Pošalji mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4841,21 +5564,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "Telo poruke" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP lozinka" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4867,11 +5590,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "Primaoc(i) e-pošte" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4881,25 +5604,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Pošiljalac e-pošte" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "Poruke za slanje" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4907,11 +5630,11 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP Url" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" @@ -4920,60 +5643,60 @@ msgstr "" "Ovo podešavanje daje temu e-pošte. Vrednosti se zamenjuju kao što je opisano" " u opisu za --{0}." -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "Predmet e-pošte" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP korisničko ime" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "Cela SMTP komunikacija: {0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "" "Slanje e-pošte sa servera: {0} nije uspelo, poruka: {1}, ponovni pokušaj sa " "{2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "E-pošta je uspešno poslata pomoću servera: {0}" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" "Ovaj modul pruža podršku za slanje izveštaja o statusu putem XMPP poruka" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP modul izveštaja" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP adresa e-pošte primaoca" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4986,35 +5709,35 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "Šablon poruke" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP ime korisnika" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP lozinka" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -5023,111 +5746,111 @@ msgstr "" "Možete da navedete jedan od \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "Možete da navedete više opcija sa zarezom, npr. \"{0},{1}\". Posebna vrednost \"{4}\" je skraćenica za \"{0},{1},{2},{3}\" i dovešće do slanja poruke svim operacijama rezervnih kopija." -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "Šalji poruke za sve operacije" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" "Došlo je do vremenskog ograničenja prilikom prijavljivanja na jabber server" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" "Ovaj modul pruža podršku za slanje izveštaja o statusu putem HTTP poruka" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP modul izveštaja" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "Naziv parametra za slanje poruke" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "Dodatni parametri za dodavanje u http poruku" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -5135,46 +5858,46 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "Slanje poruke nije uspelo: {0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "Filter za poruke dnevnika" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." @@ -5182,7 +5905,7 @@ msgstr "" "Koristite ovu opciju da biste podesili maksimalan broj redova dnevnika koji " "će se uključiti u izveštaj. Nula ili negativna vrednost znači neograničeno." -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -5191,16 +5914,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "Format nije podržan: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "Nevažeća vrednost veličine: {0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "Validator SSL sertifikata je pozvan netačnim redosledom" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -5209,7 +5932,7 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " @@ -5218,71 +5941,71 @@ msgstr "" "Nije uspelo pri validaciji heš sertifikata, poruka o grešci: {0}, naziv " "greške SSL: {1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "Privremena fascikla ne postoji: {0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "Raščlanjivanje segmenta nije uspelo: {0}, nevažeći ceo broj" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "Neispravan specifikator: {0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "Neparsirani podaci: {0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "Uri nije važeći: {0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "Uri-ju nedostaje ime hosta: {0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bajtova" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "String \"{0}\" nije moguće parsirati u datum" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "Nije moguće čitati i pisati iz istog izvora" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5522,11 +6245,11 @@ msgstr "Onemogući ispis u konzolu" msgid "This link may provide additional information: {0}" msgstr "Ova veza može da pruži dodatne informacije: {0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Uključi automatsko ažuriranje" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-sv_SE.mo b/Localizations/duplicati/localization-sv_SE.mo index 070ac85cd..656bc9230 100644 Binary files a/Localizations/duplicati/localization-sv_SE.mo and b/Localizations/duplicati/localization-sv_SE.mo differ diff --git a/Localizations/duplicati/localization-sv_SE.po b/Localizations/duplicati/localization-sv_SE.po index d757cf620..f5a1879fd 100644 --- a/Localizations/duplicati/localization-sv_SE.po +++ b/Localizations/duplicati/localization-sv_SE.po @@ -16,7 +16,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Tommy Kronkvist , 2024\n" "Language-Team: Swedish (Sweden) (https://app.transifex.com/duplicati/teams/67655/sv_SE/)\n" @@ -51,8 +51,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -154,78 +154,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "förvalt värde" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[FÖRÅLDRAD]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "värden" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "Uppräkning" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "Flaggor" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Heltal" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "Sökväg" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Storlek" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "Sträng" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "Tidsrymd" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "Okänd" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "Mappen kan inte skapas, eftersom den redan finns" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "Den efterfrågade mappen finns inte" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "Avbrutet" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "Den efterfrågade filen finns inte" @@ -347,9 +363,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -366,9 +381,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -379,9 +393,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -461,41 +474,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "Alternativ FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -506,47 +509,197 @@ msgstr "" "Lösenordet som används för att ansluta till servern. Det kan också " "tillhandahållas med hjälp av miljövariabeln \"AUTH_PASSWORD\"." -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "Inaktivera verifiering av uppladdningen" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "Mappen {0} hittades inte, meddelande: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "Inaktivera verifiering av uppladdningen" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "Konfigurera anslutningstyp för FTP" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "Konfigurera krypteringstyp för FTP" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" +"Denna parameter kontrollerar vilken SSL-policy som ska användas då " +"kryptering är aktiverad" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "Konfigurera vilken SSL-policy som används då kryptering är aktiverad" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -842,6 +995,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -874,112 +1032,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "Alternativ FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "Konfigurera anslutningstyp för FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "Konfigurera krypteringstyp för FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" -"Denna parameter kontrollerar vilken SSL-policy som ska användas då " -"kryptering är aktiverad" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "Konfigurera vilken SSL-policy som används då kryptering är aktiverad" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "Fel vid radering av fil: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "Fel vid läsning av fil: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "Fel vid skrivning av fil: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "Modul för att generera privata/publika SSH-nycklar" @@ -1454,7 +1506,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1462,22 +1518,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" - #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1485,28 +1541,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1518,42 +1574,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 Group" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1570,7 +1626,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1581,7 +1637,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1826,43 +1882,45 @@ msgstr "Inget lösenord angivet" msgid "No username given" msgstr "Inget användarnamn angivet" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1978,6 +2036,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2010,6 +2073,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "Mappen {0} hittades inte, meddelande: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2035,6 +2104,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2155,9 +2288,410 @@ msgstr "Oväntad felkod: {0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "En annan instans är igång och den har meddelats" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2177,13 +2711,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"Kommandoradsargument som stöds:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2249,7 +2781,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2257,8 +2789,6 @@ msgstr "" #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" msgstr "" -"En version av SQLite som inte stöds har upptäckts ({0}), måste vara {1} " -"eller högre" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2271,63 +2801,69 @@ msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." msgstr "" -"Porten som webbservern lyssnar på. Flera värden kan anges med ett komma " -"emellan." #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -"Lösenordet som krävs för att komma åt webbservern. Det här alternativet " -"sparas så att du inte behöver ställa in det vid varje körning. Om du ställer" -" in ett tomt värde inaktiveras lösenordet." -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "Ställ in tiden efter vilken loggdata ska rensas från databasen." -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "Ta bort gammal data från loggarna" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2335,11 +2871,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2347,86 +2883,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "Mapp för temporär lagring" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2434,36 +3009,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "Servern har startat och lyssnar på {0}, port {1}" - -#: Library/RestAPI/Strings.cs:74 -#, csharp-format -msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" msgstr "" -"Det gick inte att skapa SSL-certifikat med angivna parametrar. " -"Undantagsinformation: {0}" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2478,22 +3087,22 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." @@ -2502,11 +3111,11 @@ msgstr "" "inställning på noll ger ingen kompression, och en inställning på 9 ger " "maximal kompression." -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2514,69 +3123,36 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" -msgstr "Filen kan inte hittas: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "Den angivna filen är inte en del av det här arkivet" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" +#, csharp-format +msgid "File not found: {0}" +msgstr "Filen kan inte hittas: {0}" #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" @@ -2960,48 +3536,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "Begränsa storleken på volymerna" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3009,11 +3603,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "Välj vilken modul som ska användas för komprimering" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3021,27 +3615,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "Välj vilken modul som ska användas för kryptering" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3056,88 +3650,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3146,12 +3742,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3164,11 +3760,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3181,30 +3788,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3212,44 +3820,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3258,22 +3866,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3285,11 +3893,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3299,11 +3907,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3311,11 +3919,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3323,41 +3931,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3369,11 +3987,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3381,71 +3999,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3454,11 +4064,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3466,43 +4076,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3510,11 +4120,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3522,62 +4132,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "Använd lokala data vid återställning" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3589,49 +4199,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3639,11 +4249,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3653,11 +4263,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3667,11 +4277,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3683,111 +4293,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3795,11 +4405,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3807,74 +4417,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3882,11 +4492,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3894,11 +4504,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3906,11 +4516,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3920,11 +4530,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3932,22 +4542,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3955,15 +4565,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3973,42 +4583,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4016,11 +4626,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4028,11 +4638,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4040,11 +4650,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4053,11 +4663,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4066,11 +4676,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4078,11 +4688,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4090,57 +4700,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4194,29 +4953,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4302,131 +5038,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "Skicka mail" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4439,21 +5187,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP Lösenord" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4461,11 +5209,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4475,25 +5223,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4501,68 +5249,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4575,144 +5323,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4720,52 +5468,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4774,16 +5522,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4792,78 +5540,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5080,11 +5828,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "Aktivera automatiska uppdateringar" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-th.mo b/Localizations/duplicati/localization-th.mo index 37b619a63..68a3b268f 100644 Binary files a/Localizations/duplicati/localization-th.mo and b/Localizations/duplicati/localization-th.mo differ diff --git a/Localizations/duplicati/localization-th.po b/Localizations/duplicati/localization-th.po index abc7b1dfa..cf2217997 100644 --- a/Localizations/duplicati/localization-th.po +++ b/Localizations/duplicati/localization-th.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Arthit Suriyawongkul, 2024\n" "Language-Team: Thai (https://app.transifex.com/duplicati/teams/67655/th/)\n" @@ -44,8 +44,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -147,78 +147,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "ค่าปริยาย" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "ค่า" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "จำนวนเต็ม" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "ขนาด" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "สตริง" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "ช่วงเวลา" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "ไม่ทราบ" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "ยกเลิกแล้ว" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -335,9 +351,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -354,9 +369,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -367,9 +381,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -449,41 +462,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -492,47 +495,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -828,6 +979,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -860,110 +1016,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1438,7 +1490,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1446,22 +1502,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1469,28 +1525,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1502,42 +1558,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1554,7 +1610,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1565,7 +1621,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1810,43 +1866,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1962,6 +2020,11 @@ msgstr "" msgid "Dropbox" msgstr "" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1994,6 +2057,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2019,6 +2088,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2139,6 +2272,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2159,13 +2693,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"อาร์กูเมนต์ที่สนับสนุนสำหรับคอมมานด์ไลน์:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2231,7 +2763,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2254,55 +2786,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "ล้างข้อมูลปูมเก่า" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2310,11 +2853,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2322,86 +2865,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "โฟลเดอร์เก็บข้อมูลชั่วคราว" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2409,34 +2991,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2451,32 +3069,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2484,68 +3102,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2930,48 +3515,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2979,11 +3582,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2991,27 +3594,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3026,88 +3629,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3116,12 +3721,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3134,11 +3739,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3151,30 +3767,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3182,44 +3799,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3228,22 +3845,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3255,11 +3872,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3269,11 +3886,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3281,11 +3898,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3293,41 +3910,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3339,11 +3966,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3351,71 +3978,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "รายชื่อแฟ้มที่ถูกลบ" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3424,11 +4043,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3436,43 +4055,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3480,11 +4099,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3492,62 +4111,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3559,49 +4178,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3609,11 +4228,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3623,11 +4242,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3637,11 +4256,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3653,111 +4272,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3765,11 +4384,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3777,74 +4396,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3852,11 +4471,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3864,11 +4483,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3876,11 +4495,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3890,11 +4509,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3902,22 +4521,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3925,15 +4544,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3943,42 +4562,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3986,11 +4605,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -3998,11 +4617,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4010,11 +4629,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4023,11 +4642,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4036,11 +4655,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4048,11 +4667,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4060,57 +4679,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4164,29 +4932,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4272,131 +5017,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "ส่งเมล" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4409,21 +5166,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4431,11 +5188,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "ผู้รับอีเมล" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4445,25 +5202,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4471,68 +5228,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4545,144 +5302,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4690,52 +5447,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4744,16 +5501,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4762,78 +5519,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} ไบต์" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5048,11 +5805,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "สลับการปรับปรุงอัตโนมัติ" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-zh_CN.mo b/Localizations/duplicati/localization-zh_CN.mo index 14e48a9af..57820490e 100644 Binary files a/Localizations/duplicati/localization-zh_CN.mo and b/Localizations/duplicati/localization-zh_CN.mo differ diff --git a/Localizations/duplicati/localization-zh_CN.po b/Localizations/duplicati/localization-zh_CN.po index 33905bf2b..ec01d9047 100644 --- a/Localizations/duplicati/localization-zh_CN.po +++ b/Localizations/duplicati/localization-zh_CN.po @@ -9,18 +9,18 @@ # Herald Yu , 2018 # Kevin Li , 2019 # Jacob Zhong , 2021 -# vishun nadir, 2024 -# mays_wind , 2024 # Hoilc , 2024 +# mays_wind , 2024 +# vishun nadir, 2024 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" -"Last-Translator: Hoilc , 2024\n" +"Last-Translator: vishun nadir, 2024\n" "Language-Team: Chinese (China) (https://app.transifex.com/duplicati/teams/67655/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -51,8 +51,8 @@ msgstr "使用此选项设置AES加密操作允许的线程级别。" msgid "Set thread level utilized for crypting" msgstr "设置用于加密的线程级别。" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "选项--{0}已不再使用并且已被弃用。" @@ -137,7 +137,7 @@ msgstr "GPG 解密命令" msgid "" "Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." -msgstr "" +msgstr "覆盖默认的GPG加密命令 \"{0}\"。通常的用法是设置 {1} 来请求非对称加密。" #: Library/Encryption/Strings.cs:55 msgid "The GPG encryption command" @@ -156,78 +156,94 @@ msgstr "调用 GunPG 失败,程序未能正常输出" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "调用 GunPG 失败,程序未能正确关闭" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "别名" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "默认值" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "[废弃的]" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "值" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "布尔" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "枚举" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "参数" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "整数" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "路径" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "大小" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "字符串" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "时间间隔" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "未知" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "文件夹已存在,所以不能创建" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "需要的文件夹不存在" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "已取消" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "请求的文件不存在" @@ -347,16 +363,15 @@ msgid "" "must also be set." msgstr "此密码用于连接到服务器。也可以通过环境变量\"AUTH_PASSWORD\"提供密码。如果提供了密码,则还必须设置 --{0}" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 #: Library/Backend/SharePoint/Strings.cs:29 #: Library/Backend/WEBDAV/Strings.cs:29 msgid "Supply the password used to connect to the server" -msgstr "" +msgstr "提供用于连接服务器的密码" #: Library/Backend/OpenStack/Strings.cs:32 msgid "The domain name of the user used to connect to the server." @@ -364,11 +379,10 @@ msgstr "用于用户连接到服务器的域名称。" #: Library/Backend/OpenStack/Strings.cs:33 msgid "Supply the domain used to connect to the server" -msgstr "" +msgstr "提供用于连接服务器的域" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -379,9 +393,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "该用户名用来连接到服务器,它也可以由环境变量 \"AUTH_USERNAME\" 提供。" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -420,7 +433,7 @@ msgstr "认证地址用来认证用户和查找存储服务。该地址一般以 #: Library/Backend/OpenStack/Strings.cs:41 msgid "Supply the authentication URL" -msgstr "" +msgstr "提供认证地址" #: Library/Backend/OpenStack/Strings.cs:42 msgid "The keystone API version to use. Valid values are 'v2' and 'v3'." @@ -459,9 +472,9 @@ msgstr "获取配置" #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 msgid "Provide different config values" -msgstr "" +msgstr "提供不同的配置值" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " @@ -470,34 +483,24 @@ msgstr "" "此后端可以读写基于FTP的后端数据。允许的格式为 \"ftp://hostname/folder\" 和 " "\"ftp://username:password@hostname/folder\"" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "激活此选项以使FTP连接处于主动模式。即使也设置了选项 --{0},连接也将以主动模式建立。" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "备用 FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "激活此选项以使FTP连接处于被动模式,这种方式与某些防火墙配合得更好。如果设置了选项 --{0},则忽略此选项。" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -506,48 +509,197 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "该密码用来连接到服务器,它也可以由环境变量 \"AUTH_PASSWORD\" 提供。" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "使用此选项通过安全套接层(SSL)在ftp(ftps)上进行通信。" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" -msgstr "" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." +msgstr "使用此选项将FTP对话记录到终端控制台以进行调试。" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." -msgstr "为了防止网络传输错误,每次上传都将进行校验。开启该选项可以禁用校验,这会加快上传但减少可靠性。" +msgid "Log FTP dialog to terminal console" +msgstr "将FTP对话记录到终端控制台" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "禁用上传校验" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "使用此选项将FTP私有信息(用户名,密码)记录到控制台以进行调试(不要发布到互联网上!)" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "文件夹 {0} 未找到,信息:{1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "将FTP私有信息记录到控制台" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "文件夹 {0} 未找到,信息:{1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "文件 {0} 已上传,但之后未找到。返回的文件列表为 {1}" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." -msgstr "文件 {0} 已上传,但返回的大小是 {1} ,预期大小应为 {2}。" +"to be {2}" +msgstr "文件 {0} 已下载且返回大小为 {1} 但其大小应当为 {2}" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "为了防止网络或服务器失败,每次上传都会尝试校验。开启该选项将禁用校验,这会加快上传但减少可靠性。" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "禁用上传校验" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "如果设置该参数,FTP 数据连接方式将被修改为所选项。" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "配置 FTP 数据连接方式" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "如果设置该参数,FTP 加密方式将被修改为所选项。" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "配置 FTP 加密方式" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "该参数决定加密启用时的 SSL 策略。" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "配置加密启用时的 SSL 策略" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" +"一些 FTP 服务器需要在报告正确的文件大小前延迟一段时间。所需的延迟取决于网络拓扑结构。如果您遇见与上传文件大小不匹配的错误,可以尝试增加几秒的延迟。" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "在上传文件后延迟一定时间" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "使用此选项通过安全套接层(SSL)在ftp(ftps)上进行通信。" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "激活此选项以使FTP连接处于主动模式。即使也设置了选项 --{0},连接也将以主动模式建立。" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "激活此选项以使FTP连接处于被动模式,这种方式与某些防火墙配合得更好。如果设置了选项 --{0},则忽略此选项。" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" +msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 msgid "Google Cloud Storage configuration module" @@ -707,7 +859,7 @@ msgstr "用于与CloudFiles进行身份验证的用户名。" #: Library/Backend/CloudFiles/Strings.cs:37 msgid "Supply the username used to authenticate with CloudFiles" -msgstr "" +msgstr "提供用来认证 CloudFiles 的用户名" #: Library/Backend/CloudFiles/Strings.cs:38 msgid "MD5 Hash (ETag) verification failed" @@ -793,7 +945,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:40 msgid "Specify S3 location constraints" -msgstr "" +msgstr "指定 S3 位置限制" #: Library/Backend/S3/Strings.cs:41 #, csharp-format @@ -806,7 +958,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:43 msgid "Specify an alternate S3 server name" -msgstr "" +msgstr "指定可选的 S3 服务器名" #: Library/Backend/S3/Strings.cs:44 msgid "" @@ -850,13 +1002,18 @@ msgstr "使用该选项指定存储级别。如果该选项未设置,服务器 msgid "Specify storage class" msgstr "指定存储级别" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "S3 IAM 支持模块" #: Library/Backend/S3/S3IAM.cs:73 msgid "Expose S3 IAM manipulation as a web module" -msgstr "" +msgstr "将S3 IAM操作作为Web模块暴露" #: Library/Backend/S3/S3IAM.cs:81 msgid "The operation to perform" @@ -882,112 +1039,6 @@ msgstr "密码" msgid "The Amazon Secret Key" msgstr "Amazon Secret Key" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" -"这个后端可以使用替代的FTP客户端读写基于FTP的后端数据。允许的格式为\"aftp://hostname/folder\"和\"aftp://username:password@hostname/folder\"。" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "备用 FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "使用此选项将FTP对话记录到终端控制台以进行调试。" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "将FTP对话记录到终端控制台" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "使用此选项将FTP私有信息(用户名,密码)记录到控制台以进行调试(不要发布到互联网上!)" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "将FTP私有信息记录到控制台" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "文件夹 {0} 未找到,信息:{1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "文件 {0} 已下载且返回大小为 {1} 但其大小应当为 {2}" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "为了防止网络或服务器失败,每次上传都会尝试校验。开启该选项将禁用校验,这会加快上传但减少可靠性。" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "如果设置该参数,FTP 数据连接方式将被修改为所选项。" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "配置 FTP 数据连接方式" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "如果设置该参数,FTP 加密方式将被修改为所选项。" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "配置 FTP 加密方式" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "该参数决定加密启用时的 SSL 策略。" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "配置加密启用时的 SSL 策略" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" -"一些 FTP 服务器需要在报告正确的文件大小前延迟一段时间。所需的延迟取决于网络拓扑结构。如果您遇见与上传文件大小不匹配的错误,可以尝试增加几秒的延迟。" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "在上传文件后延迟一定时间" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "删除文件出错: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "读取文件出错: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "写入文件出错: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "生成 SSH 公私钥的模块" @@ -1006,7 +1057,7 @@ msgstr "公钥用户名" #: Library/Backend/SSHv2/Strings.cs:30 msgid "Determines the type of key to generate." -msgstr "" +msgstr "确定要生成的密钥类型。" #: Library/Backend/SSHv2/Strings.cs:31 msgid "The key type" @@ -1086,11 +1137,11 @@ msgid "" "Point to a valid OpenSSH keyfile. If the file is encrypted, the password " "supplied is used to decrypt it. If the keyfile is specified, the password is" " not used to authenticate." -msgstr "" +msgstr "指向一个有效的OpenSSH密钥文件。如果文件是加密的,提供的密码用于解密它。如果指定了密钥文件,密码不用于认证。" #: Library/Backend/SSHv2/Strings.cs:57 Library/Backend/SSHv2/Strings.cs:59 msgid "Use a SSH private key to authenticate" -msgstr "" +msgstr "使用 SSH 私钥认证" #: Library/Backend/SSHv2/Strings.cs:58 #, csharp-format @@ -1108,7 +1159,7 @@ msgstr "使用此选项来管理SSH操作的内部超时。如果该值设置为 #: Library/Backend/SSHv2/Strings.cs:61 msgid "Set the operation timeout value" -msgstr "" +msgstr "设置操作超时时间" #: Library/Backend/SSHv2/Strings.cs:62 msgid "" @@ -1122,7 +1173,7 @@ msgstr "" #: Library/Backend/SSHv2/Strings.cs:63 msgid "Set a keepalive value" -msgstr "" +msgstr "设置 keepalive 时间" #: Library/Backend/SSHv2/Strings.cs:64 #, csharp-format @@ -1340,7 +1391,7 @@ msgstr "" #: Library/Backend/Backblaze/Strings.cs:28 msgid "B2 Cloud Storage Application Key" -msgstr "B2 云存储应用密钥" +msgstr "B2 Cloud Storage Application Key" #: Library/Backend/Backblaze/Strings.cs:29 #, csharp-format @@ -1351,7 +1402,7 @@ msgstr "B2 Cloud Storage Account ID可以在登录您的Backblaze账户后获得 #: Library/Backend/Backblaze/Strings.cs:30 msgid "B2 Cloud Storage Account ID" -msgstr "B2 云存储帐户 ID" +msgstr "B2 Cloud Storage Account ID" #: Library/Backend/Backblaze/Strings.cs:35 msgid "No B2 Cloud Storage Application Key given" @@ -1481,59 +1532,67 @@ msgstr "使用此选项来设置HttpClient类以执行HTTP请求。" msgid "Whether the HttpClient class should be used" msgstr "是否使用 HttpClient 类" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" " Microsoft Graph API. Usage of this backend requires that you agree to the " "terms in {0} ({1}) and {2} ({3})." msgstr "" - -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" -msgstr "Microsoft OneDrive v2" +"通过Microsoft Graph API将文件存储在Microsoft OneDrive或Microsoft OneDrive for " +"Business中。使用此后端需要您同意 {0}({1}) 和 {2}({3}) 中的条款。" #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "存储数据的设备 ID。如果未指定设备,则默认通过 '{0}' 使用 OneDrive 或 OneDrive 企业版。" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "可选的设备 ID" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " "Usage of this backend requires that you agree to the terms in {0} ({1}) and " "{2} ({3})." msgstr "" +"通过Microsoft Graph API将文件存储在Microsoft SharePoint网站中。使用此后端需要您同意 {0}({1}) 和 " +"{2}({3}) 中的条款。" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "Microsoft SharePoint v2" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "用于存储数据的站点 ID。" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "站点 ID" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "未提供站点 ID" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "站点 ID 冲突:所给为 {0} 但找到 {1}" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1544,42 +1603,45 @@ msgid "" "specify the SharePoint site's ID via --{0}). Usage of this backend requires " "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" +"通过Microsoft Graph API将文件存储在Microsoft Office " +"365群组中。允许的格式为\"sharepoint://tenant.sharepoint.com/{{PathToWeb}}//{{Documents}}/subfolder\"(其中\"//\"可选地用于指示根文档文件夹)以及\"sharepoint://subfolder\"(在这种情况下,您还必须通过" +" --{0} 明确指定SharePoint站点的ID)。使用此后端需要您同意 {1}({2}) 和 {3}({4}) 中的条款。" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "Microsoft Office 365 群组" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "用于存储数据的群组ID。" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "群组ID" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "用于存储数据的区组邮件地址。" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "该群组的邮件地址" -#: Library/Backend/OneDrive/Strings.cs:65 +#: Library/Backend/OneDrive/Strings.cs:66 msgid "No group ID or group email address was provided." msgstr "未提供群组ID或群组电子邮件地址。" -#: Library/Backend/OneDrive/Strings.cs:66 +#: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format msgid "No groups were found with the given email address: {0}" msgstr "给定 Email 地址未找到组: {0}" -#: Library/Backend/OneDrive/Strings.cs:67 +#: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format msgid "Multiple groups were found with the given email address: {0}" msgstr "给定的 Email 地址存在多个组: {0}" -#: Library/Backend/OneDrive/Strings.cs:68 +#: Library/Backend/OneDrive/Strings.cs:69 #, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "组 ID 冲突:设置为 {0} 而找到的是 {1}" @@ -1597,7 +1659,7 @@ msgid "Access Key ID is used to identify the user." msgstr "Access Key ID用于识别不同用户。" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1608,7 +1670,7 @@ msgid "" msgstr "Access Key Secret是用户用来加密签名字符串的密钥,也是OSS用来验证这些签名字符串的密钥。" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1792,7 +1854,7 @@ msgstr "" #: Library/Backend/Jottacloud/Strings.cs:34 msgid "Supply the backup device to use" -msgstr "" +msgstr "指定使用的备份设备" #: Library/Backend/Jottacloud/Strings.cs:35 #, csharp-format @@ -1859,43 +1921,45 @@ msgstr "未给出密码" msgid "No username given" msgstr "未给出用户名" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." -msgstr "此后端可以读写IDrive e2的数据。" +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." +msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "Access Key Secret可以登录您的IDrive e2账户后获得。也可以通过选项 --{0} 提供。" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "Access Key ID可以登录您的IDrive e2账户后获得。也可以通过选项 --{0} 提供。" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "\"Bucket名称或完整路径\"是目标bucket的名称或bucket内文件夹的完整路径。" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "Bucket名称或完整路径" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "未提供Access Key Secret" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "未提供Access Key ID" @@ -2017,6 +2081,11 @@ msgstr "此后端可以读写Dropbox的数据。允许的格式为\"dropbox://fo msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -2051,6 +2120,12 @@ msgid "" "does not support WebDAV connections" msgstr "服务器返回错误码 {0} ({1}),表明服务器不支持 WebDAV 连接" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "文件夹 {0} 未找到,信息:{1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2078,6 +2153,71 @@ msgstr "为了帮助调试问题,可以设置一个文件路径,该文件将 msgid "Dump the PROPFIND response" msgstr "转储 PROPFIND 响应" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "使用此选项接受任何服务器证书,无论它可能存在什么错误。请尽可能使用 --{0} 代替。" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "接受任意服务器证书" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" +"如果您的服务器证书被报告为无效(例如,使用自签名证书),您可以提供证书哈希(SHA1)以无论如何批准它。哈希值必须以十六进制格式输入,不能有空格或冒号。您可以输入多个哈希,用逗号分隔。" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "视情况接受已知的 SSL 证书" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2098,7 +2238,7 @@ msgstr "Storj DCS 配置模块" #: Library/Backend/Storj/StorjConfig.cs:46 msgid "Expose Storj DCS configuration as a web module" -msgstr "" +msgstr "将Storj DCS配置作为Web模块暴露" #: Library/Backend/Storj/Strings.cs:27 msgid "This backend can read and write data to the Storj DCS." @@ -2200,9 +2340,410 @@ msgstr "预期外的错误码 :{0} - {1}" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "OAuth服务目前超出配额。请几小时后再试。" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" -msgstr "已有实例正在运行,且已被通知" +msgstr "" #: Library/RestAPI/Strings.cs:10 #, csharp-format @@ -2222,13 +2763,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"支持的命令行参数:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2244,6 +2783,10 @@ msgid "" "parameter file. Each filter must be prefixed with either a + or a -, and " "multiple filters must be joined with {5} " msgstr "" +"使用此选项来存储传递给命令行客户端的部分或全部选项。该文件必须是纯文本文件,推荐使用UTF-8编码。文件中的每一行应采用 --option=value " +"的格式。使用特殊选项 --{0}和 " +"--{1}分别覆盖本地路径和远程目标URI。此文件中的选项优先于命令行上提供的选项。您不能在文件和命令行中同时指定过滤器。相反,您可以使用特殊的 " +"--{2},--{3}或--{4} 选项在参数文件内指定过滤器。每个过滤器必须以+或-为前缀,多个过滤器必须用 {5} 连接。" #: Library/RestAPI/Strings.cs:18 CommandLine/CLI/Strings.cs:45 msgid "Path to a file with parameters" @@ -2257,8 +2800,6 @@ msgid "" "specify filters inside the parameter file. Each filter must be prefixed with" " either a + or a -, and multiple filters must be joined with {3}" msgstr "" -"如果过滤条件已在参数文件中给出,那么就不能再在命令行中指定。使用特殊选项 --{0}, --{1}, 或 --{2} " -"来指定参数文件中的过滤条件。每个过滤条件必须以 + 或 - 做前缀且多个过滤条件必须用 {3} 连接" #: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 #, csharp-format @@ -2267,11 +2808,11 @@ msgstr "未能读取参数文件 \"{0}\",原因:{1}" #: Library/RestAPI/Strings.cs:22 msgid "Output log information to the file given" -msgstr "" +msgstr "将日志信息输出到给定的文件。" #: Library/RestAPI/Strings.cs:23 msgid "Determine the amount of information written in the log file" -msgstr "" +msgstr "确定写入日志文件的信息量。" #: Library/RestAPI/Strings.cs:24 msgid "" @@ -2296,14 +2837,14 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" #: Library/RestAPI/Strings.cs:30 #, csharp-format msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" -msgstr "检测到不支持的 SQLite 版本 ({0}),必需 {1} 或更高" +msgstr "" #: Library/RestAPI/Strings.cs:31 msgid "" @@ -2315,196 +2856,282 @@ msgstr "" msgid "" "The port the webserver listens on. Multiple values may be supplied with a " "comma in between." -msgstr "Web 服务器监听的端口。可以使用逗号分割来指定多个值。" +msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." -msgstr "Web 服务器用于 SSL 的 PKCS #12 格式的证书和密钥文件。只支持 RSA/DSA 密钥。" +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." +msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "用于解密 PKCS #12 证书文件的密码。" +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "web 服务器用于 SSL 的 PKCS #12 格式的证书和密钥文件。" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." -msgstr "Web 服务监听的网络接口。特殊值 \"*\" 和 \"any\" 表示所有接口。特殊值 \"loopback\" 表示环回适配器。" +msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." -msgstr "访问 Web 服务器需要的密码。该选项会被保存,所以您不需要每次启动都设置。设置为空表示禁用密码。" +msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "逗号分隔有效的主机名。如果任何一个主机名设置为 \"*\",那么将禁用主机名检查并接受所有主机名。" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "设置时长,在此时长之后日志数据将被从数据库中清除。" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "清理旧日志数据" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" " to choose where the settings are stored. This option can also be set with " "the environment variable {0}." -msgstr "Duplicati 需要保存一个存有所有设置的小数据库。使用该选项来选择设置保存在哪里。该选项也可以通过环境变量 {0} 进行指定。" - -#: Library/RestAPI/Strings.cs:43 -msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:45 +msgid "Set the folder where settings are stored" +msgstr "设置存储设置的文件夹" + +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " "database. This option can also be set with the environment variable {0}. Use" " the option --{1} to disable the database scrambling." -msgstr "该选项设置用于加密本地配置数据库的密钥。该选项也可以通过环境变量 {0} 来设置。使用选项 --{1} 可以禁用数据库加密。" - -#: Library/RestAPI/Strings.cs:45 -msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:47 +msgid "Set the database encryption key" +msgstr "设置数据库加密密钥" + +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "使用此选项提供一个用于临时存储的替代文件夹。默认情况下使用系统默认的临时文件夹。请注意,SQLite也会将临时文件放在这个临时文件夹中。" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "临时存储文件夹" -#: Library/RestAPI/Strings.cs:48 -msgid "Reset the JWT configuration, invalidating any issued login tokens" -msgstr "" - -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - #: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +msgid "Reset the JWT configuration, invalidating any issued login tokens" +msgstr "重置JWT配置,使任何已发出的登录令牌失效" + +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" -msgstr "" +msgid "Disable the visual captcha" +msgstr "禁用视觉验证码" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 -msgid "The Windows event log is not supported on this platform" -msgstr "" - -#: Library/RestAPI/Strings.cs:58 -#, csharp-format -msgid "Server has started and is listening on port {0}" +msgid "Use this option to disable database encryption of sensitive fields" msgstr "" #: Library/RestAPI/Strings.cs:59 -#, csharp-format -msgid "Use the following link to sign in: {0}" +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." msgstr "" #: Library/RestAPI/Strings.cs:60 -#, csharp-format -msgid "The server crashed: {0}" -msgstr "" +msgid "Log to the Windows event log" +msgstr "记录到Windows事件日志" #: Library/RestAPI/Strings.cs:61 -msgid "Require database encryption" +msgid "Use this option to set the log level for the Windows event log." msgstr "" #: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" +msgid "Set the log level for the Windows event log" msgstr "" #: Library/RestAPI/Strings.cs:63 #, csharp-format -msgid "" -"Database encryption key is required. Supply an encryption key via the " -"environment variable {0} or disable database encryption with the option " -"--{1}" +msgid "The Windows event log source {0} was not found. Attempting to create." msgstr "" #: Library/RestAPI/Strings.cs:64 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 +msgid "The Windows event log is not supported on this platform" +msgstr "此平台上不支持Windows事件日志" + +#: Library/RestAPI/Strings.cs:66 +#, csharp-format +msgid "Server has started and is listening on port {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:67 +#, csharp-format +msgid "Use the following link to sign in: {0}" +msgstr "使用以下链接登录:{0}" + +#: Library/RestAPI/Strings.cs:68 +#, csharp-format +msgid "The server crashed: {0}" +msgstr "服务崩溃了:{0}" + +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 +msgid "Require database encryption" +msgstr "要求数据库加密" + +#: Library/RestAPI/Strings.cs:73 +#, csharp-format +msgid "" +"Database encryption key is required. Supply an encryption key via the " +"environment variable {0} or disable database encryption with the option " +"--{1}" +msgstr "需要数据库加密密钥。通过环境变量 {0} 提供加密密钥,或者使用选项 --{1} 禁用数据库加密" + +#: Library/RestAPI/Strings.cs:74 +#, csharp-format +msgid "" +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "未找到有效日期。给定的起始日期 {0},重复间隔 {1},规划日期 {2}" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" -msgstr "服务器已启动,正在监听 {0} 端口 {1}" +msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" -msgstr "使用所给参数创建 SSL 证书失败,错误信息:{0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" -msgstr "打开监听端口失败,尝试过的端口:{0}" +msgstr "" #: Library/DynamicLoader/Strings.cs:24 #, csharp-format @@ -2516,32 +3143,32 @@ msgstr "加载集合 {0} 失败,错误信息:{1}" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "加载处理类型 {0} 集合 {1} 失败,错误信息:{2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "此模块提供行业标准ZIP压缩。用此模块创建的文件可以被任何符合标准的ZIP应用程序读取。" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "ZIP压缩" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "使用选项 --{0} 代替。" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "该选项控制压缩级别。设置为 0 表示不压缩,设置为 9 表示最大压缩。" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" -msgstr "" +msgstr "设置ZIP压缩级别" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2549,70 +3176,37 @@ msgid "" "ignored." msgstr "使用此选项设置替代的压缩方法,例如LZMA。请注意,使用Deflate以外的其他值将导致此选项 --{0} 被忽略。" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" -msgstr "" +msgstr "设置ZIP压缩方法" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "对于大于4GiB的文件,需要ZIP64格式。使用此选项来切换它。" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." +msgstr "" + +#: Library/Compression/Strings.cs:36 +msgid "Toggles the zip library to use" +msgstr "" + +#: Library/Compression/Strings.cs:37 #, csharp-format msgid "File not found: {0}" msgstr "未找到文件:{0}" -#: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." -msgstr "*实验功能*:启用 LZMA2 算法的 7z 压缩文件" - -#: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "*实验功能*:7z 压缩文件" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "压缩文件写入失败" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "压缩文件读取失败" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "所给文件是压缩文件的一部分" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "LZMA 2 算法使用的线程数。默认为处理器核心数。" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "压缩使用的线程数" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "该选项决定要使用的压缩算法。启用该选项会令 7z 使用更快的算法,但压缩比会有一定下降" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" -msgstr "" - #: Library/SQLiteHelper/Strings.cs:24 msgid "backup" msgstr "备份" @@ -2846,7 +3440,7 @@ msgstr "备份或恢复期间,允许系统在不活动时进入睡眠模式 ( #: Library/Main/Strings.cs:65 msgid "Toggle system sleep mode" -msgstr "" +msgstr "切换系统睡眠模式" #: Library/Main/Strings.cs:66 msgid "" @@ -3010,48 +3604,66 @@ msgstr "限制可备份的文件大小" #: Library/Main/Strings.cs:98 msgid "" -"Select another thread priority for the process. Use this to set Duplicati to" -" be more or less CPU intensive." +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" msgstr "" #: Library/Main/Strings.cs:99 +msgid "" +"Select another thread priority for the process. Use this to set Duplicati to" +" be more or less CPU intensive." +msgstr "指定 Duplicati 的进程优先级,这可以使 Duplicati 使用更多或更少的 CPU 资源" + +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "线程优先级" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "此选项可以更改dblock文件的最大大小。如果后端对每个单独文件的大小有限制,更改大小可能是有用的。" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "限制卷的大小" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "使用此选项以确保不读取清单文件的内容。这也意味着不会检查文件哈希。仅用于灾难恢复。" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" -msgstr "" +msgstr "禁用清单验证" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -3060,11 +3672,11 @@ msgid "" msgstr "" "Duplicati 支持插件式的压缩模块。使用该选项来选择创建新卷时用于压缩的模块。读取文件时,Duplicati 将根据文件名自动选择压缩模块。" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "选择用于压缩的模块" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -3073,27 +3685,27 @@ msgid "" msgstr "" "Duplicati 支持插件式的加密模块。使用该选项来选择创建新卷时用于加密的模块。读取文件时,Duplicati 将根据文件名自动选择加密模块。" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "选择用于加密的模块" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "提供一个或多个模块名称,用逗号分隔以卸载它们。" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "提供一个或多个模块名称,用逗号分隔以加载它们。" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3112,89 +3724,90 @@ msgstr "" "Duplicati 尝试创建快照,而创建失败时会在日志中产生警告信息。设置为 \"必须\" 时,Duplicati 会在创建快照失败后停止备份。在 " "Windows 上,快照将使用卷影复制服务 (VSS) 且需要管理员权限,在 Linux 上,使用的是逻辑卷管理 (LVM) 且需要 root 权限。" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" -msgstr "" +msgstr "控制磁盘快照使用与否" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "预生成的卷默认将放置在临时文件夹中。此选项可以设置一个不同的文件夹来放置临时卷。尽管名称如此,这也适用于同步运行。" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "上传完成前预生成卷的存放路径" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -"在执行异步上传时,Duplicati会创建可以上传的卷。为了防止Duplicati生成太多卷,此选项限制了待上传的数量。设置为零以禁用限制。" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "限制提前创建的卷数" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "执行异步上传时,允许的最大并行上传数。 设置为零以禁用限制。" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "允许的并行上传数" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "激活此选项可以使一些错误消息更加详细,这可能有助于您追踪特定的问题。" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "将内部信息日志记录到文件" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." -msgstr "" +msgstr "指定要写入由选项 --{0} 指定的文件中的日志信息量。" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "日志信息级别" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "使用选项 --{0} 和 --{1} 来替代。" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "如果检测到目标文件夹缺失, Duplicati 将自动创建它。激活该选项会禁止自动创建文件夹。" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3205,12 +3818,12 @@ msgstr "" "使用该选项可以从快照中排除有错误的写入者。这等同于 vshadow.exe 工具的 -wx 参数,除了它只接受写入者类的 " "GUID,而不支持组件名称或实例的 GUID。多个 GUID 可以用半角逗号分隔,也支持大多数的 GUID 形式,包括有无花括号。" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "需要排除的 VSS 写入者的 GUID 列表 (仅 Windows)" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3227,11 +3840,22 @@ msgstr "" "Duplicati 尝试使用 USN,而尝试失败时会在日志中产生警告信息。设置为 \"必须\" 时,Duplicati会在使用 USN " "失败后停止备份。此特性仅支持 Windows,且需要管理员权限。" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" +msgstr "控制 NTFS USN 使用与否" + +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3246,30 +3870,31 @@ msgstr "" "在匹配时间戳时,Duplicati会稍微调整时间,以确保小的时间差异不会导致意外的更新。如果选项 --{0} " "设置为保留一周的备份,并且每周都在相同的时间进行备份,时钟可能会稍微漂移,以至于整整一周刚刚过去,导致Duplicati比预期更早地删除较旧的备份。为了避免这种情况,Duplicati引入了1%的容忍度(最多1小时)。使用此选项可以禁用容忍度,并使用严格的时间检查。" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "使用此选项通过列出内容来验证上传。" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "通过列出内容校验上传文件" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." -msgstr "Duplicati将在扫描磁盘并生成卷的同时上传文件,这通常会使备份更快。使用此选项关闭该行为,以便Duplicati等待每个卷完成。" +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." +msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "同步上传文件" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3278,22 +3903,22 @@ msgid "" msgstr "" "Duplicati将尝试在单个连接上执行多个操作,因为这样可以避免重复的登录尝试,从而加快进程。使用此选项确保每个操作都在单独的连接上执行。" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "禁用重用连接" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "当某个错误发生,Duplicati 会静默地重试,而只在多次重试后报错。启用该选项将在每次重试时报错。" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "重试时显示错误信息" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " @@ -3301,11 +3926,11 @@ msgid "" msgstr "" "如果没有文件更改,Duplicati将不会上传备份集。如果备份数据用于验证备份是否已执行,此选项将使Duplicati即使备份集为空也上传备份集。" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "上传空的备份文件" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " @@ -3313,11 +3938,11 @@ msgid "" msgstr "" "设置后端使用的存储量限制(由此备份使用)。这是除了完整后端配额(,如果有的话)之外的额外限制。注意:备份将继续进行,超过配额。这只产生警告和错误消息。" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "限制存储使用" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3325,23 +3950,24 @@ msgid "" "size. If the backend does not report the quota information, this value will " "be ignored." msgstr "" +"设置一个阈值,当接近超过后端配额时发出警告。它以百分比给出,如果可用配额量少于总备份大小的这个百分比,就会生成警告。如果后端不报告配额信息,这个值将被忽略。" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "低存储配额报警的阈值" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "禁用后端报告的配额。仍然可以使用选项 --{0} 来设置手动配额。" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "禁用后端配额" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3354,11 +3980,11 @@ msgid "" msgstr "" "使用此选项以不同方式处理符号链接。\"{0}\"选项将简单地记录符号链接及其名称和目标,恢复时会将符号链接作为链接重新创建。使用\"{1}\"选项忽略所有符号链接,不存储有关它们的任何信息。\"{2}\"选项将导致符号链接的目标作为普通文件备份和恢复,并使用符号链接的名称。Duplicati的早期版本不支持此选项,其行为就好像指定了\"{2}\"一样。" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "符号链接处理方式" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3370,11 +3996,11 @@ msgstr "" "该选项用来选择对于符号链接的不同处理方式 (仅在 Linux/OSX 上生效)。选项 \"{0}\" 记录每个硬链接的ID以避免多次保存路径。选项 " "\"{1}\" 将忽略硬链接信息,并将每个硬链接作为不同的路径。选项 \"{2}\" 将忽略所有多余一个链接的硬链接。" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "硬链接处理方式" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3382,11 +4008,11 @@ msgid "" "are: {0}." msgstr "使用此选项排除具有某些属性的文件。使用逗号分隔的属性名称列表来指定多个属性。可能的值有:{0}。" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "根据属性排除文件" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3396,41 +4022,51 @@ msgstr "" "激活该选项会把 VSS 快照映射到一个磁盘 (类似于 SUBST,使用 Win32 " "DefineDosDevice)。这将创建用于访问快照内容的临时磁盘,可以加快 Windows XP 上的文件访问。" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "映射快照至磁盘 (仅 Windows)" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "附加到此备份的显示名称。这可以在发送邮件或运行脚本时用来识别备份。" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "备份名称" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "此备份的唯一标识。这可以在发送邮件或运行脚本时用来识别备份。" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "备份ID" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "运行备份的机器的唯一标识。这可以在发送邮件或运行脚本时用来识别机器。" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "机器ID" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3443,11 +4079,11 @@ msgid "" msgstr "" "使用此选项指向一个文本文件,其中每行包含一个文件扩展名,表示不可压缩的文件。具有在文件中找到的扩展名的文件将不会被压缩,而是简单地存储在存档中。文件格式忽略不以句点(.)开头的任何行,并认为空格表示扩展名的结束。提供一个默认文件,也作为示例。默认文件放置在{0}中。" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "管理不被压缩的文件扩展名" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3456,71 +4092,63 @@ msgid "" msgstr "" "块大小决定了文件的分片的方式。这个值过大会导致文件改动的额外开销更多,这个值过小会导致存储文件列表的额外开销更多。请注意,这个值在创建远程文件后不能再更改。" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "哈希时的文件块大小" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "使用此选项将扫描限制为仅已知更改的文件。这通常只与跟踪文件更改的文件系统监视器结合时激活。" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "已知更改文件的列表" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "包含远程文件数据库本地缓存的文件路径。" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "本地状态数据库的路径" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "使用此选项提供已删除文件的列表。除非同时设置了 --{0} 选项,否则此选项将被忽略。" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "已删除文件的列表" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "使用此选项通过不在内存中保留路径和修改时间戳来减少内存占用。" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "通过禁用内存内查询减少内存占用" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "使用此选项以提高速度,但需要额外的内存使用。" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "在内存中缓存块数据" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "如果设置了此选项,在启动时不会将本地数据库与远程文件列表进行比较。此选项的预期用途是在文件列表损坏或不可用的情况下正确工作。" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "不在启动时查询后端" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3531,11 +4159,11 @@ msgstr "" "索引文件用来在没有本地数据库时减少 dblock " "文件的下载。索引文件中记录的信息越多,没有数据库时的操作越快。代价是越大的索引文件占用越多的远程空间,而且可能永远用不到。" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3544,43 +4172,43 @@ msgid "" msgstr "" "随着文件的更改,一部分远程数据可能不再需要。该选项控制在回收再利用前,远程存储能容纳多少无用数据。这个值是一百分比,用于每一个卷和所有存储。" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "最大无用空间百分比" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "使用此选项尝试不同的设置并观察结果,而不实际更改文件。" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "这是一个非常高级的选项!使用此选项选择具有更小或更大哈希大小的块哈希算法,用于改变性能或存储空间。" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "用于文件块的哈希算法" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "这是一个非常高级的选项!使用此选项选择具有更小或更大哈希大小的文件哈希算法,用于改变性能或存储空间。" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "用于文件的哈希算法" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3588,11 +4216,11 @@ msgid "" "running the compact command." msgstr "如果在备份时检测到大量的小文件,或者在删除备份后发现无用的空间,远程数据将被压实。使用该选项来禁用这种自动压实,而仅在执行压实命令时压缩。" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "禁用自动压实" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3601,62 +4229,62 @@ msgid "" msgstr "" "Duplicati 使用该阈值评估卷的大小是否需要压实,使用一个小的公差值,默认为卷大小的 20%。这确保那些有一些无用空间的大的卷不需要被下载和修改。" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "卷大小阈值" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "为了避免远程存储中填满小文件,这个值可以强制聚合小文件。小文件总会在它们可以填满整个卷时合并。" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "小卷的最大个数" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "启用该选项可以在本机其它文件中查找存在的文件块。这是一个相当慢的操作,但能减少需要下载的数据量。" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "恢复时使用本地文件数据" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "在列出内容或恢复文件时,可以跳过本地数据库。这通常会慢一些,但可以用来验证远程存储的实际内容。" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" -msgstr "" +msgstr "禁用本地数据库" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "使用此选项设置要保留的版本数量。设置-1以保留所有版本。" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "保留指定版本数" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "使用该选项设置保留备份的时间间隔" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "保留指定时间间隔内的所有版本" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3670,49 +4298,49 @@ msgstr "" "该选项可以通过删除大多数旧备份,从而减少随着备份增长的版本数。要求的格式为逗号分隔的列表,其中每项都是分号分隔的时间范围和时间间隔。例如,\"7D:0s,3M:1D,10Y:2M\"" " 意味着保留7天中所有备份,保留3个月中每天一份,保留10年中每两个月一份,清理所有早于此期限的备份。该选项也支持使用 \"U\" 代表永久保留。" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "删除旧的中间备份以减少版本数" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "使用该选项在部分源数据丢失的情况下继续操作" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "忽略丢失的源元素" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "使用此选项在恢复时覆盖目标文件。如果未设置此选项,文件将在恢复时附加时间戳和数字。" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "恢复时覆盖文件" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "使用该选项来增加运行时的输出信息。一般来说,该选项将每处理一个文件,打印一行信息。" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "输出更多进度信息" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "使用该选项来增加的输出的操作结果信息,包括所有的文件名。" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "输出完整结果" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3720,11 +4348,11 @@ msgid "" "files." msgstr "使用该选项在改变远程存储后上传校验文件。此文件没有加密,且包含所有远程文件的大小和 SHA256 哈希值,用来校验文件的完整性。" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "决定是否上传校验文件" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3736,11 +4364,11 @@ msgstr "" "备份完成后,将从远程后端选择一些(dblock、dindex、dlist)文件进行验证。使用此选项来更改要验证的数量。如果同时提供了 --{0} " "选项,则测试的样本数量是两个选项所暗示的最大值。如果此值设置为0或设置了 --{1} 选项,则不会验证任何远程文件。" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "备份后的校验样本数" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3752,11 +4380,11 @@ msgstr "" "备份完成后,将从远程后端选择一些(dblock、dindex、dlist)文件进行验证。使用此选项指定要测试的文件的百分比(介于0到100之间)。如果同时提供了" " --{0} 选项,则测试的样本数量是两个选项所暗示的最大值。如果提供了 --{1} 选项,则不会验证任何远程文件。" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "备份后的校验样本百分比" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3770,111 +4398,111 @@ msgstr "" "备份完成后,将从远程后端选择一些(dblock、dindex、dlist)文件进行验证。使用此选项开启完整验证,这将解密文件并检查每个卷的内部,而不仅仅是验证外部哈希。如果设置了" " --{0} 选项,则不会验证任何远程文件。当直接执行验证时,此选项会自动设置。ListAndIndexes类似于True,但只处理dlist和索引卷。" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "使用这个大小来控制在处理之前从文件中读取多少字节。" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "文件读取缓冲大小" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "使用此选项允许更改密码。请注意,此选项不允许用于备份或修复操作。" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "允许更改备份密码" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "使用此选项仅列出文件集,避免遍历文件名和其他元数据以免减慢过程。" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "仅列出文件集" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "使用该选项来禁用保存元数据,例如文件的时间戳。不保存元数据可以加快备份和恢复的速度,但是对文件大小影响不大。" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" -msgstr "" +msgstr "不保存元数据" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "默认情况下,权限不会被还原,因为这可能影响您访问恢复出的文件。使用该选项可以还原权限。" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "恢复文件权限" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "恢复文件后,Duplicati 将对比哈希值验证恢复是否成功。使用该选项来禁用此检查来跳过等待校验的时间。" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "跳过恢复文件校验" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "Duplicati 将尝试使用源文件中的数据来最小化需要下载的数据量。使用该选项来跳过此项优化,只使用远程数据。" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "不使用本地数据" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "现在默认不使用本地块进行恢复。要选择使用本地块,请设置 --{0} 选项。" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "使用此选项允许Duplicati在执行恢复时使用磁盘上找到的块,而不仅仅使用远程存储中的文件。" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "使用现有数据进行恢复" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "使用该选项可在将数据恢复至文件中时,通过检查卷中保存的文件块哈希值增加校验。" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "检查文件块哈希值" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3883,11 +4511,11 @@ msgid "" msgstr "" "使用该选项将构建一个只包含路径信息的本地可搜索的数据库。这可以快速构建数据库来定位文件,而不需要重构所有信息。产生的数据库可以搜索,但不能用来恢复数据。" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "修复路径数据库" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3897,74 +4525,74 @@ msgstr "" "默认情况下,将使用您的系统区域设置和文化设置。在某些情况下,您可能更愿意使用另一个区域设置来运行,例如以获得另一种语言的消息。使用此选项来设置区域设置。提供一个空字符串以选择\"Invariant" " Culture\"。" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "指定语言区域设置" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "默认情况下,日期以日历格式显示,即 \"今天\" 或 \"上周四\"。通过设置该选项,仅显示实际日期,例如 \"2018 年 11 月 12 日 08:01\"。" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "使用此选项禁用上传和下载的多线程处理。根据您运行的硬件以及后端的传输速率,这可以显著加快后端操作的速度。" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "使用单线程处理与后端的文件通信" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "使用该选项可设置使用的最大线程数。将此值设置为零或更小将动态平衡活动线程的数量以适应硬件。" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "限制并发线程数" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "使用该选项可设置执行数据哈希的进程数。" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "指定并发哈希进程的数量" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "使用该选项可设置执行输出数据压缩的进程数" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "指定并发压缩进程数" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "如果 Duplicati 检测到前一备份没有完成,它将生成一份文件列表,其中包括上一次完成的备份和在未完成备份会话中已上传的内容。" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" -msgstr "" +msgstr "禁用虚拟文件列表" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3973,11 +4601,11 @@ msgid "" msgstr "" "此选项指示Duplicati在决定是否扫描文件以查找更改时不查看元数据或文件大小。如果您有大量文件,并且注意到对未修改的文件进行扫描需要很长时间,请使用此选项。" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3986,11 +4614,11 @@ msgid "" msgstr "" "当将备份的一个子集恢复到一个新文件夹时,会使用最短的路径来避免生成带有空文件夹的深层路径。使用此选项跳过这种压缩,以便保留整个原始文件夹结构,包括上层的空文件夹。" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3999,11 +4627,11 @@ msgid "" msgstr "" "默认情况下,最后一个文件集无法被移除。这是一个安全措施,以确保不会因配置错误而删除所有远程数据。使用此选项禁用该保护,以便可以删除所有文件集。" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "允许删除所有文件集" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -4016,11 +4644,11 @@ msgstr "" "操作清理。长远来看,此操作会节省磁盘空间,但它需要临时创建一份包含所有有效条目的数据库副本。设为 true 将允许 Duplicati 自动执行 " "VACUUM操作。" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "允许自动重建本地数据库以节省空间" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -4029,22 +4657,22 @@ msgid "" msgstr "" "启用此标志后,将禁用计算源文件大小的扫描器,而是从数据库中读取报告的大小。使用此选项可以通过减少磁盘访问来加快备份速度,但会提供一个不太准确的进度指示器。" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "禁用预读扫描器" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "当备份的文件量很大时,验证可能占用大部分备份时间。如果禁用检查,请确保运行常规检查命令以确保一切按预期工作。" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "禁用文件列表一致性检查" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -4052,15 +4680,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "电量不足时禁用备份" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "日志文件信息等级" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -4072,42 +4700,42 @@ msgstr "" "该选项接受删除或包含消息的过滤器,无论其日志级别。通过使用 {0} 分隔来支持多个过滤器。过滤器与日志标签匹配并假定包含,除非它们以 \"-\" " "开头。方括号内支持正则表达式。如:\"+Path*{0}+*Mail*{0}-[.*DNS]\"" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "控制台信息级别" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "此选项指示操作系统将当前进程设置为使用最低的IO优先级,这可能会使操作运行得更慢,但同时进行的其他操作受到的干扰会更少。" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" -msgstr "" +msgstr "将进程设置为使用低IO优先级" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "使用此选项从备份中移除所有空文件夹。" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" -msgstr "" +msgstr "排除空文件夹" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -4116,11 +4744,11 @@ msgid "" msgstr "" "使用该选项可设置文件名或文件名列表,以指示排除包含它的文件夹。常见的用法是将文件命名为 \".nobackup\",并将此文件放入不应备份的文件夹中。" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "排除文件夹中的文件名列表" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4129,11 +4757,11 @@ msgid "" msgstr "" "如果应用了符号链接的元数据,通常意味着改变符号链接的目标,而不是符号链接本身。因此,元数据不会应用于符号链接,但可以使用此选项来覆盖这一点,使得元数据也应用于符号链接。" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "将元数据应用于符号链接" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4142,11 +4770,11 @@ msgid "" msgstr "" "在单元测试模式下运行时,不会应用任何自动修复,这假设输入数据总是完美无缺的。此选项不适用于日常备份,但出于测试目的需要使用,以揭示潜在问题。" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "激活单元测试默默" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4157,11 +4785,11 @@ msgstr "" "为了提高备份的性能,默认情况下不会记录频繁的数据库查询。启用该选项以记录所有数据库查询,并记住设置 --{0}={2} 或 --{1}={2} " "以报告额外的日志数据" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4171,11 +4799,11 @@ msgid "" msgstr "" "如果目标位置缺少dblock文件,您可以尝试使用本地源数据重建它们。然而,由于本地数据可能已更改,可能无法检索到所有必需的数据,并且该过程可能会很慢。使用此选项尝试重建缺失的dblock文件。" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "当缺失时重建dblock文件" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4184,11 +4812,11 @@ msgid "" msgstr "" "在上次压缩之后必须经过的最短时间,之后才会在备份作业结束时自动触发另一次压缩。自动压缩可能是一个长时间运行的过程,并且可能不希望在每次备份之后都运行。" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "自动压缩之间的最短时间" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4197,57 +4825,206 @@ msgid "" msgstr "" "在上次vacuum处理之后必须经过的最短时间,之后才会在备份作业结束时自动触发另一次vacuum处理。自动vacuum处理可能是一个长时间运行的过程,并且可能不希望在每次备份之后都运行。" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "自动vacuum处理之间的最短时间" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "加密库不支持哈希算法 {0} 的重用变换" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "加密库不支持哈希算法 {0}" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "不能更改已有备份的加密密码" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "创建快照失败:{0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "删除文件 {0} 失败,正在测试文件是否存在" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "已从删除不存在文件 {0} 的尝试中恢复" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "从删除文件错误中 {0} 恢复失败" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "由于文件未找到,对 {0} 的删除操作失败,正在列出内容" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "清单表示文件 {0} 已正确删除" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "清单确认文件 {0} 没有被删除" @@ -4304,30 +5081,6 @@ msgstr "该模块提供一系列可以改变 http 请求方式的属性" msgid "Configure http requests" msgstr "配置 http 请求" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "使用此选项接受任何服务器证书,无论它可能存在什么错误。请尽可能使用 --{0} 代替。" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "接受任意服务器证书" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" -"如果您的服务器证书被报告为无效(例如,使用自签名证书),您可以提供证书哈希(SHA1)以无论如何批准它。哈希值必须以十六进制格式输入,不能有空格或冒号。您可以输入多个哈希,用逗号分隔。" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "视情况接受已知的 SSL 证书" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4383,7 +5136,7 @@ msgstr "此选项更改任何HTTP请求的默认超时时间,该时间涵盖 #: Library/Modules/Builtin/Strings.cs:55 msgid "Set the default operation timeout" -msgstr "" +msgstr "设置默认的操作超时时间" #: Library/Modules/Builtin/Strings.cs:56 msgid "" @@ -4405,7 +5158,7 @@ msgstr "该选项设置 HTTP 缓冲,设为 \"{0}\" 可能导致内存泄漏, #: Library/Modules/Builtin/Strings.cs:59 msgid "Set HTTP buffering" -msgstr "" +msgstr "设置 HTTP 缓冲" #: Library/Modules/Builtin/Strings.cs:63 msgid "" @@ -4417,131 +5170,143 @@ msgstr "该内置模块解析源参数来备份 Hyper-V 虚拟机" msgid "Configure Hyper-V module" msgstr "配置 Hyper-V 模块" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "该内置模块解析源参数来备份 Microsoft SQL 服务器的数据库" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "配置 Microsoft SQL 服务器模块" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" -msgstr "" +msgstr "在开始某一操作前执行一段脚本,并在完成时再次运行" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "运行脚本" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." -msgstr "" +msgstr "在执行某一操作结束后执行一段脚本。此脚本将会接收到写入标准输出 stdout 的操作结果。" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "结束时运行脚本" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "脚本 \"{0}\" 返回退出码 {1}" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "脚本 \"{0}\" 返回退出代码 {1}{2}" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." -msgstr "" +msgstr "在开始执行某一操作前运行一段脚本。在脚本完成或超时之前,操作将不会开始。如果脚本超时或返回了非0错误码,操作将会中止。" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "开始时运行必要脚本" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "使用此选项选择结果的输出格式。可用格式: {0} " -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "运行脚本 \"{0}\" 出错:{1}" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "运行脚本 \"{0}\" 超时" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." -msgstr "" +msgstr "在开始执行某一操作前运行一段脚本。在脚本完成或超时之前,操作将不会开始。" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "开始时运行脚本" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "脚本 \"{0}\" 报错:{1}" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" -msgstr "" +msgstr "设置脚本超时时间" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "此选项启用脚本参数的使用。如果设置了此选项,脚本参数将被视为命令行字符串。使用单引号或双引号来分隔参数。" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "启用脚本参数" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "该模块可以在操作完成后发送邮件" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "发送邮件" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." -msgstr "" +msgstr "通过MX查找无法找到目标邮件服务器。请使用选项 --{0} 指定要使用的smtp服务器。" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4563,21 +5328,21 @@ msgstr "" "\n" "所有命令行选项也会在%value%中报告,例如:%volsize%。任何未知/未设置的值将被移除" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "邮件正文" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "使用此选项设置用于与SMTP服务器进行身份验证的密码(如果需要)。" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP 密码" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4589,11 +5354,11 @@ msgstr "" "\n" "Peter Sample , John Sample , admin@example.com" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "收件人" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4609,11 +5374,11 @@ msgstr "" "Mail Sender \n" "Mail Sender " -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "发件人" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" @@ -4622,14 +5387,14 @@ msgstr "" "您可以指定以下选项之一:\"{0}\", \"{1}\", \"{2}\", \"{3}\"。\n" "您可以使用逗号分隔符提供多个选项,例如\"{0},{1}\"。特殊值\"{4}\"是\"{0},{1},{2},{3}\"的简写,它将导致所有备份操作发送电子邮件。" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "要发送的消息" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4641,68 +5406,68 @@ msgstr "" "\n" "要启用SMTP over SSL,请使用格式:smtps://example.com。要启用SMTP STARTTLS,请使用格式:smtp://example.com:25/?starttls=when-available 或 smtp://example.com:25/?starttls=always。如果没有指定端口,则非SSL连接使用端口25,SSL连接使用端口465。要强制不使用STARTTLS,请使用格式:smtp://example.com:25/?starttls=never。" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP 地址" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "该设置指定邮件标题。如 --{0} 的描述的值将被替换。" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "邮件主题" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "使用此选项设置用于与SMTP服务器进行身份验证的用户名(如果需要)。" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP 用户名" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "完整的 SMTP 通信:{0}" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "通过服务器 {0} 发送邮件消息 {1} 失败,重试次数:{2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "通过服务器 {0} 发送邮件成功" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "该模块可以通过 XMPP 消息发送状态报告" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP 报告模块" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "使用此选项设置应收到消息的用户。您可以指定用逗号分隔的多个用户。" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP 接收邮箱" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4724,35 +5489,35 @@ msgstr "" "\n" "所有命令行选项也会在%value%中报告,例如:%volsize%。任何未知/未设置的值将被移除。" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "消息模板" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "使用此选项为将要发送消息的账户设置用户名,包括主机名,例如:\"account@jabber.org/Home\"" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP 用户名" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "使用此选项为将要发送消息的账户设置密码。" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP 密码" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4761,109 +5526,109 @@ msgstr "" "您可以指定 \"{0}\", \"{1}\", \"{2}\", \"{3}\" 其中之一\n" "您也可以使用逗号分隔指定多个选项,例如 \"{0},{1}\"。特殊值 \"{4}\" 是 \"{0},{1},{2},{3}\" 的简写,将使所有备份操作都发送消息。" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "默认情况下,只有在备份操作后才会发送消息。使用此选项可以为所有操作发送消息。" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "为所有操作发送消息" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "登陆 jabber 服务器超时" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" -msgstr "" +msgstr "该模块提供了通过Telegram消息发送状态报告的支持" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." +msgstr "使用此选项设置频道ID。" + +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." -msgstr "" +msgstr "使用此选项为将要发送消息的机器人设置API密钥。" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" -msgstr "" +msgstr "发送到Telegram服务器时发生超时" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "该模块可以通过 HTTP 消息发送状态报告" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP 报告模块" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "使用此选项设置HTTP报告URL。" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "HTTP报告URL" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "使用此选项设置发送消息时使用的参数名称。" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "作为消息发送的参数的名字" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "使用此选项设置要添加到HTTP消息中的额外参数。例如:\"parameter1=value1¶meter2=value2\"" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "添加到 http 消息的额外参数" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "使用此选项更改用于提交报告的默认HTTP动作。" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" -msgstr "" +msgstr "设置 HTTP 动作" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4872,11 +5637,11 @@ msgid "" msgstr "" "使用此选项设置用于发送表单编码数据的HTTP报告URL。此选项接受多个URL,用分号分隔。所有URL将接收相同的数据。请注意,此选项会忽略格式和动作设置。" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "用于发送表单数据的HTTP报告URLs" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " @@ -4884,41 +5649,41 @@ msgid "" msgstr "" "使用此选项设置用于发送JSON数据的HTTP报告URL。此选项接受多个URL,用分号分隔。所有URL将接收相同的数据。请注意,此选项会忽略格式和动作设置。" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "用于发送JSON数据的HTTP报告URLs" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "发送消息失败:{0}" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "使用此选项设置报告中包含的消息的日志级别。" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" -msgstr "" +msgstr "定义消息的日志级别" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "使用此选项设置一个过滤表达式,定义报告中包含哪些选项。" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "过滤消息日志" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "指定报告中的日志最大行数。0 或负值代表不限制。" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4927,16 +5692,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "不支持的格式: {0}" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "无效的大小值:{0}" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "SSL 证书验证器调用顺序不正确。" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4947,78 +5712,78 @@ msgstr "" "服务器证书出现错误 {0} ,哈希值为 {1}{2}。如果您信任此证书,请使用命令行选项 --{3}={1} 来接受服务器证书。 {2} " "您也可以尝试将服务器证书导入操作系统的信任池中。" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "验证服务器证书哈希值失败,错误信息:{0},SSL 错误名称:{1}" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "临时文件夹不存在:{0}" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "解析分段失败:{0},无效的整数" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "无效的指示符:{0}" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "未解析的数据:{0}" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "地址无效:{0}" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "地址中缺少主机名:{0}" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} Bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "字符串 \"{0}\" 不能被解析成日期" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "不能同时读写同一个流" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5247,11 +6012,11 @@ msgstr "禁用控制台输出" msgid "This link may provide additional information: {0}" msgstr "额外信息请参见此链接:{0}" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "启用自动更新" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-zh_HK.mo b/Localizations/duplicati/localization-zh_HK.mo index 1cca7e254..d8aff94df 100644 Binary files a/Localizations/duplicati/localization-zh_HK.mo and b/Localizations/duplicati/localization-zh_HK.mo differ diff --git a/Localizations/duplicati/localization-zh_HK.po b/Localizations/duplicati/localization-zh_HK.po index 37e62c7fc..b1af3670a 100644 --- a/Localizations/duplicati/localization-zh_HK.po +++ b/Localizations/duplicati/localization-zh_HK.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Aticaler, 2024\n" "Language-Team: Chinese (Hong Kong) (https://app.transifex.com/duplicati/teams/67655/zh_HK/)\n" @@ -44,8 +44,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -147,78 +147,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 -msgid "aliases" +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" msgstr "" -#: Library/Interface/Strings.cs:25 -msgid "default value" +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" msgstr "" #: Library/Interface/Strings.cs:26 -msgid "[DEPRECATED]" +msgid "aliases" msgstr "" #: Library/Interface/Strings.cs:27 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:28 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "Boolean" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "Integer" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "路徑" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "Size" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "String" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "資料夾已存在,無法建立" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "已取消" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -335,9 +351,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -354,9 +369,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -367,9 +381,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -449,41 +462,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." -msgstr "" +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" +msgstr "備用 FTP" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -492,47 +495,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "停用上傳驗證" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "停用上傳驗證" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -828,6 +979,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -860,110 +1016,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "備用 FTP" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "刪除檔案錯誤:{0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "讀取檔案錯誤:{0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "寫入檔案錯誤:{0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1438,7 +1490,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1446,22 +1502,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1469,28 +1525,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1502,42 +1558,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1554,7 +1610,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1565,7 +1621,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1810,43 +1866,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1962,6 +2020,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1994,6 +2057,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2019,6 +2088,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2139,6 +2272,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2229,7 +2763,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2252,55 +2786,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "清理舊記錄資料" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2308,11 +2853,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2320,86 +2865,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "臨時儲存資料夾" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2407,34 +2991,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2449,32 +3069,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2482,68 +3102,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2928,48 +3515,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "Thread 優先度" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2977,11 +3582,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2989,27 +3594,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3024,88 +3629,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "記錄資訊等級" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3114,12 +3721,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3132,11 +3739,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3149,30 +3767,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3180,44 +3799,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "不要重用現有連線" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3226,22 +3845,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3253,11 +3872,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "符號連結 (Symlink)處理方式" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3267,11 +3886,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "硬式連結 (Hardlink)處理方式" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3279,11 +3898,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "排除檔案(根據屬性)" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3291,41 +3910,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "備份名稱" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3337,11 +3966,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3349,71 +3978,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3422,11 +4043,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3434,43 +4055,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3478,11 +4099,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "停用自動壓縮" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3490,62 +4111,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "保留的版本數量" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3557,49 +4178,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "輸出完整結果" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3607,11 +4228,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3621,11 +4242,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3635,11 +4256,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3651,111 +4272,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "不使用本地資料" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3763,11 +4384,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3775,74 +4396,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3850,11 +4471,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3862,11 +4483,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3874,11 +4495,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3888,11 +4509,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3900,22 +4521,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3923,15 +4544,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3941,42 +4562,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3984,11 +4605,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -3996,11 +4617,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4008,11 +4629,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4021,11 +4642,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4034,11 +4655,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4046,11 +4667,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4058,57 +4679,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4162,29 +4932,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4270,131 +5017,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4407,21 +5166,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4429,11 +5188,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4443,25 +5202,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4469,68 +5228,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4543,144 +5302,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4688,52 +5447,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4742,16 +5501,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4760,78 +5519,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "{0:N} MB" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5046,11 +5805,11 @@ msgstr "停用Console輸出" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "切換自動更新" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization-zh_TW.mo b/Localizations/duplicati/localization-zh_TW.mo index fa8176ff7..934ad9ee3 100644 Binary files a/Localizations/duplicati/localization-zh_TW.mo and b/Localizations/duplicati/localization-zh_TW.mo differ diff --git a/Localizations/duplicati/localization-zh_TW.po b/Localizations/duplicati/localization-zh_TW.po index b3891ad20..4fd66ab50 100644 --- a/Localizations/duplicati/localization-zh_TW.po +++ b/Localizations/duplicati/localization-zh_TW.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-30 15:22+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: 2016-10-04 18:53+0000\n" "Last-Translator: Jason Cheng , 2024\n" "Language-Team: Chinese (Taiwan) (https://app.transifex.com/duplicati/teams/67655/zh_TW/)\n" @@ -45,8 +45,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -148,78 +148,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" +msgstr "" + +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" +msgstr "" + +#: Library/Interface/Strings.cs:26 msgid "aliases" msgstr "別名" -#: Library/Interface/Strings.cs:25 +#: Library/Interface/Strings.cs:27 msgid "default value" msgstr "預設值" -#: Library/Interface/Strings.cs:26 +#: Library/Interface/Strings.cs:28 msgid "[DEPRECATED]" msgstr "" -#: Library/Interface/Strings.cs:27 +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "值" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "布林" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "列舉" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "旗標" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "整數" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "路徑" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "大小" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "字串" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "時間刻度" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "未知" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "已取消" -#: Library/Interface/Strings.cs:44 +#: Library/Interface/Strings.cs:48 msgid "" "Encryption key used to encrypt target settings does not match current key." msgstr "" +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -339,9 +355,8 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 @@ -358,9 +373,8 @@ msgstr "" msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -371,9 +385,8 @@ msgid "" " environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 @@ -453,41 +466,31 @@ msgstr "" msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and " "\"ftp://username:password@hostname/folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and " +"\"aftp://username:password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "FTP" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggle the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works" -" better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -496,47 +499,195 @@ msgid "" " environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging " +"purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" -msgstr "停用上傳驗證" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" +msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" -msgstr "資料夾 {0} 找不到,訊息: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" +msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "資料夾 {0} 找不到,訊息: {1}" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "停用上傳驗證" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "設定 FTP 資料連線類型" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected" +" option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "設定 FTP 加密模式" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "設定 FTP 加密啟用時採用的 SSL 策略" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works" +" better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting" +" folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -832,6 +983,11 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" @@ -864,110 +1020,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging " -"purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "資料夾 {0} 找不到,訊息: {1}" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "設定 FTP 資料連線類型" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected" -" option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "設定 FTP 加密模式" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "設定 FTP 加密啟用時採用的 SSL 策略" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "刪除檔案錯誤: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "讀取檔案錯誤: {0}" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "寫入檔案錯誤: {0}" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1442,7 +1494,11 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:42 #, csharp-format msgid "" "Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the" @@ -1450,22 +1506,22 @@ msgid "" "terms in {0} ({1}) and {2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:43 +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" "Store files in a Microsoft SharePoint site via the Microsoft Graph API. " @@ -1473,28 +1529,28 @@ msgid "" "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" "Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " @@ -1506,42 +1562,42 @@ msgid "" "that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1558,7 +1614,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1569,7 +1625,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1814,43 +1870,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is " +"\"e2://bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account." " This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of" " a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1966,6 +2024,11 @@ msgstr "" msgid "Dropbox" msgstr "Dropbox" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1998,6 +2061,12 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 +#: Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "資料夾 {0} 找不到,訊息: {1}" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2023,6 +2092,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "允許所有伺服器憑證" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "允許已知的 SSL 憑證" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2143,6 +2276,407 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all" +" requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not" +" supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=...&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password " +"manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + #: Library/RestAPI/Strings.cs:9 msgid "Another instance is running, and was notified" msgstr "" @@ -2165,13 +2699,11 @@ msgid "" "Supported commandline arguments:\n" "\n" msgstr "" -"支援的命令列參數:\n" -"\n" #: Library/RestAPI/Strings.cs:16 #, csharp-format msgid "--{0}: {1}" -msgstr "--{0}: {1}" +msgstr "" #: Library/RestAPI/Strings.cs:17 #, csharp-format @@ -2237,7 +2769,7 @@ msgid "" "Error message: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:51 +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 msgid "Disable database encryption" msgstr "" @@ -2260,55 +2792,66 @@ msgstr "" #: Library/RestAPI/Strings.cs:33 msgid "" -"The certificate and key file in PKCS #12 format the webserver use for SSL. " -"Only RSA/DSA keys are supported." +"Deactivates the use of HTTPS even if a certificate is stored in the database" +" or provided on the commandline." msgstr "" #: Library/RestAPI/Strings.cs:34 -msgid "The password for decryption of certificate PKCS #12 file." -msgstr "PKCS#12 憑帳檔的解密用密碼。" +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" #: Library/RestAPI/Strings.cs:35 msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" "The interface the webserver listens on. The special values \"*\" and \"any\"" " means any interface. The special value \"loopback\" means the loopback " "adapter." msgstr "" -#: Library/RestAPI/Strings.cs:36 +#: Library/RestAPI/Strings.cs:38 msgid "" "The password required to access the webserver. This option is saved so you " "do not need to set it on each run. Setting an empty value disables the " "password." msgstr "" -#: Library/RestAPI/Strings.cs:37 +#: Library/RestAPI/Strings.cs:39 msgid "" "The hostnames that are accepted, separated with semicolons. If any of the " "hostnames are \"*\", all hostnames are allowed and the hostname checking is " "disabled." msgstr "" -#: Library/RestAPI/Strings.cs:38 +#: Library/RestAPI/Strings.cs:40 msgid "" "When running as a server, the service daemon must verify that the process is" " responding. If this option is enabled, the server reads stdin and writes a " "reply to each line read." msgstr "" -#: Library/RestAPI/Strings.cs:39 +#: Library/RestAPI/Strings.cs:41 msgid "Enable the ping-pong responder" msgstr "" -#: Library/RestAPI/Strings.cs:40 Library/Main/Strings.cs:250 +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 msgid "Set the time after which log data will be purged from the database." msgstr "" -#: Library/RestAPI/Strings.cs:41 Library/Main/Strings.cs:251 +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 msgid "Clean up old log data" msgstr "清理舊的記錄資料" -#: Library/RestAPI/Strings.cs:42 +#: Library/RestAPI/Strings.cs:44 #, csharp-format msgid "" "Duplicati needs to store a small database with all settings. Use this option" @@ -2316,11 +2859,11 @@ msgid "" "the environment variable {0}." msgstr "" -#: Library/RestAPI/Strings.cs:43 +#: Library/RestAPI/Strings.cs:45 msgid "Set the folder where settings are stored" msgstr "" -#: Library/RestAPI/Strings.cs:44 +#: Library/RestAPI/Strings.cs:46 #, csharp-format msgid "" "This option sets the encryption key used to scramble the local settings " @@ -2328,86 +2871,125 @@ msgid "" " the option --{1} to disable the database scrambling." msgstr "" -#: Library/RestAPI/Strings.cs:45 +#: Library/RestAPI/Strings.cs:47 msgid "Set the database encryption key" msgstr "" -#: Library/RestAPI/Strings.cs:46 Library/Main/Strings.cs:96 +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 msgid "" "Use this option to supply an alternative folder for temporary storage. By " "default the system default temporary folder is used. Note that also SQLite " "will put temporary files in this temporary folder." msgstr "" -#: Library/RestAPI/Strings.cs:47 Library/Main/Strings.cs:97 +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 msgid "Temporary storage folder" msgstr "暫存儲存資料夾" -#: Library/RestAPI/Strings.cs:48 +#: Library/RestAPI/Strings.cs:50 msgid "Reset the JWT configuration, invalidating any issued login tokens" msgstr "" -#: Library/RestAPI/Strings.cs:49 -msgid "Disable the visual captcha" -msgstr "" - -#: Library/RestAPI/Strings.cs:50 -msgid "Use this option to disable database encryption of sensitive fields" +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" msgstr "" #: Library/RestAPI/Strings.cs:52 -msgid "Log to the Windows event log" +msgid "Disable the visual captcha" msgstr "" #: Library/RestAPI/Strings.cs:53 -msgid "Use this option to log to the Windows event log" +msgid "Disable the web interface and only allow API access" msgstr "" #: Library/RestAPI/Strings.cs:54 -msgid "Set the log level for the Windows event log" +msgid "Disable the use of signin tokens" msgstr "" #: Library/RestAPI/Strings.cs:55 -msgid "Use this option to set the log level for the Windows event log" +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." msgstr "" #: Library/RestAPI/Strings.cs:56 -#, csharp-format msgid "" -"The Windows event log source {0} was not found. The source must be " -"registered before the log can be written." +"The timezone to use for the webserver. The timezone must be a valid timezone" +" identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." msgstr "" #: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the" +" format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 msgid "The Windows event log is not supported on this platform" msgstr "" -#: Library/RestAPI/Strings.cs:58 +#: Library/RestAPI/Strings.cs:66 #, csharp-format msgid "Server has started and is listening on port {0}" msgstr "" -#: Library/RestAPI/Strings.cs:59 +#: Library/RestAPI/Strings.cs:67 #, csharp-format msgid "Use the following link to sign in: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:60 +#: Library/RestAPI/Strings.cs:68 #, csharp-format msgid "The server crashed: {0}" msgstr "" -#: Library/RestAPI/Strings.cs:61 +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 msgid "Require database encryption" msgstr "" -#: Library/RestAPI/Strings.cs:62 -msgid "" -"Use this option to require a custom provided key for database encryption of " -"sensitive fields and not rely on the serial number" -msgstr "" - -#: Library/RestAPI/Strings.cs:63 +#: Library/RestAPI/Strings.cs:73 #, csharp-format msgid "" "Database encryption key is required. Supply an encryption key via the " @@ -2415,34 +2997,70 @@ msgid "" "--{1}" msgstr "" -#: Library/RestAPI/Strings.cs:64 +#: Library/RestAPI/Strings.cs:74 #, csharp-format msgid "" -"The encryption key is blacklisted and cannot be used. The database has been " -"decrypted. Supply a new encryption key via the environment variable {0} or " +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable" +" {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " "disable database encryption with the option --{1}" msgstr "" -#: Library/RestAPI/Strings.cs:68 +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 #, csharp-format msgid "" "Unable to find a valid date, given the start date {0}, the repetition " "interval {1} and the allowed days {2}" msgstr "" -#: Library/RestAPI/Strings.cs:73 +#: Library/RestAPI/Strings.cs:88 #, csharp-format msgid "Server has started and is listening on {0}, port {1}" msgstr "" -#: Library/RestAPI/Strings.cs:74 -#, csharp-format +#: Library/RestAPI/Strings.cs:89 msgid "" -"Unable to create SSL certificate using provided parameters. Exception " -"detail: {0}" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" msgstr "" -#: Library/RestAPI/Strings.cs:75 +#: Library/RestAPI/Strings.cs:90 #, csharp-format msgid "Unable to open a socket for listening, tried ports: {0}" msgstr "" @@ -2457,32 +3075,32 @@ msgstr "無法載入組建 {0},錯誤訊息:{1}" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "載入處理程序類型 {0} 組建 {1} 失敗,錯誤訊息: {2}" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 +#: Library/Compression/Strings.cs:30 msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2490,68 +3108,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 +#: Library/Compression/Strings.cs:32 msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 +#: Library/Compression/Strings.cs:34 msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Set the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Set the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2936,48 +3521,66 @@ msgstr "" #: Library/Main/Strings.cs:98 msgid "" +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" +msgstr "" + +#: Library/Main/Strings.cs:99 +msgid "" "Select another thread priority for the process. Use this to set Duplicati to" " be more or less CPU intensive." msgstr "" -#: Library/Main/Strings.cs:99 +#: Library/Main/Strings.cs:100 msgid "Thread priority" msgstr "執行緒優先權" -#: Library/Main/Strings.cs:100 +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual " "file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 +#: Library/Main/Strings.cs:103 msgid "" "Use this option to disallow usage of the streaming interface, which means " "that transfer progress bars will not show, and bandwidth throttle settings " "will be ignored." msgstr "" -#: Library/Main/Strings.cs:103 +#: Library/Main/Strings.cs:104 msgid "Disable use of the streaming transfer method" msgstr "" -#: Library/Main/Strings.cs:104 +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read." " This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 +#: Library/Main/Strings.cs:108 msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2985,11 +3588,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a" " module to use for encryption. This is only applied when creating new " @@ -2997,27 +3600,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 +#: Library/Main/Strings.cs:114 msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 +#: Library/Main/Strings.cs:116 msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -3032,88 +3635,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 +#: Library/Main/Strings.cs:118 msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can" " be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 +#: Library/Main/Strings.cs:126 msgid "Enable debugging output" msgstr "" -#: Library/Main/Strings.cs:124 +#: Library/Main/Strings.cs:127 msgid "Log information to the file specified." msgstr "" -#: Library/Main/Strings.cs:125 +#: Library/Main/Strings.cs:128 msgid "Log internal information to a file" msgstr "記錄內部資訊到檔案" -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 #, csharp-format msgid "" "Specify the amount of log information to write into the file specified by " "the option --{0}." msgstr "" -#: Library/Main/Strings.cs:127 +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "記錄資訊等級" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 +#: Library/Main/Strings.cs:133 msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -3122,12 +3727,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -3140,11 +3745,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 +#: Library/Main/Strings.cs:137 msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -3157,30 +3773,31 @@ msgid "" " strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 +#: Library/Main/Strings.cs:141 msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -3188,44 +3805,44 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "當重新嘗試時顯示錯誤訊息" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" "Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " @@ -3234,22 +3851,22 @@ msgid "" "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -3261,11 +3878,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -3275,11 +3892,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -3287,11 +3904,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -3299,41 +3916,51 @@ msgid "" " file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify" " the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the" +" machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3345,11 +3972,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3357,71 +3984,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "已刪除檔案清單" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly" " in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when" " there is no local database present. The more information is recorded in the" @@ -3430,11 +4049,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 +#: Library/Main/Strings.cs:190 msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3442,43 +4061,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 +#: Library/Main/Strings.cs:194 msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space" " reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3486,11 +4105,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small" " tolerance value is used, by default 20 percent of the volume size. This " @@ -3498,62 +4117,62 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing" " blocks. This is a fairly slow operation but can limit the size of " "downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 +#: Library/Main/Strings.cs:209 msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3565,49 +4184,49 @@ msgid "" "interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " @@ -3615,11 +4234,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3629,11 +4248,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3643,11 +4262,11 @@ msgid "" "two options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3659,111 +4278,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 +#: Library/Main/Strings.cs:232 msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "檔案讀取緩衝區大小" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "允許變更密碼" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps." " Disabling metadata storage will speed up the backup and restore operations," " but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 +#: Library/Main/Strings.cs:242 msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check" " and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "略過已還原檔案檢查" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "不要使用本機資料" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read" " from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3771,11 +4390,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages" @@ -3783,74 +4402,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or" " \"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 +#: Library/Main/Strings.cs:262 msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That" " can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 +#: Library/Main/Strings.cs:272 msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3858,11 +4477,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 +#: Library/Main/Strings.cs:274 msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3870,11 +4489,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 +#: Library/Main/Strings.cs:276 msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3882,11 +4501,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3896,11 +4515,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3908,22 +4527,22 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" "Use this option to disable a scheduled backup if the system is detected to " "be running on battery power (manual or command line backups will still be " @@ -3931,15 +4550,15 @@ msgid "" "scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "在低電量時停用備份作業" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "記錄檔案資訊等級" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3949,42 +4568,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 +#: Library/Main/Strings.cs:291 msgid "Apply filters to the file log data" msgstr "" -#: Library/Main/Strings.cs:287 +#: Library/Main/Strings.cs:292 msgid "Specify the amount of log information to output to the console." msgstr "" -#: Library/Main/Strings.cs:288 +#: Library/Main/Strings.cs:293 msgid "Console information level" msgstr "主控台資訊等級" -#: Library/Main/Strings.cs:290 +#: Library/Main/Strings.cs:295 msgid "Apply filters to the console log data" msgstr "" -#: Library/Main/Strings.cs:292 +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use" " the lowest IO priority level, which can make operations run slower but will" " interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 +#: Library/Main/Strings.cs:298 msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 +#: Library/Main/Strings.cs:301 msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3992,11 +4611,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -4004,11 +4623,11 @@ msgid "" " metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes" " that the input data is always in perfect shape. This option is not intended" @@ -4016,11 +4635,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -4029,11 +4648,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 +#: Library/Main/Strings.cs:310 msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild" " them using local source data. However, since the local data may have " @@ -4042,11 +4661,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before" " another will be automatically triggered at the end of a backup job. " @@ -4054,11 +4673,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -4066,57 +4685,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the" +" cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used" +" during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "建立快照失敗:{0}" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -4170,29 +4938,6 @@ msgstr "" msgid "Configure http requests" msgstr "http 相關選項" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "允許所有伺服器憑證" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "允許已知的 SSL 憑證" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -4278,131 +5023,143 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "設定 Hyper-V 模組" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "設定 Microsoft SQL Server 模組" -#: Library/Modules/Builtin/Strings.cs:73 +#: Library/Modules/Builtin/Strings.cs:75 msgid "Execute a script before starting an operation, and again on completion" msgstr "" -#: Library/Modules/Builtin/Strings.cs:74 +#: Library/Modules/Builtin/Strings.cs:76 msgid "Run script" msgstr "執行 script" -#: Library/Modules/Builtin/Strings.cs:75 +#: Library/Modules/Builtin/Strings.cs:77 msgid "" "Execute a script after performing an operation. The script will receive the " "operation results written to stdout." msgstr "" -#: Library/Modules/Builtin/Strings.cs:76 +#: Library/Modules/Builtin/Strings.cs:78 msgid "Run a script on exit" msgstr "完成時執行 script" -#: Library/Modules/Builtin/Strings.cs:77 +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "啟動時執行 script 並依結果決定是否繼續" -#: Library/Modules/Builtin/Strings.cs:81 -#: Library/Modules/Builtin/Strings.cs:237 +#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 -#: Library/Modules/Builtin/Strings.cs:238 +#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:244 msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" "Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "啟動時執行 script" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" "Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 +#: Library/Modules/Builtin/Strings.cs:91 msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "寄送郵件" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the" " option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message body.\n" "\n" @@ -4415,21 +5172,21 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "訊息內容" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "SMTP 密碼" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have default values. You can supply multiple email addresses separated with commas, and you can use the normal address format as specified by RFC2822 section 3.4.\n" "Example with 3 recipients: \n" @@ -4437,11 +5194,11 @@ msgid "" "Peter Sample , John Sample , admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is supplied, the hostname of the first recipient is used. Examples of allowed formats:\n" "\n" @@ -4451,25 +5208,25 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "Email 寄件人" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:189 -#: Library/Modules/Builtin/Strings.cs:217 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "要寄送的訊息" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent.\n" "If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent.\n" @@ -4477,68 +5234,68 @@ msgid "" "To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STARTTLS, use the format smtp://example.com:25/?starttls=when-available or smtp://example.com:25/?starttls=always. If no port is specified, port 25 is used for non-ssl, and 465 for SSL connections. To force not to use STARTTLS use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "SMTP 位址" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in" " the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 +#: Library/Modules/Builtin/Strings.cs:135 msgid "SMTP Username" msgstr "SMTP 使用者名稱" -#: Library/Modules/Builtin/Strings.cs:134 +#: Library/Modules/Builtin/Strings.cs:136 #, csharp-format msgid "Whole SMTP communication: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:135 +#: Library/Modules/Builtin/Strings.cs:137 #, csharp-format msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" msgstr "無法寄送電子郵件到伺服器: {0}, 訊息: {1},正在重試 {2}" -#: Library/Modules/Builtin/Strings.cs:136 +#: Library/Modules/Builtin/Strings.cs:138 #, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "這個模組提供以 XMPP 寄送狀態報告" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "XMPP 通知模組" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "XMPP 收件者電子郵件" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be used as the message.\n" "\n" @@ -4551,144 +5308,144 @@ msgid "" "All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 -#: Library/Modules/Builtin/Strings.cs:210 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "訊息範本" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "XMPP 帳號" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the " "message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "XMPP 密碼" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:187 -#: Library/Modules/Builtin/Strings.cs:215 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" "You can supply multiple options with a comma separator, e.g. \"{0},{1}\". The special value \"{4}\" is a shorthand for \"{0},{1},{2},{3}\" and will cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:190 -#: Library/Modules/Builtin/Strings.cs:218 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:191 -#: Library/Modules/Builtin/Strings.cs:219 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "寄送訊息給所有的操作者" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 msgid "" "This module provides support for sending status reports via Telegram " "messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:171 msgid "Telegram report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:172 msgid "Use this option to set the channel ID." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 -msgid "Telegram channel id" +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:184 msgid "" "Use this option to set a bot ID for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:186 msgid "The Telegram bot ID" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:187 msgid "" "Use this option to set a API key for the bot that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:188 msgid "The Telegram API key" msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:194 msgid "Timeout occurred while sending to Telegram server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:197 +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:198 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "HTTP 通知功能" -#: Library/Modules/Builtin/Strings.cs:199 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:200 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:211 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:212 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:213 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:214 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:220 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:221 +#: Library/Modules/Builtin/Strings.cs:223 msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:222 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4696,52 +5453,52 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:223 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:224 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:225 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:230 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:231 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:232 +#: Library/Modules/Builtin/Strings.cs:238 msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:233 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:234 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:235 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:236 +#: Library/Modules/Builtin/Strings.cs:242 msgid "Limit log lines" msgstr "" @@ -4750,16 +5507,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4768,78 +5525,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "{0} bytes" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "{0:N} GB" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "{0:N} KB" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "{0:N} TB" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -5056,11 +5813,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "啟用自動更新" -#: CommandLine/CLI/Program.cs:290 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/duplicati/localization.pot b/Localizations/duplicati/localization.pot index a163b59a1..5803350be 100644 --- a/Localizations/duplicati/localization.pot +++ b/Localizations/duplicati/localization.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-08-14 09:15+0200\n" +"POT-Creation-Date: 2024-12-23 00:21+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -40,8 +40,8 @@ msgstr "" msgid "Set thread level utilized for crypting" msgstr "" -#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:179 -#: Library/Main/Strings.cs:230 +#: Library/Encryption/Strings.cs:33 Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:235 #, csharp-format msgid "The option --{0} is no longer used and has been deprecated." msgstr "" @@ -112,7 +112,7 @@ msgid "Use GPG Armor" msgstr "" #: Library/Encryption/Strings.cs:52 -msgid "Overrides the GPG command supplied for decryption." +msgid "Override the GPG command supplied for decryption." msgstr "" #: Library/Encryption/Strings.cs:53 @@ -122,7 +122,7 @@ msgstr "" #: Library/Encryption/Strings.cs:54 #, csharp-format msgid "" -"Overrides the default GPG encryption command \"{0}\". Normal usage is to " +"Override the default GPG encryption command \"{0}\". Normal usage is to " "request asymetric encryption with the setting {1}." msgstr "" @@ -143,73 +143,94 @@ msgstr "" msgid "Failure while invoking GnuPG, program won't terminate" msgstr "" -#: Library/Interface/Strings.cs:24 -msgid "aliases" +#: Library/Encryption/Strings.cs:65 +msgid "Key must be at least 8 characters long" msgstr "" -#: Library/Interface/Strings.cs:25 -msgid "default value" +#: Library/Encryption/Strings.cs:66 +msgid "Key must not be empty" +msgstr "" + +#: Library/Encryption/Strings.cs:67 +msgid "Refusing to encrypt with blacklisted key" msgstr "" #: Library/Interface/Strings.cs:26 -msgid "[DEPRECATED]" +msgid "aliases" msgstr "" #: Library/Interface/Strings.cs:27 +msgid "default value" +msgstr "" + +#: Library/Interface/Strings.cs:28 +msgid "[DEPRECATED]" +msgstr "" + +#: Library/Interface/Strings.cs:29 msgid "values" msgstr "" -#: Library/Interface/Strings.cs:30 +#: Library/Interface/Strings.cs:33 msgid "Boolean" msgstr "" -#: Library/Interface/Strings.cs:31 +#: Library/Interface/Strings.cs:34 msgid "Enumeration" msgstr "" -#: Library/Interface/Strings.cs:32 +#: Library/Interface/Strings.cs:35 msgid "Flags" msgstr "" -#: Library/Interface/Strings.cs:33 +#: Library/Interface/Strings.cs:36 msgid "Integer" msgstr "" -#: Library/Interface/Strings.cs:34 +#: Library/Interface/Strings.cs:37 msgid "Path" msgstr "" -#: Library/Interface/Strings.cs:35 +#: Library/Interface/Strings.cs:38 msgid "Size" msgstr "" -#: Library/Interface/Strings.cs:36 +#: Library/Interface/Strings.cs:39 msgid "String" msgstr "" -#: Library/Interface/Strings.cs:37 +#: Library/Interface/Strings.cs:40 msgid "Timespan" msgstr "" -#: Library/Interface/Strings.cs:38 +#: Library/Interface/Strings.cs:41 msgid "Unknown" msgstr "" -#: Library/Interface/Strings.cs:41 +#: Library/Interface/Strings.cs:45 msgid "The folder cannot be created because it already exists" msgstr "" -#: Library/Interface/Strings.cs:42 +#: Library/Interface/Strings.cs:46 msgid "The requested folder does not exist" msgstr "" -#: Library/Interface/Strings.cs:43 +#: Library/Interface/Strings.cs:47 msgid "Cancelled" msgstr "" +#: Library/Interface/Strings.cs:48 +msgid "" +"Encryption key used to encrypt target settings does not match current key." +msgstr "" + +#: Library/Interface/Strings.cs:49 +msgid "Encryption key is missing." +msgstr "" + #: Library/Interface/CustomExceptions.cs:84 #: Library/Interface/CustomExceptions.cs:92 -#: Library/Backend/Jottacloud/Jottacloud.cs:286 +#: Library/Backend/Jottacloud/Jottacloud.cs:285 msgid "The requested file does not exist" msgstr "" @@ -326,15 +347,14 @@ msgid "" "must also be set." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:34 +#: Library/Backend/OpenStack/Strings.cs:31 Library/Backend/FTP/Strings.cs:35 #: Library/Backend/CloudFiles/Strings.cs:29 Library/Backend/S3/Strings.cs:33 -#: Library/Backend/AlternativeFTP/Strings.cs:33 #: Library/Backend/SSHv2/Strings.cs:49 Library/Backend/File/Strings.cs:31 #: Library/Backend/Backblaze/Strings.cs:32 #: Library/Backend/AzureBlob/Strings.cs:38 Library/Backend/Mega/Strings.cs:28 #: Library/Backend/SharePoint/Strings.cs:29 #: Library/Backend/WEBDAV/Strings.cs:29 -msgid "Supplies the password used to connect to the server" +msgid "Supply the password used to connect to the server" msgstr "" #: Library/Backend/OpenStack/Strings.cs:32 @@ -342,12 +362,11 @@ msgid "The domain name of the user used to connect to the server." msgstr "" #: Library/Backend/OpenStack/Strings.cs:33 -msgid "Supplies the domain used to connect to the server" +msgid "Supply the domain used to connect to the server" msgstr "" -#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:35 +#: Library/Backend/OpenStack/Strings.cs:34 Library/Backend/FTP/Strings.cs:36 #: Library/Backend/CloudFiles/Strings.cs:30 Library/Backend/S3/Strings.cs:34 -#: Library/Backend/AlternativeFTP/Strings.cs:34 #: Library/Backend/SSHv2/Strings.cs:50 Library/Backend/File/Strings.cs:32 #: Library/Backend/Backblaze/Strings.cs:33 #: Library/Backend/AzureBlob/Strings.cs:39 Library/Backend/Mega/Strings.cs:29 @@ -358,15 +377,14 @@ msgid "" "environment variable \"AUTH_USERNAME\"." msgstr "" -#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:36 +#: Library/Backend/OpenStack/Strings.cs:35 Library/Backend/FTP/Strings.cs:37 #: Library/Backend/CloudFiles/Strings.cs:31 Library/Backend/S3/Strings.cs:35 -#: Library/Backend/AlternativeFTP/Strings.cs:35 #: Library/Backend/SSHv2/Strings.cs:51 Library/Backend/File/Strings.cs:33 #: Library/Backend/Backblaze/Strings.cs:34 #: Library/Backend/AzureBlob/Strings.cs:40 Library/Backend/Mega/Strings.cs:30 #: Library/Backend/SharePoint/Strings.cs:31 #: Library/Backend/WEBDAV/Strings.cs:31 -msgid "Supplies the username used to connect to the server" +msgid "Supply the username used to connect to the server" msgstr "" #: Library/Backend/OpenStack/Strings.cs:36 @@ -377,7 +395,7 @@ msgid "" msgstr "" #: Library/Backend/OpenStack/Strings.cs:37 -msgid "Supplies the Tenant Name used to connect to the server" +msgid "Supply the Tenant Name used to connect to the server" msgstr "" #: Library/Backend/OpenStack/Strings.cs:38 @@ -387,7 +405,7 @@ msgid "" msgstr "" #: Library/Backend/OpenStack/Strings.cs:39 -msgid "Supplies the API key used to connect to the server" +msgid "Supply the API key used to connect to the server" msgstr "" #: Library/Backend/OpenStack/Strings.cs:40 @@ -398,7 +416,7 @@ msgid "" msgstr "" #: Library/Backend/OpenStack/Strings.cs:41 -msgid "Supplies the authentication URL" +msgid "Supply the authentication URL" msgstr "" #: Library/Backend/OpenStack/Strings.cs:42 @@ -417,7 +435,7 @@ msgid "" msgstr "" #: Library/Backend/OpenStack/Strings.cs:45 -msgid "Supplies the region used for creating a container" +msgid "Supply the region used for creating a container" msgstr "" #: Library/Backend/OpenStack/OpenStackConfig.cs:67 @@ -425,7 +443,7 @@ msgid "OpenStack configuration module" msgstr "" #: Library/Backend/OpenStack/OpenStackConfig.cs:69 -msgid "Exposes OpenStack configuration as a web module" +msgid "Expose OpenStack configuration as a web module" msgstr "" #: Library/Backend/OpenStack/OpenStackConfig.cs:77 @@ -437,44 +455,34 @@ msgstr "" #: Library/Backend/OpenStack/OpenStackConfig.cs:77 #: Library/Backend/GoogleServices/GCSConfig.cs:81 #: Library/Backend/S3/S3Config.cs:84 Library/Backend/Storj/StorjConfig.cs:53 -msgid "Provides different config values" +msgid "Provide different config values" msgstr "" -#: Library/Backend/FTP/Strings.cs:27 +#: Library/Backend/FTP/Strings.cs:30 msgid "" "This backend can read and write data to an FTP based backend. Allowed " "formats are \"ftp://hostname/folder\" and \"ftp://username:password@hostname/" "folder\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:28 +#: Library/Backend/FTP/Strings.cs:31 +msgid "" +"This backend can read and write data to an FTP based backend. Allowed " +"formats are \"aftp://hostname/folder\" and \"aftp://username:" +"password@hostname/folder\"." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:32 msgid "FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:29 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in active mode. Even if the " -"option --{0} is also set, the connection will be made in active mode." +#: Library/Backend/FTP/Strings.cs:33 +msgid "Alternative FTP" msgstr "" -#: Library/Backend/FTP/Strings.cs:30 Library/Backend/FTP/Strings.cs:32 -msgid "Toggles the FTP connections method" -msgstr "" - -#: Library/Backend/FTP/Strings.cs:31 -#, csharp-format -msgid "" -"Activate this option to make the FTP connection in passive mode, which works " -"better with some firewalls. If the option --{0} is set, this option is " -"ignored." -msgstr "" - -#: Library/Backend/FTP/Strings.cs:33 Library/Backend/CloudFiles/Strings.cs:28 -#: Library/Backend/S3/Strings.cs:32 -#: Library/Backend/AlternativeFTP/Strings.cs:32 -#: Library/Backend/SSHv2/Strings.cs:48 Library/Backend/File/Strings.cs:30 -#: Library/Backend/Backblaze/Strings.cs:31 +#: Library/Backend/FTP/Strings.cs:34 Library/Backend/CloudFiles/Strings.cs:28 +#: Library/Backend/S3/Strings.cs:32 Library/Backend/SSHv2/Strings.cs:48 +#: Library/Backend/File/Strings.cs:30 Library/Backend/Backblaze/Strings.cs:31 #: Library/Backend/AzureBlob/Strings.cs:37 Library/Backend/Mega/Strings.cs:27 #: Library/Backend/SharePoint/Strings.cs:28 #: Library/Backend/WEBDAV/Strings.cs:28 @@ -483,47 +491,194 @@ msgid "" "environment variable \"AUTH_PASSWORD\"." msgstr "" -#: Library/Backend/FTP/Strings.cs:37 -msgid "" -"Use this option to communicate using Secure Socket Layer (SSL) over ftp " -"(ftps)." -msgstr "" - #: Library/Backend/FTP/Strings.cs:38 -msgid "Instructs Duplicati to use an SSL (ftps) connection" +msgid "" +"Use this option to log FTP dialog to terminal console for debugging purposes." msgstr "" #: Library/Backend/FTP/Strings.cs:39 -msgid "" -"To protect against network failures, every upload will be attempted " -"verified. Use this option to disable this verification to make the upload " -"faster but less reliable." +msgid "Log FTP dialog to terminal console" msgstr "" #: Library/Backend/FTP/Strings.cs:40 -#: Library/Backend/AlternativeFTP/Strings.cs:44 -msgid "Disable upload verification" +msgid "" +"Use this option to log FTP PRIVATE info (username, password) to console for " +"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" msgstr "" -#: Library/Backend/FTP/Strings.cs:41 Library/Backend/WEBDAV/Strings.cs:35 -#: Library/Backend/TahoeLAFS/Strings.cs:28 -#, csharp-format -msgid "The folder {0} was not found, message: {1}" +#: Library/Backend/FTP/Strings.cs:41 +msgid "Log FTP PRIVATE info to console" msgstr "" #: Library/Backend/FTP/Strings.cs:42 -#: Library/Backend/AlternativeFTP/Strings.cs:41 +#, csharp-format +msgid "The folder {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:43 #, csharp-format msgid "" "The file {0} was uploaded but not found afterwards. The file listing " "returned {1}" msgstr "" -#: Library/Backend/FTP/Strings.cs:43 +#: Library/Backend/FTP/Strings.cs:44 #, csharp-format msgid "" "The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}." +"to be {2}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:45 +msgid "" +"To protect against network or server failures, every upload will be " +"attempted to be verified. Use this option to disable this verification to " +"make the upload faster but less reliable." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:46 +msgid "Disable upload verification" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:47 +msgid "" +"If this flag is set, the FTP data connection type will be changed to the " +"selected option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:48 +msgid "Configure the FTP data connection type" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:49 +msgid "" +"If this flag is set, the FTP encryption mode will be changed to the selected " +"option." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:50 +msgid "Configure the FTP encryption mode" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:51 +msgid "This flag controls the SSL policy to use when encryption is enabled." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:52 +msgid "Configure the SSL policy to use when encryption is enabled" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:53 +msgid "" +"Some FTP servers need a small delay before reporting the correct file size. " +"The required delay depends on network topology. If you experience errors " +"related to the upload size not matching, try adding a few seconds delay." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:54 +msgid "Add a delay after uploading a file" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:55 +#, csharp-format +msgid "Error on deleting file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:56 +#, csharp-format +msgid "Error reading file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:57 +#, csharp-format +msgid "Error writing file: {0}, error: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:58 +msgid "" +"Use this option to communicate using Secure Socket Layer (SSL) over ftp " +"(ftps)." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:59 +msgid "Instruct Duplicati to use an SSL (ftps) connection" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:60 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in active mode. Even if the " +"option --{0} is also set, the connection will be made in active mode." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:61 Library/Backend/FTP/Strings.cs:63 +msgid "Toggle the FTP connections method" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:62 +#, csharp-format +msgid "" +"Activate this option to make the FTP connection in passive mode, which works " +"better with some firewalls. If the option --{0} is set, this option is " +"ignored." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:64 +msgid "" +"The option ftp-passive is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:65 +msgid "ftp-regular is deprecated, use ftp-data-connection-type instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:66 +msgid "use-ssl is deprecated, use ftp-ssl-protocols instead." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:67 +#, csharp-format +msgid "The file {0} was not found. Message: {1}" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:68 +msgid "Treat the url path as absolute" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:69 +msgid "" +"Use this option to interpret the url path as an absolute path. This option " +"only has an effect if the initial starting folder in the FTP server is not " +"the (virtual) root folder. If not set, the path in the url is treated as " +"relative to the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:70 +msgid "Treat the url path as relative" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:71 +msgid "" +"Use this option to interpret the url path as a path that is relative to the " +"initial login folder. This option only has an effect if the initial starting " +"folder in the FTP server is not the (virtual) root folder. If not set, the " +"path in the url is treated as absolute, ignoring the initial login folder." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:72 +msgid "Use CWD instead of absolute paths" +msgstr "" + +#: Library/Backend/FTP/Strings.cs:73 +msgid "" +"Use this option to start the connection with a CWD command instead of an " +"absolute path. This can be useful if the FTP server does not support " +"absolute paths." +msgstr "" + +#: Library/Backend/FTP/Strings.cs:74 +#, csharp-format +msgid "Error creating folder {0}, gave folder: {1}" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:71 @@ -531,7 +686,7 @@ msgid "Google Cloud Storage configuration module" msgstr "" #: Library/Backend/GoogleServices/GCSConfig.cs:73 -msgid "Exposes Google Cloud Storage configuration as a web module" +msgid "Expose Google Cloud Storage configuration as a web module" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:27 @@ -584,7 +739,7 @@ msgid "" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:35 -msgid "Specifies location option for creating a bucket" +msgid "Specify location option for creating a bucket" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:36 @@ -597,7 +752,7 @@ msgid "" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:38 -msgid "Specifies storage class for creating a bucket" +msgid "Specify storage class for creating a bucket" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:39 @@ -608,7 +763,7 @@ msgid "" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:40 -msgid "Specifies project for creating a bucket" +msgid "Specify project for creating a bucket" msgstr "" #: Library/Backend/GoogleServices/Strings.cs:44 @@ -663,7 +818,7 @@ msgid "The API Access Key used to authenticate with CloudFiles." msgstr "" #: Library/Backend/CloudFiles/Strings.cs:33 -msgid "Supplies the access key used to connect to the server" +msgid "Supply the access key used to connect to the server" msgstr "" #: Library/Backend/CloudFiles/Strings.cs:34 @@ -683,7 +838,7 @@ msgid "The username used to authenticate with CloudFiles." msgstr "" #: Library/Backend/CloudFiles/Strings.cs:37 -msgid "Supplies the username used to authenticate with CloudFiles" +msgid "Supply the username used to authenticate with CloudFiles" msgstr "" #: Library/Backend/CloudFiles/Strings.cs:38 @@ -716,7 +871,7 @@ msgid "S3 configuration module" msgstr "" #: Library/Backend/S3/S3Config.cs:77 -msgid "Exposes S3 configuration as a web module" +msgid "Expose S3 configuration as a web module" msgstr "" #: Library/Backend/S3/Strings.cs:26 @@ -769,7 +924,7 @@ msgid "" msgstr "" #: Library/Backend/S3/Strings.cs:40 -msgid "Specifies S3 location constraints" +msgid "Specify S3 location constraints" msgstr "" #: Library/Backend/S3/Strings.cs:41 @@ -782,7 +937,7 @@ msgid "" msgstr "" #: Library/Backend/S3/Strings.cs:43 -msgid "Specifies an alternate S3 server name" +msgid "Specify an alternate S3 server name" msgstr "" #: Library/Backend/S3/Strings.cs:44 @@ -792,7 +947,7 @@ msgid "" msgstr "" #: Library/Backend/S3/Strings.cs:45 -msgid "Specifies the S3 client library to use" +msgid "Specify the S3 client library to use" msgstr "" #: Library/Backend/S3/Strings.cs:46 @@ -804,7 +959,7 @@ msgstr "" #: Library/Backend/S3/Strings.cs:47 Library/Backend/WEBDAV/Strings.cs:40 #: Library/Backend/TahoeLAFS/Strings.cs:27 -msgid "Instructs Duplicati to use an SSL (https) connection" +msgid "Instruct Duplicati to use an SSL (https) connection" msgstr "" #: Library/Backend/S3/Strings.cs:48 @@ -827,12 +982,17 @@ msgstr "" msgid "Specify storage class" msgstr "" +#: Library/Backend/S3/Strings.cs:52 +#, csharp-format +msgid "Unknown S3 client: {0}" +msgstr "" + #: Library/Backend/S3/S3IAM.cs:71 msgid "S3 IAM support module" msgstr "" #: Library/Backend/S3/S3IAM.cs:73 -msgid "Exposes S3 IAM manipulation as a web module" +msgid "Expose S3 IAM manipulation as a web module" msgstr "" #: Library/Backend/S3/S3IAM.cs:81 @@ -840,7 +1000,7 @@ msgid "The operation to perform" msgstr "" #: Library/Backend/S3/S3IAM.cs:81 -msgid "Selects the operation to perform" +msgid "Select the operation to perform" msgstr "" #: Library/Backend/S3/S3IAM.cs:82 @@ -859,109 +1019,6 @@ msgstr "" msgid "The Amazon Secret Key" msgstr "" -#: Library/Backend/AlternativeFTP/Strings.cs:30 -msgid "" -"This backend can read and write data to an FTP based backend using an " -"alternative FTP client. Allowed formats are \"aftp://hostname/folder\" and " -"\"aftp://username:password@hostname/folder\"." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:31 -msgid "Alternative FTP" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:36 -msgid "" -"Use this option to log FTP dialog to terminal console for debugging purposes." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:37 -msgid "Log FTP dialog to terminal console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:38 -msgid "" -"Use this option to log FTP PRIVATE info (username, password) to console for " -"debugging purposes (DO NOT POST THIS TO THE INTERNET!)" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:39 -msgid "Log FTP PRIVATE info to console" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:40 -#, csharp-format -msgid "The folder {0} was not found. Message: {1}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:42 -#, csharp-format -msgid "" -"The file {0} was uploaded but the returned size was {1} and it was expected " -"to be {2}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:43 -msgid "" -"To protect against network or server failures, every upload will be " -"attempted to be verified. Use this option to disable this verification to " -"make the upload faster but less reliable." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:45 -msgid "" -"If this flag is set, the FTP data connection type will be changed to the " -"selected option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:46 -msgid "Configure the FTP data connection type" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:47 -msgid "" -"If this flag is set, the FTP encryption mode will be changed to the selected " -"option." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:48 -msgid "Configure the FTP encryption mode" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:49 -msgid "This flag controls the SSL policy to use when encryption is enabled." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:50 -msgid "Configure the SSL policy to use when encryption is enabled" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:51 -msgid "" -"Some FTP servers need a small delay before reporting the correct file size. " -"The required delay depends on network topology. If you experience errors " -"related to the upload size not matching, try adding a few seconds delay." -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:52 -msgid "Add a delay after uploading a file" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:53 -#, csharp-format -msgid "Error on deleting file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:54 -#, csharp-format -msgid "Error reading file: {0}" -msgstr "" - -#: Library/Backend/AlternativeFTP/Strings.cs:55 -#, csharp-format -msgid "Error writing file: {0}" -msgstr "" - #: Library/Backend/SSHv2/Strings.cs:26 msgid "Module for generating SSH private/public keys" msgstr "" @@ -1038,7 +1095,7 @@ msgid "" msgstr "" #: Library/Backend/SSHv2/Strings.cs:53 -msgid "Supplies server fingerprint used for validation of server identity" +msgid "Supply server fingerprint used for validation of server identity" msgstr "" #: Library/Backend/SSHv2/Strings.cs:54 @@ -1049,18 +1106,18 @@ msgid "" msgstr "" #: Library/Backend/SSHv2/Strings.cs:55 -msgid "Disables fingerprint validation" +msgid "Disable fingerprint validation" msgstr "" #: Library/Backend/SSHv2/Strings.cs:56 msgid "" -"Points to a valid OpenSSH keyfile. If the file is encrypted, the password " +"Point to a valid OpenSSH keyfile. If the file is encrypted, the password " "supplied is used to decrypt it. If the keyfile is specified, the password is " "not used to authenticate." msgstr "" #: Library/Backend/SSHv2/Strings.cs:57 Library/Backend/SSHv2/Strings.cs:59 -msgid "Uses a SSH private key to authenticate" +msgid "Use a SSH private key to authenticate" msgstr "" #: Library/Backend/SSHv2/Strings.cs:58 @@ -1078,7 +1135,7 @@ msgid "" msgstr "" #: Library/Backend/SSHv2/Strings.cs:61 -msgid "Sets the operation timeout value" +msgid "Set the operation timeout value" msgstr "" #: Library/Backend/SSHv2/Strings.cs:62 @@ -1090,7 +1147,7 @@ msgid "" msgstr "" #: Library/Backend/SSHv2/Strings.cs:63 -msgid "Sets a keepalive value" +msgid "Set a keepalive value" msgstr "" #: Library/Backend/SSHv2/Strings.cs:64 @@ -1436,62 +1493,66 @@ msgstr "" msgid "Whether the HttpClient class should be used" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:41 -#, csharp-format -msgid "" -"Stores files in Microsoft OneDrive or Microsoft OneDrive for Business via " -"the Microsoft Graph API. Usage of this backend requires that you agree to " -"the terms in {0} ({1}) and {2} ({3})." +#: Library/Backend/OneDrive/Strings.cs:37 +msgid "The option --use-http-client is deprecated and has no effect." msgstr "" #: Library/Backend/OneDrive/Strings.cs:42 -msgid "Microsoft OneDrive v2" +#, csharp-format +msgid "" +"Store files in Microsoft OneDrive or Microsoft OneDrive for Business via the " +"Microsoft Graph API. Usage of this backend requires that you agree to the " +"terms in {0} ({1}) and {2} ({3})." msgstr "" #: Library/Backend/OneDrive/Strings.cs:43 +msgid "Microsoft OneDrive" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:44 #, csharp-format msgid "" "ID of the drive to store data in. If no drive is specified, the default " "OneDrive or OneDrive for Business drive will be used via '{0}'." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:44 +#: Library/Backend/OneDrive/Strings.cs:45 msgid "Optional ID of the drive" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:49 +#: Library/Backend/OneDrive/Strings.cs:50 #, csharp-format msgid "" -"Stores files in a Microsoft SharePoint site via the Microsoft Graph API. " +"Store files in a Microsoft SharePoint site via the Microsoft Graph API. " "Usage of this backend requires that you agree to the terms in {0} ({1}) and " "{2} ({3})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:50 +#: Library/Backend/OneDrive/Strings.cs:51 msgid "Microsoft SharePoint v2" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:51 +#: Library/Backend/OneDrive/Strings.cs:52 msgid "ID of the site to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:52 +#: Library/Backend/OneDrive/Strings.cs:53 msgid "ID of the site" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:53 +#: Library/Backend/OneDrive/Strings.cs:54 msgid "No site ID was provided" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:54 +#: Library/Backend/OneDrive/Strings.cs:55 #, csharp-format msgid "Conflicting site IDs used: given {0} but found {1}" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:59 +#: Library/Backend/OneDrive/Strings.cs:60 #, csharp-format msgid "" -"Stores files in a Microsoft Office 365 Group via the Microsoft Graph API. " +"Store files in a Microsoft Office 365 Group via the Microsoft Graph API. " "Allowed formats are \"sharepoint://tenant.sharepoint.com/{{PathToWeb}}//" "{{Documents}}/subfolder\" (with \"//\" being optionally used to indicate the " "root document folder) and \"sharepoint://subfolder\" (in which case you must " @@ -1499,42 +1560,42 @@ msgid "" "backend requires that you agree to the terms in {1} ({2}) and {3} ({4})." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:60 +#: Library/Backend/OneDrive/Strings.cs:61 msgid "Microsoft Office 365 Group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:61 +#: Library/Backend/OneDrive/Strings.cs:62 msgid "ID of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:62 +#: Library/Backend/OneDrive/Strings.cs:63 msgid "ID of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:63 +#: Library/Backend/OneDrive/Strings.cs:64 msgid "Email address of the group to store data in." msgstr "" -#: Library/Backend/OneDrive/Strings.cs:64 +#: Library/Backend/OneDrive/Strings.cs:65 msgid "Email address of the group" msgstr "" -#: Library/Backend/OneDrive/Strings.cs:65 -msgid "No group ID or group email address was provided." -msgstr "" - #: Library/Backend/OneDrive/Strings.cs:66 -#, csharp-format -msgid "No groups were found with the given email address: {0}" +msgid "No group ID or group email address was provided." msgstr "" #: Library/Backend/OneDrive/Strings.cs:67 #, csharp-format -msgid "Multiple groups were found with the given email address: {0}" +msgid "No groups were found with the given email address: {0}" msgstr "" #: Library/Backend/OneDrive/Strings.cs:68 #, csharp-format +msgid "Multiple groups were found with the given email address: {0}" +msgstr "" + +#: Library/Backend/OneDrive/Strings.cs:69 +#, csharp-format msgid "Conflicting group IDs used: given {0} but found {1}" msgstr "" @@ -1551,7 +1612,7 @@ msgid "Access Key ID is used to identify the user." msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:10 -#: Library/Backend/Idrivee2/Strings.cs:29 +#: Library/Backend/Idrivee2/Strings.cs:31 msgid "Access Key ID" msgstr "" @@ -1562,7 +1623,7 @@ msgid "" msgstr "" #: Library/Backend/AliyunOSS/Strings.cs:12 -#: Library/Backend/Idrivee2/Strings.cs:27 +#: Library/Backend/Idrivee2/Strings.cs:29 msgid "Access Key Secret" msgstr "" @@ -1696,7 +1757,7 @@ msgid "" msgstr "" #: Library/Backend/TencentCOS/Strings.cs:38 -msgid "Specifies COS location constraints" +msgid "Specify COS location constraints" msgstr "" #: Library/Backend/TencentCOS/Strings.cs:39 @@ -1741,7 +1802,7 @@ msgid "" msgstr "" #: Library/Backend/Jottacloud/Strings.cs:34 -msgid "Supplies the backup device to use" +msgid "Supply the backup device to use" msgstr "" #: Library/Backend/Jottacloud/Strings.cs:35 @@ -1755,7 +1816,7 @@ msgid "" msgstr "" #: Library/Backend/Jottacloud/Strings.cs:36 -msgid "Supplies the mount point to use on the server" +msgid "Supply the mount point to use on the server" msgstr "" #: Library/Backend/Jottacloud/Strings.cs:37 @@ -1806,43 +1867,45 @@ msgstr "" msgid "No username given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:24 -msgid "This backend can read and write data to IDrive e2." +#: Library/Backend/Idrivee2/Strings.cs:26 +msgid "" +"This backend can read and write data to IDrive e2. Allowed format is \"e2://" +"bucket/folder\"." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:25 +#: Library/Backend/Idrivee2/Strings.cs:27 msgid "IDrive e2" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:26 +#: Library/Backend/Idrivee2/Strings.cs:28 #, csharp-format msgid "" "Access Key Secret can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:28 +#: Library/Backend/Idrivee2/Strings.cs:30 #, csharp-format msgid "" "Access Key ID can be obtained after logging into your IDrive e2 account. " "This can also be supplied through the option --{0}." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:31 +#: Library/Backend/Idrivee2/Strings.cs:33 msgid "" "The \"Bucket Name or Complete Path\" is name of target bucket or complete of " "a folder inside the bucket." msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:32 +#: Library/Backend/Idrivee2/Strings.cs:34 msgid "The \"Bucket Name or Complete Path\"" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:34 +#: Library/Backend/Idrivee2/Strings.cs:36 msgid "No Access Key Secret given" msgstr "" -#: Library/Backend/Idrivee2/Strings.cs:35 +#: Library/Backend/Idrivee2/Strings.cs:37 msgid "No Access Key ID given" msgstr "" @@ -1955,6 +2018,11 @@ msgstr "" msgid "Dropbox" msgstr "" +#: Library/Backend/Dropbox/Strings.cs:31 +#, csharp-format +msgid "The Dropbox account is over quota: {0}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:24 msgid "" "This backend can read and write data to a WEBDAV enabled web server, using " @@ -1987,6 +2055,11 @@ msgid "" "does not support WebDAV connections" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:35 Library/Backend/TahoeLAFS/Strings.cs:28 +#, csharp-format +msgid "The folder {0} was not found, message: {1}" +msgstr "" + #: Library/Backend/WEBDAV/Strings.cs:36 #, csharp-format msgid "" @@ -2014,6 +2087,70 @@ msgstr "" msgid "Dump the PROPFIND response" msgstr "" +#: Library/Backend/WEBDAV/Strings.cs:43 Library/Modules/Builtin/Strings.cs:42 +#: Library/Modules/Builtin/Strings.cs:228 +#, csharp-format +msgid "" +"Use this option to accept any server certificate, regardless of what errors " +"it may have. Please use --{0} instead, whenever possible." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:44 Library/Modules/Builtin/Strings.cs:43 +#: Library/Modules/Builtin/Strings.cs:229 +msgid "Accept any server certificate" +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:45 Library/Modules/Builtin/Strings.cs:44 +#: Library/Modules/Builtin/Strings.cs:230 +msgid "" +"If your server certificate is reported as invalid (e.g. with self-signed " +"certificates), you can supply the certificate hash (SHA1) to approve it " +"anyway. The hash value must be entered in hex format without spaces or " +"colons. You can enter multiple hashes separated by commas." +msgstr "" + +#: Library/Backend/WEBDAV/Strings.cs:46 Library/Modules/Builtin/Strings.cs:45 +#: Library/Modules/Builtin/Strings.cs:231 +msgid "Optionally accept a known SSL certificate" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:27 +msgid "" +"This backend can read and write data to pCloud with native API. Allowed " +"format is \"pcloud://api.pcloud.com\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:28 +msgid "pCloud (Native API)" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:29 +msgid "" +"The oAuth token used to connect to the server. This may also be supplied as " +"the environment variable \"AUTHID\"." +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:30 +msgid "Supply the oAuth token used to connect to the server" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:31 +msgid "" +"No server specified, must be either api.pcloud.com or eapi.pcloud.com for " +"European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:32 +msgid "" +"Invalid server specified, must be either api.pcloud.com or eapi.pcloud.com " +"for European hosting" +msgstr "" + +#: Library/Backend/pCloud/Strings.cs:33 +#, csharp-format +msgid "Operation {0} failed with unexpected result code: {1}" +msgstr "" + #: Library/Backend/TahoeLAFS/Strings.cs:24 msgid "" "This backend can read and write data to a Tahoe-LAFS based backend. Allowed " @@ -2033,7 +2170,7 @@ msgid "Storj DCS configuration module" msgstr "" #: Library/Backend/Storj/StorjConfig.cs:46 -msgid "Exposes Storj DCS configuration as a web module" +msgid "Expose Storj DCS configuration as a web module" msgstr "" #: Library/Backend/Storj/Strings.cs:27 @@ -2134,6 +2271,802 @@ msgstr "" msgid "The OAuth service is currently over quota. Try again in a few hours" msgstr "" +#: Library/SecretProvider/Strings.cs:7 +msgid "Secrets from environment variables" +msgstr "" + +#: Library/SecretProvider/Strings.cs:9 +msgid "" +"Secret provider that reads secrets from environment variables.\n" +"Example use:\n" +" env:\n" +"\n" +"For environment variables, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:19 +msgid "Secrets from a file" +msgstr "" + +#: Library/SecretProvider/Strings.cs:21 +msgid "" +"Secret provider that reads secrets from a file\n" +"Example use:\n" +" file://path/to/file?passphrase=secret\n" +"\n" +"The file should be a JSON-encoded object with the secrets as key-value " +"pairs.\n" +"If the file is not encrypted with a passphrase, the passphrase parameter can " +"be omitted.\n" +"The file must be encrypted with AESCrypt if encryption is desired.\n" +"For file-based secrets, the lookup is case-insensitive.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:31 +msgid "The decryption passphrase" +msgstr "" + +#: Library/SecretProvider/Strings.cs:32 +msgid "The passphrase to use for decrypting the file with secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:37 +msgid "Secrets from AWS Secrets Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:39 +msgid "" +"Secret provider that reads secrets from AWS Secrets Manager\n" +"Example use:\n" +" aws://?access-key=...&secret-key=...®ion-endpoint=us-" +"east-1&secrets=secret1,secret2\n" +"\n" +"Either the region endpoint or the service URL must be provided.\n" +"Each secret is retrieved in the order specified until all requested keys are " +"found.\n" +"Each secret must be a key-value pair type secret.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:47 +msgid "The AWS access key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:48 +msgid "" +"The access key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:49 +msgid "The AWS secret key" +msgstr "" + +#: Library/SecretProvider/Strings.cs:50 +msgid "" +"The secret key to use for authentication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:51 +msgid "The AWS region endpoint" +msgstr "" + +#: Library/SecretProvider/Strings.cs:52 +msgid "" +"The region endpoint to use for communication with AWS. Can also be supplied " +"via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:53 +msgid "The AWS service URL" +msgstr "" + +#: Library/SecretProvider/Strings.cs:54 +msgid "" +"The service URL to use for communication with AWS. Can also be supplied via " +"the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:55 +msgid "The secret names to retrieve" +msgstr "" + +#: Library/SecretProvider/Strings.cs:56 +msgid "" +"The names of the secrets to retrieve from AWS Secrets Manager, separated by " +"semicolons or commas. Secrets are retrieved in the order specified until all " +"requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:57 Library/SecretProvider/Strings.cs:84 +#: Library/SecretProvider/Strings.cs:178 +msgid "Case sensitivity" +msgstr "" + +#: Library/SecretProvider/Strings.cs:58 Library/SecretProvider/Strings.cs:85 +#: Library/SecretProvider/Strings.cs:179 +msgid "Whether to use case-sensitive matching for secret names" +msgstr "" + +#: Library/SecretProvider/Strings.cs:63 +msgid "Secrets from HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:65 +msgid "" +"Secret provider that reads secrets from HashiCorp Vault\n" +"Example use:\n" +" hcv://localhost:1234?token=...&mount=secret&secrets=secret1,secret2\n" +"\n" +"The secrets parameter should be a comma- or semicolon-separated list of " +"secret names to retrieve from the Vault.\n" +"Each vault secret is retrieved in the order specified until all requested " +"keys are found.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:72 +msgid "The Vault token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:73 +msgid "The token to use for authentication with HashiCorp Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:74 +msgid "The vault server protocol" +msgstr "" + +#: Library/SecretProvider/Strings.cs:75 +msgid "The protocol to use for communication with the Vault server" +msgstr "" + +#: Library/SecretProvider/Strings.cs:76 +msgid "Secret entries" +msgstr "" + +#: Library/SecretProvider/Strings.cs:77 +msgid "" +"The names of the secrets (aka Vault Apps) to retrieve from the Vault, " +"separated by semicolons or commas. Secrets are retrieved in the order " +"specified until all requested keys are found." +msgstr "" + +#: Library/SecretProvider/Strings.cs:78 +msgid "The client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:79 +msgid "" +"The client ID to use for authentication with HashiCorp Vault. Can also be " +"supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:80 +msgid "The client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:81 +msgid "" +"The client secret to use for authentication with HashiCorp Vault. Can also " +"be supplied via the environment variable {envname}" +msgstr "" + +#: Library/SecretProvider/Strings.cs:82 +msgid "The mount point" +msgstr "" + +#: Library/SecretProvider/Strings.cs:83 +msgid "The mount point for the secrets in the Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:90 +msgid "Secrets from Google Cloud Storage Secret Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:92 +msgid "" +"Secret provider that reads secrets from Google Cloud Storage Secret Manager\n" +"Example use:\n" +" gcs://?project-id=...&version=latest\n" +"\n" +"If the access token is not supplied, the default GCS credentials from the " +"machine will be used.\n" +"Use the accesstoken property to specify a custom access token.\n" +"\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:100 +msgid "The API type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:101 +msgid "The type of API to use for communication with Google Cloud Storage" +msgstr "" + +#: Library/SecretProvider/Strings.cs:102 +msgid "The GCP project ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:103 +msgid "The ID of the Google Cloud Platform project to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:104 +msgid "The access token" +msgstr "" + +#: Library/SecretProvider/Strings.cs:105 +msgid "" +"The access token to use for authentication with Google Cloud Storage. If not " +"supplied, the default GCS credentials from the machine will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:106 +msgid "The secret version to get (or alias)" +msgstr "" + +#: Library/SecretProvider/Strings.cs:107 +msgid "" +"The version of the secret to retrieve from Google Cloud Storage. If not " +"supplied, the latest version will be used." +msgstr "" + +#: Library/SecretProvider/Strings.cs:112 +msgid "Secrets from Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:114 +msgid "" +"Secret provider that reads secrets from Azure Key Vault\n" +"Example use:\n" +" az://?keyvault-name=...&auth-type=ManagedIdentity\n" +"\n" +"If a client ID and secret are provided, use the auth type ClientSecret:\n" +" az://?keyvault-name=...&auth-type=ClientSecret&tenant-id=...&client-" +"id=...&client-secret=...\n" +"\n" +"If a username and password are provided, use the auth type " +"UsernamePassword:\n" +" az://?keyvault-name=...&auth-type=UsernamePassword&tenant-id=..." +"&clientid=...&username=...&password=...\n" +"\n" +"If the vault-uri parameter is not provided, the keyvault-name parameter will " +"be used to construct the URI.\n" +msgstr "" + +#: Library/SecretProvider/Strings.cs:126 +msgid "The name of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:127 +msgid "The name of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:128 +msgid "The connection type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:129 +msgid "The type of connection to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:130 +msgid "The URI of the Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:131 +msgid "The URI of the Azure Key Vault to use for retrieving secrets" +msgstr "" + +#: Library/SecretProvider/Strings.cs:132 +msgid "The Azure tenant ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:133 +msgid "The ID of the Azure tenant to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:134 +msgid "The Azure client ID" +msgstr "" + +#: Library/SecretProvider/Strings.cs:135 +msgid "The ID of the Azure client to use for authentication" +msgstr "" + +#: Library/SecretProvider/Strings.cs:136 +msgid "The Azure client secret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:137 +msgid "The secret to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:138 +msgid "The Azure username" +msgstr "" + +#: Library/SecretProvider/Strings.cs:139 +msgid "The username to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:140 +msgid "The Azure password" +msgstr "" + +#: Library/SecretProvider/Strings.cs:141 +msgid "The password to use for authentication with Azure" +msgstr "" + +#: Library/SecretProvider/Strings.cs:142 +msgid "The authentication type to use" +msgstr "" + +#: Library/SecretProvider/Strings.cs:143 +msgid "" +"The type of authentication to use for communication with Azure Key Vault" +msgstr "" + +#: Library/SecretProvider/Strings.cs:148 +msgid "Secrets from macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:149 +msgid "Secret provider that reads secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:150 +msgid "The service name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:151 +msgid "The service name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:152 +msgid "The account name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:153 +msgid "The account name to use for retrieving secrets from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:154 +msgid "The type of password to get" +msgstr "" + +#: Library/SecretProvider/Strings.cs:155 +msgid "The type of password to get from the macOS Keychain" +msgstr "" + +#: Library/SecretProvider/Strings.cs:160 +msgid "Secrets from Unix pass" +msgstr "" + +#: Library/SecretProvider/Strings.cs:161 +msgid "Secret provider that reads secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:162 +msgid "The pass command" +msgstr "" + +#: Library/SecretProvider/Strings.cs:163 +msgid "" +"The command to use for retrieving secrets from the Unix pass password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:168 +msgid "Secrets from Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:169 +msgid "Secret provider that reads secrets from the Windows Credential Manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:174 +msgid "Secrets from libsecret" +msgstr "" + +#: Library/SecretProvider/Strings.cs:175 +msgid "Secret provider that reads secrets from the libsecret password manager" +msgstr "" + +#: Library/SecretProvider/Strings.cs:176 +msgid "The collection name" +msgstr "" + +#: Library/SecretProvider/Strings.cs:177 +msgid "" +"The collection name to use for retrieving secrets from the libsecret " +"password manager" +msgstr "" + +#: Library/RestAPI/Strings.cs:9 +msgid "Another instance is running, and was notified" +msgstr "" + +#: Library/RestAPI/Strings.cs:10 +#, csharp-format +msgid "" +"Failed to create, open or upgrade the database.\n" +"Error message: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:12 +msgid "Display this help" +msgstr "" + +#: Library/RestAPI/Strings.cs:13 +msgid "" +"Supported commandline arguments:\n" +"\n" +msgstr "" + +#: Library/RestAPI/Strings.cs:16 +#, csharp-format +msgid "--{0}: {1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:17 +#, csharp-format +msgid "" +"Use this option to store some or all of the options given to the commandline " +"client. The file must be a plain text file, and UTF-8 encoding is preferred. " +"Each line in the file should be of the format --option=value. Use the " +"special options --{0} and --{1} to override the localpath and the remote " +"destination uri, respectively. The options in this file take precedence over " +"the options provided on the commandline. You cannot specify filters in both " +"the file and on the commandline. Instead, you can use the special --{2}, --" +"{3}, or --{4} options to specify filters inside the parameter file. Each " +"filter must be prefixed with either a + or a -, and multiple filters must be " +"joined with {5} " +msgstr "" + +#: Library/RestAPI/Strings.cs:18 CommandLine/CLI/Strings.cs:45 +msgid "Path to a file with parameters" +msgstr "" + +#: Library/RestAPI/Strings.cs:19 +#, csharp-format +msgid "" +"Filters cannot be specified on the commandline if filters are also present " +"in the parameter file. Use the special --{0}, --{1}, or --{2} options to " +"specify filters inside the parameter file. Each filter must be prefixed with " +"either a + or a -, and multiple filters must be joined with {3}" +msgstr "" + +#: Library/RestAPI/Strings.cs:20 CommandLine/CLI/Strings.cs:41 +#, csharp-format +msgid "Unable to read the parameters file \"{0}\", reason: {1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:22 +msgid "Output log information to the file given" +msgstr "" + +#: Library/RestAPI/Strings.cs:23 +msgid "Determine the amount of information written in the log file" +msgstr "" + +#: Library/RestAPI/Strings.cs:24 +msgid "" +"Activate portable mode where the database is placed below the program " +"executable" +msgstr "" + +#: Library/RestAPI/Strings.cs:25 +#, csharp-format +msgid "A serious error occurred in Duplicati: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:26 +#, csharp-format +msgid "An error occurred on server tear down: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:27 +#, csharp-format +msgid "" +"Unable to start up. Perhaps another process is already running?\n" +"Error message: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:29 Library/RestAPI/Strings.cs:58 +msgid "Disable database encryption" +msgstr "" + +#: Library/RestAPI/Strings.cs:30 +#, csharp-format +msgid "Unsupported version of SQLite detected ({0}), must be {1} or higher" +msgstr "" + +#: Library/RestAPI/Strings.cs:31 +msgid "" +"The path to the folder where the static files for the webserver is present. " +"The folder must be located beneath the installation folder." +msgstr "" + +#: Library/RestAPI/Strings.cs:32 +msgid "" +"The port the webserver listens on. Multiple values may be supplied with a " +"comma in between." +msgstr "" + +#: Library/RestAPI/Strings.cs:33 +msgid "" +"Deactivates the use of HTTPS even if a certificate is stored in the database " +"or provided on the commandline." +msgstr "" + +#: Library/RestAPI/Strings.cs:34 +msgid "" +"Removes any existing certificate from the database. This option also " +"disables HTTPS." +msgstr "" + +#: Library/RestAPI/Strings.cs:35 +msgid "" +"The certificate and key file in PKCS #12 format the webserver use for SSL." +msgstr "" + +#: Library/RestAPI/Strings.cs:36 +msgid "The password for decryption of the provided certificate PKCS #12 file." +msgstr "" + +#: Library/RestAPI/Strings.cs:37 +msgid "" +"The interface the webserver listens on. The special values \"*\" and \"any\" " +"means any interface. The special value \"loopback\" means the loopback " +"adapter." +msgstr "" + +#: Library/RestAPI/Strings.cs:38 +msgid "" +"The password required to access the webserver. This option is saved so you " +"do not need to set it on each run. Setting an empty value disables the " +"password." +msgstr "" + +#: Library/RestAPI/Strings.cs:39 +msgid "" +"The hostnames that are accepted, separated with semicolons. If any of the " +"hostnames are \"*\", all hostnames are allowed and the hostname checking is " +"disabled." +msgstr "" + +#: Library/RestAPI/Strings.cs:40 +msgid "" +"When running as a server, the service daemon must verify that the process is " +"responding. If this option is enabled, the server reads stdin and writes a " +"reply to each line read." +msgstr "" + +#: Library/RestAPI/Strings.cs:41 +msgid "Enable the ping-pong responder" +msgstr "" + +#: Library/RestAPI/Strings.cs:42 Library/Main/Strings.cs:255 +msgid "Set the time after which log data will be purged from the database." +msgstr "" + +#: Library/RestAPI/Strings.cs:43 Library/Main/Strings.cs:256 +msgid "Clean up old log data" +msgstr "" + +#: Library/RestAPI/Strings.cs:44 +#, csharp-format +msgid "" +"Duplicati needs to store a small database with all settings. Use this option " +"to choose where the settings are stored. This option can also be set with " +"the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:45 +msgid "Set the folder where settings are stored" +msgstr "" + +#: Library/RestAPI/Strings.cs:46 +#, csharp-format +msgid "" +"This option sets the encryption key used to scramble the local settings " +"database. This option can also be set with the environment variable {0}. Use " +"the option --{1} to disable the database scrambling." +msgstr "" + +#: Library/RestAPI/Strings.cs:47 +msgid "Set the database encryption key" +msgstr "" + +#: Library/RestAPI/Strings.cs:48 Library/Main/Strings.cs:96 +msgid "" +"Use this option to supply an alternative folder for temporary storage. By " +"default the system default temporary folder is used. Note that also SQLite " +"will put temporary files in this temporary folder." +msgstr "" + +#: Library/RestAPI/Strings.cs:49 Library/Main/Strings.cs:97 +msgid "Temporary storage folder" +msgstr "" + +#: Library/RestAPI/Strings.cs:50 +msgid "Reset the JWT configuration, invalidating any issued login tokens" +msgstr "" + +#: Library/RestAPI/Strings.cs:51 +msgid "Enable the use of long-lived access tokens" +msgstr "" + +#: Library/RestAPI/Strings.cs:52 +msgid "Disable the visual captcha" +msgstr "" + +#: Library/RestAPI/Strings.cs:53 +msgid "Disable the web interface and only allow API access" +msgstr "" + +#: Library/RestAPI/Strings.cs:54 +msgid "Disable the use of signin tokens" +msgstr "" + +#: Library/RestAPI/Strings.cs:55 +msgid "" +"The relative paths that should be served as single page applications, " +"separated with semicolons." +msgstr "" + +#: Library/RestAPI/Strings.cs:56 +msgid "" +"The timezone to use for the webserver. The timezone must be a valid timezone " +"identifier, such as \"America/New_York\" or \"UTC\". Common three-letter " +"abbreviations like \"CET\" are supported, but ambiguous in some cases." +msgstr "" + +#: Library/RestAPI/Strings.cs:57 +msgid "Use this option to disable database encryption of sensitive fields" +msgstr "" + +#: Library/RestAPI/Strings.cs:59 +msgid "" +"Use this option to log to the Windows event log. The provided name is in the " +"format Log:Source. If no log name is provided, Duplicati is used." +msgstr "" + +#: Library/RestAPI/Strings.cs:60 +msgid "Log to the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:61 +msgid "Use this option to set the log level for the Windows event log." +msgstr "" + +#: Library/RestAPI/Strings.cs:62 +msgid "Set the log level for the Windows event log" +msgstr "" + +#: Library/RestAPI/Strings.cs:63 +#, csharp-format +msgid "The Windows event log source {0} was not found. Attempting to create." +msgstr "" + +#: Library/RestAPI/Strings.cs:64 +#, csharp-format +msgid "" +"The Windows Event Log was not created for: {0}, not logging to eventlog." +msgstr "" + +#: Library/RestAPI/Strings.cs:65 +msgid "The Windows event log is not supported on this platform" +msgstr "" + +#: Library/RestAPI/Strings.cs:66 +#, csharp-format +msgid "Server has started and is listening on port {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:67 +#, csharp-format +msgid "Use the following link to sign in: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:68 +#, csharp-format +msgid "The server crashed: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:69 +msgid "Server is stopping, tearing down handlers" +msgstr "" + +#: Library/RestAPI/Strings.cs:70 +msgid "Server has stopped" +msgstr "" + +#: Library/RestAPI/Strings.cs:71 +msgid "" +"Use this option to require a custom provided key for database encryption of " +"sensitive fields and not rely on the serial number." +msgstr "" + +#: Library/RestAPI/Strings.cs:72 +msgid "Require database encryption" +msgstr "" + +#: Library/RestAPI/Strings.cs:73 +#, csharp-format +msgid "" +"Database encryption key is required. Supply an encryption key via the " +"environment variable {0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:74 +#, csharp-format +msgid "" +"The database encryption key is blacklisted and cannot be used. The database " +"has been decrypted. Supply a new encryption key via the environment variable " +"{0} or disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:75 +#, csharp-format +msgid "" +"No database encryption key was found. The database will be stored " +"unencrypted. Supply an encryption key via the environment variable {0} or " +"disable database encryption with the option --{1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:76 +#, csharp-format +msgid "" +"The database appears to be encrypted, but no key was specified. Opening the " +"database will likely fail. Use the environment variable {0} to specify the " +"key." +msgstr "" + +#: Library/RestAPI/Strings.cs:77 +#, csharp-format +msgid "The timezone {0} is not valid" +msgstr "" + +#: Library/RestAPI/Strings.cs:78 +msgid "Set the encryption key for the settings database" +msgstr "" + +#: Library/RestAPI/Strings.cs:79 +#, csharp-format +msgid "" +"Use this option to set the encryption key for the settings database. This " +"option can also be set with the environment variable {0}." +msgstr "" + +#: Library/RestAPI/Strings.cs:80 +#, csharp-format +msgid "Invalid pause/resume state: {0}" +msgstr "" + +#: Library/RestAPI/Strings.cs:84 +#, csharp-format +msgid "" +"Unable to find a valid date, given the start date {0}, the repetition " +"interval {1} and the allowed days {2}" +msgstr "" + +#: Library/RestAPI/Strings.cs:88 +#, csharp-format +msgid "Server has started and is listening on {0}, port {1}" +msgstr "" + +#: Library/RestAPI/Strings.cs:89 +msgid "" +"SSL certificate password option has no meaning when provided without SSL " +"certificate file option!" +msgstr "" + +#: Library/RestAPI/Strings.cs:90 +#, csharp-format +msgid "Unable to open a socket for listening, tried ports: {0}" +msgstr "" + #: Library/DynamicLoader/Strings.cs:24 #, csharp-format msgid "Failed to load assembly {0}, error message: {1}" @@ -2144,32 +3077,32 @@ msgstr "" msgid "Failed to load process type {0} assembly {1}, error message: {2}" msgstr "" -#: Library/Compression/Strings.cs:24 +#: Library/Compression/Strings.cs:26 msgid "" "This module provides the industry standard ZIP compression. Files created " "with this module can be read by any standard-compliant ZIP application." msgstr "" -#: Library/Compression/Strings.cs:25 +#: Library/Compression/Strings.cs:27 msgid "ZIP compression" msgstr "" -#: Library/Compression/Strings.cs:26 Library/Main/Strings.cs:202 +#: Library/Compression/Strings.cs:28 Library/Main/Strings.cs:207 #, csharp-format msgid "Use the option --{0} instead." msgstr "" -#: Library/Compression/Strings.cs:27 Library/Compression/Strings.cs:43 +#: Library/Compression/Strings.cs:29 msgid "" "This option controls the compression level used. A setting of zero gives no " "compression, and a setting of 9 gives maximum compression." msgstr "" -#: Library/Compression/Strings.cs:28 -msgid "Sets the ZIP compression level" +#: Library/Compression/Strings.cs:30 +msgid "Set the ZIP compression level" msgstr "" -#: Library/Compression/Strings.cs:29 +#: Library/Compression/Strings.cs:31 #, csharp-format msgid "" "Use this option to set an alternative compressor method, such as LZMA. Note " @@ -2177,68 +3110,35 @@ msgid "" "ignored." msgstr "" -#: Library/Compression/Strings.cs:30 -msgid "Sets the ZIP compression method" +#: Library/Compression/Strings.cs:32 +msgid "Set the ZIP compression method" msgstr "" -#: Library/Compression/Strings.cs:31 +#: Library/Compression/Strings.cs:33 msgid "" "The ZIP64 format is required for files larger than 4GiB. Use this option to " "toggle it." msgstr "" -#: Library/Compression/Strings.cs:32 -msgid "Toggles ZIP64 support" +#: Library/Compression/Strings.cs:34 +msgid "Toggle ZIP64 support" msgstr "" -#: Library/Compression/Strings.cs:33 -#, csharp-format -msgid "File not found: {0}" +#: Library/Compression/Strings.cs:35 +msgid "" +"This option changes the compression library used to read and write files. " +"The SharpCompress library has more features and is more resilient where the " +"built-in library is faster. When Auto is chosen, the built-in library will " +"be used unless an option is added that requires SharpCompress." msgstr "" #: Library/Compression/Strings.cs:36 -msgid "*Experimental*: 7z Archive with LZMA2 support." +msgid "Toggles the zip library to use" msgstr "" #: Library/Compression/Strings.cs:37 -msgid "Experimental - 7z Archive" -msgstr "" - -#: Library/Compression/Strings.cs:38 -msgid "Archive not opened for writing" -msgstr "" - -#: Library/Compression/Strings.cs:39 -msgid "Archive not opened for reading" -msgstr "" - -#: Library/Compression/Strings.cs:40 -msgid "The given file is not part of this archive" -msgstr "" - -#: Library/Compression/Strings.cs:41 -msgid "" -"The number of threads used in LZMA 2 compression. Defaults to the number of " -"processor cores." -msgstr "" - -#: Library/Compression/Strings.cs:42 -msgid "Number of threads used in compression" -msgstr "" - -#: Library/Compression/Strings.cs:44 -msgid "Sets the 7z compression level" -msgstr "" - -#: Library/Compression/Strings.cs:45 -msgid "" -"This option controls the compression algorithm used. Enabling this option " -"will cause 7z to use the fast algorithm, which produces slightly less " -"compression." -msgstr "" - -#: Library/Compression/Strings.cs:46 -msgid "Sets the 7z fast algorithm usage" +#, csharp-format +msgid "File not found: {0}" msgstr "" #: Library/SQLiteHelper/Strings.cs:24 @@ -2464,7 +3364,7 @@ msgid "" msgstr "" #: Library/Main/Strings.cs:65 -msgid "Toggles system sleep mode" +msgid "Toggle system sleep mode" msgstr "" #: Library/Main/Strings.cs:66 @@ -2622,60 +3522,67 @@ msgstr "" msgid "Limit the size of files being backed up" msgstr "" -#: Library/Main/Strings.cs:96 -msgid "" -"Use this option to supply an alternative folder for temporary storage. By " -"default the system default temporary folder is used. Note that also SQLite " -"will put temporary files in this temporary folder." -msgstr "" - -#: Library/Main/Strings.cs:97 -msgid "Temporary storage folder" -msgstr "" - #: Library/Main/Strings.cs:98 msgid "" -"Selects another thread priority for the process. Use this to set Duplicati " -"to be more or less CPU intensive." +"The option --thread-priority has no effect, use the operating system " +"controls to set the process priority" msgstr "" #: Library/Main/Strings.cs:99 -msgid "Thread priority" +msgid "" +"Select another thread priority for the process. Use this to set Duplicati to " +"be more or less CPU intensive." msgstr "" #: Library/Main/Strings.cs:100 +msgid "Thread priority" +msgstr "" + +#: Library/Main/Strings.cs:101 msgid "" "This option can change the maximum size of dblock files. Changing the size " "can be useful if the backend has a limit on the size of each individual file." msgstr "" -#: Library/Main/Strings.cs:101 +#: Library/Main/Strings.cs:102 msgid "Limit the size of the volumes" msgstr "" -#: Library/Main/Strings.cs:102 -msgid "" -"Enabling this option will disallow usage of the streaming interface, which " -"means that transfer progress bars will not show, and bandwidth throttle " -"settings will be ignored." -msgstr "" - #: Library/Main/Strings.cs:103 -msgid "Disables use of the streaming transfer method" +msgid "" +"Use this option to disallow usage of the streaming interface, which means " +"that transfer progress bars will not show, and bandwidth throttle settings " +"will be ignored." msgstr "" #: Library/Main/Strings.cs:104 +msgid "Disable use of the streaming transfer method" +msgstr "" + +#: Library/Main/Strings.cs:105 +msgid "Set the read/write timeout for the connection" +msgstr "" + +#: Library/Main/Strings.cs:106 +msgid "" +"The read/write timeout is the maximum amount of time to wait for any " +"activity during a transfer. If no activity is detected for this period, the " +"connection is considered broken and the transfer is aborted. Set to 0s to " +"disabled" +msgstr "" + +#: Library/Main/Strings.cs:107 msgid "" "Use this option to make sure the contents of the manifest file are not read. " "This also implies that file hashes are not checked either. Use only for " "disaster recovery." msgstr "" -#: Library/Main/Strings.cs:105 -msgid "Disables manifests verification" +#: Library/Main/Strings.cs:108 +msgid "Disable manifests verification" msgstr "" -#: Library/Main/Strings.cs:106 +#: Library/Main/Strings.cs:109 msgid "" "Duplicati supports pluggable compression modules. Use this option to select " "a module to use for compression. This is only applied when creating new " @@ -2683,11 +3590,11 @@ msgid "" "compression module." msgstr "" -#: Library/Main/Strings.cs:107 +#: Library/Main/Strings.cs:110 msgid "Select what module to use for compression" msgstr "" -#: Library/Main/Strings.cs:108 +#: Library/Main/Strings.cs:111 msgid "" "Duplicati supports pluggable encryption modules. Use this option to select a " "module to use for encryption. This is only applied when creating new " @@ -2695,27 +3602,27 @@ msgid "" "encryption module." msgstr "" -#: Library/Main/Strings.cs:109 +#: Library/Main/Strings.cs:112 msgid "Select what module to use for encryption" msgstr "" -#: Library/Main/Strings.cs:110 +#: Library/Main/Strings.cs:113 msgid "Supply one or more module names, separated by commas to unload them." msgstr "" -#: Library/Main/Strings.cs:111 -msgid "Disables one or more modules" +#: Library/Main/Strings.cs:114 +msgid "Disable one or more modules" msgstr "" -#: Library/Main/Strings.cs:112 +#: Library/Main/Strings.cs:115 msgid "Supply one or more module names, separated by commas to load them." msgstr "" -#: Library/Main/Strings.cs:113 -msgid "Enables one or more modules" +#: Library/Main/Strings.cs:116 +msgid "Enable one or more modules" msgstr "" -#: Library/Main/Strings.cs:114 +#: Library/Main/Strings.cs:117 msgid "" "This setting controls the usage of snapshots, which allows Duplicati to " "backup files that are locked by other programs. If this is set to \"off\", " @@ -2730,88 +3637,90 @@ msgid "" "Management (LVM) and requires root privileges." msgstr "" -#: Library/Main/Strings.cs:115 -msgid "Controls the use of disk snapshots" +#: Library/Main/Strings.cs:118 +msgid "Control the use of disk snapshots" msgstr "" -#: Library/Main/Strings.cs:116 +#: Library/Main/Strings.cs:119 msgid "" "The pre-generated volumes will be placed into the temporary folder by " "default. This option can set a different folder for placing the temporary " "volumes. Despite the name, this also works for synchronous runs." msgstr "" -#: Library/Main/Strings.cs:117 +#: Library/Main/Strings.cs:120 msgid "The path where ready volumes are placed until uploaded" msgstr "" -#: Library/Main/Strings.cs:118 +#: Library/Main/Strings.cs:121 msgid "" "When performing asynchronous uploads, Duplicati will create volumes that can " "be uploaded. To prevent Duplicati from generating too many volumes, this " "option limits the number of pending uploads. Set to zero to disable the " -"limit." +"limit. The volume(s) that are being created are not counted in this limit. " +"Use the option --concurrency-compressors=1 to limit the number of volumes " +"being created." msgstr "" -#: Library/Main/Strings.cs:119 +#: Library/Main/Strings.cs:122 msgid "The number of volumes to create ahead of time" msgstr "" -#: Library/Main/Strings.cs:120 +#: Library/Main/Strings.cs:123 msgid "" "When performing asynchronous uploads, the maximum number of concurrent " "uploads allowed. Set to zero to disable the limit." msgstr "" -#: Library/Main/Strings.cs:121 +#: Library/Main/Strings.cs:124 msgid "The number of concurrent uploads allowed" msgstr "" -#: Library/Main/Strings.cs:122 +#: Library/Main/Strings.cs:125 msgid "" "Activate this option to make some error messages more verbose, which may " "help you track down a particular issue." msgstr "" -#: Library/Main/Strings.cs:123 -msgid "Enables debugging output" -msgstr "" - -#: Library/Main/Strings.cs:124 -msgid "Logs information to the file specified." -msgstr "" - -#: Library/Main/Strings.cs:125 -msgid "Log internal information to a file" -msgstr "" - -#: Library/Main/Strings.cs:126 Library/Main/Strings.cs:283 -#, csharp-format -msgid "" -"Specifies the amount of log information to write into the file specified by " -"the option --{0}." +#: Library/Main/Strings.cs:126 +msgid "Enable debugging output" msgstr "" #: Library/Main/Strings.cs:127 +msgid "Log information to the file specified." +msgstr "" + +#: Library/Main/Strings.cs:128 +msgid "Log internal information to a file" +msgstr "" + +#: Library/Main/Strings.cs:129 Library/Main/Strings.cs:288 +#, csharp-format +msgid "" +"Specify the amount of log information to write into the file specified by " +"the option --{0}." +msgstr "" + +#: Library/Main/Strings.cs:130 msgid "Log information level" msgstr "" -#: Library/Main/Strings.cs:128 Library/Main/Strings.cs:217 +#: Library/Main/Strings.cs:131 Library/Main/Strings.cs:222 #, csharp-format msgid "Use the options --{0} and --{1} instead." msgstr "" -#: Library/Main/Strings.cs:129 +#: Library/Main/Strings.cs:132 msgid "" "If Duplicati detects that the target folder is missing, it will create it " "automatically. Activate this option to prevent automatic folder creation." msgstr "" -#: Library/Main/Strings.cs:130 -msgid "Disables automatic folder creation" +#: Library/Main/Strings.cs:133 +msgid "Disable automatic folder creation" msgstr "" -#: Library/Main/Strings.cs:131 +#: Library/Main/Strings.cs:134 msgid "" "Use this option to exclude faulty writers from a snapshot. This is " "equivalent to the -wx flag of the vshadow.exe tool, except that it only " @@ -2820,12 +3729,12 @@ msgid "" "are allowed, including with and without curly braces." msgstr "" -#: Library/Main/Strings.cs:132 +#: Library/Main/Strings.cs:135 msgid "" "A semicolon separated list of guids of VSS writers to exclude (Windows only)" msgstr "" -#: Library/Main/Strings.cs:133 +#: Library/Main/Strings.cs:136 msgid "" "This setting controls the usage of NTFS USN numbers, which allows Duplicati " "to obtain a list of files and folders much faster. If this is set to " @@ -2838,11 +3747,22 @@ msgid "" "administrative privileges." msgstr "" -#: Library/Main/Strings.cs:134 -msgid "Controls the use of NTFS Update Sequence Numbers" +#: Library/Main/Strings.cs:137 +msgid "Control the use of NTFS Update Sequence Numbers" msgstr "" -#: Library/Main/Strings.cs:135 +#: Library/Main/Strings.cs:138 +msgid "Ignore advisory locking" +msgstr "" + +#: Library/Main/Strings.cs:139 +msgid "" +"When reading files Duplicati can skip files that are marked locked by " +"another application to ensure consistency. This flag can disable the check " +"and perform optimistic reads of locked files." +msgstr "" + +#: Library/Main/Strings.cs:140 #, csharp-format msgid "" "When matching timestamps, Duplicati will adjust the times by a small " @@ -2855,30 +3775,31 @@ msgid "" "strict time checking." msgstr "" -#: Library/Main/Strings.cs:136 -msgid "Deactivates tolerance when comparing times" +#: Library/Main/Strings.cs:141 +msgid "Deactivate tolerance when comparing times" msgstr "" -#: Library/Main/Strings.cs:137 +#: Library/Main/Strings.cs:142 msgid "Use this option to verify uploads by listing contents." msgstr "" -#: Library/Main/Strings.cs:138 +#: Library/Main/Strings.cs:143 msgid "Verify uploads by listing contents" msgstr "" -#: Library/Main/Strings.cs:139 +#: Library/Main/Strings.cs:144 msgid "" -"Duplicati will upload files while scanning the disk and producing volumes, " -"which usually makes the backup faster. Use this option to turn the behavior " -"off, so that Duplicati will wait for each volume to complete." +"Disables uploading multiple files concurrently to preserve bandwith. This " +"will have the same effect as setting --asynchronous-upload-limit=1 but " +"additionally wait for related uploads. The volume that is being created is " +"not counted in the upload limit." msgstr "" -#: Library/Main/Strings.cs:140 +#: Library/Main/Strings.cs:145 msgid "Upload files synchronously" msgstr "" -#: Library/Main/Strings.cs:141 +#: Library/Main/Strings.cs:146 msgid "" "Duplicati will attempt to perform multiple operations on a single " "connection, as this avoids repeated login attempts, and thus speeds up the " @@ -2886,68 +3807,68 @@ msgid "" "seperate connection." msgstr "" -#: Library/Main/Strings.cs:142 +#: Library/Main/Strings.cs:147 msgid "Do not re-use connections" msgstr "" -#: Library/Main/Strings.cs:143 +#: Library/Main/Strings.cs:148 msgid "" "When an error occurs, Duplicati will silently retry, and only report the " "number of retries. Enable this option to have the error messages displayed " "when a retry is performed." msgstr "" -#: Library/Main/Strings.cs:144 +#: Library/Main/Strings.cs:149 msgid "Show error messages when a retry is performed" msgstr "" -#: Library/Main/Strings.cs:145 +#: Library/Main/Strings.cs:150 msgid "" "If no files have changed, Duplicati will not upload a backup set. If the " "backup data is used to verify that a backup was executed, this option will " "make Duplicati upload a backupset even if it is empty." msgstr "" -#: Library/Main/Strings.cs:146 +#: Library/Main/Strings.cs:151 msgid "Upload empty backup files" msgstr "" -#: Library/Main/Strings.cs:147 +#: Library/Main/Strings.cs:152 msgid "" "Set a limit to the amount of storage used on the backend (by this backup). " "This is in addition to the full backend quota, if available. Note: Backups " "will continue past the quota. This only creates warnings and error messages." msgstr "" -#: Library/Main/Strings.cs:148 +#: Library/Main/Strings.cs:153 msgid "Limit storage use" msgstr "" -#: Library/Main/Strings.cs:149 +#: Library/Main/Strings.cs:154 msgid "" -"Sets a threshold for when to warn about the backend quota being nearly " +"Set a threshold for when to warn about the backend quota being nearly " "exceeded. It is given as a percentage, and a warning is generated if the " "amount of available quota is less than this percentage of the total backup " "size. If the backend does not report the quota information, this value will " "be ignored." msgstr "" -#: Library/Main/Strings.cs:150 +#: Library/Main/Strings.cs:155 msgid "Threshold for warning about low quota" msgstr "" -#: Library/Main/Strings.cs:151 +#: Library/Main/Strings.cs:156 #, csharp-format msgid "" "Disable the quota reported by the backend. The option --{0} can still be " "used to set a manual quota" msgstr "" -#: Library/Main/Strings.cs:152 +#: Library/Main/Strings.cs:157 msgid "Disable backend quota" msgstr "" -#: Library/Main/Strings.cs:153 +#: Library/Main/Strings.cs:158 #, csharp-format msgid "" "Use this option to handle symlinks differently. The \"{0}\" option will " @@ -2959,11 +3880,11 @@ msgid "" "option and behaved as if \"{2}\" was specified." msgstr "" -#: Library/Main/Strings.cs:154 +#: Library/Main/Strings.cs:159 msgid "Symlink handling" msgstr "" -#: Library/Main/Strings.cs:155 +#: Library/Main/Strings.cs:160 #, csharp-format msgid "" "Use this option to handle hardlinks (only works on Linux/OSX). The \"{0}\" " @@ -2973,11 +3894,11 @@ msgid "" "will ignore all hardlinks with more than one link." msgstr "" -#: Library/Main/Strings.cs:156 +#: Library/Main/Strings.cs:161 msgid "Hardlink handling" msgstr "" -#: Library/Main/Strings.cs:157 +#: Library/Main/Strings.cs:162 #, csharp-format msgid "" "Use this option to exclude files with certain attributes. Use a comma " @@ -2985,11 +3906,11 @@ msgid "" "are: {0}." msgstr "" -#: Library/Main/Strings.cs:158 +#: Library/Main/Strings.cs:163 msgid "Exclude files by attribute" msgstr "" -#: Library/Main/Strings.cs:159 +#: Library/Main/Strings.cs:164 msgid "" "Activate this option to map VSS snapshots to a drive (similar to SUBST, " "using Win32 DefineDosDevice). This will create temporary drives that are " @@ -2997,41 +3918,51 @@ msgid "" "file access on Windows XP." msgstr "" -#: Library/Main/Strings.cs:160 +#: Library/Main/Strings.cs:165 msgid "Map snapshots to a drive (Windows only)" msgstr "" -#: Library/Main/Strings.cs:161 +#: Library/Main/Strings.cs:166 msgid "" "A display name that is attached to this backup. This can be used to identify " "the backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:162 +#: Library/Main/Strings.cs:167 msgid "Name of the backup" msgstr "" -#: Library/Main/Strings.cs:163 +#: Library/Main/Strings.cs:168 msgid "" "A unique identification for this backup. This can be used to identify the " "backup when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:164 +#: Library/Main/Strings.cs:169 msgid "Backup ID" msgstr "" -#: Library/Main/Strings.cs:165 +#: Library/Main/Strings.cs:170 msgid "" "A unique identification of the machine running the backup. This can be used " "to identify the machine when sending mail or running scripts." msgstr "" -#: Library/Main/Strings.cs:166 +#: Library/Main/Strings.cs:171 msgid "Machine ID" msgstr "" -#: Library/Main/Strings.cs:167 +#: Library/Main/Strings.cs:172 +msgid "" +"The name of the machine running the backup. This can be used to identify the " +"machine when sending mail or running scripts." +msgstr "" + +#: Library/Main/Strings.cs:173 +msgid "Machine name" +msgstr "" + +#: Library/Main/Strings.cs:174 #, csharp-format msgid "" "Use this option to point to a text file where each line contains a file " @@ -3043,11 +3974,11 @@ msgid "" "{0}." msgstr "" -#: Library/Main/Strings.cs:168 +#: Library/Main/Strings.cs:175 msgid "Manage non-compressible file extensions" msgstr "" -#: Library/Main/Strings.cs:169 +#: Library/Main/Strings.cs:176 msgid "" "The block size determines how files are fragmented. Choosing a large value " "will cause a larger overhead on file changes, choosing a small value will " @@ -3055,71 +3986,63 @@ msgid "" "be changed after remote files are created." msgstr "" -#: Library/Main/Strings.cs:170 +#: Library/Main/Strings.cs:177 msgid "Block size used in hashing" msgstr "" -#: Library/Main/Strings.cs:171 +#: Library/Main/Strings.cs:178 msgid "" "Use this option to limit the scan to only files that are known to have " "changed. This is usually only activated in combination with a filesystem " "watcher that keeps track of file changes." msgstr "" -#: Library/Main/Strings.cs:172 +#: Library/Main/Strings.cs:179 msgid "List of files to examine for changes" msgstr "" -#: Library/Main/Strings.cs:173 +#: Library/Main/Strings.cs:180 msgid "" "Path to the file containing the local cache of the remote file database." msgstr "" -#: Library/Main/Strings.cs:174 +#: Library/Main/Strings.cs:181 msgid "Path to the local state database" msgstr "" -#: Library/Main/Strings.cs:175 +#: Library/Main/Strings.cs:182 #, csharp-format msgid "" "Use this option to supply a list of deleted files. This option will be " "ignored unless the option --{0} is also set." msgstr "" -#: Library/Main/Strings.cs:176 +#: Library/Main/Strings.cs:183 msgid "List of deleted files" msgstr "" -#: Library/Main/Strings.cs:177 +#: Library/Main/Strings.cs:184 msgid "" "Use this option to reduce the memory footprint by not keeping paths and " "modification timestamps in memory." msgstr "" -#: Library/Main/Strings.cs:178 +#: Library/Main/Strings.cs:185 msgid "Reduce memory footprint by disabling in-memory lookups" msgstr "" -#: Library/Main/Strings.cs:180 -msgid "Use this option to increase speed in exchange for extra memory use." -msgstr "" - -#: Library/Main/Strings.cs:181 -msgid "Store an in-memory block cache" -msgstr "" - -#: Library/Main/Strings.cs:182 +#: Library/Main/Strings.cs:187 msgid "" "If this option is set, the local database is not compared to the remote " "filelist on startup. The intended usage for this option is to work correctly " "in cases where the filelisting is broken or unavailable." msgstr "" -#: Library/Main/Strings.cs:183 +#: Library/Main/Strings.cs:188 msgid "Do not query backend at startup" msgstr "" -#: Library/Main/Strings.cs:184 +#: Library/Main/Strings.cs:189 msgid "" "The index files are used to limit the need for downloading dblock files when " "there is no local database present. The more information is recorded in the " @@ -3128,11 +4051,11 @@ msgid "" "never be used." msgstr "" -#: Library/Main/Strings.cs:185 -msgid "Determines usage of index files" +#: Library/Main/Strings.cs:190 +msgid "Determine usage of index files" msgstr "" -#: Library/Main/Strings.cs:186 +#: Library/Main/Strings.cs:191 msgid "" "As files are changed, some data stored at the remote destination may not be " "required. This option controls how much wasted space the destination can " @@ -3140,43 +4063,43 @@ msgid "" "volume and the total storage." msgstr "" -#: Library/Main/Strings.cs:187 +#: Library/Main/Strings.cs:192 msgid "The maximum wasted space in percent" msgstr "" -#: Library/Main/Strings.cs:188 +#: Library/Main/Strings.cs:193 msgid "" "Use this option to experiment with different settings and observe the " "outcome without changing actual files." msgstr "" -#: Library/Main/Strings.cs:189 -msgid "Does not perform any modifications" +#: Library/Main/Strings.cs:194 +msgid "Do not perform any modifications" msgstr "" -#: Library/Main/Strings.cs:190 +#: Library/Main/Strings.cs:195 msgid "" "This is a very advanced option! Use this option to select a block hash " "algorithm with smaller or larger hash size, for performance or storage space " "reasons." msgstr "" -#: Library/Main/Strings.cs:191 +#: Library/Main/Strings.cs:196 msgid "The hash algorithm used on blocks" msgstr "" -#: Library/Main/Strings.cs:192 +#: Library/Main/Strings.cs:197 msgid "" "This is a very advanced option! Use this option to select a file hash " "algorithm with smaller or larger hash size, for performance or storage space " "reasons." msgstr "" -#: Library/Main/Strings.cs:193 +#: Library/Main/Strings.cs:198 msgid "The hash algorithm used on files" msgstr "" -#: Library/Main/Strings.cs:194 +#: Library/Main/Strings.cs:199 msgid "" "If a large number of small files are detected during a backup, or wasted " "space is found after deleting backups, the remote data will be compacted. " @@ -3184,11 +4107,11 @@ msgid "" "running the compact command." msgstr "" -#: Library/Main/Strings.cs:195 +#: Library/Main/Strings.cs:200 msgid "Disable automatic compacting" msgstr "" -#: Library/Main/Strings.cs:196 +#: Library/Main/Strings.cs:201 msgid "" "When examining the size of a volume in consideration for compacting, a small " "tolerance value is used, by default 20 percent of the volume size. This " @@ -3196,61 +4119,61 @@ msgid "" "downloaded and rewritten." msgstr "" -#: Library/Main/Strings.cs:197 +#: Library/Main/Strings.cs:202 msgid "Volume size threshold" msgstr "" -#: Library/Main/Strings.cs:198 +#: Library/Main/Strings.cs:203 msgid "" "To avoid filling the remote storage with small files, this value can force " "grouping small files. The small volumes will always be combined when they " "can fill an entire volume." msgstr "" -#: Library/Main/Strings.cs:199 +#: Library/Main/Strings.cs:204 msgid "Maximum number of small volumes" msgstr "" -#: Library/Main/Strings.cs:200 +#: Library/Main/Strings.cs:205 msgid "" "Enable this option to look into other files on this machine to find existing " "blocks. This is a fairly slow operation but can limit the size of downloads." msgstr "" -#: Library/Main/Strings.cs:201 +#: Library/Main/Strings.cs:206 msgid "Use local file data when restoring" msgstr "" -#: Library/Main/Strings.cs:203 +#: Library/Main/Strings.cs:208 msgid "" "When listing contents or when restoring files, the local database can be " "skipped. This is usually slower, but can be used to verify the actual " "contents of the remote store." msgstr "" -#: Library/Main/Strings.cs:204 -msgid "Disables the local database" +#: Library/Main/Strings.cs:209 +msgid "Disable the local database" msgstr "" -#: Library/Main/Strings.cs:205 +#: Library/Main/Strings.cs:210 msgid "" "Use this option to set number of versions to keep. Supply -1 to keep all " "versions." msgstr "" -#: Library/Main/Strings.cs:206 +#: Library/Main/Strings.cs:211 msgid "Keep a number of versions" msgstr "" -#: Library/Main/Strings.cs:207 +#: Library/Main/Strings.cs:212 msgid "Use this option to set the timespan in which backups are kept." msgstr "" -#: Library/Main/Strings.cs:208 +#: Library/Main/Strings.cs:213 msgid "Keep all versions within a timespan" msgstr "" -#: Library/Main/Strings.cs:209 +#: Library/Main/Strings.cs:214 msgid "" "Use this option to reduce the number of versions that are kept with " "increasing version age by deleting most of the old backups. The expected " @@ -3261,60 +4184,60 @@ msgid "" "supports using the specifier \"U\" to indicate an unlimited time interval." msgstr "" -#: Library/Main/Strings.cs:210 +#: Library/Main/Strings.cs:215 msgid "Reduce number of versions by deleting old intermediate backups" msgstr "" -#: Library/Main/Strings.cs:211 +#: Library/Main/Strings.cs:216 msgid "Use this option to continue even if some source entries are missing." msgstr "" -#: Library/Main/Strings.cs:212 +#: Library/Main/Strings.cs:217 msgid "Ignore missing source elements" msgstr "" -#: Library/Main/Strings.cs:213 +#: Library/Main/Strings.cs:218 msgid "" "Use this option to overwrite target files when restoring. If this option is " "not set, the files will be restored with a timestamp and a number appended." msgstr "" -#: Library/Main/Strings.cs:214 +#: Library/Main/Strings.cs:219 msgid "Overwrite files when restoring" msgstr "" -#: Library/Main/Strings.cs:215 +#: Library/Main/Strings.cs:220 msgid "" "Use this option to increase the amount of output generated when running an " "option. Generally this option will produce a line for each file processed." msgstr "" -#: Library/Main/Strings.cs:216 +#: Library/Main/Strings.cs:221 msgid "Output more progress information" msgstr "" -#: Library/Main/Strings.cs:218 +#: Library/Main/Strings.cs:223 msgid "" "Use this option to increase the amount of output generated as the result of " "the operation, including all filenames." msgstr "" -#: Library/Main/Strings.cs:219 +#: Library/Main/Strings.cs:224 msgid "Output full results" msgstr "" -#: Library/Main/Strings.cs:220 +#: Library/Main/Strings.cs:225 msgid "" "Use this option to upload a verification file after changing the remote " "storage. The file is not encrypted and contains the size and SHA256 hashes " "of all the remote files and can be used to verify the integrity of the files." msgstr "" -#: Library/Main/Strings.cs:221 +#: Library/Main/Strings.cs:226 msgid "Determine if verification files are uploaded" msgstr "" -#: Library/Main/Strings.cs:222 +#: Library/Main/Strings.cs:227 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3324,11 +4247,11 @@ msgid "" "option --{1} is set, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:223 +#: Library/Main/Strings.cs:228 msgid "The number of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:224 +#: Library/Main/Strings.cs:229 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3338,11 +4261,11 @@ msgid "" "options. If the option --{1} is provided, no remote files are verified." msgstr "" -#: Library/Main/Strings.cs:225 +#: Library/Main/Strings.cs:230 msgid "The percentage of samples to test after a backup" msgstr "" -#: Library/Main/Strings.cs:226 +#: Library/Main/Strings.cs:231 #, csharp-format msgid "" "After a backup is completed, some (dblock, dindex, dlist) files from the " @@ -3354,119 +4277,111 @@ msgid "" "but only dlist and index volumes are handled." msgstr "" -#: Library/Main/Strings.cs:227 -msgid "Activates in-depth verification of files" +#: Library/Main/Strings.cs:232 +msgid "Activate in-depth verification of files" msgstr "" -#: Library/Main/Strings.cs:228 +#: Library/Main/Strings.cs:233 msgid "" "Use this size to control how many bytes are read from a file before " "processing." msgstr "" -#: Library/Main/Strings.cs:229 +#: Library/Main/Strings.cs:234 msgid "Size of the file read buffer" msgstr "" -#: Library/Main/Strings.cs:231 +#: Library/Main/Strings.cs:236 msgid "" "Use this option to allow the passphrase to change. Note that this option is " "not permitted for a backup or repair operation." msgstr "" -#: Library/Main/Strings.cs:232 +#: Library/Main/Strings.cs:237 msgid "Allow the passphrase to change" msgstr "" -#: Library/Main/Strings.cs:233 +#: Library/Main/Strings.cs:238 msgid "" "Use this option to only list filesets and avoid traversing file names and " "other metadata which slows down the process." msgstr "" -#: Library/Main/Strings.cs:234 +#: Library/Main/Strings.cs:239 msgid "List only filesets" msgstr "" -#: Library/Main/Strings.cs:236 +#: Library/Main/Strings.cs:241 msgid "" "Use this option to disable the storage of metadata, such as file timestamps. " "Disabling metadata storage will speed up the backup and restore operations, " "but does not affect file size much." msgstr "" -#: Library/Main/Strings.cs:237 -msgid "Don't store metadata" +#: Library/Main/Strings.cs:242 +msgid "Do not store metadata" msgstr "" -#: Library/Main/Strings.cs:238 +#: Library/Main/Strings.cs:243 msgid "" "By default permissions are not restored as they might prevent you from " "accessing your files. Use this option to restore the permissions as well." msgstr "" -#: Library/Main/Strings.cs:239 +#: Library/Main/Strings.cs:244 msgid "Restore file permissions" msgstr "" -#: Library/Main/Strings.cs:240 +#: Library/Main/Strings.cs:245 msgid "" "After restoring files, the file hash of all restored files are checked to " "verify that the restore was successful. Use this option to disable the check " "and avoid waiting for the verification." msgstr "" -#: Library/Main/Strings.cs:241 +#: Library/Main/Strings.cs:246 msgid "Skip restored file check" msgstr "" -#: Library/Main/Strings.cs:242 +#: Library/Main/Strings.cs:247 msgid "" "Duplicati will attempt to use data from source files to minimize the amount " "of downloaded data. Use this option to skip this optimization and only use " "remote data." msgstr "" -#: Library/Main/Strings.cs:243 +#: Library/Main/Strings.cs:248 msgid "Do not use local data" msgstr "" -#: Library/Main/Strings.cs:244 +#: Library/Main/Strings.cs:249 #, csharp-format msgid "" "The default is now to not use local blocks for restore. To opt-in for using " "local blocks, set the option --{0}." msgstr "" -#: Library/Main/Strings.cs:245 +#: Library/Main/Strings.cs:250 msgid "" "Use this option to allow Duplicati to use blocks found on disk when " "performing restores, instead of only using files in remote storage." msgstr "" -#: Library/Main/Strings.cs:246 +#: Library/Main/Strings.cs:251 msgid "Use existing data for restore" msgstr "" -#: Library/Main/Strings.cs:248 +#: Library/Main/Strings.cs:253 msgid "" "Use this option to increase verification by checking the hash of blocks read " "from a volume before patching restored files with the data." msgstr "" -#: Library/Main/Strings.cs:249 +#: Library/Main/Strings.cs:254 msgid "Check block hashes" msgstr "" -#: Library/Main/Strings.cs:250 -msgid "Set the time after which log data will be purged from the database." -msgstr "" - -#: Library/Main/Strings.cs:251 -msgid "Clean up old log data" -msgstr "" - -#: Library/Main/Strings.cs:252 +#: Library/Main/Strings.cs:257 msgid "" "Use this option to build a searchable local database which only contains " "path information. This option is usable for quickly building a database to " @@ -3474,11 +4389,11 @@ msgid "" "resulting database can be searched, but cannot be used to restore data with." msgstr "" -#: Library/Main/Strings.cs:253 +#: Library/Main/Strings.cs:258 msgid "Repair database with paths" msgstr "" -#: Library/Main/Strings.cs:254 +#: Library/Main/Strings.cs:259 msgid "" "By default, your system locale and culture settings will be used. In some " "cases you may prefer to run with another locale, for example to get messages " @@ -3486,74 +4401,74 @@ msgid "" "string to choose the \"Invariant Culture\"." msgstr "" -#: Library/Main/Strings.cs:255 +#: Library/Main/Strings.cs:260 msgid "Force the locale setting" msgstr "" -#: Library/Main/Strings.cs:256 +#: Library/Main/Strings.cs:261 msgid "" "By default, dates are displayed in the calendar format, meaning \"Today\" or " "\"Last Thursday\". By setting this option, only the actual dates are " "displayed, \"Nov 12, 2018, 8:01 AM\" for example." msgstr "" -#: Library/Main/Strings.cs:257 -msgid "Forces the display of the actual date instead of calendar date" +#: Library/Main/Strings.cs:262 +msgid "Force the display of the actual date instead of calendar date" msgstr "" -#: Library/Main/Strings.cs:258 +#: Library/Main/Strings.cs:263 msgid "" "Use this option to disable multithreaded handling of up- and downloads. That " "can significantly speed up backend operations depending on the hardware " "you're running on and the transfer rate of your backend." msgstr "" -#: Library/Main/Strings.cs:259 +#: Library/Main/Strings.cs:264 msgid "Handle file communication with backend using threaded pipes" msgstr "" -#: Library/Main/Strings.cs:260 +#: Library/Main/Strings.cs:265 msgid "" "Use this option to set the maximum number of threads used. Setting this " "value to zero or less will dynamically balance the number of active threads " "to fit the hardware." msgstr "" -#: Library/Main/Strings.cs:261 +#: Library/Main/Strings.cs:266 msgid "Limit number of concurrent threads" msgstr "" -#: Library/Main/Strings.cs:262 +#: Library/Main/Strings.cs:267 msgid "" "Use this option to set the number of processes that perform hashing of data." msgstr "" -#: Library/Main/Strings.cs:263 +#: Library/Main/Strings.cs:268 msgid "Specify the number of concurrent hashing processes" msgstr "" -#: Library/Main/Strings.cs:264 +#: Library/Main/Strings.cs:269 msgid "" "Use this option to set the number of processes that perform compression of " "output data." msgstr "" -#: Library/Main/Strings.cs:265 +#: Library/Main/Strings.cs:270 msgid "Specify the number of concurrent compression processes" msgstr "" -#: Library/Main/Strings.cs:266 +#: Library/Main/Strings.cs:271 msgid "" "If Duplicati detects that the previous backup did not complete, it will " "generate a filelist that is a merge of the last completed backup and the " "contents that were uploaded in the incomplete backup session." msgstr "" -#: Library/Main/Strings.cs:267 -msgid "Disables synthetic filelist" +#: Library/Main/Strings.cs:272 +msgid "Disable synthetic filelist" msgstr "" -#: Library/Main/Strings.cs:268 +#: Library/Main/Strings.cs:273 msgid "" "This option instructs Duplicati to not look at metadata or filesize when " "deciding to scan a file for changes. Use this option if you have a large " @@ -3561,11 +4476,11 @@ msgid "" "unmodified files." msgstr "" -#: Library/Main/Strings.cs:269 -msgid "Checks only file lastmodified" +#: Library/Main/Strings.cs:274 +msgid "Check only file lastmodified" msgstr "" -#: Library/Main/Strings.cs:270 +#: Library/Main/Strings.cs:275 msgid "" "When restore a subset of a backup into a new folder, the shortest possible " "path is used to avoid generating deep paths with empty folders. Use this " @@ -3573,11 +4488,11 @@ msgid "" "structure is preserved, including upper level empty folders." msgstr "" -#: Library/Main/Strings.cs:271 -msgid "Disables path compression on restore" +#: Library/Main/Strings.cs:276 +msgid "Disable path compression on restore" msgstr "" -#: Library/Main/Strings.cs:272 +#: Library/Main/Strings.cs:277 msgid "" "By default, the last fileset cannot be removed. This is a safeguard to make " "sure that all remote data is not deleted by a configuration mistake. Use " @@ -3585,11 +4500,11 @@ msgid "" "deleted." msgstr "" -#: Library/Main/Strings.cs:273 +#: Library/Main/Strings.cs:278 msgid "Allow removing all filesets" msgstr "" -#: Library/Main/Strings.cs:274 +#: Library/Main/Strings.cs:279 msgid "" "Some operations that manipulate the local database leave unused entries " "behind. These entries are not deleted from a hard drive until a VACUUM " @@ -3599,11 +4514,11 @@ msgid "" "discretion." msgstr "" -#: Library/Main/Strings.cs:275 +#: Library/Main/Strings.cs:280 msgid "Allow automatic rebuilding of local database to save space" msgstr "" -#: Library/Main/Strings.cs:276 +#: Library/Main/Strings.cs:281 msgid "" "When this flag is enabled, the scanner that computes the size of source " "files is disabled, and instead the reported size is read from the database. " @@ -3611,38 +4526,38 @@ msgid "" "give a less accurate progress indicator." msgstr "" -#: Library/Main/Strings.cs:277 +#: Library/Main/Strings.cs:282 msgid "Disable the read-ahead scanner" msgstr "" -#: Library/Main/Strings.cs:278 +#: Library/Main/Strings.cs:283 msgid "" "In backups with a large number of filesets, the verification can take up a " "large part of the backup time. If you disable the checks, make sure you run " "regular check commands to ensure that everything is working as expected." msgstr "" -#: Library/Main/Strings.cs:279 +#: Library/Main/Strings.cs:284 msgid "Disable filelist consistency checks" msgstr "" -#: Library/Main/Strings.cs:280 +#: Library/Main/Strings.cs:285 msgid "" -"Use this option to run a scheduled backup if the system is detected to be " -"running on battery power (manual or command line backups will still be run). " -"If the detected power source is mains (e.g., AC) or unknown, then scheduled " -"backups will proceed as normal." +"Use this option to disable a scheduled backup if the system is detected to " +"be running on battery power (manual or command line backups will still be " +"run). If the detected power source is mains (e.g., AC) or unknown, then " +"scheduled backups will proceed as normal." msgstr "" -#: Library/Main/Strings.cs:281 +#: Library/Main/Strings.cs:286 msgid "Disable the backup when on battery power" msgstr "" -#: Library/Main/Strings.cs:284 +#: Library/Main/Strings.cs:289 msgid "Log file information level" msgstr "" -#: Library/Main/Strings.cs:285 +#: Library/Main/Strings.cs:290 #, csharp-format msgid "" "This option accepts filters that removes or includes messages regardless of " @@ -3652,42 +4567,42 @@ msgid "" "Example: \"+Path*{0}+*Mail*{0}-[.*DNS]\" " msgstr "" -#: Library/Main/Strings.cs:286 -msgid "Applies filters to the file log data" -msgstr "" - -#: Library/Main/Strings.cs:287 -msgid "Specifies the amount of log information to output to the console." -msgstr "" - -#: Library/Main/Strings.cs:288 -msgid "Console information level" -msgstr "" - -#: Library/Main/Strings.cs:290 -msgid "Applies filters to the console log data" +#: Library/Main/Strings.cs:291 +msgid "Apply filters to the file log data" msgstr "" #: Library/Main/Strings.cs:292 +msgid "Specify the amount of log information to output to the console." +msgstr "" + +#: Library/Main/Strings.cs:293 +msgid "Console information level" +msgstr "" + +#: Library/Main/Strings.cs:295 +msgid "Apply filters to the console log data" +msgstr "" + +#: Library/Main/Strings.cs:297 msgid "" "This option instructs the operating system to set the current process to use " "the lowest IO priority level, which can make operations run slower but will " "interfere less with other operations running at the same time." msgstr "" -#: Library/Main/Strings.cs:293 -msgid "Sets the process to use low IO priority" +#: Library/Main/Strings.cs:298 +msgid "Set the process to use low IO priority" msgstr "" -#: Library/Main/Strings.cs:295 +#: Library/Main/Strings.cs:300 msgid "Use this option to remove all empty folders from a backup." msgstr "" -#: Library/Main/Strings.cs:296 -msgid "Excludes empty folders" +#: Library/Main/Strings.cs:301 +msgid "Exclude empty folders" msgstr "" -#: Library/Main/Strings.cs:297 +#: Library/Main/Strings.cs:302 msgid "" "Use this option to set a filename, or list of filenames, that indicate " "exclusion of a folder which contains it. A common use would be to have a " @@ -3695,11 +4610,11 @@ msgid "" "that should not be backed up." msgstr "" -#: Library/Main/Strings.cs:298 +#: Library/Main/Strings.cs:303 msgid "List of filenames that exclude folders" msgstr "" -#: Library/Main/Strings.cs:299 +#: Library/Main/Strings.cs:304 msgid "" "If symlink metadata is applied, it will usually mean changing the symlink " "target, instead of the symlink itself. For this reason, metadata is not " @@ -3707,11 +4622,11 @@ msgid "" "metadata is applied to symlinks as well." msgstr "" -#: Library/Main/Strings.cs:300 +#: Library/Main/Strings.cs:305 msgid "Apply metadata to symlinks" msgstr "" -#: Library/Main/Strings.cs:301 +#: Library/Main/Strings.cs:306 msgid "" "When running in unittest mode, no automatic fixes are applied, which assumes " "that the input data is always in perfect shape. This option is not intended " @@ -3719,11 +4634,11 @@ msgid "" "potential problems." msgstr "" -#: Library/Main/Strings.cs:302 +#: Library/Main/Strings.cs:307 msgid "Activate unittest mode" msgstr "" -#: Library/Main/Strings.cs:304 +#: Library/Main/Strings.cs:309 #, csharp-format msgid "" "To improve performance of the backups, frequent database queries are not " @@ -3732,11 +4647,11 @@ msgid "" "data" msgstr "" -#: Library/Main/Strings.cs:305 -msgid "Activates logging of all database queries" +#: Library/Main/Strings.cs:310 +msgid "Activate logging of all database queries" msgstr "" -#: Library/Main/Strings.cs:306 +#: Library/Main/Strings.cs:311 msgid "" "If dblock files are missing from the destination, you can attempt to rebuild " "them using local source data. However, since the local data may have " @@ -3745,11 +4660,11 @@ msgid "" "files." msgstr "" -#: Library/Main/Strings.cs:307 +#: Library/Main/Strings.cs:312 msgid "Rebuild dblock files when missing" msgstr "" -#: Library/Main/Strings.cs:309 +#: Library/Main/Strings.cs:314 msgid "" "The minimum amount of time that must elapse after the last compaction before " "another will be automatically triggered at the end of a backup job. " @@ -3757,11 +4672,11 @@ msgid "" "to run after every single backup." msgstr "" -#: Library/Main/Strings.cs:310 +#: Library/Main/Strings.cs:315 msgid "Minimum time between auto compactions" msgstr "" -#: Library/Main/Strings.cs:311 +#: Library/Main/Strings.cs:316 msgid "" "The minimum amount of time that must elapse after the last vacuum before " "another will be automatically triggered at the end of a backup job. " @@ -3769,57 +4684,206 @@ msgid "" "run after every single backup." msgstr "" -#: Library/Main/Strings.cs:312 +#: Library/Main/Strings.cs:317 msgid "Minimum time between auto vacuums" msgstr "" -#: Library/Main/Strings.cs:317 +#: Library/Main/Strings.cs:318 +msgid "Secret provider to use for reading credentials" +msgstr "" + +#: Library/Main/Strings.cs:319 +#, csharp-format +msgid "" +"Configures a secret provider to use for reading credentials. Use the " +"commandline tool {0} to test the provider and see supported options. This " +"value is interpreted as an environment variable if it starts with '$' or " +"begins and ends with '%'." +msgstr "" + +#: Library/Main/Strings.cs:320 +msgid "Pattern for secrets" +msgstr "" + +#: Library/Main/Strings.cs:321 +msgid "" +"Use this option to specify a pattern for secret provider options. The " +"pattern is used to find values that are intended to be translated by the " +"secret provider. Patterns are treated as a prefix, with support for braces." +msgstr "" + +#: Library/Main/Strings.cs:322 +msgid "Cache rules for the secret provider" +msgstr "" + +#: Library/Main/Strings.cs:323 +msgid "" +"Use this option to set the allowed caching of credentials from the secret " +"provider. Setting a cache level may reduce the security but allow the " +"backups to continue despite provider outages." +msgstr "" + +#: Library/Main/Strings.cs:325 +msgid "CPU intensity level" +msgstr "" + +#: Library/Main/Strings.cs:326 +msgid "" +"Set the CPU intensity level to limit CPU resource utilization. A higher " +"number translates into a higher utilization budget. E.g. 10 would mean no " +"restrictions. Must be an integer between 1-10." +msgstr "" + +#: Library/Main/Strings.cs:328 +msgid "Maximum cache size for restoring files" +msgstr "" + +#: Library/Main/Strings.cs:329 +msgid "" +"Use this option to set the maximum size of the cache used for restoring " +"files. The cache is used to store the data blocks that are downloaded from " +"the remote storage. It assumes that the value is divisable by the block " +"size, except for when it is 0, which disables the block cache." +msgstr "" + +#: Library/Main/Strings.cs:330 +msgid "Eviction ratio of the data block cache during restore" +msgstr "" + +#: Library/Main/Strings.cs:331 +msgid "" +"Use this option to set the eviction ratio of the data block cache during " +"restore. The eviction ratio is the percentage of the cache that is evicted " +"when the cache is full. The default value is 50, which means that 50% of the " +"cache is evicted when the cache is full." +msgstr "" + +#: Library/Main/Strings.cs:332 +msgid "Number of concurrent FileProcessors processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:333 +msgid "" +"Use this option to set the number of concurrent FileProcessors processes " +"used during restore. A FileProcessor processes one file at a time, and " +"increasing the number of FileProcessors may improve restore performance." +msgstr "" + +#: Library/Main/Strings.cs:334 +msgid "Use legacy restore method" +msgstr "" + +#: Library/Main/Strings.cs:335 +msgid "" +"Use this option to use the legacy restore method. The legacy restore method " +"is slower than the new method, but may be more reliable in some cases." +msgstr "" + +#: Library/Main/Strings.cs:336 +msgid "Preallocate size of restored files" +msgstr "" + +#: Library/Main/Strings.cs:337 +msgid "" +"Use this option to toggle whether to set the size of the restored files " +"before they are written to disk. This can help to reduce fragmentation and " +"improve performance on some filesystems." +msgstr "" + +#: Library/Main/Strings.cs:338 +msgid "Number of concurrent FileDecompressor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:339 +msgid "" +"Use this option to set the number of concurrent FileDecompressor processes " +"used during restore. A FileDecompressor processes one volume at a time, and " +"increasing the number of FileDecompressors may improve restore performance " +"if the bottleneck is decompression." +msgstr "" + +#: Library/Main/Strings.cs:340 +msgid "Number of concurrent FileDecryptor processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:341 +msgid "" +"Use this option to set the number of concurrent FileDecryptor processes used " +"during restore. A FileDecryptor processes one volume at a time, and " +"increasing the number of FileDecryptors may improve restore performance if " +"the bottleneck is decryption." +msgstr "" + +#: Library/Main/Strings.cs:342 +msgid "Number of concurrent FileDownloader processes used during restore" +msgstr "" + +#: Library/Main/Strings.cs:343 +msgid "" +"Use this option to set the number of concurrent FileDownloader processes " +"used during restore. A FileDownloader processes one volume at a time, and " +"increasing the number of FileDownloaders may improve restore performance if " +"the bottleneck is downloading." +msgstr "" + +#: Library/Main/Strings.cs:344 +msgid "Enable internal profiling" +msgstr "" + +#: Library/Main/Strings.cs:345 +msgid "" +"Use this option to enable internal profiling. Profiling is used to measure " +"the performance of the internal code. The profiling data is written to the " +"log file and can be used to identify performance bottlenecks." +msgstr "" + +#: Library/Main/Strings.cs:350 #, csharp-format msgid "" "The cryptolibrary does not support re-usable transforms for the hash " "algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:318 +#: Library/Main/Strings.cs:351 #, csharp-format msgid "The cryptolibrary does not support the hash algorithm {0}" msgstr "" -#: Library/Main/Strings.cs:319 +#: Library/Main/Strings.cs:352 msgid "The passphrase cannot be changed for an existing backup" msgstr "" -#: Library/Main/Strings.cs:320 +#: Library/Main/Strings.cs:353 #, csharp-format msgid "Failed to create a snapshot: {0}" msgstr "" -#: Library/Main/BackendManager.cs:610 +#: Library/Main/BackendManager.cs:585 #, csharp-format msgid "Failed to delete file {0}, testing if file exists" msgstr "" -#: Library/Main/BackendManager.cs:616 +#: Library/Main/BackendManager.cs:591 #, csharp-format msgid "Recovered from problem with attempting to delete non-existing file {0}" msgstr "" -#: Library/Main/BackendManager.cs:621 +#: Library/Main/BackendManager.cs:596 #, csharp-format msgid "Failed to recover from error deleting file {0}" msgstr "" -#: Library/Main/BackendManager.cs:1123 +#: Library/Main/BackendManager.cs:962 #, csharp-format msgid "Delete operation failed for {0} with FileNotFound, listing contents" msgstr "" -#: Library/Main/BackendManager.cs:1136 +#: Library/Main/BackendManager.cs:975 #, csharp-format msgid "Listing indicates file {0} was deleted correctly" msgstr "" -#: Library/Main/BackendManager.cs:1141 +#: Library/Main/BackendManager.cs:980 #, csharp-format msgid "Listing confirms file {0} was not deleted" msgstr "" @@ -3873,29 +4937,6 @@ msgstr "" msgid "Configure http requests" msgstr "" -#: Library/Modules/Builtin/Strings.cs:42 -#, csharp-format -msgid "" -"Use this option to accept any server certificate, regardless of what errors " -"it may have. Please use --{0} instead, whenever possible." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:43 -msgid "Accept any server certificate" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:44 -msgid "" -"If your server certificate is reported as invalid (e.g. with self-signed " -"certificates), you can supply the certificate hash (SHA1) to approve it " -"anyway. The hash value must be entered in hex format without spaces or " -"colons. You can enter multiple hashes separated by commas." -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:45 -msgid "Optionally accept a known SSL certificate" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:46 msgid "" "The default HTTP request has the header \"Expect: 100-Continue\" attached, " @@ -3936,7 +4977,7 @@ msgid "" msgstr "" #: Library/Modules/Builtin/Strings.cs:53 -msgid "Sets allowed SSL versions" +msgid "Set allowed SSL versions" msgstr "" #: Library/Modules/Builtin/Strings.cs:54 @@ -3946,7 +4987,7 @@ msgid "" msgstr "" #: Library/Modules/Builtin/Strings.cs:55 -msgid "Sets the default operation timeout" +msgid "Set the default operation timeout" msgstr "" #: Library/Modules/Builtin/Strings.cs:56 @@ -3957,7 +4998,7 @@ msgid "" msgstr "" #: Library/Modules/Builtin/Strings.cs:57 -msgid "Sets readwrite" +msgid "Set readwrite" msgstr "" #: Library/Modules/Builtin/Strings.cs:58 @@ -3968,7 +5009,7 @@ msgid "" msgstr "" #: Library/Modules/Builtin/Strings.cs:59 -msgid "Sets HTTP buffering" +msgid "Set HTTP buffering" msgstr "" #: Library/Modules/Builtin/Strings.cs:63 @@ -3981,129 +5022,141 @@ msgstr "" msgid "Configure Hyper-V module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:68 +#: Library/Modules/Builtin/Strings.cs:65 +msgid "Ignore consistency warning" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:66 +msgid "" +"This option will suppress the consistency warning that is normally issued " +"when running on a client version of Windows. Enable this option if you are " +"running on a client version of Windows and you are sure that crash-level " +"consistency is acceptable for your use." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:70 msgid "" "This module works internaly to parse source parameters to backup Microsoft " "SQL Server databases" msgstr "" -#: Library/Modules/Builtin/Strings.cs:69 +#: Library/Modules/Builtin/Strings.cs:71 msgid "Configure Microsoft SQL Server module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:73 -msgid "Executes a script before starting an operation, and again on completion" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:74 -msgid "Run script" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:75 -msgid "" -"Executes a script after performing an operation. The script will receive the " -"operation results written to stdout." +msgid "Execute a script before starting an operation, and again on completion" msgstr "" #: Library/Modules/Builtin/Strings.cs:76 -msgid "Run a script on exit" +msgid "Run script" msgstr "" #: Library/Modules/Builtin/Strings.cs:77 +msgid "" +"Execute a script after performing an operation. The script will receive the " +"operation results written to stdout." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:78 +msgid "Run a script on exit" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:79 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:78 +#: Library/Modules/Builtin/Strings.cs:80 #, csharp-format msgid "The script \"{0}\" returned with exit code {1}{2}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:79 +#: Library/Modules/Builtin/Strings.cs:81 msgid "" -"Executes a script before performing an operation. The operation will block " +"Execute a script before performing an operation. The operation will block " "until the script has completed or timed out. If the script returns a non-" "zero error code or times out, the operation will be aborted." msgstr "" -#: Library/Modules/Builtin/Strings.cs:80 +#: Library/Modules/Builtin/Strings.cs:82 msgid "Run a required script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:81 Library/Modules/Builtin/Strings.cs:208 +#: Library/Modules/Builtin/Strings.cs:83 Library/Modules/Builtin/Strings.cs:243 #, csharp-format msgid "" "Use this option to select the output format for results. Available formats: " "{0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:82 Library/Modules/Builtin/Strings.cs:209 -msgid "Selects the output format for results" +#: Library/Modules/Builtin/Strings.cs:84 Library/Modules/Builtin/Strings.cs:244 +msgid "Select the output format for results" msgstr "" -#: Library/Modules/Builtin/Strings.cs:83 +#: Library/Modules/Builtin/Strings.cs:85 #, csharp-format msgid "Error while executing script \"{0}\": {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:84 +#: Library/Modules/Builtin/Strings.cs:86 #, csharp-format msgid "Execution of the script \"{0}\" timed out" msgstr "" -#: Library/Modules/Builtin/Strings.cs:85 +#: Library/Modules/Builtin/Strings.cs:87 msgid "" -"Executes a script before performing an operation. The operation will block " +"Execute a script before performing an operation. The operation will block " "until the script has completed or timed out." msgstr "" -#: Library/Modules/Builtin/Strings.cs:86 +#: Library/Modules/Builtin/Strings.cs:88 msgid "Run a script on startup" msgstr "" -#: Library/Modules/Builtin/Strings.cs:87 +#: Library/Modules/Builtin/Strings.cs:89 #, csharp-format msgid "The script \"{0}\" reported error messages: {1}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:88 +#: Library/Modules/Builtin/Strings.cs:90 msgid "" -"Sets the maximum time a script is allowed to execute. If the script has not " +"Set the maximum time a script is allowed to execute. If the script has not " "completed within this time, it will continue to execute but the operation " "will continue too, and no script output will be processed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:89 -msgid "Sets the script timeout" +#: Library/Modules/Builtin/Strings.cs:91 +msgid "Set the script timeout" msgstr "" -#: Library/Modules/Builtin/Strings.cs:90 +#: Library/Modules/Builtin/Strings.cs:92 msgid "" "This option enables the use of script arguments. If this option is set, the " "script arguments are treated as commandline strings. Use single or double " "quotes to separate arguments." msgstr "" -#: Library/Modules/Builtin/Strings.cs:91 +#: Library/Modules/Builtin/Strings.cs:93 msgid "Enable script arguments" msgstr "" -#: Library/Modules/Builtin/Strings.cs:95 +#: Library/Modules/Builtin/Strings.cs:97 msgid "This module can send email after an operation completes" msgstr "" -#: Library/Modules/Builtin/Strings.cs:96 +#: Library/Modules/Builtin/Strings.cs:98 msgid "Send mail" msgstr "" -#: Library/Modules/Builtin/Strings.cs:97 +#: Library/Modules/Builtin/Strings.cs:99 #, csharp-format msgid "" "Unable to find the destination mail server through MX lookup. Please use the " -"option {0} to specify what smtp server to use." +"option --{0} to specify what SMTP server to use." msgstr "" -#: Library/Modules/Builtin/Strings.cs:98 +#: Library/Modules/Builtin/Strings.cs:100 msgid "" "This value can be a filename. If the file exists, the file contents will be " "used as the message body.\n" @@ -4120,21 +5173,21 @@ msgid "" "Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:107 +#: Library/Modules/Builtin/Strings.cs:109 msgid "The message body" msgstr "" -#: Library/Modules/Builtin/Strings.cs:108 +#: Library/Modules/Builtin/Strings.cs:110 msgid "" "Use this option to set the password used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:109 +#: Library/Modules/Builtin/Strings.cs:111 msgid "SMTP Password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:110 +#: Library/Modules/Builtin/Strings.cs:112 msgid "" "This setting is required if mail should be sent, all other settings have " "default values. You can supply multiple email addresses separated with " @@ -4146,11 +5199,11 @@ msgid "" "admin@example.com" msgstr "" -#: Library/Modules/Builtin/Strings.cs:114 +#: Library/Modules/Builtin/Strings.cs:116 msgid "Email recipient(s)" msgstr "" -#: Library/Modules/Builtin/Strings.cs:115 +#: Library/Modules/Builtin/Strings.cs:117 msgid "" "Use this option to set an address of the email sender. If no host is " "supplied, the hostname of the first recipient is used. Examples of allowed " @@ -4162,11 +5215,11 @@ msgid "" "Mail Sender " msgstr "" -#: Library/Modules/Builtin/Strings.cs:121 +#: Library/Modules/Builtin/Strings.cs:123 msgid "Email sender" msgstr "" -#: Library/Modules/Builtin/Strings.cs:122 +#: Library/Modules/Builtin/Strings.cs:124 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\".\n" @@ -4175,13 +5228,14 @@ msgid "" "cause all backup operations to send an email." msgstr "" -#: Library/Modules/Builtin/Strings.cs:124 -#: Library/Modules/Builtin/Strings.cs:160 -#: Library/Modules/Builtin/Strings.cs:188 +#: Library/Modules/Builtin/Strings.cs:126 +#: Library/Modules/Builtin/Strings.cs:162 +#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:219 msgid "The messages to send" msgstr "" -#: Library/Modules/Builtin/Strings.cs:125 +#: Library/Modules/Builtin/Strings.cs:127 msgid "" "Use this option to set a URL for the SMTP server, e.g. smtp://example." "com:25. Multiple servers can be supplied in a prioritized list, separated " @@ -4198,67 +5252,68 @@ msgid "" "use smtp://example.com:25/?starttls=never." msgstr "" -#: Library/Modules/Builtin/Strings.cs:129 +#: Library/Modules/Builtin/Strings.cs:131 msgid "SMTP Url" msgstr "" -#: Library/Modules/Builtin/Strings.cs:130 +#: Library/Modules/Builtin/Strings.cs:132 #, csharp-format msgid "" "This setting supplies the email subject. Values are replaced as described in " "the description for --{0}." msgstr "" -#: Library/Modules/Builtin/Strings.cs:131 +#: Library/Modules/Builtin/Strings.cs:133 msgid "The email subject" msgstr "" -#: Library/Modules/Builtin/Strings.cs:132 +#: Library/Modules/Builtin/Strings.cs:134 msgid "" "Use this option to set the username used to authenticate with the SMTP " "server if required." msgstr "" -#: Library/Modules/Builtin/Strings.cs:133 -msgid "SMTP Username" -msgstr "" - -#: Library/Modules/Builtin/Strings.cs:134 -#, csharp-format -msgid "Whole SMTP communication: {0}" -msgstr "" - #: Library/Modules/Builtin/Strings.cs:135 -#, csharp-format -msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgid "SMTP Username" msgstr "" #: Library/Modules/Builtin/Strings.cs:136 #, csharp-format +msgid "Whole SMTP communication: {0}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:137 +#, csharp-format +msgid "Failed to send email with server: {0}, message: {1}, retrying with {2}" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:138 +#, csharp-format msgid "Email sent successfully using server: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:140 +#: Library/Modules/Builtin/Strings.cs:142 msgid "" "This module provides support for sending status reports via XMPP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:141 +#: Library/Modules/Builtin/Strings.cs:143 msgid "XMPP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:142 +#: Library/Modules/Builtin/Strings.cs:144 msgid "" "Use this option to set the users who should have the messages sent. You can " "specify multiple users separated with commas." msgstr "" -#: Library/Modules/Builtin/Strings.cs:143 +#: Library/Modules/Builtin/Strings.cs:145 msgid "XMPP recipient email" msgstr "" -#: Library/Modules/Builtin/Strings.cs:144 -#: Library/Modules/Builtin/Strings.cs:172 +#: Library/Modules/Builtin/Strings.cs:146 +#: Library/Modules/Builtin/Strings.cs:174 +#: Library/Modules/Builtin/Strings.cs:203 msgid "" "This value can be a filename. If the file exists, the file contents will be " "used as the message.\n" @@ -4275,32 +5330,34 @@ msgid "" "Any unknown/unset value is removed." msgstr "" -#: Library/Modules/Builtin/Strings.cs:153 -#: Library/Modules/Builtin/Strings.cs:181 +#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:212 msgid "The message template" msgstr "" -#: Library/Modules/Builtin/Strings.cs:154 +#: Library/Modules/Builtin/Strings.cs:156 msgid "" "Use this option to set a username for the account that will send the " "message, including the hostname, e.g. \"account@jabber.org/Home\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:155 +#: Library/Modules/Builtin/Strings.cs:157 msgid "The XMPP username" msgstr "" -#: Library/Modules/Builtin/Strings.cs:156 +#: Library/Modules/Builtin/Strings.cs:158 msgid "" "Use this option to set a password for the account that will send the message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:157 +#: Library/Modules/Builtin/Strings.cs:159 msgid "The XMPP password" msgstr "" -#: Library/Modules/Builtin/Strings.cs:158 -#: Library/Modules/Builtin/Strings.cs:186 +#: Library/Modules/Builtin/Strings.cs:160 +#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:217 #, csharp-format msgid "" "You can specify one of \"{0}\", \"{1}\", \"{2}\", \"{3}\". \n" @@ -4309,67 +5366,107 @@ msgid "" "cause all backup operations to send a message." msgstr "" -#: Library/Modules/Builtin/Strings.cs:161 -#: Library/Modules/Builtin/Strings.cs:189 +#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:192 +#: Library/Modules/Builtin/Strings.cs:220 msgid "" "By default, messages will only be sent after a backup operation. Use this " "option to send messages for all operations." msgstr "" -#: Library/Modules/Builtin/Strings.cs:162 -#: Library/Modules/Builtin/Strings.cs:190 +#: Library/Modules/Builtin/Strings.cs:164 +#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:221 msgid "Send messages for all operations" msgstr "" -#: Library/Modules/Builtin/Strings.cs:163 +#: Library/Modules/Builtin/Strings.cs:165 msgid "Timeout occurred while logging in to jabber server" msgstr "" -#: Library/Modules/Builtin/Strings.cs:168 +#: Library/Modules/Builtin/Strings.cs:170 +msgid "" +"This module provides support for sending status reports via Telegram messages" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:171 +msgid "Telegram report module" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:172 +msgid "Use this option to set the channel ID." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:173 +msgid "Telegram channel ID" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:184 +msgid "Use this option to set a bot ID for the bot that will send the message." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:186 +msgid "The Telegram bot ID" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:187 +msgid "" +"Use this option to set a API key for the bot that will send the message." +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:188 +msgid "The Telegram API key" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:194 +msgid "Timeout occurred while sending to Telegram server" +msgstr "" + +#: Library/Modules/Builtin/Strings.cs:199 msgid "" "This module provides support for sending status reports via HTTP messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:169 +#: Library/Modules/Builtin/Strings.cs:200 msgid "HTTP report module" msgstr "" -#: Library/Modules/Builtin/Strings.cs:170 +#: Library/Modules/Builtin/Strings.cs:201 msgid "Use this option to set a HTTP report URL." msgstr "" -#: Library/Modules/Builtin/Strings.cs:171 +#: Library/Modules/Builtin/Strings.cs:202 msgid "HTTP report URL" msgstr "" -#: Library/Modules/Builtin/Strings.cs:182 +#: Library/Modules/Builtin/Strings.cs:213 msgid "Use this option to set a name of the parameter to send the message as." msgstr "" -#: Library/Modules/Builtin/Strings.cs:183 +#: Library/Modules/Builtin/Strings.cs:214 msgid "The name of the parameter to send the message as" msgstr "" -#: Library/Modules/Builtin/Strings.cs:184 +#: Library/Modules/Builtin/Strings.cs:215 msgid "" "Use this option to set extra parameters to add to the http message, e.g. " "\"parameter1=value1¶meter2=value2\"" msgstr "" -#: Library/Modules/Builtin/Strings.cs:185 +#: Library/Modules/Builtin/Strings.cs:216 msgid "Extra parameters to add to the http message" msgstr "" -#: Library/Modules/Builtin/Strings.cs:191 +#: Library/Modules/Builtin/Strings.cs:222 msgid "" "Use this option to change the default HTTP verb used to submit a report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:192 -msgid "Sets the HTTP verb to use" +#: Library/Modules/Builtin/Strings.cs:223 +msgid "Set the HTTP verb to use" msgstr "" -#: Library/Modules/Builtin/Strings.cs:193 +#: Library/Modules/Builtin/Strings.cs:224 msgid "" "Use this option to set HTTP report URLs for sending form-encoded data. This " "option accepts multiple URLs, seperated by a semi-colon. All URLs will " @@ -4377,53 +5474,53 @@ msgid "" "settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:194 +#: Library/Modules/Builtin/Strings.cs:225 msgid "HTTP report URLs for sending form data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:195 +#: Library/Modules/Builtin/Strings.cs:226 msgid "" "Use this option to set HTTP report URLs for sending JSON data. This option " "accepts multiple URLs, seperated by a semi-colon. All URLs will receive the " "same data. Note that this option ignores the format and verb settings." msgstr "" -#: Library/Modules/Builtin/Strings.cs:196 +#: Library/Modules/Builtin/Strings.cs:227 msgid "HTTP report URLs for sending JSON data" msgstr "" -#: Library/Modules/Builtin/Strings.cs:201 +#: Library/Modules/Builtin/Strings.cs:236 #, csharp-format msgid "Failed to send message: {0}" msgstr "" -#: Library/Modules/Builtin/Strings.cs:202 +#: Library/Modules/Builtin/Strings.cs:237 msgid "" "Use this option to set the log level for messages to include in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:203 -msgid "Defines a log level for messages" +#: Library/Modules/Builtin/Strings.cs:238 +msgid "Define a log level for messages" msgstr "" -#: Library/Modules/Builtin/Strings.cs:204 +#: Library/Modules/Builtin/Strings.cs:239 msgid "" "Use this option to set a filter expression that defines what options are " "included in the report." msgstr "" -#: Library/Modules/Builtin/Strings.cs:205 +#: Library/Modules/Builtin/Strings.cs:240 msgid "Log message filter" msgstr "" -#: Library/Modules/Builtin/Strings.cs:206 +#: Library/Modules/Builtin/Strings.cs:241 msgid "" "Use this option to set the maximum number of log lines to include in the " "report. Zero or negative values means unlimited." msgstr "" -#: Library/Modules/Builtin/Strings.cs:207 -msgid "Limits log lines" +#: Library/Modules/Builtin/Strings.cs:242 +msgid "Limit log lines" msgstr "" #: Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs:39 @@ -4431,16 +5528,16 @@ msgstr "" msgid "The format is not supported: {0}" msgstr "" -#: Library/Utility/Strings.cs:27 +#: Library/Utility/Strings.cs:29 #, csharp-format msgid "Invalid size value: {0}" msgstr "" -#: Library/Utility/Strings.cs:30 +#: Library/Utility/Strings.cs:33 msgid "The SSL certificate validator was called in an incorrect order" msgstr "" -#: Library/Utility/Strings.cs:31 +#: Library/Utility/Strings.cs:34 #, csharp-format msgid "" "The server certificate had the error {0} and the hash {1}{2}If you trust " @@ -4449,78 +5546,78 @@ msgid "" "into your operating systems trust pool." msgstr "" -#: Library/Utility/Strings.cs:32 +#: Library/Utility/Strings.cs:35 #, csharp-format msgid "" "Failed while validating certificate hash, error message: {0}, SSL error " "name: {1}" msgstr "" -#: Library/Utility/Strings.cs:35 +#: Library/Utility/Strings.cs:39 #, csharp-format msgid "Temporary folder does not exist: {0}" msgstr "" -#: Library/Utility/Strings.cs:38 +#: Library/Utility/Strings.cs:43 #, csharp-format msgid "Failed to parse the segment: {0}, invalid integer" msgstr "" -#: Library/Utility/Strings.cs:39 +#: Library/Utility/Strings.cs:44 #, csharp-format msgid "Invalid specifier: {0}" msgstr "" -#: Library/Utility/Strings.cs:40 +#: Library/Utility/Strings.cs:45 #, csharp-format msgid "Unparsed data: {0}" msgstr "" -#: Library/Utility/Strings.cs:43 +#: Library/Utility/Strings.cs:49 #, csharp-format msgid "The Uri is invalid: {0}" msgstr "" -#: Library/Utility/Strings.cs:44 +#: Library/Utility/Strings.cs:50 #, csharp-format msgid "The Uri is missing a hostname: {0}" msgstr "" -#: Library/Utility/Strings.cs:47 +#: Library/Utility/Strings.cs:54 #, csharp-format msgid "{0} bytes" msgstr "" -#: Library/Utility/Strings.cs:48 +#: Library/Utility/Strings.cs:55 #, csharp-format msgid "{0:N} GB" msgstr "" -#: Library/Utility/Strings.cs:49 +#: Library/Utility/Strings.cs:56 #, csharp-format msgid "{0:N} KB" msgstr "" -#: Library/Utility/Strings.cs:50 +#: Library/Utility/Strings.cs:57 #, csharp-format msgid "{0:N} MB" msgstr "" -#: Library/Utility/Strings.cs:51 +#: Library/Utility/Strings.cs:58 #, csharp-format msgid "{0:N} TB" msgstr "" -#: Library/Utility/Strings.cs:52 +#: Library/Utility/Strings.cs:59 #, csharp-format msgid "The string \"{0}\" could not be parsed into a date" msgstr "" -#: Library/Utility/Strings.cs:55 +#: Library/Utility/Strings.cs:63 msgid "Cannot read and write on the same stream" msgstr "" -#: Library/Utility/Strings.cs:58 +#: Library/Utility/Strings.cs:67 #, csharp-format msgid "" "The string {0} does not represent a known filter group name. Valid values " @@ -4638,11 +5735,6 @@ msgstr "" msgid "Supported generic modules:" msgstr "" -#: CommandLine/CLI/Strings.cs:41 -#, csharp-format -msgid "Unable to read the parameters file \"{0}\", reason: {1}" -msgstr "" - #: CommandLine/CLI/Strings.cs:42 #, csharp-format msgid "" @@ -4674,10 +5766,6 @@ msgid "" "joined with {5}." msgstr "" -#: CommandLine/CLI/Strings.cs:45 -msgid "Path to a file with parameters" -msgstr "" - #: CommandLine/CLI/Strings.cs:46 #, csharp-format msgid "An error occured: {0}" @@ -4742,11 +5830,11 @@ msgstr "" msgid "This link may provide additional information: {0}" msgstr "" -#: CommandLine/CLI/Program.cs:288 +#: CommandLine/CLI/Program.cs:296 msgid "Toggle automatic updates" msgstr "" -#: CommandLine/CLI/Program.cs:288 +#: CommandLine/CLI/Program.cs:296 msgid "" "Set this option if you prefer to have the commandline version automatically " "update" diff --git a/Localizations/webroot/localization_webroot-ca.po b/Localizations/webroot/localization_webroot-ca.po index 0b5f16e1d..a553066cd 100644 --- a/Localizations/webroot/localization_webroot-ca.po +++ b/Localizations/webroot/localization_webroot-ca.po @@ -37,7 +37,7 @@ msgstr "" msgid "- pick an option -" msgstr "- trieu una opció -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "S'està carregant..." @@ -55,33 +55,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "ID d'accés d'AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Clau d'accés d'AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Política IAM d'AWS" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "Quant a" @@ -89,7 +89,7 @@ msgstr "Quant a" msgid "About {{appname}}" msgstr "Quant al {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Clau d'accés" @@ -117,7 +117,7 @@ msgstr "" msgid "Access to user interface" msgstr "Accés a la interfície d'usuari" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nom del compte" @@ -134,7 +134,7 @@ msgstr "Afegeix una ruta directament" msgid "Add advanced option" msgstr "Afegeix una opció avançada" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Afegeix una còpia de seguretat" @@ -150,8 +150,8 @@ msgstr "Afegeix una ruta" msgid "Added" msgstr "Afegits" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Voleu modificar el nom del contenidor?" @@ -184,7 +184,7 @@ msgstr "Totes les màquines de l'Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Totes les bases de dades SQL de Microsoft" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -206,6 +206,10 @@ msgstr "Permet l'accés remot (cal reiniciar el programa)" msgid "Allowed days" msgstr "Dies permesos" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "S'ha trobat un fitxer existent a la nova ubicació" @@ -230,7 +234,7 @@ msgstr "" "\n" " Voleu fer servir la base de dades existent?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Informes d'ús anònims" @@ -238,20 +242,26 @@ msgstr "Informes d'ús anònims" msgid "Applications" msgstr "Aplicacions" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Com a línia d'ordres" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -283,7 +293,7 @@ msgstr "" msgid "B2 Application Key" msgstr "Clau d'aplicació de B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "ID del compte de B2 Cloud Storage" @@ -291,7 +301,7 @@ msgstr "ID del compte de B2 Cloud Storage" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Clau d'aplicació de B2 Cloud Storage" @@ -333,7 +343,7 @@ msgstr "Preservació de la còpia de seguretat" msgid "Backup:" msgstr "Còpia de seguretat:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -345,7 +355,7 @@ msgstr "L'accés està trencat" msgid "Browse" msgstr "Navega" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Valor per defecte del navegador" @@ -353,14 +363,14 @@ msgstr "Valor per defecte del navegador" msgid "Bucket create location" msgstr "Ubicació de creació del contenidor" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -369,7 +379,7 @@ msgstr "Ubicació de creació del contenidor" msgid "Bucket name" msgstr "Nom del contenidor" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -442,12 +452,13 @@ msgstr "" msgid "Cache Files" msgstr "Fitxers de memòria cau" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -462,14 +473,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancel·la" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -486,6 +501,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Registre de canvis" @@ -506,7 +525,7 @@ msgstr "Comprova ara si hi ha actualitzacions" msgid "Checking for updates …" msgstr "" -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -522,11 +541,11 @@ msgstr "Trieu un tipus d'emmagatzematge per començar" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Feu clic a l'enllaç d'AuthID per crear una AuthID" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Feu clic per definir les opcions de velocitat" @@ -610,7 +629,7 @@ msgstr "Confirma l'eliminació" msgid "Confirm encryption passphrase" msgstr "" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -626,11 +645,11 @@ msgstr "Es requereix una confirmació" msgid "Connect" msgstr "Connecta" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Connecta ara" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "" @@ -638,11 +657,11 @@ msgstr "" msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "S'ha perdut la connexió" @@ -651,7 +670,7 @@ msgstr "S'ha perdut la connexió" msgid "Connection worked!" msgstr "Ha funcionat la connexió!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nom del contenidor" @@ -693,7 +712,7 @@ msgstr "Ha fallat la còpia. Copieu l'URL manualment" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opcions principals" @@ -701,7 +720,7 @@ msgstr "Opcions principals" msgid "Counting ({{files}} files found, {{size}})" msgstr "S'està comptant (s'han trobat {{files}} fitxers, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Només fallades" @@ -713,7 +732,7 @@ msgstr "" msgid "Create folder?" msgstr "Voleu crear una carpeta?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "S'ha creat un nou usuari limitat" @@ -721,7 +740,7 @@ msgstr "S'ha creat un nou usuari limitat" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -733,7 +752,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -753,7 +772,7 @@ msgstr "La versió actual és {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Extrem d'S3 personalitzat" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -805,11 +824,11 @@ msgstr "" msgid "Days" msgstr "Dies" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Per defecte" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Per defecte ({{channelname}})" @@ -817,7 +836,7 @@ msgstr "Per defecte ({{channelname}})" msgid "Default excludes" msgstr "Exclusions per defecte" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opcions per defecte" @@ -845,6 +864,10 @@ msgstr "Elimina les còpies de seguretat anteriors a" msgid "Delete local database" msgstr "Elimina la base de dades local" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Elimina els fitxers remots" @@ -901,7 +924,8 @@ msgstr "Destinació" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Ruta de destinació" @@ -913,6 +937,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Desactivat" @@ -926,7 +954,7 @@ msgstr "Ignora" msgid "Dismiss all" msgstr "Ignora-ho tot" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Visualització i tema de color" @@ -967,22 +995,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "Opció duplicada {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Lloc web del Duplicati" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Fòrum del Duplicati" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1021,12 +1049,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Edita com a llista" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Edita com a text" @@ -1038,6 +1066,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Xifra el fitxer" @@ -1057,7 +1089,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1171,7 +1203,7 @@ msgstr "Error" msgid "Error!" msgstr "S'ha produït un error!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Errors i fallades" @@ -1219,7 +1251,7 @@ msgstr "Exclou una expressió regular" msgid "Existing file found" msgstr "S'ha trobat un fitxer existent" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1314,7 +1346,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "No s'han pogut restaurar els fitxers: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "No s'ha pogut desar:" @@ -1343,7 +1375,7 @@ msgstr "Filtres" msgid "Finished!" msgstr "S'ha acabat!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Configuració inicial" @@ -1414,7 +1446,7 @@ msgstr "Genera una política d'accés IAM" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Adreça electrònica del grup" @@ -1430,7 +1462,7 @@ msgstr "Amaga" msgid "Hide hidden folders" msgstr "Amaga les carpetes ocultes" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Inici" @@ -1439,7 +1471,7 @@ msgid "Hostnames" msgstr "Noms" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Hores" @@ -1469,11 +1501,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1524,6 +1556,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Si no introduïu una clau API, heu d'indicar el nom d'inquilí" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1571,11 +1608,11 @@ msgstr "Inclou una expressió" msgid "Include regular expression" msgstr "Inclou una expressió regular" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "La resposta és incorrecta, torneu-ho a provar" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1590,7 +1627,7 @@ msgstr "Informació" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "La ruta conté caràcters no vàlids" @@ -1600,7 +1637,7 @@ msgstr "La ruta conté caràcters no vàlids" msgid "Invalid retention time" msgstr "El període de preservació no és vàlid" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1628,7 +1665,7 @@ msgstr "Preserva totes les còpies de seguretat" msgid "Keystone API version" msgstr "Versió de l'API de Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Idioma de la interfície d'usuari" @@ -1693,15 +1730,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Dipòsit local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}}" msgid "Log data from the server" msgstr "Dades de registre del servidor" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "Surt" @@ -1753,6 +1786,10 @@ msgstr "MBytes" msgid "MByte/s" msgstr "MByte/s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "Manteniment" @@ -1763,7 +1800,7 @@ msgid "" " advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1783,7 +1820,7 @@ msgstr "Velocitat màxima de baixada" msgid "Max upload speed" msgstr "Velocitat màxima de càrrega" -#: index.html:144 templates/addoredit.html:127 templates/addoredit.html:169 +#: index.html:145 templates/addoredit.html:127 templates/addoredit.html:169 #: templates/addoredit.html:382 templates/addoredit.html:95 #: templates/edituri.html:34 templates/restoredirect.html:34 msgid "Menu" @@ -1801,12 +1838,12 @@ msgstr "Bases de dades SQL de Microsoft" msgid "Minimum redundancy" msgstr "Redundància mínima" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "La redundància mínima és de 1.0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minuts" @@ -1866,7 +1903,7 @@ msgstr "Nom" msgid "Never" msgstr "Mai" -#: templates/settings.html:11 +#: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" msgstr "" @@ -1874,7 +1911,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1893,11 +1930,11 @@ msgstr "Següent" msgid "Next scheduled run:" msgstr "Pròxima execució programada:" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Pròxima tasca programada:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Pròxima tasca:" @@ -1905,7 +1942,7 @@ msgstr "Pròxima tasca:" msgid "Next time" msgstr "La pròxima vegada" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1918,9 +1955,9 @@ msgstr "La pròxima vegada" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "No" @@ -1955,7 +1992,7 @@ msgstr "No hi ha elements per restaurar, seleccioneu-ne un o més" msgid "No passphrase entered" msgstr "No s'ha introduït cap contrasenya" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "No hi ha tasques planificades" @@ -1963,7 +2000,7 @@ msgstr "No hi ha tasques planificades" msgid "Non-matching passphrase" msgstr "La contrasenya no coincideix" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Cap / desactivat" @@ -1984,16 +2021,17 @@ msgstr "" "No s'eliminarà res. La mida de la còpia de seguretat augmentarà després de " "cada canvi." -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "D'acord" @@ -2025,7 +2063,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2049,7 +2087,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Oberts" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2086,11 +2124,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opcions" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2100,7 +2138,7 @@ msgstr "" msgid "Original location" msgstr "Ubicació original" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Altres" @@ -2140,7 +2178,7 @@ msgstr "Les contrasenyes no coincideixen" msgid "Passphrases do not match" msgstr "Les contrasenyes no coincideixen" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2151,8 +2189,8 @@ msgstr "Contrasenya" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Ruta" @@ -2161,7 +2199,7 @@ msgid "Path not found" msgstr "No s'ha trobat la ruta" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Ruta al servidor" @@ -2170,15 +2208,15 @@ msgstr "Ruta al servidor" msgid "Path or subfolder in the bucket" msgstr "Ruta o subcarpeta al contenidor" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pausa" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausa després de l'arrencada o la hibernació" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Opcions de pausa" @@ -2214,6 +2252,10 @@ msgstr "Impedeix l'inici de sessió automàtic de la safata del sistema" msgid "Previous" msgstr "Enrere" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progrés:" @@ -2222,7 +2264,7 @@ msgstr "Progrés:" msgid "ProjectID is optional if the bucket exist" msgstr "La ProjectID és opcional si el contenidor existeix" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "De propietat" @@ -2258,15 +2300,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "No es permet l'ús de rutes relatives" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Actualitza" @@ -2274,14 +2336,34 @@ msgstr "Actualitza" msgid "Remote" msgstr "Remot" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Ruta remota" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Dipòsit remot" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Ruta remota" @@ -2330,7 +2412,7 @@ msgstr "S'està informant:" msgid "Reset" msgstr "Reinicialitza" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Restaura" @@ -2388,7 +2470,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Reprèn" @@ -2408,7 +2490,7 @@ msgstr "Executa ara" msgid "Running commandline entry" msgstr "S'està executant una entrada de la línia d'ordres" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "Tasca en execució:" @@ -2424,7 +2506,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Compatible amb S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "La mateixa que la versió base d'instal·lació: {{channelname}}" @@ -2472,7 +2554,7 @@ msgstr "Cerca" msgid "Search for files" msgstr "Cerca fitxers" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Segons" @@ -2485,8 +2567,8 @@ msgstr "Trieu un nivell de registre i vegeu els nous missatges al moment:" msgid "Select files" msgstr "Seleccioneu els fitxers" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Servidor" @@ -2524,7 +2606,11 @@ msgstr "S'ha pausat el servidor" msgid "Server state properties" msgstr "Propietats de l'estat del servidor" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Configuració" @@ -2595,17 +2681,17 @@ msgstr "Carpetes d'origen" msgid "Source:" msgstr "Origen:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Compilacions específiques només per a desenvolupadors. No ho feu servir amb " "dades importants." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocols estàndard" @@ -2632,19 +2718,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Atura després del fitxer actual" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Atura ara" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Atura la còpia de seguretat en execució" @@ -2653,11 +2735,11 @@ msgstr "Atura la còpia de seguretat en execució" msgid "Stop running task" msgstr "Atura la tasca en execució" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "S'està aturant la tasca:" @@ -2698,7 +2780,7 @@ msgstr "Enllaç simbòlic" msgid "System Files" msgstr "Fitxers del sistema" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Valor per defecte del sistema ({{levelname}})" @@ -2754,6 +2836,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Fase de comprovació" @@ -2766,7 +2853,7 @@ msgstr "Comprova la connexió" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2774,10 +2861,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2804,7 +2891,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "El nom del contenidor ha d'estar en minúscules; voleu convertir-lo " @@ -2818,19 +2905,19 @@ msgstr "" "És recomanable que deseu la configuració en un lloc segur. Segur que voleu " "desar un fitxer sense xifrar amb les vostres contrasenyes?" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tema fosc (per Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Tema per defecte, blau sobre blanc (per Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2859,7 +2946,7 @@ msgstr "" "Voleu substituir la clau d'amfitrió actual («{{prev}}») amb la clau d'amfitrió «{{key}}»?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Les contrasenyes no coincideixen" @@ -2913,7 +3000,7 @@ msgstr "" "La carpeta de destinació conté fitxers encriptats; introduïu-ne la " "contrasenya" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2941,7 +3028,7 @@ msgstr "Aquest mes" msgid "This week" msgstr "Aquesta setmana" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Opcions de velocitat" @@ -2954,6 +3041,10 @@ msgstr "Dijous" msgid "Time" msgstr "Hora" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "A un fitxer" @@ -2972,7 +3063,7 @@ msgstr "" "Per fer una exportació sense contrasenya, desactiveu la casella «Xifra el " "fitxer»" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3006,7 +3097,7 @@ msgstr "Voleu confiar en el certificat de l'amfitrió?" msgid "Trust server certificate?" msgstr "Voleu confiar en el certificat del servidor?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3044,7 +3135,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal d'actualitzacions" @@ -3064,19 +3155,18 @@ msgstr "Fitxers carregats" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Estadístiques d'ús" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Estadístiques d'ús, avisos, errors i fallades" @@ -3104,17 +3194,17 @@ msgstr "Dades d'usuari" msgid "User domain name" msgstr "Nom de domini de l'usuari" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "L'usuari té massa permisos" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Paràmetres de la interfície d'usuari" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3178,7 +3268,7 @@ msgstr "Molt forta" msgid "Very weak" msgstr "Molt dèbil" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "Visiteu-nos a" @@ -3204,7 +3294,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avisos, errors i fallades" @@ -3242,7 +3332,7 @@ msgstr "On voleu restaurar els fitxers?" msgid "Years" msgstr "Anys" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3255,9 +3345,9 @@ msgstr "Anys" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Sí" @@ -3296,13 +3386,15 @@ msgstr "Actualment esteu executant el {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3349,7 +3441,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Heu de triar com a mínim una carpeta d'origen" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Heu d'introduir un nom de domini per fer servir l'API v3" @@ -3361,7 +3453,7 @@ msgstr "Heu d'introduir un nom per a la còpia de seguretat" msgid "You must enter a passphrase or disable encryption" msgstr "Heu d'introduir una contrasenya o desactivar el xifratge" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Heu d'introduir una contrasenya per fer servir l'API v3" @@ -3370,11 +3462,11 @@ msgid "You must enter a positive number of backups to keep" msgstr "" "Heu d'introduir un nombre positiu de còpies de seguretat que voleu preservar" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Heu d'introduir un nom d'inquilí (projecte) per fer servir l'API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3387,11 +3479,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3412,11 +3504,11 @@ msgstr "Heu d'introduir el nom d'usuari" msgid "You must fill in {{field}}" msgstr "Heu d'introduir el camp «{{field}}»" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Heu de triar o introduir l'AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Heu de triar o introduir el servidor" @@ -3448,23 +3540,23 @@ msgstr "bytes" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3479,31 +3571,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3518,11 +3614,11 @@ msgstr "" msgid "resume now" msgstr "reprèn ara" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "excepte si especifiqueu explícitament el paràmetre --group-id" diff --git a/Localizations/webroot/localization_webroot-cs.po b/Localizations/webroot/localization_webroot-cs.po index 95d917924..af239fa07 100644 --- a/Localizations/webroot/localization_webroot-cs.po +++ b/Localizations/webroot/localization_webroot-cs.po @@ -45,7 +45,7 @@ msgstr "" msgid "- pick an option -" msgstr "- vyberte jednu z možností -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "…načítání…" @@ -69,23 +69,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Klíč k aplikačnímu programovému rozhraní (API)" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "Přístupový identifikátor ke službě AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Přístupový klíč ke službě AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Zásady IAM služby AWS" @@ -97,7 +97,7 @@ msgstr "O aplikaci" msgid "About {{appname}}" msgstr "O aplikaci {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Přístupový klíč" @@ -125,7 +125,7 @@ msgstr "" msgid "Access to user interface" msgstr "Přístup k uživatelskému rozhraní" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Název účtu" @@ -158,8 +158,8 @@ msgstr "Přidat popis umístění" msgid "Added" msgstr "Přidáno" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Přizpůsobit název „nádoby“ (bucket)?" @@ -192,7 +192,7 @@ msgstr "Všechny Hyper-V stroje" msgid "All Microsoft SQL Databases" msgstr "Všechny Microsoft SQL databáze" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -211,6 +211,10 @@ msgstr "Umožnit přístup na dálku (vyžaduje restart)" msgid "Allowed days" msgstr "Dny, ve které je přístup umožněn" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "V novém umístění byl nalezen už existující soubor" @@ -235,7 +239,7 @@ msgstr "" "\n" "Chcete použít existující databázi?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonymní hlášení o použití" @@ -243,20 +247,26 @@ msgstr "Anonymní hlášení o použití" msgid "Applications" msgstr "Aplikace" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Jako příkazový řádek" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Způsob autentizace" @@ -288,7 +298,7 @@ msgstr "B2 Aplikační ID" msgid "B2 Application Key" msgstr "Aplikační klíč ke službě B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "Identifikátor účtu u cloudového úložiště B2" @@ -296,7 +306,7 @@ msgstr "Identifikátor účtu u cloudového úložiště B2" msgid "B2 Cloud Storage Application ID" msgstr "Aplikační klíč ke cloudovému úložišti B2" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Aplikační klíč ke cloudovému úložišti B2" @@ -338,7 +348,7 @@ msgstr "Doba uchovávání záloh" msgid "Backup:" msgstr "Záloha:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Vývojová testovací (beta)" @@ -350,7 +360,7 @@ msgstr "Nefunkční přístup" msgid "Browse" msgstr "Procházet" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Výchozí nastavení webového prohlížeče" @@ -358,14 +368,14 @@ msgstr "Výchozí nastavení webového prohlížeče" msgid "Bucket create location" msgstr "Umístění ve kterém „nádobu“ (bucket) vytvořit" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -374,7 +384,7 @@ msgstr "Umístění ve kterém „nádobu“ (bucket) vytvořit" msgid "Bucket name" msgstr "Název „nádoby“ (bucket)" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -452,13 +462,13 @@ msgstr "" msgid "Cache Files" msgstr "Soubory mezipaměti" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Kanárek" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -473,14 +483,18 @@ msgstr "Kanárek" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Storno" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -497,7 +511,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -537,7 +551,7 @@ msgstr "Pro začátek vyberte typ úložiště" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "AuthID vytvoříte kliknutím na odkaz AuthID" @@ -666,7 +680,7 @@ msgstr "Spojení ztraceno" msgid "Connection worked!" msgstr "Spojení funguje!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Název kontejneru" @@ -708,7 +722,7 @@ msgstr "Kopie se nezdařila. Zkopírujte URL adresu ručně" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Základní volby" @@ -716,7 +730,7 @@ msgstr "Základní volby" msgid "Counting ({{files}} files found, {{size}})" msgstr "Počítání ({{files}} souborů nalezeno, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Pouze pády" @@ -728,7 +742,7 @@ msgstr "Vytvořit hlášení chyby…" msgid "Create folder?" msgstr "Vytvořit složku?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Vytvořen nový uživatelský účet s omezenými oprávněními" @@ -736,7 +750,7 @@ msgstr "Vytvořen nový uživatelský účet s omezenými oprávněními" msgid "Creating bug report …" msgstr "Vytvořit hlášení chyby…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Vytváření nového uživatele s omezeným přístupem…" @@ -748,7 +762,7 @@ msgstr "Vytváření cílových složek…" msgid "Creating temporary backup …" msgstr "Vytváření dočasné zálohy…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -768,7 +782,7 @@ msgstr "Stávající verze je {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Vlastní S3 koncový bod" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Vlastní satelit" @@ -820,11 +834,11 @@ msgstr "Databáze…" msgid "Days" msgstr "Dnů" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Výchozí" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Výchozí ({{channelname}})" @@ -832,7 +846,7 @@ msgstr "Výchozí ({{channelname}})" msgid "Default excludes" msgstr "Ve výchozím stavu vynecháno" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Výchozí volby" @@ -860,6 +874,10 @@ msgstr "Smazat zálohy starší než" msgid "Delete local database" msgstr "Smazat místní databázi" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Smazat soubory na protějšku" @@ -914,7 +932,8 @@ msgstr "Cíl" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Cílové umístění" @@ -926,6 +945,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Vypnuto" @@ -939,7 +962,7 @@ msgstr "Zavřít" msgid "Dismiss all" msgstr "Zavřít vše" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Motiv vzhledu zobrazení a barev" @@ -988,14 +1011,14 @@ msgstr "Webové stránky projektu Duplicati" msgid "Duplicati forum" msgstr "Diskuzní fórum o Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1034,12 +1057,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Upravit jako seznam" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Upravit jako text" @@ -1051,6 +1074,10 @@ msgstr "Upravit…" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Zašifrovat soubor" @@ -1070,7 +1097,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Šifrovací heslová fráze" @@ -1184,7 +1211,7 @@ msgstr "Chyba" msgid "Error!" msgstr "Chyba!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Chyby a pády" @@ -1232,7 +1259,7 @@ msgstr "Regulární výraz pro vynechávané" msgid "Existing file found" msgstr "Nalezen existující soubor" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimentální" @@ -1326,7 +1353,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Nepodařilo se obnovit soubory: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Nepodařilo se uložit:" @@ -1355,7 +1382,7 @@ msgstr "Filtry" msgid "Finished!" msgstr "Dokončeno!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Úvodní nastavení při prvním spuštění" @@ -1426,7 +1453,7 @@ msgstr "Vytvořit IAM zásady přístupu" msgid "Getting file versions …" msgstr "Získávání verzí souboru…" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "E-mail skupiny" @@ -1451,7 +1478,7 @@ msgid "Hostnames" msgstr "Názvy strojů" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Hodin" @@ -1481,11 +1508,11 @@ msgstr "Identifikátor:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1534,6 +1561,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Pokud nezadáte klíč k API, je vyžadováno jméno nájemníka (tenant)" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1585,7 +1617,7 @@ msgstr "Regulární výraz pro zahrnutí" msgid "Incorrect answer, try again" msgstr "Nesprávná odpověď, zkuste to znovu" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1600,7 +1632,7 @@ msgstr "Informace" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Neplatné znaky v popisu umístění" @@ -1610,7 +1642,7 @@ msgstr "Neplatné znaky v popisu umístění" msgid "Invalid retention time" msgstr "Neplatná doba ponechání" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1638,7 +1670,7 @@ msgstr "Ponechat všechny zálohy" msgid "Keystone API version" msgstr "Verze aplikačního program. rozhraní stavebního bloku" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Jazyk textů v uživatelském rozhraní" @@ -1703,10 +1735,6 @@ msgstr "" msgid "Loading …" msgstr "Načítání…" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Místní repozitář" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1910,7 +1942,7 @@ msgstr "Příští úloha:" msgid "Next time" msgstr "Příště" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1923,9 +1955,9 @@ msgstr "Příště" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Ne" @@ -1967,7 +1999,7 @@ msgstr "Žádné naplánované úlohy" msgid "Non-matching passphrase" msgstr "Zadání heslové fráze se neshodují" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Žádné / vypnuté" @@ -1986,17 +2018,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Nic nebude smazáno. Velikost zálohy naroste při každé změně." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2028,7 +2060,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2051,7 +2083,7 @@ msgstr "Objektové úložiště OpenStack (Swift)" msgid "Opened" msgstr "Otevřeno" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2088,11 +2120,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Předvolby" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2102,7 +2134,7 @@ msgstr "" msgid "Original location" msgstr "Původní umístění" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Ostatní" @@ -2142,7 +2174,7 @@ msgstr "Zadání heslové fráze se neshodují" msgid "Passphrases do not match" msgstr "Zadání heslové fráze se neshodují" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2153,8 +2185,8 @@ msgstr "Heslo" msgid "Patching files with local blocks …" msgstr "Opravování souborů pomocí místních bloků…" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Popis umístění" @@ -2163,7 +2195,7 @@ msgid "Path not found" msgstr "Umístění nenalezeno" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Popis umístění na serveru" @@ -2172,11 +2204,11 @@ msgstr "Popis umístění na serveru" msgid "Path or subfolder in the bucket" msgstr "Umístění nebo podsložka v „nádobě“ (bucket)" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pozastavit" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pozastavit po spuštění nebo hibernaci" @@ -2214,6 +2246,10 @@ msgstr "Zabránit automatickému přihlašování ikony v oznamovací oblasti" msgid "Previous" msgstr "Předchozí" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Postup:" @@ -2224,7 +2260,7 @@ msgstr "" "Pokud „nádoba“ (bucket) existuje, je identifikátor projektu (ProjectID) " "nepovinný" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietární" @@ -2260,10 +2296,30 @@ msgstr "Znovuvytváření databáze…" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrace dočasné zálohy…" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Vztažené (relativní) popisy umístění není možné použít" @@ -2276,14 +2332,34 @@ msgstr "Načíst znovu" msgid "Remote" msgstr "Vzdálené" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Vzdálené umístění" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Vzdálený repozitář" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Vzdálené umístění" @@ -2426,7 +2502,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Kompatibilní s S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Stejné jako základní nainstalovaná verze: {{channelname}}" @@ -2474,7 +2550,7 @@ msgstr "Hledat" msgid "Search for files" msgstr "Hledat soubory" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekund" @@ -2488,8 +2564,8 @@ msgstr "" msgid "Select files" msgstr "Vybrat soubory" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2527,6 +2603,10 @@ msgstr "Server pozastaven" msgid "Server state properties" msgstr "Vlastnosti stavu serveru" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Nastavení" @@ -2600,17 +2680,17 @@ msgstr "Zdrojové složky" msgid "Source:" msgstr "Zdroj:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Konkrétní sestavení určená pouze pro vývojáře. Nepoužívejte pro důležitá " "data." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standardní protokoly" @@ -2637,19 +2717,15 @@ msgstr "Spouštění obnovení…" msgid "Starting the restore process …" msgstr "Spouštění procesu obnovení…" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Zastavit po stávajícím souboru" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Zastavit po stávajícím souboru" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Zastavit nyní" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Zastavit probíhající zálohu" @@ -2703,7 +2779,7 @@ msgstr "Symbolický odkaz" msgid "System Files" msgstr "Systémové soubory" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Systémové výchozí ({{levelname}})" @@ -2759,6 +2835,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Fáze zkoušení" @@ -2771,7 +2852,7 @@ msgstr "Vyzkoušet spojení" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Zkoušení přístupových práv…" @@ -2779,10 +2860,10 @@ msgstr "Zkoušení přístupových práv…" msgid "Testing …" msgstr "Testování…" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2809,7 +2890,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Název nádoby by měl být malými písmeny, převést automaticky?" @@ -2825,15 +2906,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tmavé téma vzhledu (od Michala)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Výchozí téma vzhledu modrá na bílé (od Alexe)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2862,7 +2943,7 @@ msgstr "" "Chcete NAHRADIT STÁVAJÍCÍ klíč stroje \"{{prev}}\" NAHLÁŠENÝM klíčem stroje: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Zadání hesla se neshodují" @@ -2913,7 +2994,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "Cílová složka obsahuje zašifrované soubory, zadejte heslovou frázi" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2940,7 +3021,7 @@ msgstr "Tento měsíc" msgid "This week" msgstr "Tento týden" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Nastavení přiškrcování" @@ -2953,6 +3034,10 @@ msgstr "Čt" msgid "Time" msgstr "Čas" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Do souboru" @@ -2969,7 +3054,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Pro exportování bez heslové fráze odškrtněte „Šifrovat soubor“" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3004,7 +3089,7 @@ msgstr "Důvěřovat certifikátu stroje?" msgid "Trust server certificate?" msgstr "Důvěřovat certifikátu serveru?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3041,7 +3126,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Aktualizační kanál" @@ -3061,18 +3146,18 @@ msgstr "Nahrané soubory" msgid "Uploading verification file …" msgstr "Nahrávání ověřovacího souboru…" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistiky využití" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistiky využití, varování, chyby a pády" @@ -3100,17 +3185,17 @@ msgstr "Uživatelská data" msgid "User domain name" msgstr "Název domény uživatele" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Uživatel má příliš mnoho oprávnění" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Nastavení uživatelského rozhraní" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3200,7 +3285,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Čeká se na dokončení nahrání…" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Varování, chyby a pády" @@ -3237,7 +3322,7 @@ msgstr "Kam chcete soubory obnovit?" msgid "Years" msgstr "Let" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3250,9 +3335,9 @@ msgstr "Let" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Ano" @@ -3291,16 +3376,16 @@ msgstr "Nyní provozujete {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." -msgstr "Zálohu můžete zastavit po dokončení probíhajícího nahrávání souboru." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." +msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Úlohu můžete ukončit buď teď hned, nebo procesu umožnit zpracovat stávající " -"soubor a pak zastavit." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3343,7 +3428,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Je třeba zvolit alespoň jednu zdrojovou složku" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" "Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba " @@ -3357,7 +3442,7 @@ msgstr "Je třeba zadat název zálohy" msgid "You must enter a passphrase or disable encryption" msgstr "Buď je třeba zadat heslovou frázi nebo šifrování vypnout" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" "Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba " @@ -3367,13 +3452,13 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "Je třeba zadat kladný počet záloh které uchovávat" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Aby bylo možné použít verzi 3 aplikačního programového rozhraní, je třeba " "zadat název projektu (tenant)" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3385,11 +3470,11 @@ msgstr "Je třeba zadat platnou dobu po kterou ponechávat zálohy" msgid "You must enter a valid retention policy string" msgstr "Je třeba zadat platný řetězec zásady doby uchovávání záloh" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3410,11 +3495,11 @@ msgstr "Je třeba vyplnit uživatelské jméno" msgid "You must fill in {{field}}" msgstr "Je třeba vyplnit kolonku {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Je třeba vybrat nebo vyplnit AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Je třeba vybrat nebo vyplnit server" @@ -3446,23 +3531,23 @@ msgstr "B" msgid "byte/s" msgstr "B/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3477,29 +3562,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3512,11 +3605,11 @@ msgstr "" msgid "resume now" msgstr "pokračovat nyní" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "pokud výslovně neuvedete --group-id" diff --git a/Localizations/webroot/localization_webroot-da.po b/Localizations/webroot/localization_webroot-da.po index bd304934c..b725afb4f 100644 --- a/Localizations/webroot/localization_webroot-da.po +++ b/Localizations/webroot/localization_webroot-da.po @@ -45,7 +45,7 @@ msgstr "" msgid "- pick an option -" msgstr "- vælg indstilling -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...indlæser..." @@ -63,33 +63,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API Key" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "Om" @@ -97,7 +97,7 @@ msgstr "Om" msgid "About {{appname}}" msgstr "Om {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Access Key" @@ -125,7 +125,7 @@ msgstr "" msgid "Access to user interface" msgstr "Adgang til brugerflade" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Kontonavn" @@ -142,7 +142,7 @@ msgstr "Tilføj en sti" msgid "Add advanced option" msgstr "Tilføj en avanceret indstilling" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Tilføj backup" @@ -158,8 +158,8 @@ msgstr "Tilføj sti" msgid "Added" msgstr "Tilføjet" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Tilpas bucket navnet?" @@ -192,7 +192,7 @@ msgstr "Alle Hyper-V maskiner" msgid "All Microsoft SQL Databases" msgstr "Alle Microsoft SQL databaser" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -214,6 +214,10 @@ msgstr "Tillad fjernadgang (kræver genstart)" msgid "Allowed days" msgstr "Tilladte dage" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "En eksisterende fil blev fundet på den nye placering" @@ -238,7 +242,7 @@ msgstr "" "\n" "Vil du bruge den eksisterende database?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonyme brugsstatistiker" @@ -246,20 +250,26 @@ msgstr "Anonyme brugsstatistiker" msgid "Applications" msgstr "Applikationer" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Som kommandolinie" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Godkendelsesmetode" @@ -291,7 +301,7 @@ msgstr "B2 Application ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -299,7 +309,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -341,7 +351,7 @@ msgstr "Backup fastholdelse" msgid "Backup:" msgstr "Backup:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -353,7 +363,7 @@ msgstr "Adgang defekt" msgid "Browse" msgstr "Gennemse" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browser standard" @@ -361,14 +371,14 @@ msgstr "Browser standard" msgid "Bucket create location" msgstr "Bucket placering ved oprettelse" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -377,7 +387,7 @@ msgstr "Bucket placering ved oprettelse" msgid "Bucket name" msgstr "Bucket navn" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -455,12 +465,13 @@ msgstr "" msgid "Cache Files" msgstr "Cache Filer" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -475,14 +486,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Annuller" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -499,6 +514,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Ændringslog" @@ -519,7 +538,7 @@ msgstr "Tjek for opdateringer nu" msgid "Checking for updates …" msgstr "Leder efter opdateringer ..." -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -535,11 +554,11 @@ msgstr "Valgte en destinationstype at komme i gang" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Click på AuthID linket for at oprettet et AuthID" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Klik for at sætte hastigheds begrænsning" @@ -623,7 +642,7 @@ msgstr "Bekræft sletning" msgid "Confirm encryption passphrase" msgstr "Bekræft krypteringskoden" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -639,11 +658,11 @@ msgstr "Bekræftelse kræves" msgid "Connect" msgstr "Forbind" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Forbind nu" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "Forbinder til server ..." @@ -651,11 +670,11 @@ msgstr "Forbinder til server ..." msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "Forbindelse mistet" @@ -664,7 +683,7 @@ msgstr "Forbindelse mistet" msgid "Connection worked!" msgstr "Forbindelsen virkede!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Container navn" @@ -706,7 +725,7 @@ msgstr "Kopiering mislykkedes. Kopier venligst URL-adressen manuelt" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Grund indstillinger" @@ -714,7 +733,7 @@ msgstr "Grund indstillinger" msgid "Counting ({{files}} files found, {{size}})" msgstr "Tæller ({{files}} filer fundet, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Kun nedbrud" @@ -726,7 +745,7 @@ msgstr "Opret fejlrapport ..." msgid "Create folder?" msgstr "Opret mappe?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Opret en ny begrænset bruger" @@ -734,7 +753,7 @@ msgstr "Opret en ny begrænset bruger" msgid "Creating bug report …" msgstr "Opretter fejlrapport ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Opretter en ny bruger med begrænset adgang ..." @@ -746,7 +765,7 @@ msgstr "Opretter destinations mapper ..." msgid "Creating temporary backup …" msgstr "Opretter en midlertidig backup ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -766,7 +785,7 @@ msgstr "Nuværende version er {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Brugerdefineret S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Brugerdefineret Satellit" @@ -818,11 +837,11 @@ msgstr "Database ..." msgid "Days" msgstr "Dage" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Standard" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Standard ({{channelname}})" @@ -830,7 +849,7 @@ msgstr "Standard ({{channelname}})" msgid "Default excludes" msgstr "Standard ekskluderinger" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Standardindstillinger" @@ -858,6 +877,10 @@ msgstr "Slet sikkerhedskopier, der er ældre end" msgid "Delete local database" msgstr "Slet lokal database" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Slette filer fra destinationen" @@ -912,7 +935,8 @@ msgstr "Destination" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Destinations sti" @@ -924,6 +948,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Deaktiveret" @@ -937,7 +965,7 @@ msgstr "Afvis" msgid "Dismiss all" msgstr "Afvis alle" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Visning og farvevalg" @@ -978,22 +1006,22 @@ msgstr "Downloader opdatering ..." msgid "Duplicate option {{opt}}" msgstr "Dublet af indstilling {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Duplicati hjemmeside" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Duplicati forum" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1031,12 +1059,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Rediger som liste" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Rediger som tekst" @@ -1048,6 +1076,10 @@ msgstr "Rediger ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Krypter fil" @@ -1067,7 +1099,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Krypteringssætning" @@ -1181,7 +1213,7 @@ msgstr "Fejl" msgid "Error!" msgstr "Fejl!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Fejl og nedbrud" @@ -1229,7 +1261,7 @@ msgstr "Ekskluder regulært udtryk" msgid "Existing file found" msgstr "Eksisterende fil fundet" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Eksperimental" @@ -1323,7 +1355,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Kunne ikke gendanne filer: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Kunne ikke gemme:" @@ -1352,7 +1384,7 @@ msgstr "Filtre" msgid "Finished!" msgstr "Færdig!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Førstegangsopsætning" @@ -1423,7 +1455,7 @@ msgstr "Generér IAM access policy" msgid "Getting file versions …" msgstr "Henter fil versioner ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Gruppe email" @@ -1439,7 +1471,7 @@ msgstr "Skjul" msgid "Hide hidden folders" msgstr "Skjul skjulte filer" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Hjem" @@ -1448,7 +1480,7 @@ msgid "Hostnames" msgstr "Hostnavne" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Timer" @@ -1478,11 +1510,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1533,6 +1565,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Hvis du ikke indtaster en API key, skal du angive tenant navnet" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1580,11 +1617,11 @@ msgstr "Inkluder udtryk" msgid "Include regular expression" msgstr "Inkluder regulært udtryk" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "Forkert svar, prøv igen" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1598,7 +1635,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Ugyldige tegn i stien" @@ -1608,7 +1645,7 @@ msgstr "Ugyldige tegn i stien" msgid "Invalid retention time" msgstr "Ugyldig bevaringstid" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1636,7 +1673,7 @@ msgstr "Gem alle backups" msgid "Keystone API version" msgstr "Keystone API version" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Sprog i brugergrænsefladen" @@ -1697,15 +1734,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "Indlæser ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Lokal fortegnelse" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}}" msgid "Log data from the server" msgstr "Logdata fra serveren" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "Log ud" @@ -1757,6 +1790,10 @@ msgstr "MByte" msgid "MByte/s" msgstr "MByte/s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "Vedligehold" @@ -1767,7 +1804,7 @@ msgid "" " advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1787,7 +1824,7 @@ msgstr "Max downloadhastighed" msgid "Max upload speed" msgstr "Maks uploadhastighed" -#: index.html:144 templates/addoredit.html:127 templates/addoredit.html:169 +#: index.html:145 templates/addoredit.html:127 templates/addoredit.html:169 #: templates/addoredit.html:382 templates/addoredit.html:95 #: templates/edituri.html:34 templates/restoredirect.html:34 msgid "Menu" @@ -1805,12 +1842,12 @@ msgstr "Microsoft SQL Databaser" msgid "Minimum redundancy" msgstr "Mindste tilladte redundans" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "Mindste redundans er 1.0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minutter" @@ -1870,7 +1907,7 @@ msgstr "Navn" msgid "Never" msgstr "Aldrig" -#: templates/settings.html:11 +#: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" msgstr "" @@ -1878,7 +1915,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1897,11 +1934,11 @@ msgstr "Næste" msgid "Next scheduled run:" msgstr "Næste planlagte kørsel:" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Næste planlagte opgave:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Næste opgave:" @@ -1909,7 +1946,7 @@ msgstr "Næste opgave:" msgid "Next time" msgstr "Næste tidspunkt" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1922,9 +1959,9 @@ msgstr "Næste tidspunkt" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nej" @@ -1959,7 +1996,7 @@ msgstr "" msgid "No passphrase entered" msgstr "Ingen adgangssætning angivet" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "Ingen planlagte opgaver" @@ -1967,7 +2004,7 @@ msgstr "Ingen planlagte opgaver" msgid "Non-matching passphrase" msgstr "Uoverenstemmelse mellem adgangssætninger" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Ingen / deaktiveret" @@ -1986,16 +2023,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Intet vil blive slettet. Backup størrelsen vokser med hver ændring." -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2027,7 +2065,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2051,7 +2089,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Åbnet" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2088,11 +2126,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Indstillinger" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2102,7 +2140,7 @@ msgstr "" msgid "Original location" msgstr "Oprindelig placering" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Andre" @@ -2141,7 +2179,7 @@ msgstr "Adgangssætninger er ikke ens" msgid "Passphrases do not match" msgstr "Adgangssætninger er ikke identiske" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2152,8 +2190,8 @@ msgstr "Adgangskode" msgid "Patching files with local blocks …" msgstr "Opdaterer filer med lokale blokke ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Sti" @@ -2162,7 +2200,7 @@ msgid "Path not found" msgstr "Stien blev ikke fundet" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Sti på server" @@ -2171,15 +2209,15 @@ msgstr "Sti på server" msgid "Path or subfolder in the bucket" msgstr "Sti eller undermappe i bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pause" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pause efter start eller dvale" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Pause indstillinger" @@ -2213,6 +2251,10 @@ msgstr "Forhindre automatisk login-in fra system ikonet" msgid "Previous" msgstr "Forrige" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Fremgang:" @@ -2221,7 +2263,7 @@ msgstr "Fremgang:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID er valgfrit hvis bucket eksisterer" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietære" @@ -2257,15 +2299,35 @@ msgstr "Gendanner database ..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrerer midlertidig backup ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relative stier er ikke tilladt" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Genindlæs" @@ -2273,14 +2335,34 @@ msgstr "Genindlæs" msgid "Remote" msgstr "Destination" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Destinations sti" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Ekstern fortegnelse" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Destinations sti" @@ -2329,7 +2411,7 @@ msgstr "Rapporterer:" msgid "Reset" msgstr "Nulstil" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Gendan" @@ -2387,7 +2469,7 @@ msgstr "Gendannede Symlinks" msgid "Restoring files …" msgstr "Gendanner filer ..." -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Genoptag" @@ -2407,7 +2489,7 @@ msgstr "Kør nu" msgid "Running commandline entry" msgstr "Kører kommandolinie opgave" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "Kørende opgave:" @@ -2423,7 +2505,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 kompatibel" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Samme som grundinstallationsversionen: {{channelname}}" @@ -2471,7 +2553,7 @@ msgstr "Søg" msgid "Search for files" msgstr "Søg efter filer" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekunder" @@ -2484,8 +2566,8 @@ msgstr "Vælg et log niveau og se beskeder som de kommer:" msgid "Select files" msgstr "Vælg filer" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2523,7 +2605,11 @@ msgstr "Server på pause" msgid "Server state properties" msgstr "Egenskaber for serveren" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Indstillinger" @@ -2594,16 +2680,16 @@ msgstr "Kilde mapper" msgid "Source:" msgstr "Kilde:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Specifikke versioner kun til udviklere. Bør ikke bruges med vigtig data." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standard protokoller" @@ -2630,19 +2716,15 @@ msgstr "Starter gendannelse ..." msgid "Starting the restore process …" msgstr "Starter gendannelses processen ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stop efter den nuværende fil" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Stop efter den nuværende fil" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Stop nu" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Stop den kørende backup" @@ -2651,11 +2733,11 @@ msgstr "Stop den kørende backup" msgid "Stop running task" msgstr "Stop den kørende opgave" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "Stopper efter den nuværende fil:" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "Stopper opgave:" @@ -2696,7 +2778,7 @@ msgstr "Symbolsk kæde" msgid "System Files" msgstr "System Filer" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "System standard ({{levelname}})" @@ -2752,6 +2834,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Testfase" @@ -2764,7 +2851,7 @@ msgstr "Test forbindelse" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Tester tilladelser ..." @@ -2772,10 +2859,10 @@ msgstr "Tester tilladelser ..." msgid "Testing …" msgstr "Tester ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2801,7 +2888,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Bucket navnet bør være med små bogstaver, konverter automatisk?" @@ -2813,19 +2900,19 @@ msgstr "" "Opsætningen bør holdes hemmelig. Er du sikker på at du vil gemme en ikke-" "krypteret fil der indeholder dine adgangskoder?" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Mørke farver (af Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Standard blå på hvid (af Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2854,7 +2941,7 @@ msgstr "" "Vil du ERSTATTE din NUVÆRENDE værtsnøgle \"{{prev}}\" med den RAPPORTEREDE værtsnøgle: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Adgangskoderne er ikke ens" @@ -2906,7 +2993,7 @@ msgstr "" "Destinationsmappen indeholder krypterede filer, angiv venligst " "adgangssætningen" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2934,7 +3021,7 @@ msgstr "Denne måned" msgid "This week" msgstr "Denne uge" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Indstillinger for hastighedsbegrænsning" @@ -2947,6 +3034,10 @@ msgstr "Tor" msgid "Time" msgstr "Tid" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Til fil" @@ -2965,7 +3056,7 @@ msgstr "" "For at eksportere uden en adgangsætning, fjern mærket ud for \"Krypter " "filen\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2999,7 +3090,7 @@ msgstr "Stol på værtscertifikatet?" msgid "Trust server certificate?" msgstr "Stol på server certifikatet?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3036,7 +3127,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Opdateringskanal" @@ -3056,19 +3147,18 @@ msgstr "Uploadede filer" msgid "Uploading verification file …" msgstr "Uploader verifikationsfil ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Brugsstatistik" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Brugsstatistik, advarsler, fejl og nedbrud" @@ -3096,17 +3186,17 @@ msgstr "Brugerdata" msgid "User domain name" msgstr "Bruger domæne navn" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Brugeren har for mange tilladelser" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Indstillinger til brugergrænseflade" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3170,7 +3260,7 @@ msgstr "Meget stærk" msgid "Very weak" msgstr "Meget svag" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "Besøg os på" @@ -3196,7 +3286,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Advarsler, fejl og nedbrud" @@ -3233,7 +3323,7 @@ msgstr "Hvor vil du gendanne filerne til?" msgid "Years" msgstr "År" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3246,9 +3336,9 @@ msgstr "År" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Ja" @@ -3287,13 +3377,15 @@ msgstr "Du kører med {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3337,7 +3429,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Du skal vælge mindst en kilde mappe" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Du er nødt til at angive et domæne navn for at bruge v3 API'en" @@ -3349,7 +3441,7 @@ msgstr "Du skal angive et navn for denne backup" msgid "You must enter a passphrase or disable encryption" msgstr "Du skal indtaste en adgangssætning eller fravælge kryptering" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Du skal angive en adgangskode for at bruge v3 API'en" @@ -3357,12 +3449,12 @@ msgstr "Du skal angive en adgangskode for at bruge v3 API'en" msgid "You must enter a positive number of backups to keep" msgstr "Du skal indtaste et positivt antal backups der skal bevares" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Du er nødt til at angive et tenant (projekt) navn for at bruge v3 API'en" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3374,11 +3466,11 @@ msgstr "Du skal angive en gyldig periode som backups gemmes i" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3399,11 +3491,11 @@ msgstr "Du skal angive et brugernavn" msgid "You must fill in {{field}}" msgstr "Du skal udfylde {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Du skal vælge eller udfylde AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Du skal vælge eller indtaste server navnet" @@ -3435,23 +3527,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3466,31 +3558,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3505,11 +3601,11 @@ msgstr "" msgid "resume now" msgstr "genoptag nu" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "Medmindre du eksplicit angiver --group-id" diff --git a/Localizations/webroot/localization_webroot-de.po b/Localizations/webroot/localization_webroot-de.po index e46601c3c..287f0ba38 100644 --- a/Localizations/webroot/localization_webroot-de.po +++ b/Localizations/webroot/localization_webroot-de.po @@ -59,7 +59,7 @@ msgstr "" msgid "- pick an option -" msgstr "- Option auswählen -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...laden..." @@ -83,23 +83,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API-Key" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -111,7 +111,7 @@ msgstr "Über" msgid "About {{appname}}" msgstr "Über {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Zugriffsschlüssel" @@ -139,7 +139,7 @@ msgstr "" msgid "Access to user interface" msgstr "Zugriff auf die Benutzeroberfläche" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Kontoname" @@ -172,8 +172,8 @@ msgstr "Pfad hinzufügen" msgid "Added" msgstr "Hinzugefügt" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Bucket-Name anpassen?" @@ -206,7 +206,7 @@ msgstr "Alle Hyper-V Maschinen" msgid "All Microsoft SQL Databases" msgstr "Alle Microsoft SQL-Datenbanken" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -228,6 +228,10 @@ msgstr "Fernzugriff erlauben (Neustart notwendig)" msgid "Allowed days" msgstr "Erlaubte Tage" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "" @@ -253,7 +257,7 @@ msgstr "" "\n" "Soll die lokale Datenbank genutzt werden?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonyme Nutzungsberichte" @@ -261,20 +265,26 @@ msgstr "Anonyme Nutzungsberichte" msgid "Applications" msgstr "Anwendungen" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "als Befehl für Kommandozeile" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Authentifizierungs-Methode" @@ -306,7 +316,7 @@ msgstr "B2-Anwendungs-ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -314,7 +324,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -356,7 +366,7 @@ msgstr "Sicherungsaufbewahrung" msgid "Backup:" msgstr "Sicherung:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -368,7 +378,7 @@ msgstr "Defekter Zugriff" msgid "Browse" msgstr "Durchsuchen" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browserstandard" @@ -376,14 +386,14 @@ msgstr "Browserstandard" msgid "Bucket create location" msgstr "Bucket-Speicherort" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -392,7 +402,7 @@ msgstr "Bucket-Speicherort" msgid "Bucket name" msgstr "Bucket-Name" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -471,13 +481,13 @@ msgstr "" msgid "Cache Files" msgstr "Dateien cachen" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -492,14 +502,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Abbrechen" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -516,7 +530,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -556,7 +570,7 @@ msgstr "Wähle einen Speichertypen zum Starten" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Auf AuthID klicken um eine AuthID zu erstellen" @@ -685,7 +699,7 @@ msgstr "Verbindung verloren" msgid "Connection worked!" msgstr "Verbindung erfolgreich!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Container-Name" @@ -727,7 +741,7 @@ msgstr "Kopie fehlgeschlagen. Bitte kopiere die URL manuell" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Allgemeine Optionen" @@ -735,7 +749,7 @@ msgstr "Allgemeine Optionen" msgid "Counting ({{files}} files found, {{size}})" msgstr "Dateien ermitteln ({{files}} files found, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Nur Abstürze" @@ -747,7 +761,7 @@ msgstr "Fehlerbericht erstellen..." msgid "Create folder?" msgstr "Ordner erstellen?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Nutzer mit eingeschränkten Rechten anlegen" @@ -755,7 +769,7 @@ msgstr "Nutzer mit eingeschränkten Rechten anlegen" msgid "Creating bug report …" msgstr "Fehlerbericht wird erstellt... " -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Neuer Benutzer mit eingeschränktem Zugriff wird erstellt …" @@ -767,7 +781,7 @@ msgstr "Zielverzeichnisse erstellen... " msgid "Creating temporary backup …" msgstr "Temporäre Sicherung wird erstellt …" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -787,7 +801,7 @@ msgstr "Aktuelle Version: {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Benutzerdefinierter S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Benutzerdefinierter Satellit" @@ -839,11 +853,11 @@ msgstr "Datenbank …" msgid "Days" msgstr "Tage" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Standard" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Standard ({{channelname}})" @@ -851,7 +865,7 @@ msgstr "Standard ({{channelname}})" msgid "Default excludes" msgstr "Standardmäßig ausgeschlossen" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Standard-Optionen" @@ -879,6 +893,10 @@ msgstr "Sicherungen löschen, die älter sind als" msgid "Delete local database" msgstr "Lokale Datenbank löschen" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Remote-Dateien löschen" @@ -933,7 +951,8 @@ msgstr "Ziel" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Ziel-Pfad" @@ -945,6 +964,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Deaktiviert" @@ -958,7 +981,7 @@ msgstr "Verwerfen" msgid "Dismiss all" msgstr "Alles ausblenden" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Darstellung und Farbthema" @@ -1007,14 +1030,14 @@ msgstr "Duplicati Website" msgid "Duplicati forum" msgstr "Duplicati Forum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1056,12 +1079,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Als Liste bearbeiten" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Als Text bearbeiten" @@ -1073,6 +1096,10 @@ msgstr "Bearbeiten …" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Datei verschlüsseln" @@ -1092,7 +1119,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Verschlüsselungspassphrase" @@ -1206,7 +1233,7 @@ msgstr "Fehler" msgid "Error!" msgstr "Fehler!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Fehler und Abstürze" @@ -1254,7 +1281,7 @@ msgstr "Regulären Ausdruck (ausschließen)" msgid "Existing file found" msgstr "Vorhandene Datenbank gefunden" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1348,7 +1375,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Wiederherstellung der Dateien fehlgeschlagen: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Fehler beim Speichern:" @@ -1377,7 +1404,7 @@ msgstr "Filter" msgid "Finished!" msgstr "Fertiggestellt!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Zuerst Setup starten" @@ -1448,7 +1475,7 @@ msgstr "IAM-Zugriffsrichtlinie generieren" msgid "Getting file versions …" msgstr "Dateiversionen werden abgerufen …" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Gruppen-E-Mail" @@ -1473,7 +1500,7 @@ msgid "Hostnames" msgstr "Hostnamen" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Stunden" @@ -1503,11 +1530,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1558,6 +1585,11 @@ msgid "If you do not enter an API Key, the tenant name is required" msgstr "" "Wenn kein API Schlüssel angegeben wurde, ist der Tenant-Name erforderlich." +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1609,7 +1641,7 @@ msgstr "Regulären Ausdruck (einschließen)" msgid "Incorrect answer, try again" msgstr "Fehlerhafte Antwort, versuche es erneut" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1624,7 +1656,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Unzulässige Zeichen im Pfad" @@ -1634,7 +1666,7 @@ msgstr "Unzulässige Zeichen im Pfad" msgid "Invalid retention time" msgstr "Ungültige Aufbewahrungszeit" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1662,7 +1694,7 @@ msgstr "Alle Sicherungen behalten" msgid "Keystone API version" msgstr "Keystone API Version" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Sprache der Benutzeroberfläche" @@ -1729,10 +1761,6 @@ msgstr "" msgid "Loading …" msgstr "Laden..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Lokales Repository" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1936,7 +1968,7 @@ msgstr "Nächste Aufgabe:" msgid "Next time" msgstr "Nächstes Mal" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1949,9 +1981,9 @@ msgstr "Nächstes Mal" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nein" @@ -1995,7 +2027,7 @@ msgstr "Keine geplanten Aufgaben" msgid "Non-matching passphrase" msgstr "Nicht übereinstimmende Passphrase" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Keine / deaktiviert" @@ -2015,17 +2047,17 @@ msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" "Es wird nichts gelöscht. Die Sicherungsgröße erhöht sich mit jeder Änderung." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2057,7 +2089,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "Offizielle Versionen" @@ -2081,7 +2113,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Geöffnet" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2118,11 +2150,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Optionen" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2132,7 +2164,7 @@ msgstr "" msgid "Original location" msgstr "Ursprünglicher Speicherort" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Weitere" @@ -2172,7 +2204,7 @@ msgstr "Passphrasen stimmen nicht überein" msgid "Passphrases do not match" msgstr "Passphrasen stimmen nicht überein" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2183,8 +2215,8 @@ msgstr "Passwort" msgid "Patching files with local blocks …" msgstr "Dateien mit vorhandenen Daten aufbauen..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Pfad" @@ -2193,7 +2225,7 @@ msgid "Path not found" msgstr "Pfad nicht gefunden" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Pfad auf Server" @@ -2202,11 +2234,11 @@ msgstr "Pfad auf Server" msgid "Path or subfolder in the bucket" msgstr "Pfad oder Unterverzeichnis im Bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pause" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pause nach dem Start oder Aufwachen" @@ -2244,6 +2276,10 @@ msgstr "Verhindert das automatische Anmelden per Taskleistensymbol" msgid "Previous" msgstr "Zurück" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Fortschritt:" @@ -2252,7 +2288,7 @@ msgstr "Fortschritt:" msgid "ProjectID is optional if the bucket exist" msgstr "Die Projekt-ID ist optional, wenn der Bucket existiert" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietär" @@ -2288,10 +2324,30 @@ msgstr "Datenbank wird neu erstellt …" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Temporäre Sicherung wird registriert …" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relative Pfade sind nicht möglich" @@ -2304,14 +2360,34 @@ msgstr "Neu laden" msgid "Remote" msgstr "Remote" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Entfernter Pfad" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Entferntes Repository" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Entfernter Pfad" @@ -2454,7 +2530,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Kompatibel" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Wie die zuerst installierte Version: {{channelname}}" @@ -2502,7 +2578,7 @@ msgstr "Suche" msgid "Search for files" msgstr "Dateien suchen" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekunden" @@ -2517,8 +2593,8 @@ msgstr "" msgid "Select files" msgstr "Wähle Dateien" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2556,6 +2632,10 @@ msgstr "Server pausiert" msgid "Server state properties" msgstr "Server Zustandseigenschaften" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Einstellungen" @@ -2628,17 +2708,17 @@ msgstr "Quell-Verzeichnisse" msgid "Source:" msgstr "Quelle:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Spezifische Builds nur für Entwickler. Nicht für die Verwendung mit " "wichtigen Daten." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "Stabil" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standardprotokolle" @@ -2665,19 +2745,15 @@ msgstr "Wiederherstellung wird gestartet …" msgid "Starting the restore process …" msgstr "Starten des Wiederherstellungsprozesses..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stopp nach aktueller Datei" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Beende nach aktueller Datei" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Beenden" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Laufende Sicherung anhalten" @@ -2731,7 +2807,7 @@ msgstr "Symbolischer Link" msgid "System Files" msgstr "Systemdateien" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "System-Standard ({{levelname}})" @@ -2787,6 +2863,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Test Phase" @@ -2799,7 +2880,7 @@ msgstr "Verbindung prüfen" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Berechtigungen werden überprüft …" @@ -2807,10 +2888,10 @@ msgstr "Berechtigungen werden überprüft …" msgid "Testing …" msgstr "Prüfung..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2837,7 +2918,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Der Bucket sollte klein geschrieben sein. Jetzt klein schreiben?" @@ -2853,15 +2934,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Dunkles Thema (von Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Blau-auf-Weiß Thema (von Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2891,7 +2972,7 @@ msgstr "" "Schüssel \"{{prev}}\" durch den GEMELDETEN Host-Schüssel {{key}} ersetzen?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Die Passwörter stimmen nicht überein" @@ -2949,7 +3030,7 @@ msgstr "" "Der Zielordner enthält verschlüsselte Dateien, bitte stelle die Passphrase " "bereit" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2978,7 +3059,7 @@ msgstr "Dieser Monat" msgid "This week" msgstr "Diese Woche" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Drosselungseinstellungen" @@ -2991,6 +3072,10 @@ msgstr "Do" msgid "Time" msgstr "Zeit" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "als Datei" @@ -3008,7 +3093,7 @@ msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" "Deaktiviere »Datei verschlüsseln«, um ohne eine Passphrase zu exportieren" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3043,7 +3128,7 @@ msgstr "Host Zertifikat vertrauen?" msgid "Trust server certificate?" msgstr "Server Zertifikat vertrauen?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3080,7 +3165,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Update-Kanal" @@ -3100,18 +3185,18 @@ msgstr "Hochgeladene Dateien" msgid "Uploading verification file …" msgstr "Verifikationsdatei wird hochgeladen …" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Nutzungsstatistiken" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Nutzungsberichte, Warnungen, Fehler und Abstürze" @@ -3139,17 +3224,17 @@ msgstr "Benutzer Daten" msgid "User domain name" msgstr "Benutzer Domänenname " -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Nutzer hat zu viele Rechte" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Einstellungen der Benutzeroberfläche" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3240,7 +3325,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Warte auf Ende des Uploads... " -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Warnungen, Fehler und Abstürze" @@ -3278,7 +3363,7 @@ msgstr "Wohin sollen die Dateien wiederhergestellt werden?" msgid "Years" msgstr "Jahre" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3291,9 +3376,9 @@ msgstr "Jahre" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Ja" @@ -3332,18 +3417,16 @@ msgstr "Aktuell wird {{appname}} {{version}} verwendet" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Nachdem alle derzeit laufenden Datei-Uploads abgeschlossen sind, kann das " -"Backup gestoppt werden." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Die Aufgabe kann sofort angehalten werden, oder nachdem der Prozess die " -"aktuelle Datei abgeschlossen hat." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3389,7 +3472,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Sie müssen mindestens ein Quellverzeichnis wählen." -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Eingabe vom Domänennamens für die Verwendungder v3-API" @@ -3402,7 +3485,7 @@ msgid "You must enter a passphrase or disable encryption" msgstr "" "Sie müssen eine Passphrase eingeben oder die Verschlüsselung deaktivieren." -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Gib ein Passwort für die Verwendungder v3-API an" @@ -3411,12 +3494,12 @@ msgid "You must enter a positive number of backups to keep" msgstr "" "Sie müssen eine positive Anzahl der zu behaltenden Sicherungen eingeben." -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Gib einen Kundennamen (bzw. Projektnamen) für die Verwendungder v3-API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3429,11 +3512,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Sie müssen eine gültige Aufbewahrungsregel angeben." -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3454,11 +3537,11 @@ msgstr "Sie müssen einen Benutzernamen eintragen." msgid "You must fill in {{field}}" msgstr "{{field}} muss ausgefüllt sein" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Sie müssen die AuthURI auswählen oder eintragen." -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Sie müssen den Server auswählen oder eintragen." @@ -3492,23 +3575,23 @@ msgstr "Byte" msgid "byte/s" msgstr "Byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3523,29 +3606,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3558,11 +3649,11 @@ msgstr "" msgid "resume now" msgstr "Jetzt starten" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "es sei denn, Sie geben explizit --group-id an" diff --git a/Localizations/webroot/localization_webroot-en_GB.po b/Localizations/webroot/localization_webroot-en_GB.po index 274ac73c5..822e850c2 100644 --- a/Localizations/webroot/localization_webroot-en_GB.po +++ b/Localizations/webroot/localization_webroot-en_GB.po @@ -38,7 +38,7 @@ msgstr "" msgid "- pick an option -" msgstr "- pick an option -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...loading..." @@ -62,23 +62,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API key" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -90,7 +90,7 @@ msgstr "About" msgid "About {{appname}}" msgstr "About {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Access Key" @@ -118,7 +118,7 @@ msgstr "" msgid "Access to user interface" msgstr "Access to user interface" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Account name" @@ -151,8 +151,8 @@ msgstr "Add path" msgid "Added" msgstr "Added" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Adjust bucket name?" @@ -185,7 +185,7 @@ msgstr "All Hyper-V Machines" msgid "All Microsoft SQL Databases" msgstr "All Microsoft SQL Databases" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -207,6 +207,10 @@ msgstr "Allow remote access (requires restart)" msgid "Allowed days" msgstr "Allowed days" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "An existing file was found at the new location" @@ -231,7 +235,7 @@ msgstr "" "\n" " Do you wish to use the existing database?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonymous usage reports" @@ -239,20 +243,26 @@ msgstr "Anonymous usage reports" msgid "Applications" msgstr "Applications" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "As Command-line" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Authentication method" @@ -284,7 +294,7 @@ msgstr "B2 Application ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -292,7 +302,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -334,7 +344,7 @@ msgstr "Backup retention" msgid "Backup:" msgstr "Backup:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -346,7 +356,7 @@ msgstr "Broken access" msgid "Browse" msgstr "Browse" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browser default" @@ -354,14 +364,14 @@ msgstr "Browser default" msgid "Bucket create location" msgstr "Bucket create location" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -370,7 +380,7 @@ msgstr "Bucket create location" msgid "Bucket name" msgstr "Bucket name" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -448,13 +458,13 @@ msgstr "" msgid "Cache Files" msgstr "Cache Files" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -469,14 +479,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancel" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -493,7 +507,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -533,7 +547,7 @@ msgstr "Chose a storage type to get started" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Click the AuthID link to create an AuthID" @@ -662,7 +676,7 @@ msgstr "Connection lost" msgid "Connection worked!" msgstr "Connection worked!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Container name" @@ -704,7 +718,7 @@ msgstr "Copy failed. Please manually copy the URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Core options" @@ -712,7 +726,7 @@ msgstr "Core options" msgid "Counting ({{files}} files found, {{size}})" msgstr "Counting ({{files}} files found, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Crashes only" @@ -724,7 +738,7 @@ msgstr "Create bug report …" msgid "Create folder?" msgstr "Create folder?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Created new limited user" @@ -732,7 +746,7 @@ msgstr "Created new limited user" msgid "Creating bug report …" msgstr "Creating bug report …" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Creating new user with limited access …" @@ -744,7 +758,7 @@ msgstr "Creating target folders …" msgid "Creating temporary backup …" msgstr "Creating temporary backup …" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -764,7 +778,7 @@ msgstr "Current version is {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Custom S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Custom Satellite" @@ -816,11 +830,11 @@ msgstr "Database …" msgid "Days" msgstr "Days" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Default" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Default ({{channelname}})" @@ -828,7 +842,7 @@ msgstr "Default ({{channelname}})" msgid "Default excludes" msgstr "Default excludes" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Default options" @@ -856,6 +870,10 @@ msgstr "Delete backups that are older than" msgid "Delete local database" msgstr "Delete local database" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Delete remote files" @@ -910,7 +928,8 @@ msgstr "Destination" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Destination path" @@ -922,6 +941,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Disabled" @@ -935,7 +958,7 @@ msgstr "Dismiss" msgid "Dismiss all" msgstr "Dismiss all" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Display and color theme" @@ -984,14 +1007,14 @@ msgstr "Duplicati Website" msgid "Duplicati forum" msgstr "Duplicati forum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1030,12 +1053,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Edit as list" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Edit as text" @@ -1047,6 +1070,10 @@ msgstr "Edit …" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Encrypt file" @@ -1066,7 +1093,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Encryption passphrase" @@ -1180,7 +1207,7 @@ msgstr "Error" msgid "Error!" msgstr "Error!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Errors and crashes" @@ -1228,7 +1255,7 @@ msgstr "Exclude regular expression" msgid "Existing file found" msgstr "Existing file found" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1322,7 +1349,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Failed to restore files: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Failed to save:" @@ -1351,7 +1378,7 @@ msgstr "Filters" msgid "Finished!" msgstr "Finished!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "First run setup" @@ -1422,7 +1449,7 @@ msgstr "" msgid "Getting file versions …" msgstr "Getting file versions …" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Group email" @@ -1447,7 +1474,7 @@ msgid "Hostnames" msgstr "Hostnames" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Hours" @@ -1477,11 +1504,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1530,6 +1557,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "If you do not enter an API Key, the tenant name is required" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1581,7 +1613,7 @@ msgstr "Include regular expression" msgid "Incorrect answer, try again" msgstr "Incorrect answer, try again" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1595,7 +1627,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Invalid characters in path" @@ -1605,7 +1637,7 @@ msgstr "Invalid characters in path" msgid "Invalid retention time" msgstr "Invalid retention time" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1633,7 +1665,7 @@ msgstr "Keep all backups" msgid "Keystone API version" msgstr "Keystone API version" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Language in user interface" @@ -1698,10 +1730,6 @@ msgstr "" msgid "Loading …" msgstr "Loading …" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Local Repository" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1905,7 +1937,7 @@ msgstr "Next task:" msgid "Next time" msgstr "Next time" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1918,9 +1950,9 @@ msgstr "Next time" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "No" @@ -1962,7 +1994,7 @@ msgstr "No scheduled tasks" msgid "Non-matching passphrase" msgstr "Non-matching passphrase" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "None / disabled" @@ -1981,17 +2013,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Nothing will be deleted. The backup size will grow with each change." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2023,7 +2055,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2047,7 +2079,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Opened" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2084,11 +2116,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Options" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2098,7 +2130,7 @@ msgstr "" msgid "Original location" msgstr "Original location" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Others" @@ -2137,7 +2169,7 @@ msgstr "Passphrases are not matching" msgid "Passphrases do not match" msgstr "Passphrases do not match" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2148,8 +2180,8 @@ msgstr "Password" msgid "Patching files with local blocks …" msgstr "Patching files with local blocks …" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Path" @@ -2158,7 +2190,7 @@ msgid "Path not found" msgstr "Path not found" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Path on server" @@ -2167,11 +2199,11 @@ msgstr "Path on server" msgid "Path or subfolder in the bucket" msgstr "Path or subfolder in the bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pause" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pause after startup or hibernation" @@ -2209,6 +2241,10 @@ msgstr "Prevent tray icon automatic log-in" msgid "Previous" msgstr "Previous" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progress:" @@ -2217,7 +2253,7 @@ msgstr "Progress:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID is optional if the bucket exist" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietary" @@ -2253,10 +2289,30 @@ msgstr "Recreating database …" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registering temporary backup …" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relative paths not allowed" @@ -2269,14 +2325,34 @@ msgstr "Reload" msgid "Remote" msgstr "Remote" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Remote Path" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Remote Repository" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Remote path" @@ -2419,7 +2495,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Compatible" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Same as the base install version: {{channelname}}" @@ -2467,7 +2543,7 @@ msgstr "Search" msgid "Search for files" msgstr "Search for files" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Seconds" @@ -2480,8 +2556,8 @@ msgstr "Select a log level and see messages as they happen:" msgid "Select files" msgstr "Select files" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2519,6 +2595,10 @@ msgstr "Server paused" msgid "Server state properties" msgstr "Server state properties" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Settings" @@ -2591,15 +2671,15 @@ msgstr "Source folders" msgid "Source:" msgstr "Source:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "Specific builds for developers only. Not for use with important data." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standard protocols" @@ -2626,19 +2706,15 @@ msgstr "Starting restore …" msgid "Starting the restore process …" msgstr "Starting the restore process …" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Stop after the current file" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Stop now" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Stop running backup" @@ -2692,7 +2768,7 @@ msgstr "Symbolic link" msgid "System Files" msgstr "System Files" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "System default ({{levelname}})" @@ -2748,6 +2824,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Test Phase" @@ -2760,7 +2841,7 @@ msgstr "Test connection" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Testing permissions …" @@ -2768,10 +2849,10 @@ msgstr "Testing permissions …" msgid "Testing …" msgstr "Testing …" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2797,7 +2878,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "The bucket name should be all lower-case, convert automatically?" @@ -2813,15 +2894,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "The dark theme (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "The default blue on white theme (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2850,7 +2931,7 @@ msgstr "" "Do you want to REPLACE your CURRENT host key \"{{prev}}\" with the REPORTED host key: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "The passwords do not match" @@ -2903,7 +2984,7 @@ msgid "" msgstr "" "The target folder contains encrypted files, please supply the passphrase" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2931,7 +3012,7 @@ msgstr "This month" msgid "This week" msgstr "This week" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Throttle settings" @@ -2944,6 +3025,10 @@ msgstr "Thu" msgid "Time" msgstr "Time" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "To File" @@ -2960,7 +3045,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "To export without a passphrase, uncheck the \"Encrypt file\" box" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2994,7 +3079,7 @@ msgstr "Trust host certificate?" msgid "Trust server certificate?" msgstr "Trust server certificate?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3031,7 +3116,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Update channel" @@ -3051,18 +3136,18 @@ msgstr "Uploaded files" msgid "Uploading verification file …" msgstr "Uploading verification file …" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Usage statistics" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Usage statistics, warnings, errors, and crashes" @@ -3090,17 +3175,17 @@ msgstr "User data" msgid "User domain name" msgstr "User domain name" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "User has too many permissions" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "User interface settings" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3190,7 +3275,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Waiting for upload to finish …" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Warnings, errors and crashes" @@ -3226,7 +3311,7 @@ msgstr "Where do you want to restore the files to?" msgid "Years" msgstr "Years" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3239,9 +3324,9 @@ msgstr "Years" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Yes" @@ -3280,18 +3365,16 @@ msgstr "You are currently running {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"You can stop the backup after any file uploads currently in progress have " -"finished." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"You can stop the task immediately, or allow the process to continue its " -"current file and then stop." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3335,7 +3418,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "You must choose at least one source folder" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "You must enter a domain name to use v3 API" @@ -3347,7 +3430,7 @@ msgstr "You must enter a name for the backup" msgid "You must enter a passphrase or disable encryption" msgstr "You must enter a passphrase or disable encryption" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "You must enter a password to use v3 API" @@ -3355,11 +3438,11 @@ msgstr "You must enter a password to use v3 API" msgid "You must enter a positive number of backups to keep" msgstr "You must enter a positive number of backups to keep" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "You must enter a tenant (aka project) name to use v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3371,11 +3454,11 @@ msgstr "You must enter a valid duration for the time to keep backups" msgid "You must enter a valid retention policy string" msgstr "You must enter a valid retention policy string" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3396,11 +3479,11 @@ msgstr "You must fill in the username" msgid "You must fill in {{field}}" msgstr "You must fill in {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "You must select or fill in the AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "You must select or fill in the server" @@ -3432,23 +3515,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3463,29 +3546,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3498,11 +3589,11 @@ msgstr "" msgid "resume now" msgstr "resume now" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "unless you are explicitly specifying --group-id" diff --git a/Localizations/webroot/localization_webroot-es.po b/Localizations/webroot/localization_webroot-es.po index 5d063820d..42c92bc6f 100644 --- a/Localizations/webroot/localization_webroot-es.po +++ b/Localizations/webroot/localization_webroot-es.po @@ -46,7 +46,7 @@ msgstr "" msgid "- pick an option -" msgstr "- escoja una opción -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...cargando..." @@ -70,23 +70,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Clave API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Acceso ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Clave de aceso" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Política" @@ -98,7 +98,7 @@ msgstr "Acerca de" msgid "About {{appname}}" msgstr "Acerca de {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Clave de acceso" @@ -126,7 +126,7 @@ msgstr "" msgid "Access to user interface" msgstr "Acceso a la interfaz de usuario" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nombre de la cuenta" @@ -159,8 +159,8 @@ msgstr "Añadir ruta" msgid "Added" msgstr "Agregado" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "¿Ajustar el nombre del deposito?" @@ -193,7 +193,7 @@ msgstr "Todas las máquinas de Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Las bases de datos de Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -215,6 +215,10 @@ msgstr "Permitir el acceso remoto (requiere reiniciar)" msgid "Allowed days" msgstr "Días permitidos" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Se encontró un archivo existente en la nueva ubicación" @@ -239,7 +243,7 @@ msgstr "" "\n" "¿Desea utilizar la base de datos existente?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Informes de uso anónimos" @@ -247,20 +251,26 @@ msgstr "Informes de uso anónimos" msgid "Applications" msgstr "Aplicaciones" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Como Línea de comandos" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Método de autentificación" @@ -292,7 +302,7 @@ msgstr "ID de la aplicación B2" msgid "B2 Application Key" msgstr "B2 clave de aplicación" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cuenta Cloud Storage ID" @@ -300,7 +310,7 @@ msgstr "B2 Cuenta Cloud Storage ID" msgid "B2 Cloud Storage Application ID" msgstr "ID de la aplicación de almacenamiento en la nube B2" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Clave de aplicación de Cloud Storage" @@ -342,7 +352,7 @@ msgstr "Conservación de copia de respaldo" msgid "Backup:" msgstr "Copia de seguridad:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -354,7 +364,7 @@ msgstr "Acceso roto" msgid "Browse" msgstr "Navega" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Navegador por defecto" @@ -362,14 +372,14 @@ msgstr "Navegador por defecto" msgid "Bucket create location" msgstr "Crear la ubicación del depósito" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -378,7 +388,7 @@ msgstr "Crear la ubicación del depósito" msgid "Bucket name" msgstr "Nombre del depósito" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -457,13 +467,13 @@ msgstr "" msgid "Cache Files" msgstr "Archivos caché" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Experimental e inestable (Canary)" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -478,14 +488,18 @@ msgstr "Experimental e inestable (Canary)" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancelar" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -502,7 +516,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -542,7 +556,7 @@ msgstr "Elija un tipo de almacenamiento para empezar" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Haga clic en el enlace de AuthID para crear una AuthID" @@ -671,7 +685,7 @@ msgstr "Conexión perdida" msgid "Connection worked!" msgstr "¡La conexión funcionó!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nombre del contenedor" @@ -713,7 +727,7 @@ msgstr "Copía fallida. Por favor, copia manualmente la dirección URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opciones de base" @@ -721,7 +735,7 @@ msgstr "Opciones de base" msgid "Counting ({{files}} files found, {{size}})" msgstr "Contando ({{files}} archivos encontrados, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Sólo bloqueos" @@ -733,7 +747,7 @@ msgstr "Crear informe de errores ..." msgid "Create folder?" msgstr "¿Crear carpeta?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Creó un nuevo usuario limitado" @@ -741,7 +755,7 @@ msgstr "Creó un nuevo usuario limitado" msgid "Creating bug report …" msgstr "Creando informe de errores ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Creando nuevo usuario con acceso limitado ..." @@ -753,7 +767,7 @@ msgstr "Creando carpetas de destino …" msgid "Creating temporary backup …" msgstr "Creando copia de seguridad temporal ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -773,7 +787,7 @@ msgstr "La versión actual es {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Personalizada S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Satélite personalizado" @@ -825,11 +839,11 @@ msgstr "Base de datos ..." msgid "Days" msgstr "Días" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Por defecto" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "({{channelname}}) por defecto" @@ -837,7 +851,7 @@ msgstr "({{channelname}}) por defecto" msgid "Default excludes" msgstr "Exclusiones por defecto" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opciones por defecto" @@ -865,6 +879,10 @@ msgstr "Eliminar copias de seguridad que tengan mas de" msgid "Delete local database" msgstr "Eliminar base de datos local" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Eliminar archivos remotos" @@ -921,7 +939,8 @@ msgstr "Destino" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Ruta de destino" @@ -933,6 +952,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Desactivar" @@ -946,7 +969,7 @@ msgstr "Descartar" msgid "Dismiss all" msgstr "Ignorar todo" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Apariencia y esquema de colores" @@ -995,14 +1018,14 @@ msgstr "Sitio Web Duplicati" msgid "Duplicati forum" msgstr "Foro de Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1040,12 +1063,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Editar lista" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Editar como texto" @@ -1057,6 +1080,10 @@ msgstr "Editar ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Cifrar archivo" @@ -1076,7 +1103,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Contraseña de cifrado" @@ -1190,7 +1217,7 @@ msgstr "Error" msgid "Error!" msgstr "¡Error!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Errores y bloqueos" @@ -1238,7 +1265,7 @@ msgstr "Excluir la expresión regular" msgid "Existing file found" msgstr "Archivo existente encontrado" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1332,7 +1359,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Fallo al restaurar archivos: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Error al guardar:" @@ -1361,7 +1388,7 @@ msgstr "Filtros" msgid "Finished!" msgstr "¡Terminado!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Configuración de primera ejecución" @@ -1432,7 +1459,7 @@ msgstr "Generar política de acceso IAM" msgid "Getting file versions …" msgstr "Obteniendo versiones de archivos ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Correo del grupo" @@ -1457,7 +1484,7 @@ msgid "Hostnames" msgstr "Nombres de host" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Horas" @@ -1487,11 +1514,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1541,6 +1568,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Si no introduce una clave API, requerirá el nombre de cliente" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1592,7 +1624,7 @@ msgstr "Incluir una expresión regular" msgid "Incorrect answer, try again" msgstr "Respuesta incorrecta, intente de nuevo" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1607,7 +1639,7 @@ msgstr "Información" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caracteres no válidos en la ruta" @@ -1617,7 +1649,7 @@ msgstr "Caracteres no válidos en la ruta" msgid "Invalid retention time" msgstr "Tiempo de retención no válido" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1645,7 +1677,7 @@ msgstr "Mantener todas las copias de seguridad" msgid "Keystone API version" msgstr "Versión de la API de Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Idioma de interfaz de usuario" @@ -1715,10 +1747,6 @@ msgstr "" msgid "Loading …" msgstr "Cargando ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Repositorio Local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1922,7 +1954,7 @@ msgstr "Siguiente tarea:" msgid "Next time" msgstr "La próxima vez" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1935,9 +1967,9 @@ msgstr "La próxima vez" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "No" @@ -1979,7 +2011,7 @@ msgstr "No hay tareas programadas" msgid "Non-matching passphrase" msgstr "No coincide la frase de seguridad" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Ninguno / desactivado" @@ -2000,17 +2032,17 @@ msgstr "" "Nada será borrado. El tamaño de la copia de seguridad aumentará con cada " "cambio." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2042,7 +2074,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2066,7 +2098,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Abierto" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2103,11 +2135,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opciones" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2117,7 +2149,7 @@ msgstr "" msgid "Original location" msgstr "Localización original" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Otros" @@ -2157,7 +2189,7 @@ msgstr "Las frases de seguridad no coinciden" msgid "Passphrases do not match" msgstr "Las frases de seguridad no coinciden" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2168,8 +2200,8 @@ msgstr "Contraseña" msgid "Patching files with local blocks …" msgstr "Parchear archivos con bloques locales" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Ruta" @@ -2178,7 +2210,7 @@ msgid "Path not found" msgstr "Ruta no encontrada" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Ruta del servidor" @@ -2187,11 +2219,11 @@ msgstr "Ruta del servidor" msgid "Path or subfolder in the bucket" msgstr "Ruta o subcarpeta en el depósito" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pausa" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausar después del arranque o de hibernación" @@ -2229,6 +2261,10 @@ msgstr "Impedir el inicio de sesión automático con el icono de la bandeja" msgid "Previous" msgstr "Anterior" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progreso" @@ -2237,7 +2273,7 @@ msgstr "Progreso" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID es opcional si el depósito existe" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Propietario" @@ -2273,10 +2309,30 @@ msgstr "Recreando base de datos …" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrando copia de seguridad temporal …" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "No se permiten rutas relativas" @@ -2289,14 +2345,34 @@ msgstr "Recargar" msgid "Remote" msgstr "Remoto" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Ruta Remota" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Repositorio Remoto" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Ruta remota" @@ -2439,7 +2515,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Compatible" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Igual que la versión base instalada: {{channelname}}" @@ -2487,7 +2563,7 @@ msgstr "Buscar" msgid "Search for files" msgstr "Buscar archivos" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Segundos" @@ -2501,8 +2577,8 @@ msgstr "" msgid "Select files" msgstr "Seleccionar ficheros" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Servidor" @@ -2540,6 +2616,10 @@ msgstr "Servidor pausado" msgid "Server state properties" msgstr "Propiedades del estado del servidor" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Configuraciones" @@ -2613,17 +2693,17 @@ msgstr "Carpetas de origen" msgid "Source:" msgstr "Origen:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Compilaciones específicas solo para desarrolladores. No usar con datos " "importantes." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocolos estándar" @@ -2650,19 +2730,15 @@ msgstr "Comenzando restauración ..." msgid "Starting the restore process …" msgstr "Comenzando el proceso de restauración ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Parar después del archivo actual" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Detener después del archivo actual" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Detener ahora" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Detener respaldo en curso" @@ -2716,7 +2792,7 @@ msgstr "Enlace simbólico" msgid "System Files" msgstr "Archivos del sistema" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Sistema por defecto ({{levelname}})" @@ -2772,6 +2848,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Fase de pruebas" @@ -2784,7 +2865,7 @@ msgstr "Conexión de prueba" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Probando permisos…" @@ -2792,10 +2873,10 @@ msgstr "Probando permisos…" msgid "Testing …" msgstr "Probando ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2822,7 +2903,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "El nombre del depósito debe ser todo en minúsculas, ¿convertir " @@ -2840,15 +2921,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tema oscuro (por Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Tema por defecto azul sobre blanco (por Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2877,7 +2958,7 @@ msgstr "" "¿Desea REMPALAZAR su ACTUAL clave de host \"{{prev}}\" con la clave del host REGISTRADA: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Las contraseñas no coinciden" @@ -2932,7 +3013,7 @@ msgstr "" "La carpeta de destino contiene archivos encriptados, por favor suministra la" " frase de seguridad" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2960,7 +3041,7 @@ msgstr "Este mes" msgid "This week" msgstr "Esta semana" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Ajustes de aceleración." @@ -2973,6 +3054,10 @@ msgstr "Jue" msgid "Time" msgstr "Hora" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "A archivo" @@ -2991,7 +3076,7 @@ msgstr "" "Para exportar sin una frase de seguridad, desactive la casilla \"Cifrar el " "archivo\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3026,7 +3111,7 @@ msgstr "¿Confiar en el certificado del host?" msgid "Trust server certificate?" msgstr "¿Confiar en el certificado del servidor?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3063,7 +3148,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal de actualización" @@ -3083,18 +3168,18 @@ msgstr "Archivos subidos" msgid "Uploading verification file …" msgstr "Subiendo archivo de verificación…" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Estadísticas de uso" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Estadísticas de uso, advertencias, errores y bloqueos" @@ -3122,17 +3207,17 @@ msgstr "Datos de usuario" msgid "User domain name" msgstr "Nombre de dominio de usuario" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "El usuario tiene demasiados permisos" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Preferencias de la interfaz de usuario" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3222,7 +3307,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Esperando a que finalice la carga …" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Advertencias, errores y bloqueos" @@ -3260,7 +3345,7 @@ msgstr "¿Dónde desea restaurar los archivos?" msgid "Years" msgstr "Años" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3273,9 +3358,9 @@ msgstr "Años" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Sí" @@ -3314,18 +3399,16 @@ msgstr "Actualmente está ejecutando {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Puede detener la copia de seguridad después de que finalice cualquier carga " -"de archivo en curso." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Puede detener la tarea inmediatamente o permitir que el proceso continúe con" -" su archivo actual y luego se detenga." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3369,7 +3452,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Debe seleccionar al menos una carpeta de origen" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Debe ingresar un nombre de dominio para usar la API v3" @@ -3381,7 +3464,7 @@ msgstr "Debe introducir un nombre para la copia de seguridad" msgid "You must enter a passphrase or disable encryption" msgstr "Debe ingresar una frase de seguridad o deshabilitar el cifrado" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Debe ingresar una contraseña para usar la API v3" @@ -3389,13 +3472,13 @@ msgstr "Debe ingresar una contraseña para usar la API v3" msgid "You must enter a positive number of backups to keep" msgstr "Debe especificar un número positivo de copias de seguridad a guardar" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Debe ingresar un nombre de cliente (también conocido como proyecto) para " "usar la API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3409,11 +3492,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Debes ingresar una cadena de política de retención válida" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3434,11 +3517,11 @@ msgstr "Debe rellenar el nombre de usuario" msgid "You must fill in {{field}}" msgstr "Debe rellenar el {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Debe seleccionar o rellenar la AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Debe seleccionar o rellenar en el servidor" @@ -3470,23 +3553,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3501,29 +3584,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3536,11 +3627,11 @@ msgstr "" msgid "resume now" msgstr "reanudar ahora" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "a menos que usted haya especificando explícitamente --group-id" diff --git a/Localizations/webroot/localization_webroot-fi.po b/Localizations/webroot/localization_webroot-fi.po index 899cee744..18951636a 100644 --- a/Localizations/webroot/localization_webroot-fi.po +++ b/Localizations/webroot/localization_webroot-fi.po @@ -38,7 +38,7 @@ msgstr "" msgid "- pick an option -" msgstr "- Valitse jokin vaihtoehto -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...ladataan..." @@ -62,23 +62,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API-avain" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "Tunniste \"Access Key ID\" palveluun AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Tunniste \"Access Key ID\" palveluun AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Palvelun AWS IAM-asetukset" @@ -90,7 +90,7 @@ msgstr "Tietoja" msgid "About {{appname}}" msgstr "Tietoja sovelluksesta {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Pääsyavain" @@ -118,7 +118,7 @@ msgstr "" msgid "Access to user interface" msgstr "Käyttöoikeus käyttöliittymään" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Käyttäjätunnus" @@ -151,8 +151,8 @@ msgstr "Lisää polku" msgid "Added" msgstr "Lisätty" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Muuta ämpärin nimeä?" @@ -185,7 +185,7 @@ msgstr "Kaikki Hyper-V-virtuaalikoneet" msgid "All Microsoft SQL Databases" msgstr "Kaikki Microsoft SQL -tietokannat" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -207,6 +207,10 @@ msgstr "Salli etäyhteydet (Vaatii Duplicatin uudeleenkäynnistämisen)" msgid "Allowed days" msgstr "Sallitut päivät" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Olemassaoleva tiedosto löydettiin uudesta paikasta" @@ -231,7 +235,7 @@ msgstr "" "\n" "Haluatko käyttää samaa tietokantaa?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonyymit käyttöraportit" @@ -239,20 +243,26 @@ msgstr "Anonyymit käyttöraportit" msgid "Applications" msgstr "Sovellukset" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Komentona" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Tunnistautumistapa" @@ -284,7 +294,7 @@ msgstr "" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "Tunnus B2 Cloud Storage Account ID" @@ -292,7 +302,7 @@ msgstr "Tunnus B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -334,7 +344,7 @@ msgstr "" msgid "Backup:" msgstr "Varmuuskopio:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -346,7 +356,7 @@ msgstr "Pääsy epäonnistui" msgid "Browse" msgstr "Selaa" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Selaimen oletusasetus" @@ -354,14 +364,14 @@ msgstr "Selaimen oletusasetus" msgid "Bucket create location" msgstr "Luo ämpäri sijaintiin" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -370,7 +380,7 @@ msgstr "Luo ämpäri sijaintiin" msgid "Bucket name" msgstr "Ämpärin nimi" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -448,13 +458,13 @@ msgstr "" msgid "Cache Files" msgstr "Välimuistitiedostot" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -469,14 +479,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Peruuta" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -493,7 +507,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -533,7 +547,7 @@ msgstr "Valitseensin tallennustyyppi" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Klikkaa AuthID-linkkiä luodaksesi AuthID-tunnisteen" @@ -662,7 +676,7 @@ msgstr "Yhteys katkesi" msgid "Connection worked!" msgstr "Yhteys toimi!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Kontin nimi" @@ -704,7 +718,7 @@ msgstr "Kopionti epäonnistui. Kopio osoite käsin" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Ydinasetukset" @@ -712,7 +726,7 @@ msgstr "Ydinasetukset" msgid "Counting ({{files}} files found, {{size}})" msgstr "Lasketaan tiedostoja. (Löydetty {{files}} tiedostoa, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Vain kaatumiset" @@ -724,7 +738,7 @@ msgstr "Luo virheraportti ..." msgid "Create folder?" msgstr "Luo kansio?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Luotiin uusi rajoitettu käyttäjä" @@ -732,7 +746,7 @@ msgstr "Luotiin uusi rajoitettu käyttäjä" msgid "Creating bug report …" msgstr "Luodaan virheraporttia ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Luodaan uusi rajoitettu käyttäjä" @@ -744,7 +758,7 @@ msgstr "Luodaan kohdekansiot ..." msgid "Creating temporary backup …" msgstr "Luodaan tilapäinen varmuuskopio ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -764,7 +778,7 @@ msgstr "Nykyinen versio on {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Vaihtoehtoinen S3 päätepiste" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -816,11 +830,11 @@ msgstr "Tietokanta ..." msgid "Days" msgstr "Päivää" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Oletus" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Oletus ({{channelname}})" @@ -828,7 +842,7 @@ msgstr "Oletus ({{channelname}})" msgid "Default excludes" msgstr "" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Oletusasetukset" @@ -856,6 +870,10 @@ msgstr "Poista varmuuskopiot, jotka ovat vanhempia kuin" msgid "Delete local database" msgstr "Poista paikallinen tietokanta" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Poista tiedostot etäpalvelimelta" @@ -910,7 +928,8 @@ msgstr "Kohde" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Kohdepolku" @@ -922,6 +941,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Poistettu käytöstä" @@ -935,7 +958,7 @@ msgstr "Ohita" msgid "Dismiss all" msgstr "Hylkää kaikki" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Näyttö ja väriteema" @@ -985,14 +1008,14 @@ msgstr "Duplicatin verkkosivu" msgid "Duplicati forum" msgstr "Duplicatin keskustelualue" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1025,12 +1048,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Muokkaa listana" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Muokkaa tekstinä" @@ -1042,6 +1065,10 @@ msgstr "Muokkaa ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Salaa tiedosto" @@ -1061,7 +1088,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Salausavain" @@ -1170,7 +1197,7 @@ msgstr "Virhe" msgid "Error!" msgstr "Virhe!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Virheet ja kaatumiset" @@ -1218,7 +1245,7 @@ msgstr "Ohita säännöllistä ilmaisua vastaavat kohteet" msgid "Existing file found" msgstr "Löydettiin olemassaoleva tiedosto" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1312,7 +1339,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Tiedostojen palauttaminen epäonnistui: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Tallennus epäonnistui:" @@ -1341,7 +1368,7 @@ msgstr "Suodattimet" msgid "Finished!" msgstr "Valmis!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "" @@ -1412,7 +1439,7 @@ msgstr "Luo Amazon IAM access policy" msgid "Getting file versions …" msgstr "Haetaan tiedostojen versioita ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Ryhmäsähköpostiosoite" @@ -1437,7 +1464,7 @@ msgid "Hostnames" msgstr "Isäntänimet" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "tuntia" @@ -1467,11 +1494,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1522,6 +1549,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Jos et anna tunnistetta API key, on tunniste \"tenant name\" pakollinen" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1573,7 +1605,7 @@ msgstr "Sisällytä säännöllistä ilmaisua vastaavat kohteet" msgid "Incorrect answer, try again" msgstr "Virheellinen vastaus. Yritä uudelleen." -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1588,7 +1620,7 @@ msgstr "Informaatio" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Virheellisiä merkkejä polussa" @@ -1598,7 +1630,7 @@ msgstr "Virheellisiä merkkejä polussa" msgid "Invalid retention time" msgstr "Epäkelpo säilytysaika" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1626,7 +1658,7 @@ msgstr "Säilytä kaikki varmuuskopiot" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Käytettävä kieli" @@ -1691,10 +1723,6 @@ msgstr "" msgid "Loading …" msgstr "Ladataan ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1898,7 +1930,7 @@ msgstr "Seuraava tehtävä:" msgid "Next time" msgstr "Seuraavalla kerralla" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1911,9 +1943,9 @@ msgstr "Seuraavalla kerralla" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Ei" @@ -1957,7 +1989,7 @@ msgstr "Ei ajastettuja tehtäviä" msgid "Non-matching passphrase" msgstr "Salasanat eivät ole samat" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Ei mitään/poistettu käytöstä" @@ -1976,17 +2008,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Mitään ei poisteta. Varmuuskopion koko kasvaa jokaisella muutoksella." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2018,7 +2050,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2042,7 +2074,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Avattu" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2079,11 +2111,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Valitsimet" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2093,7 +2125,7 @@ msgstr "" msgid "Original location" msgstr "Alkuperäinen sijainti" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Muut" @@ -2129,7 +2161,7 @@ msgstr "Salauslauseet eivät täsmää" msgid "Passphrases do not match" msgstr "Salausavaimet eivät täsmää" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2140,8 +2172,8 @@ msgstr "Salasana" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Polku" @@ -2150,7 +2182,7 @@ msgid "Path not found" msgstr "Polkua ei löydy" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Polku etäpalvelimella" @@ -2159,11 +2191,11 @@ msgstr "Polku etäpalvelimella" msgid "Path or subfolder in the bucket" msgstr "Ämpärin polku tai alikansio" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Tauko" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Tauko käynnistyksen tai lepotilasta heräämisen jälkeen" @@ -2201,6 +2233,10 @@ msgstr "" msgid "Previous" msgstr "Edellinen" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Edistyminen: " @@ -2209,7 +2245,7 @@ msgstr "Edistyminen: " msgid "ProjectID is optional if the bucket exist" msgstr "Tunniste ProjectID on valinnainen, jos ämpäri on jo olemassa" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Suljettu" @@ -2245,10 +2281,30 @@ msgstr "Luodaan tietokanta uudelleen ..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Rekisteröidään tilapäinen varmuuskopio ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Suhteelliset polut eivät ole sallittuja" @@ -2261,14 +2317,34 @@ msgstr "Lataa uudelleen" msgid "Remote" msgstr "Etäpalvelimella" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Kohteen polku" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Kohteen polku" @@ -2411,7 +2487,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3-yhteensopiva" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Sama kuin asennettu versio: {{channelname}}" @@ -2459,7 +2535,7 @@ msgstr "Etsi" msgid "Search for files" msgstr "Etsi tiedostoja" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekuntia" @@ -2472,8 +2548,8 @@ msgstr "Valitse lokitiedot ja näe ne heti, kun ne ilmoitetaan lokiin:" msgid "Select files" msgstr "Valitse tiedostot" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Palvelin" @@ -2511,6 +2587,10 @@ msgstr "Palvelin on pysäytetty" msgid "Server state properties" msgstr "Palvelimen tila" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Asetukset" @@ -2582,15 +2662,15 @@ msgstr "Lähekansiot" msgid "Source:" msgstr "Varmuuskopioitavat tiedostot:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standardinmukaiset protokollat" @@ -2617,19 +2697,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Keskeytä nykyisen tiedoston jälkeen" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Keskeytä nyt" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Keskeytä käynnissä oleva varmuuskopiointi" @@ -2683,7 +2759,7 @@ msgstr "Symbolinen linkki" msgid "System Files" msgstr "Järjestelmätiedostot" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Järjestelmän oletus ({{levelname}})" @@ -2739,6 +2815,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2751,7 +2832,7 @@ msgstr "Kokeile yhteysasetuksia" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2759,10 +2840,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2785,7 +2866,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Bucketin nimen pitää olla kirjoitettu pienillä kirjaimilla. Muuta " @@ -2801,15 +2882,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tumma teema (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Oletusteema, sinistä valkoisella (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2833,7 +2914,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Salasanat eivät täsmää" @@ -2882,7 +2963,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "Kohdekansio sisältää salattuja tiedostoja. Anna salasana" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2910,7 +2991,7 @@ msgstr "Tässä kuussa" msgid "This week" msgstr "Tällä viikolla" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "" @@ -2923,6 +3004,10 @@ msgstr "To" msgid "Time" msgstr "Aika" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Tiedostoon" @@ -2939,7 +3024,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Viedäksesi ilmaan salasanaa poista rasti \"Salaa tiedosto\" -valinnasta" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2973,7 +3058,7 @@ msgstr "Luota palvelimen varmenteeseen?" msgid "Trust server certificate?" msgstr "Luota palvelimen varmenteeseen?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3007,7 +3092,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Päivityskanava" @@ -3027,18 +3112,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "Lähetetään varmennustiedosto ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Käyttötilastot" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Käyttötilastot, varoitukset, virheet ja kaatumiset" @@ -3066,17 +3151,17 @@ msgstr "Käyttäjätiedot" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Käyttäjällä on liikaa oikeuksia" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Käyttöliittymän asetukset" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3166,7 +3251,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Varoitukset, virheet ja kaatumiset" @@ -3204,7 +3289,7 @@ msgstr "Mihin tiedostot palautetaan?" msgid "Years" msgstr "Vuotta" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3217,9 +3302,9 @@ msgstr "Vuotta" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Kyllä" @@ -3256,13 +3341,15 @@ msgstr "Käytössä oleva versio: {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3304,7 +3391,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Vähintään yksi lähdekansio pitää valita" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3316,7 +3403,7 @@ msgstr "Varmuuskopiolle pitää antaa nimi" msgid "You must enter a passphrase or disable encryption" msgstr "Anna salasana tai poista salaus käytöstä" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3325,11 +3412,11 @@ msgid "You must enter a positive number of backups to keep" msgstr "" "Syötä säilytettävien varmuuskopioiden määrä (positiivinen kokonaisluku)" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3341,11 +3428,11 @@ msgstr "Syötä sallittu varmuuskopioiden säilytysaika" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3366,11 +3453,11 @@ msgstr "Täytä käyttäjätunnus" msgid "You must fill in {{field}}" msgstr "Täytä kenttä {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Valitse tai syötä AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Valitse tai syötä palvelin" @@ -3402,23 +3489,23 @@ msgstr "tavu" msgid "byte/s" msgstr "tavua/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3433,29 +3520,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3468,11 +3563,11 @@ msgstr "" msgid "resume now" msgstr "jatka nyt" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-fr.po b/Localizations/webroot/localization_webroot-fr.po index 796270d99..073c3a20f 100644 --- a/Localizations/webroot/localization_webroot-fr.po +++ b/Localizations/webroot/localization_webroot-fr.po @@ -49,7 +49,7 @@ msgstr "" msgid "- pick an option -" msgstr "- choisir une option -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...chargement..." @@ -73,23 +73,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Clé API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -101,7 +101,7 @@ msgstr "À propos" msgid "About {{appname}}" msgstr "À propos de {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Clé d'accès" @@ -129,7 +129,7 @@ msgstr "" msgid "Access to user interface" msgstr "Accès à l'interface utilisateur" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nom du compte" @@ -162,8 +162,8 @@ msgstr "Ajouter un chemin" msgid "Added" msgstr "Ajouté" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Modifier le nom du bucket ?" @@ -196,7 +196,7 @@ msgstr "Toutes les machines Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Toutes les bases de données Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -220,6 +220,10 @@ msgstr "Autoriser l'accès à distance (nécessite un redémarrage)" msgid "Allowed days" msgstr "Jours autorisés" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Un fichier existant a été trouvé au nouvel emplacement" @@ -244,7 +248,7 @@ msgstr "" "\n" "Voulez-vous utiliser la base de donnée existante ?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Rapports d'utilisation anonyme" @@ -252,20 +256,26 @@ msgstr "Rapports d'utilisation anonyme" msgid "Applications" msgstr "Applications" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Comme ligne de commande" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Méthode d'authentification" @@ -297,7 +307,7 @@ msgstr "B2 Application ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -305,7 +315,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -347,7 +357,7 @@ msgstr "Rétention de la sauvegarde" msgid "Backup:" msgstr "Sauvegarde :" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Bêta" @@ -359,7 +369,7 @@ msgstr "Accès rompu" msgid "Browse" msgstr "Parcourir" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Paramètre par défaut du navigateur" @@ -367,14 +377,14 @@ msgstr "Paramètre par défaut du navigateur" msgid "Bucket create location" msgstr "Emplacement de la création du bucket" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -383,7 +393,7 @@ msgstr "Emplacement de la création du bucket" msgid "Bucket name" msgstr "Nom du bucket" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -463,13 +473,13 @@ msgstr "" msgid "Cache Files" msgstr "Mettre les fichiers en cache" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -484,14 +494,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Annuler" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -508,7 +522,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -548,7 +562,7 @@ msgstr "Sélectionner un type de stockage pour commencer" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Cliquer sur le lien pour créer un AuthID" @@ -677,7 +691,7 @@ msgstr "Connexion perdue" msgid "Connection worked!" msgstr "Connection fonctionnelle !" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nom du conteneur" @@ -719,7 +733,7 @@ msgstr "Échec de la copie. Copier l'URL manuellement" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Options du noyau" @@ -727,7 +741,7 @@ msgstr "Options du noyau" msgid "Counting ({{files}} files found, {{size}})" msgstr "Énumération ({{files}} fichiers trouvés, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Plantages uniquement" @@ -739,7 +753,7 @@ msgstr "Créer un rapport d'erreur..." msgid "Create folder?" msgstr "Créer un dossier ?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Nouvel utilisateur limité créé" @@ -747,7 +761,7 @@ msgstr "Nouvel utilisateur limité créé" msgid "Creating bug report …" msgstr "Création du rapport d'erreur..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Création d'un nouvel utilisateur avec un accès limité..." @@ -759,7 +773,7 @@ msgstr "Création des dossiers de destination..." msgid "Creating temporary backup …" msgstr "Création d'une sauvegarde temporaire..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -779,7 +793,7 @@ msgstr "Version actuelle : {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "S3 endpoint personnalisé" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Satellite personnalisé" @@ -831,11 +845,11 @@ msgstr "Base de données..." msgid "Days" msgstr "Jours" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Défaut" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "({{channelname}}) par défaut" @@ -843,7 +857,7 @@ msgstr "({{channelname}}) par défaut" msgid "Default excludes" msgstr "Exclusions par défaut" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Options par défaut" @@ -871,6 +885,10 @@ msgstr "Supprimer les sauvegardes plus anciennes que" msgid "Delete local database" msgstr "Supprimer la base de données locale" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Supprimer les fichiers distants" @@ -925,7 +943,8 @@ msgstr "Destination" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Chemin de destination" @@ -937,6 +956,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Désactivé" @@ -950,7 +973,7 @@ msgstr "Rejeter" msgid "Dismiss all" msgstr "Rejeter la totalité" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Thème d'affichage et de couleur" @@ -1000,14 +1023,14 @@ msgstr "Site internet de Duplicati" msgid "Duplicati forum" msgstr "Forum de Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1046,12 +1069,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Éditer en tant que liste" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Éditer en tant que texte" @@ -1063,6 +1086,10 @@ msgstr "Édition..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Chiffrement de fichier" @@ -1082,7 +1109,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Phrase de chiffrement" @@ -1197,7 +1224,7 @@ msgstr "Erreur" msgid "Error!" msgstr "Erreur !" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Erreurs et plantages" @@ -1245,7 +1272,7 @@ msgstr "Exclure l'expression régulière" msgid "Existing file found" msgstr "Fichier existant trouvé" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Expérimental" @@ -1340,7 +1367,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Échec de la restauration des fichiers : {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Échec d'enregistrement :" @@ -1369,7 +1396,7 @@ msgstr "Filtres" msgid "Finished!" msgstr "Terminé !" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Première mise en route" @@ -1440,7 +1467,7 @@ msgstr "Générer une politique d'accès IAM" msgid "Getting file versions …" msgstr "Récupération des versions de fichier..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Courriel de groupe" @@ -1465,7 +1492,7 @@ msgid "Hostnames" msgstr "Noms d'hôtes" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Heures" @@ -1495,11 +1522,11 @@ msgstr "ID :" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1548,6 +1575,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Si vous n'entrez pas de clé API, le nom de l'entité est requis" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1599,7 +1631,7 @@ msgstr "Inclure expression régulière" msgid "Incorrect answer, try again" msgstr "Réponse incorrecte, essayez encore" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1614,7 +1646,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caractères invalides dans le chemin" @@ -1624,7 +1656,7 @@ msgstr "Caractères invalides dans le chemin" msgid "Invalid retention time" msgstr "Temps de rétention invalide" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1652,7 +1684,7 @@ msgstr "Conserver toutes les sauvegardes" msgid "Keystone API version" msgstr "Version de l'API Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Langue de l'interface utilisateur" @@ -1720,10 +1752,6 @@ msgstr "" msgid "Loading …" msgstr "Chargement..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Stockage local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1927,7 +1959,7 @@ msgstr "Prochaine tâche :" msgid "Next time" msgstr "Prochaine fois" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1940,9 +1972,9 @@ msgstr "Prochaine fois" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Non" @@ -1985,7 +2017,7 @@ msgstr "Aucune tâche planifiée" msgid "Non-matching passphrase" msgstr "La phrase secrète ne correspond pas" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Aucun / Désactivé" @@ -2006,17 +2038,17 @@ msgstr "" "Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque " "modification." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "Ok" @@ -2048,7 +2080,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2072,7 +2104,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Ouvert" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2109,11 +2141,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Options" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2123,7 +2155,7 @@ msgstr "" msgid "Original location" msgstr "Emplacement d'origine" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Autres" @@ -2163,7 +2195,7 @@ msgstr "Les phrases secrètes ne correspondent pas" msgid "Passphrases do not match" msgstr "La phrase secrète ne correspond pas" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2174,8 +2206,8 @@ msgstr "Mot de passe" msgid "Patching files with local blocks …" msgstr "Correction des fichiers avec les blocs locaux..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Chemin" @@ -2184,7 +2216,7 @@ msgid "Path not found" msgstr "Chemin non trouvé" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Chemin sur le serveur" @@ -2193,11 +2225,11 @@ msgstr "Chemin sur le serveur" msgid "Path or subfolder in the bucket" msgstr "Chemin ou sous-dossier dans le bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pause" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pause après le démarrage ou l'hibernation" @@ -2235,6 +2267,10 @@ msgstr "Empêcher la connexion automatique de l'icône de la barre de tâches" msgid "Previous" msgstr "Précédent" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Statut :" @@ -2243,7 +2279,7 @@ msgstr "Statut :" msgid "ProjectID is optional if the bucket exist" msgstr "Le ProjectID est optionel si le bucket existe" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Propriétaire" @@ -2279,10 +2315,30 @@ msgstr "Régénération de la base de données..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Enregistrement d'une sauvegarde temporaire..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Les chemins relatifs ne sont pas autorisés" @@ -2295,14 +2351,34 @@ msgstr "Recharger" msgid "Remote" msgstr "Distant" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Chemin d'accès distant" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Stockage distant" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Chemin d'accès distant" @@ -2445,7 +2521,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Compatible S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Identique à la version de base installée : {{channelname}}" @@ -2495,7 +2571,7 @@ msgstr "Rechercher" msgid "Search for files" msgstr "Rechercher les fichiers" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Secondes" @@ -2510,8 +2586,8 @@ msgstr "" msgid "Select files" msgstr "Sélectionner les fichiers" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Serveur" @@ -2550,6 +2626,10 @@ msgstr "Serveur en pause" msgid "Server state properties" msgstr "Propriétés du statut serveur" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Paramètres" @@ -2623,17 +2703,17 @@ msgstr "Dossiers source" msgid "Source:" msgstr "Source :" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Versions spécifiques pour les développeurs uniquement. Ne pas utiliser avec " "des données importantes." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocoles standards" @@ -2660,19 +2740,15 @@ msgstr "Démarrage de la restauration..." msgid "Starting the restore process …" msgstr "Démarrage du processus de restauration..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Arrêter après le fichier en cours" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Arrêter après le fichier en cours" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Arrêter maintenant" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Arrêter la sauvegarde en cours" @@ -2726,7 +2802,7 @@ msgstr "Lien symbolique" msgid "System Files" msgstr "Fichiers système" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Paramètre par défaut du système ({{levelname}})" @@ -2782,6 +2858,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Étape de test" @@ -2794,7 +2875,7 @@ msgstr "Tester la connexion" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Test des permissions..." @@ -2802,10 +2883,10 @@ msgstr "Test des permissions..." msgid "Testing …" msgstr "Test..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2832,7 +2913,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Le nom du bucket devrait être entièrement en minuscule, convertir " @@ -2850,15 +2931,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Le thème sombre (de Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Thème par défaut bleu sur fond blanc (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2887,7 +2968,7 @@ msgstr "" "Voulez-vous remplacer votre clé d'hôte actuelle \"{{prev}}\" par la clé indiquée : {{key}} ?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Les mots de passe ne correspondent pas" @@ -2942,7 +3023,7 @@ msgid "" msgstr "" "Le fichier cible contient des fichiers chiffrés. Indiquer la phrase secrète" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2971,7 +3052,7 @@ msgstr "Ce mois" msgid "This week" msgstr "Cette semaine" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Options de contrôle du débit" @@ -2984,6 +3065,10 @@ msgstr "Jeu." msgid "Time" msgstr "Heure" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Vers un fichier" @@ -3000,7 +3085,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3035,7 +3120,7 @@ msgstr "Faire confiance au certificat de l'hôte ?" msgid "Trust server certificate?" msgstr "Faire confiance au certificat du serveur ?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3072,7 +3157,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal de mise à jour" @@ -3092,18 +3177,18 @@ msgstr "Fichiers téléversés" msgid "Uploading verification file …" msgstr "Envoi du fichier de vérification..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistiques d'utilisation" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistiques d'utilisation, avertissements, erreurs et accidents" @@ -3131,17 +3216,17 @@ msgstr "Données utilisateur" msgid "User domain name" msgstr "Nom de domaine de l'utilisateur" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "L'utilisateur à trop d'autorisations" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Réglages interface utilisateur" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3232,7 +3317,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Attente de la fin du téléversement..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avertissements, erreurs et accidents" @@ -3270,7 +3355,7 @@ msgstr "Ou voulez-vous restaurer vos fichiers ?" msgid "Years" msgstr "Années" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3283,9 +3368,9 @@ msgstr "Années" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Oui" @@ -3324,18 +3409,16 @@ msgstr "Version installée : {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Vous pouvez arrêter la sauvegarde une fois que l'envoi de fichiers en cours " -"est terminé." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Vous pouvez arrêter la tâche immédiatement, ou permettre au processus de " -"terminer le fichier en cours, puis l'arrêter." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3381,7 +3464,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Vous devez choisir au moins un dossier source" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Vous devez entrer un nom de domaine pour utiliser l'API v3" @@ -3393,7 +3476,7 @@ msgstr "Vous devez entrer un nom pour votre sauvegarde" msgid "You must enter a passphrase or disable encryption" msgstr "Vous devez saisir une phrase secrète ou désactiver le chiffrement" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Vous devez saisir un mot de passe pour utiliser l'API v3" @@ -3401,12 +3484,12 @@ msgstr "Vous devez saisir un mot de passe pour utiliser l'API v3" msgid "You must enter a positive number of backups to keep" msgstr "Vous devez entrer un nombre positif de sauvegarde à conserver" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3420,11 +3503,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Vous devez saisir une chaîne de politique de conservation valide" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3445,11 +3528,11 @@ msgstr "Vous devez renseigner le nom d'utilisateur" msgid "You must fill in {{field}}" msgstr "Vous devez renseigner le champ : {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Vous devez sélectionner ou renseigner l'AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Vous devez sélectionner ou renseigner le serveur" @@ -3481,23 +3564,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3512,29 +3595,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3547,11 +3638,11 @@ msgstr "" msgid "resume now" msgstr "reprendre maintenant" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "sauf si vous spécifiez explicitement --group-id" diff --git a/Localizations/webroot/localization_webroot-fr_CA.po b/Localizations/webroot/localization_webroot-fr_CA.po index eb99748e0..2a6f618f0 100644 --- a/Localizations/webroot/localization_webroot-fr_CA.po +++ b/Localizations/webroot/localization_webroot-fr_CA.po @@ -39,7 +39,7 @@ msgstr "" msgid "- pick an option -" msgstr "- choisissez une option -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "... chargement..." @@ -57,33 +57,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "Clé d'accès AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Clé d'accès secrète AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Stratégies" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "À propos" @@ -91,7 +91,7 @@ msgstr "À propos" msgid "About {{appname}}" msgstr "À propos de {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Clé d'accès" @@ -119,7 +119,7 @@ msgstr "" msgid "Access to user interface" msgstr "Accès à l'interface utilisateur" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nom du compte" @@ -136,7 +136,7 @@ msgstr "Ajouter un répertoire directement" msgid "Add advanced option" msgstr "Ajouter une option avancée" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Ajouter une sauvegarde" @@ -152,8 +152,8 @@ msgstr "Ajouter un chemin" msgid "Added" msgstr "Ajouté" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Modifier le nom du bucket" @@ -186,7 +186,7 @@ msgstr "Toutes les machines Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Toutes les bases de données Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -210,6 +210,10 @@ msgstr "Autoriser l'accès à distance (nécessite un redémarrage)" msgid "Allowed days" msgstr "Jours autorisés" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Un fichier existant a été trouvé au nouvel endroit" @@ -234,7 +238,7 @@ msgstr "" "\n" "Voulez-vous utiliser la base de donnée existante ?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Rapports d'utilisation anonyme" @@ -242,20 +246,26 @@ msgstr "Rapports d'utilisation anonyme" msgid "Applications" msgstr "Applications" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Comme ligne de commande" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -287,7 +297,7 @@ msgstr "" msgid "B2 Application Key" msgstr "Clé application B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "Identifiant du compte B2 Cloud Storage" @@ -295,7 +305,7 @@ msgstr "Identifiant du compte B2 Cloud Storage" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Clé d'application B2 Cloud Storage" @@ -337,7 +347,7 @@ msgstr "Rétention de la sauvegarde" msgid "Backup:" msgstr "Sauvegarde :" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Béta" @@ -349,7 +359,7 @@ msgstr "Accès rompu" msgid "Browse" msgstr "Parcourir" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Paramètre par défaut du navigateur" @@ -357,14 +367,14 @@ msgstr "Paramètre par défaut du navigateur" msgid "Bucket create location" msgstr "Emplacement de la création du bucket" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -373,7 +383,7 @@ msgstr "Emplacement de la création du bucket" msgid "Bucket name" msgstr "Nom du bucket" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -447,12 +457,13 @@ msgstr "" msgid "Cache Files" msgstr "Fichiers de cache" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -467,14 +478,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Annuler" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -491,6 +506,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Journal des modifications" @@ -511,7 +530,7 @@ msgstr "Vérifier les mise à jour maintenant" msgid "Checking for updates …" msgstr "" -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -527,11 +546,11 @@ msgstr "Sélectionnez un type de stockage pour commencer" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Cliquez sur le lien AuthID pour créer un AuthID" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Cliquez pour définir les options d'accélération" @@ -615,7 +634,7 @@ msgstr "Confirmer suppression" msgid "Confirm encryption passphrase" msgstr "Confirmez la phrase secrète de chiffrement" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -631,11 +650,11 @@ msgstr "Confirmation nécessaire" msgid "Connect" msgstr "Connecter" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Connecter maintenant" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "" @@ -643,11 +662,11 @@ msgstr "" msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "Connexion perdue" @@ -656,7 +675,7 @@ msgstr "Connexion perdue" msgid "Connection worked!" msgstr "Connection fonctionnelle !" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nom du conteneur" @@ -698,7 +717,7 @@ msgstr "Copie échouée. Veuillez copier manuellement l'URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Options du noyau" @@ -706,7 +725,7 @@ msgstr "Options du noyau" msgid "Counting ({{files}} files found, {{size}})" msgstr "Comptage ({{files}} fichiers trouvés, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Uniquement les plantages" @@ -718,7 +737,7 @@ msgstr "" msgid "Create folder?" msgstr "Créer un dossier?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Nouvel utilisateur limité créé" @@ -726,7 +745,7 @@ msgstr "Nouvel utilisateur limité créé" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -738,7 +757,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -758,7 +777,7 @@ msgstr "La version actuelle est {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "S3 endpoint personnalisé" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -810,11 +829,11 @@ msgstr "" msgid "Days" msgstr "Jours" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Défaut" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "({{channelname}}) par défaut" @@ -822,7 +841,7 @@ msgstr "({{channelname}}) par défaut" msgid "Default excludes" msgstr "Les exclusions par défaut" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Options par défaut" @@ -850,6 +869,10 @@ msgstr "Supprimer les sauvegardes plus anciennes que" msgid "Delete local database" msgstr "Supprimer base de données locale" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Supprimer les fichiers distants" @@ -904,7 +927,8 @@ msgstr "Destination" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Chemin de destination" @@ -916,6 +940,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Désactivé" @@ -929,7 +957,7 @@ msgstr "Rejeter" msgid "Dismiss all" msgstr "Rejeter la totalité" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Affichage et couleur" @@ -971,22 +999,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "Option de duplication {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Site internet de Duplicati" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Forum de Duplicati" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1025,12 +1053,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Éditer en tant que liste" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Éditer en tant que texte" @@ -1042,6 +1070,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Chiffrement du fichier" @@ -1061,7 +1093,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1176,7 +1208,7 @@ msgstr "Erreur" msgid "Error!" msgstr "Erreur!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Erreurs et plantages" @@ -1224,7 +1256,7 @@ msgstr "Exclure expression régulière" msgid "Existing file found" msgstr "Fichier existant trouvé" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Expérimental" @@ -1319,7 +1351,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Échec de la restauration des fichiers : {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Échec d'enregistrement :" @@ -1348,7 +1380,7 @@ msgstr "Filtres" msgid "Finished!" msgstr "Terminé!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Première mise en route" @@ -1419,7 +1451,7 @@ msgstr "Générer la statégie d'accès IAM" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Courriel de groupe" @@ -1435,7 +1467,7 @@ msgstr "Cacher" msgid "Hide hidden folders" msgstr "Masquer les dossiers cachés" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Poste de travail" @@ -1444,7 +1476,7 @@ msgid "Hostnames" msgstr "Les noms d'hôtes" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Heures" @@ -1474,11 +1506,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1527,6 +1559,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Si vous n'entrez pas de clé API, le nom de l'entité est requis" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1574,11 +1611,11 @@ msgstr "Inclure expression" msgid "Include regular expression" msgstr "Inclure expression régulière" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "Réponse incorrecte, essayez encore" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1593,7 +1630,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caractères invalides dans le chemin" @@ -1603,7 +1640,7 @@ msgstr "Caractères invalides dans le chemin" msgid "Invalid retention time" msgstr "Temps de rétention invalide" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1631,7 +1668,7 @@ msgstr "Conserver toutes les sauvegardes" msgid "Keystone API version" msgstr "Version de l'API Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Langue dans l'interface utilisateur" @@ -1694,15 +1731,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Stockage local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}}" msgid "Log data from the server" msgstr "Données d'historique du serveur" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "Déconnexion" @@ -1754,6 +1787,10 @@ msgstr "MOctet" msgid "MByte/s" msgstr "MOctet/s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "Maintenance" @@ -1764,7 +1801,7 @@ msgid "" " advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1784,7 +1821,7 @@ msgstr "Vitesse maximum de téléchargement" msgid "Max upload speed" msgstr "Vitesse maximum de téléversement" -#: index.html:144 templates/addoredit.html:127 templates/addoredit.html:169 +#: index.html:145 templates/addoredit.html:127 templates/addoredit.html:169 #: templates/addoredit.html:382 templates/addoredit.html:95 #: templates/edituri.html:34 templates/restoredirect.html:34 msgid "Menu" @@ -1802,12 +1839,12 @@ msgstr "Bases de données Microsoft SQL" msgid "Minimum redundancy" msgstr "Redondance minimale" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "La redondance minimale est de 1,0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minutes" @@ -1867,7 +1904,7 @@ msgstr "Nom" msgid "Never" msgstr "Jamais" -#: templates/settings.html:11 +#: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" msgstr "" @@ -1875,7 +1912,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1894,11 +1931,11 @@ msgstr "Suivant" msgid "Next scheduled run:" msgstr "Prochaine exécution programmée :" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Prochaine tâche planifiée :" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Prochaine tâche :" @@ -1906,7 +1943,7 @@ msgstr "Prochaine tâche :" msgid "Next time" msgstr "Prochaine fois" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1919,9 +1956,9 @@ msgstr "Prochaine fois" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Non" @@ -1956,7 +1993,7 @@ msgstr "" msgid "No passphrase entered" msgstr "Aucune phrase secrète entrée" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "Pas de tâche planifié" @@ -1964,7 +2001,7 @@ msgstr "Pas de tâche planifié" msgid "Non-matching passphrase" msgstr "La phrase secrète ne correspond pas" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Aucun / Désactivé" @@ -1985,16 +2022,17 @@ msgstr "" "Rien ne sera supprimé. La taille de la sauvegarde augmentera à chaque " "modification." -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "Ok" @@ -2026,7 +2064,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2050,7 +2088,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Ouvert" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2087,11 +2125,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Options" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2101,7 +2139,7 @@ msgstr "" msgid "Original location" msgstr "Emplacement d'origine" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Autres" @@ -2141,7 +2179,7 @@ msgstr "Les phrases secrètes ne correspondent pas" msgid "Passphrases do not match" msgstr "Le mot de passe ne correspond pas" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2152,8 +2190,8 @@ msgstr "Mot de passe" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Chemin" @@ -2162,7 +2200,7 @@ msgid "Path not found" msgstr "Chemin non trouvé" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Chemin sur le serveur" @@ -2171,15 +2209,15 @@ msgstr "Chemin sur le serveur" msgid "Path or subfolder in the bucket" msgstr "Chemin ou sous-dossier dans le bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pause" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pause après le démarrage ou l'hibernation" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Options de pause" @@ -2213,6 +2251,10 @@ msgstr "Empêcher la connexion automatique de l'icône de la barre de tâches" msgid "Previous" msgstr "Précédent" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Statut:" @@ -2221,7 +2263,7 @@ msgstr "Statut:" msgid "ProjectID is optional if the bucket exist" msgstr "Le ProjectID est optionel si le bucket existe" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Propriétaire" @@ -2257,15 +2299,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Les chemins relatifs ne sont pas autorisés" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Recharger" @@ -2273,14 +2335,34 @@ msgstr "Recharger" msgid "Remote" msgstr "Distant" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Chemin d'accès distant" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Stockage distant" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Chemin d'accès distant" @@ -2329,7 +2411,7 @@ msgstr "Communication de données :" msgid "Reset" msgstr "Réinitialiser" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Restaurer" @@ -2387,7 +2469,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Reprendre" @@ -2407,7 +2489,7 @@ msgstr "Démarrer maintenant" msgid "Running commandline entry" msgstr "Execution d'une ligne de commnde" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "Tâche en cours :" @@ -2423,7 +2505,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Compatible S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Identique à la version de base installée : {{channelname}}" @@ -2473,7 +2555,7 @@ msgstr "Recherche" msgid "Search for files" msgstr "Recherche de fichiers" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Secondes" @@ -2488,8 +2570,8 @@ msgstr "" msgid "Select files" msgstr "Sélectionner les fichiers" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Serveur" @@ -2528,7 +2610,11 @@ msgstr "Serveur en pause" msgid "Server state properties" msgstr "Propriétés du statut serveur" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Paramètres" @@ -2599,17 +2685,17 @@ msgstr "Dossiers source" msgid "Source:" msgstr "Source :" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Builds spécifiques pour les développeurs uniquement. Ne pas utiliser avec " "des données importantes." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocoles standards" @@ -2636,19 +2722,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Arrêter après le fichier en cour" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Arrêter maintenant" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Arrêter la sauvegarde en cour" @@ -2657,11 +2739,11 @@ msgstr "Arrêter la sauvegarde en cour" msgid "Stop running task" msgstr "Stopper la tâche en cour" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "Arrêt de la tâche" @@ -2702,7 +2784,7 @@ msgstr "Lien symbolique" msgid "System Files" msgstr "Fichiers système" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Paramètre par défaut du système ({{levelname}})" @@ -2758,6 +2840,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Étape de test" @@ -2770,7 +2857,7 @@ msgstr "Tester la connexion" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2778,10 +2865,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2808,7 +2895,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Le nom du bucket devrait être entièrement en minuscule, convertir " @@ -2822,19 +2909,19 @@ msgstr "" "La configuration doit être gardée en sécurité. Êtes-vous sûr de vouloir " "enregistrer un fichier non crypté contenant vos mots de passe?" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Le thème sombre (de Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Thème par défaut bleu sur fond blanc (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2863,7 +2950,7 @@ msgstr "" "Voulez-vous REMPLACER votre clé d'hôte COURANTE \"{{prev}}\" par la clé MENTIONNÉE : {{key}} ?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Le mot de passe ne correspond pas" @@ -2919,7 +3006,7 @@ msgstr "" "Le fichier cible contient des fichiers chiffrés, merci de fournir la phrase " "secrète" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2948,7 +3035,7 @@ msgstr "Ce mois" msgid "This week" msgstr "Cette semaine" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Options d'accélération" @@ -2961,6 +3048,10 @@ msgstr "Jeu." msgid "Time" msgstr "temps" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Vers fichier" @@ -2977,7 +3068,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Pour exporter sans phrase secrète, décochez la case \"Chiffrer fichier\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3012,7 +3103,7 @@ msgstr "Faire confiance au certificat de l'hôte ?" msgid "Trust server certificate?" msgstr "Faire confiance au certificat du serveur ?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3049,7 +3140,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal de mise à jour" @@ -3069,19 +3160,18 @@ msgstr "Fichiers téléchargés" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistiques d'utilisation" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistiques d'utilisation, avertissements, erreurs et accidents" @@ -3109,17 +3199,17 @@ msgstr "Données utilisateur" msgid "User domain name" msgstr "Nom de domaine de l'utilisateur" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "L'utilisateur à trop d'autorisations" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Réglages interface utilisateur" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3183,7 +3273,7 @@ msgstr "Très fort" msgid "Very weak" msgstr "Très faible" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "Rendez nous visite sur" @@ -3210,7 +3300,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avertissements, erreurs et accidents" @@ -3248,7 +3338,7 @@ msgstr "Ou voulez-vous restaurer vos fichiers ?" msgid "Years" msgstr "Années" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3261,9 +3351,9 @@ msgstr "Années" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Oui" @@ -3302,13 +3392,15 @@ msgstr "Vous êtes actuellement en train d'utiliser {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3355,7 +3447,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Vous devez choisir au moins un dossier source" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Vous devez entrer un nom de domaine pour utiliser l'API v3" @@ -3367,7 +3459,7 @@ msgstr "Vous devez entrer un nom pour votre sauvegarde" msgid "You must enter a passphrase or disable encryption" msgstr "Vous devez entrer une phrase secrète ou désactiver le chiffrement" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Vous devez entrer un mot de passe pour utiliser l'API v3" @@ -3375,12 +3467,12 @@ msgstr "Vous devez entrer un mot de passe pour utiliser l'API v3" msgid "You must enter a positive number of backups to keep" msgstr "Vous devez entrer un nombre positif de sauvegarde à conserver" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Vous devez entrer un nom de tenant (nom de projet) pour utiliser l'API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3394,11 +3486,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3419,11 +3511,11 @@ msgstr "Vous devez renseigner le nom d'utilisateur" msgid "You must fill in {{field}}" msgstr "Vous devez renseigner le champ : {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Vous devez sélectionner ou renseigner l'AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Vous devez sélectionner ou renseigner le serveur" @@ -3455,23 +3547,23 @@ msgstr "octet" msgid "byte/s" msgstr "octet/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3486,31 +3578,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3525,11 +3621,11 @@ msgstr "" msgid "resume now" msgstr "reprendre maintenant" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "sauf si vous spécifiez explicitement --group-id" diff --git a/Localizations/webroot/localization_webroot-hu.po b/Localizations/webroot/localization_webroot-hu.po index ec56c5604..73f9a6f61 100644 --- a/Localizations/webroot/localization_webroot-hu.po +++ b/Localizations/webroot/localization_webroot-hu.po @@ -40,7 +40,7 @@ msgstr "" msgid "- pick an option -" msgstr "- válasszon -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...töltés..." @@ -64,23 +64,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API kulcs" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -92,7 +92,7 @@ msgstr "Névjegy" msgid "About {{appname}}" msgstr "{{appname}} néjegye" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Hozzáférési kulcs" @@ -120,7 +120,7 @@ msgstr "" msgid "Access to user interface" msgstr "Hozzáférés a felhasználói felülethez" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Fiók név" @@ -153,8 +153,8 @@ msgstr "Útvonal hozzáadás" msgid "Added" msgstr "Hozzáadva" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "" @@ -187,7 +187,7 @@ msgstr "Minden Hyper-V gép" msgid "All Microsoft SQL Databases" msgstr "Minde Microsoft SQL adatbázik" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -210,6 +210,10 @@ msgstr "Távoli hozzáférés engedélyezése (újraindítást igényel)" msgid "Allowed days" msgstr "Engedélyezett napok" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Egy létező fájt találtam az új helyen" @@ -233,7 +237,7 @@ msgstr "" "\n" "Szeretné használni a meglévő adatbázist?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Névtelen használati jelentések" @@ -241,20 +245,26 @@ msgstr "Névtelen használati jelentések" msgid "Applications" msgstr "Alkalmazások" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Parancssorként" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -286,7 +296,7 @@ msgstr "" msgid "B2 Application Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "" @@ -294,7 +304,7 @@ msgstr "" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "" @@ -336,7 +346,7 @@ msgstr "Mentés késleltetés" msgid "Backup:" msgstr "Mentés:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Béta" @@ -348,7 +358,7 @@ msgstr "Törött hozzáférés" msgid "Browse" msgstr "Tallóz" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Böngésző alapértelmezett" @@ -356,14 +366,14 @@ msgstr "Böngésző alapértelmezett" msgid "Bucket create location" msgstr "Bucket létrehozásának helye" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -372,7 +382,7 @@ msgstr "Bucket létrehozásának helye" msgid "Bucket name" msgstr "Bucket neve" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -451,13 +461,13 @@ msgstr "" msgid "Cache Files" msgstr "Gyorsítótás Fájlok" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -472,14 +482,18 @@ msgstr "" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Mégsem" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -496,7 +510,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -536,7 +550,7 @@ msgstr "A kezdéshez válassz tárhely típust" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "" @@ -665,7 +679,7 @@ msgstr "Csatlakozás megszakadt" msgid "Connection worked!" msgstr "Csatlakozás működik!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Tároló neve" @@ -707,7 +721,7 @@ msgstr "Másolás sikertelen. Próbáld meg kézzel másolni az URL-t" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Mag beállítások" @@ -715,7 +729,7 @@ msgstr "Mag beállítások" msgid "Counting ({{files}} files found, {{size}})" msgstr "Számolás ({{files}} megtalált fájl, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Csak összeomlások" @@ -727,7 +741,7 @@ msgstr "Hibajelentés készítés..." msgid "Create folder?" msgstr "Mappa készítés?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Új korlátozott felhasználó létrehozva" @@ -735,7 +749,7 @@ msgstr "Új korlátozott felhasználó létrehozva" msgid "Creating bug report …" msgstr "Hibajelentés készítés..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Új felhasználó létrehozása korlátozott hozzáféréssel..." @@ -747,7 +761,7 @@ msgstr "Cél mappák létrehozása..." msgid "Creating temporary backup …" msgstr "Ideiglenes mentés létrehozása..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -767,7 +781,7 @@ msgstr "Aktuális verzió: {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Egyéni S3 végpont" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -819,11 +833,11 @@ msgstr "Adatbázis..." msgid "Days" msgstr "Nap" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Alapértelmezett" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Alapértelmezett ({{channelname}})" @@ -831,7 +845,7 @@ msgstr "Alapértelmezett ({{channelname}})" msgid "Default excludes" msgstr "Alapértelmezett kihagyások" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Alapértelmezett beállítások" @@ -859,6 +873,10 @@ msgstr "Ennél régebbi mentések törlése" msgid "Delete local database" msgstr "Helyi adatbázis törlése" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Távoli fájlok törlése" @@ -913,7 +931,8 @@ msgstr "Cél" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Cél útvonal" @@ -925,6 +944,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Letiltva" @@ -938,7 +961,7 @@ msgstr "Elvet" msgid "Dismiss all" msgstr "Elvet mindent" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Megjelenés és szín téma" @@ -987,14 +1010,14 @@ msgstr "Duplicati webodal" msgid "Duplicati forum" msgstr "Duplicati fórum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1036,12 +1059,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Szerkesztés listaként" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Szerkesztés szövegként" @@ -1053,6 +1076,10 @@ msgstr "Szerkesztés..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Fájl titkosítás" @@ -1072,7 +1099,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1186,7 +1213,7 @@ msgstr "Hiba" msgid "Error!" msgstr "Hiba!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Hibák és összeomlások" @@ -1234,7 +1261,7 @@ msgstr "Reguláris kifejezés kizárása" msgid "Existing file found" msgstr "Meglévő fájl található" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Kísérleti" @@ -1329,7 +1356,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "A fájlok helyreállítása nem sikerült: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Nem sikerült elmenteni:" @@ -1358,7 +1385,7 @@ msgstr "Szürők" msgid "Finished!" msgstr "Kész!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Első futtatáskori beállítás" @@ -1429,7 +1456,7 @@ msgstr "" msgid "Getting file versions …" msgstr "Fájl verziók lekérdezése..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Csoport e-mail" @@ -1454,7 +1481,7 @@ msgid "Hostnames" msgstr "Gazdagép nevek" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Óra" @@ -1484,11 +1511,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1537,6 +1564,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Ha nem ad meg API-kulcsot, akkor kötelező a bérlő neve" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1588,7 +1620,7 @@ msgstr "" msgid "Incorrect answer, try again" msgstr "Érvénytelen válasz, próbáld újra" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1601,7 +1633,7 @@ msgstr "Információ" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Érvénytelen karakterek az útvonalban" @@ -1611,7 +1643,7 @@ msgstr "Érvénytelen karakterek az útvonalban" msgid "Invalid retention time" msgstr "Érvénytelen késleltetési idő" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1637,7 +1669,7 @@ msgstr "Minden mentés megtartása" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Felhasználói felület nyelve" @@ -1703,10 +1735,6 @@ msgstr "" msgid "Loading …" msgstr "Betöltés..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Helyi tároló" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1908,7 +1940,7 @@ msgstr "Következő feladat:" msgid "Next time" msgstr "Következő dátum" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1921,9 +1953,9 @@ msgstr "Következő dátum" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nem" @@ -1962,7 +1994,7 @@ msgstr "Nincs ütemezett feladat" msgid "Non-matching passphrase" msgstr "Nem egyező jelszavak" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Semmi / letiltva" @@ -1981,17 +2013,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Semmi sem lesz törölve. A mentés minden változáskor növekedni fog." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2023,7 +2055,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2047,7 +2079,7 @@ msgstr "" msgid "Opened" msgstr "Megnyitva" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2084,11 +2116,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Beállítások" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2098,7 +2130,7 @@ msgstr "" msgid "Original location" msgstr "Eredeti hely" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Egyebek" @@ -2137,7 +2169,7 @@ msgstr "A jelszavak nem egyeznek meg" msgid "Passphrases do not match" msgstr "A jelszavak nem egyeznek" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2148,8 +2180,8 @@ msgstr "Jelszó" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Útvonal" @@ -2158,7 +2190,7 @@ msgid "Path not found" msgstr "Az útvonal nem található" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Útvonal a kiszolgálón" @@ -2167,11 +2199,11 @@ msgstr "Útvonal a kiszolgálón" msgid "Path or subfolder in the bucket" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Szünet" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Szünet indítás vagy hibernálás után" @@ -2209,6 +2241,10 @@ msgstr "Tálca ikon automatikus bejelentkezés megakadályozása" msgid "Previous" msgstr "Előző" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Folyamat:" @@ -2217,7 +2253,7 @@ msgstr "Folyamat:" msgid "ProjectID is optional if the bucket exist" msgstr "" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Tulajdonosi" @@ -2253,10 +2289,30 @@ msgstr "Adatbázis újraépítése..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Ideiglenes mentés regisztrálása..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relatív útvonalak nem engedélyezettek" @@ -2269,14 +2325,34 @@ msgstr "Újratöltés" msgid "Remote" msgstr "Távoli" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Távoli útvonal" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Távoli tároló" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Távoli útvonal" @@ -2419,7 +2495,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 kompatibilis" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Ugyanaz, mint az alap telepítési verzió: {{channelname}}" @@ -2467,7 +2543,7 @@ msgstr "Keresés" msgid "Search for files" msgstr "Fájlok keresése" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Másodperc" @@ -2480,8 +2556,8 @@ msgstr "" msgid "Select files" msgstr "Fájlok kiválasztása" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Kiszolgáló" @@ -2519,6 +2595,10 @@ msgstr "Kiszolgáló szünetel" msgid "Server state properties" msgstr "Kiszolgáló állapot tulajdonságok" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Beállítások" @@ -2588,15 +2668,15 @@ msgstr "Forrás mappák" msgid "Source:" msgstr "Forrás:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "Fejlesztőknek szánt kiadások. Fontos mentésére nem használható." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Szabványos protokollok" @@ -2623,19 +2703,15 @@ msgstr "Visszaállítás indítása..." msgid "Starting the restore process …" msgstr "Visszaállítási folyamat indítása..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Leállítás az aktuális fájl után" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Leállítás az aktuális fájl után" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Leállítás most" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Mentés futtatásának leállítása" @@ -2689,7 +2765,7 @@ msgstr "Szimbolikus link" msgid "System Files" msgstr "Rendszer fájlok" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Rendszer alapértelmezés ({{levelname}})" @@ -2745,6 +2821,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Teszt fázis" @@ -2757,7 +2838,7 @@ msgstr "Kapcsolat tesztelése" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Engedélyek tesztelése..." @@ -2765,10 +2846,10 @@ msgstr "Engedélyek tesztelése..." msgid "Testing …" msgstr "Tesztelés..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2791,7 +2872,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" @@ -2805,15 +2886,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Sötét téma (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Alapértelmezett kék-fehér téma (Alextől)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2839,7 +2920,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "A jelszavak nem egyeznek meg" @@ -2884,7 +2965,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2906,7 +2987,7 @@ msgstr "Ez a hónap" msgid "This week" msgstr "Ez a hét" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Sebességkorlátozás beállítások" @@ -2919,6 +3000,10 @@ msgstr "Cs" msgid "Time" msgstr "Idő" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Fájlba" @@ -2933,7 +3018,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2961,7 +3046,7 @@ msgstr "Megbízható a gazdagép tanúsítványa?" msgid "Trust server certificate?" msgstr "Megbízható kiszolgáló tanúsítványa?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -2995,7 +3080,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Frissítési csatorna" @@ -3015,18 +3100,18 @@ msgstr "Fájlok feltöltése" msgid "Uploading verification file …" msgstr "Ellenőrző fájl feltöltése..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Használati statisztikák" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Használati statisztikák, figyelmeztetések, hibák és összeomlások" @@ -3054,17 +3139,17 @@ msgstr "Felhasználói adat" msgid "User domain name" msgstr "Felhasználói domain név" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "A felhasználónak túl sok engedélye van" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Felhasználói felület beállítások" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3155,7 +3240,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Várakozás a feltöltés befejezésére..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Figyelmeztetések, hibák és összeomlások" @@ -3193,7 +3278,7 @@ msgstr "Hova szeretnéd visszaállítani a fájlokat?" msgid "Years" msgstr "Év" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3206,9 +3291,9 @@ msgstr "Év" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Igen" @@ -3245,13 +3330,15 @@ msgstr "" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3287,7 +3374,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3299,7 +3386,7 @@ msgstr "" msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3307,11 +3394,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3323,11 +3410,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3348,11 +3435,11 @@ msgstr "Ki kell töltened a felhasználónevet" msgid "You must fill in {{field}}" msgstr "Ez ki kell töltened: {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "" @@ -3384,23 +3471,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3415,29 +3502,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3450,11 +3545,11 @@ msgstr "" msgid "resume now" msgstr "folytatás most" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-it.po b/Localizations/webroot/localization_webroot-it.po index f6ae3d1a6..15f4ef60b 100644 --- a/Localizations/webroot/localization_webroot-it.po +++ b/Localizations/webroot/localization_webroot-it.po @@ -5,13 +5,14 @@ # Devis Cerri , 2017 # Andrea De Lunardi , 2018 # Francesco Infantini , 2019 -# Folgore101 , 2024 # albanobattistella , 2024 +# Folgore101 , 2024 +# Antonio Galiero, 2024 # msgid "" msgstr "" "Project-Id-Version: \n" -"Last-Translator: albanobattistella , 2024\n" +"Last-Translator: Antonio Galiero, 2024\n" "Language-Team: Italian (https://app.transifex.com/duplicati/teams/67655/it/)\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -24,8 +25,13 @@ msgid_plural "" "({{$count}} errors{{item.Result.Interrupted? (', interrupted'|translate) : " "''}})" msgstr[0] "" +"(1 errore{{item.Result.Interrupted? (', interrupted'|translate) : ''}})" msgstr[1] "" +"({{$count}} errori{{item.Result.Interrupted? (', interrupted'|translate) : " +"''}})" msgstr[2] "" +"({{$count}} errori{{item.Result.Interrupted? (', interrupted'|translate) : " +"''}})" #: templates/backup-result/entryline.html:10 msgid "" @@ -34,8 +40,13 @@ msgid_plural "" "({{$count}} warnings{{item.Result.Interrupted? (', interrupted'|translate) :" " ''}})" msgstr[0] "" +"(1 avviso{{item.Result.Interrupted? (', interrupted'|translate) : ''}})" msgstr[1] "" +"({{$count}} avvisi{{item.Result.Interrupted? (', interrupted'|translate) : " +"''}})" msgstr[2] "" +"({{$count}} avvisi{{item.Result.Interrupted? (', interrupted'|translate) : " +"''}})" #: templates/backup-result/entryline.html:15 msgid "(interrupted)" @@ -45,7 +56,7 @@ msgstr "(interrupted)" msgid "- pick an option -" msgstr "- seleziona un'opzione -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "... caricamento in corso ..." @@ -54,38 +65,42 @@ msgid "" "Note: Sia will still boost redundancy later as long as you're " "connected to your hosts." msgstr "" +"Nota: Sia continuerà a potenziare la ridondanza in seguito finché " +"sarai connesso ai tuoi host." #: templates/commandline.html:25 msgid " Edit as text" -msgstr "" +msgstr "Modifica come testo" #: templates/commandline.html:32 msgid " Edit as text" -msgstr "" +msgstr " Modifica come testo" #: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" +"

    La connessione al server è stata rifiutata a causa di un'autenticazione non valida.

    \n" +"

    Fai accesso nuovamente, o riapri la pagina dall'icona della barra(se possibile)

    " -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Chiave API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "ID di accesso AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Chiave di accesso AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Norme AWS IAM" @@ -97,13 +112,13 @@ msgstr "Informazioni" msgid "About {{appname}}" msgstr "Informazioni {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Chiave di accesso" #: templates/backends/e2.html:2 msgid "Access Key ID" -msgstr "" +msgstr "ID chiave di accesso" #: templates/backends/e2.html:6 msgid "Access Key Secret" @@ -119,13 +134,13 @@ msgstr "Concessione accesso" #: templates/backends/azure.html:11 templates/backends/azure.html:12 msgid "Access key" -msgstr "" +msgstr "Chiave di accesso" #: templates/settings.html:5 msgid "Access to user interface" msgstr "Accesso all'interfaccia utente" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nome account" @@ -158,8 +173,8 @@ msgstr "Aggiungi percorso" msgid "Added" msgstr "Aggiunto" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Sistemare il nome bucket?" @@ -182,7 +197,7 @@ msgstr "Endpoint Aliyun OSS" #: templates/backends/aliyunoss.html:35 msgid "Aliyun OSS documents and resources" -msgstr "" +msgstr "Documenti e risorse di Aliyun OSS" #: scripts/directives/sourceFolderPicker.js:575 msgid "All Hyper-V Machines" @@ -192,7 +207,7 @@ msgstr "Tutte le Macchine Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Tutti i database Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -214,6 +229,10 @@ msgstr "Consenti accesso remoto (richiede il riavvio)" msgid "Allowed days" msgstr "Giorni consentiti" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "Metti in pausa anche i trasferimenti" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Un file esistente è stato trovato nella nuova posizione" @@ -238,28 +257,34 @@ msgstr "" "\n" "Vuoi usare il database esistente?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" -msgstr "Rapporti d'uso anonimi" +msgstr "Rapporti di utilizzo anonimi" #: scripts/services/AppUtils.js:206 msgid "Applications" msgstr "Applicazioni" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "Sei sicuro di voler eliminare la registrazione del controllo remoto?" + #: templates/export.html:8 msgid "As Command-line" msgstr "Come riga di comando" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Metodo di autenticazione" @@ -281,7 +306,7 @@ msgstr "Genera automaticamente passphrase" #: templates/addoredit.html:258 msgid "Automatically run backups" -msgstr "" +msgstr "Esegui automaticamente i backup." #: templates/backends/b2.html:12 msgid "B2 Application ID" @@ -291,7 +316,7 @@ msgstr "ID applicazione B2" msgid "B2 Application Key" msgstr "Chiave Applicazione B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "ID Account Cloud B2 Storage" @@ -299,7 +324,7 @@ msgstr "ID Account Cloud B2 Storage" msgid "B2 Cloud Storage Application ID" msgstr "ID applicazione di archiviazione cloud B2" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Chiave applicazione Archiviazione Cloud B2" @@ -312,6 +337,8 @@ msgid "" "Backend modules:

    {{item.Key}}

    " msgstr "" +"Moduli Backend:

    {{item.Key}}

    " #: scripts/services/ServerStatus.js:46 msgid "Backup complete!" @@ -327,6 +354,10 @@ msgid "" "to use for restoring your files, or, in case of GPG encryption, leave blank " "to let gpg retrieve the passphrase by invoking your system's keychain." msgstr "" +"Il backup è crittografato ma non è disponibile una passphrase. Digitare di " +"seguito una passphrase da utilizzare per il ripristino dei file o, in caso " +"di crittografia GPG, lasciare vuoto per consentire a gpg di recuperare la " +"passphrase richiamando il portachiavi del sistema." #: templates/restore.html:21 templates/restoredirect.html:21 #: templates/restoredirect.html:31 @@ -341,7 +372,7 @@ msgstr "Conservazione backup" msgid "Backup:" msgstr "Dimensione backup:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -353,7 +384,7 @@ msgstr "Accesso non riuscito" msgid "Browse" msgstr "Browse" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browser predefinito" @@ -361,14 +392,14 @@ msgstr "Browser predefinito" msgid "Bucket create location" msgstr "Crea posizione bucket" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -377,7 +408,7 @@ msgstr "Crea posizione bucket" msgid "Bucket name" msgstr "Nome bucket" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -411,7 +442,7 @@ msgstr "Creazione di un database temporaneo parziale ..." #: templates/restore.html:59 msgid "Busy …" -msgstr "" +msgstr "Occupato..." #: templates/settings.html:21 msgid "" @@ -441,7 +472,7 @@ msgstr "" #: templates/backends/cos.html:2 msgid "COS App ID" -msgstr "" +msgstr "ID app COS" #: templates/backends/cos.html:32 msgid "COS Path or subfolder in the bucket" @@ -449,7 +480,7 @@ msgstr "Percorso COS o sottocartella nel bucket" #: templates/backends/cos.html:8 msgid "COS Secret ID" -msgstr "" +msgstr "ID segreto COS" #: templates/backends/cos.html:14 msgid "COS Secret Key" @@ -459,13 +490,13 @@ msgstr "Chiave segreta COS" msgid "Cache Files" msgstr "File Cache" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -480,14 +511,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Annulla" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "Cancella registrazione" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "Non può includere \"{{text}}\"" @@ -503,11 +538,11 @@ msgstr "" #: templates/settings.html:8 msgid "Change server passphrase" -msgstr "" +msgstr "Cambia la passphrase del server" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" -msgstr "" +msgstr "Cambia la password del server" #: templates/about.html:5 msgid "Changelog" @@ -531,13 +566,15 @@ msgstr "Verifica aggiornamenti …" #: templates/captcha.html:11 msgid "Checking …" -msgstr "" +msgstr "Controllo..." #: templates/backends/sia.html:18 msgid "" "Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload" " but slow backup." msgstr "" +"Scegli 1.0 per un backup veloce, 1.5 per un'affidabilità decente, 2.0 per un" +" caricamento più sicuro ma un backup lento." #: templates/edituri.html:16 msgid "Chose a storage type to get started" @@ -545,7 +582,7 @@ msgstr "Scegliere un tipo di archiviazione per iniziare" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Clicca sul link AuthID per creare un nuovo AuthID" @@ -559,7 +596,7 @@ msgstr "Libreria client da utilizzare" #: templates/backends/cos.html:10 msgid "Cloud API Secret ID" -msgstr "" +msgstr "ID segreto API Cloud" #: templates/backends/cos.html:16 msgid "Cloud API Secret Key" @@ -567,11 +604,11 @@ msgstr "Chiave segreta API Cloud" #: templates/commandline.html:8 msgid "Command" -msgstr "" +msgstr "Comando" #: templates/commandline.html:18 msgid "Commandline arguments" -msgstr "" +msgstr "Argomenti della riga di comando" #: templates/home.html:40 msgid "Commandline …" @@ -606,6 +643,8 @@ msgid "" "Compression modules:

    {{item.Key}}

    " msgstr "" +"Moduli di compressione:

    {{item.Key}}

    " #: scripts/directives/sourceFolderPicker.js:533 msgid "Computer" @@ -635,7 +674,7 @@ msgstr "Conferma passphrase crittografia" #: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" -msgstr "" +msgstr "Conferma nuova password" #: templates/export.html:29 msgid "Confirm passphrase" @@ -659,11 +698,11 @@ msgstr "Connessione al server …" #: templates/commandline.html:48 msgid "Connecting to task …" -msgstr "" +msgstr "Connessione all'attività..." #: index.html:309 msgid "Connecting …" -msgstr "" +msgstr "Connessione..." #: index.html:294 msgid "Connection lost" @@ -674,7 +713,7 @@ msgstr "Connessione persa" msgid "Connection worked!" msgstr "Connessione funzionante!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nome contenitore" @@ -706,7 +745,7 @@ msgstr "Copia URL Destinazione negli Appunti" #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/RestoreDirectController.js:33 msgid "Copy URL" -msgstr "" +msgstr "Copia l'URL" #: scripts/controllers/DialogController.js:20 msgid "Copy failed. Please manually copy the URL" @@ -716,7 +755,7 @@ msgstr "Copia non riuscita. Per favore copia manualmente l'URL" msgid "Copy log" msgstr "Copia registro" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opzioni base" @@ -724,7 +763,7 @@ msgstr "Opzioni base" msgid "Counting ({{files}} files found, {{size}})" msgstr "Conteggio ({{files}} file trovati, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Solo arresti anomali" @@ -736,7 +775,7 @@ msgstr "Crea segnalazione bug ..." msgid "Create folder?" msgstr "Creare cartella?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Creato nuovo utente limitato" @@ -744,7 +783,7 @@ msgstr "Creato nuovo utente limitato" msgid "Creating bug report …" msgstr "Creazione segnalazione bug ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Creazione di un nuovo utente con accesso limitato ..." @@ -756,9 +795,9 @@ msgstr "Creazione di cartelle di destinazione ..." msgid "Creating temporary backup …" msgstr "Creazione backup temporaneo ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" -msgstr "" +msgstr "Creazione utente..." #: templates/home.html:76 msgid "Current action:" @@ -776,7 +815,7 @@ msgstr "La versione attuale è {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Endpoint S3 personalizzato" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Satellite personalizzato" @@ -818,7 +857,7 @@ msgstr "Classe di archiviazione personalizzata ({{class}})" #: templates/advancedoptionseditor.html:43 msgid "DEPRECATED: {{getDeprecationMessage(item)}}" -msgstr "" +msgstr "DEPRECATO: {{getDeprecationMessage(item)}}" #: templates/home.html:37 msgid "Database …" @@ -828,11 +867,11 @@ msgstr "Banca dati …" msgid "Days" msgstr "Giorni" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Predefinito" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Predefinito ({{channelname}})" @@ -840,13 +879,13 @@ msgstr "Predefinito ({{channelname}})" msgid "Default excludes" msgstr "Esclusioni predefinite" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opzioni predefinite" #: templates/advancedoptionseditor.html:44 msgid "Default value: \"{{getDefaultValue(item)}}\"" -msgstr "" +msgstr "Valore predefinito: \"{{getDefaultValue(item)}}\"" #: templates/localdatabase.html:16 msgid "Delete" @@ -868,6 +907,10 @@ msgstr "Elimina i backup più vecchi di" msgid "Delete local database" msgstr "Cancella database locale" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "Elimina impostazione di controllo remoto" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Cancella file remoti" @@ -922,17 +965,22 @@ msgstr "Destinazione" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Percorso destinazione" #: templates/restorewizard.html:9 msgid "Direct restore from backup files …" -msgstr "" +msgstr "Ripristino diretto da file di backup..." #: templates/backends/idrive.html:3 msgid "Directory path" -msgstr "" +msgstr "Percorso cartella" + +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "Disabilita controllo remoto" #: templates/log.html:32 msgid "Disabled" @@ -947,7 +995,7 @@ msgstr "Annulla" msgid "Dismiss all" msgstr "Ignora tutto" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Tema interfaccia" @@ -961,7 +1009,7 @@ msgstr "Vuoi veramente cancellare il database locale per: {{name}} ?" #: templates/backends/openstack.html:26 msgid "Domain name" -msgstr "" +msgstr "Nome dominio" #: templates/export.html:53 msgid "Done" @@ -996,14 +1044,17 @@ msgstr "Sito web di Duplicati" msgid "Duplicati forum" msgstr "Forum Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" +"Duplicati necessità di essere protetto con una passphrase e una passphrase casuale è stata generata per te.\n" +"Se apri duplicati dall'icona della barra, non hai bisogno di una passphrase, ma se hai in programma di aprirlo da un altra posizione è necessario impostare una passphrase.\n" +"Vuoi impostare una passphrase ora?" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1039,14 +1090,18 @@ msgid "" "faster to perform many operations, and reduces the amount of data that needs" " to be downloaded for each operation." msgstr "" +"Ogni backup ha un database locale associato, che salva le informazioni " +"riguardo il backup remoto sulla macchina locale. Questo rende possibile " +"eseguire molte operazioni velocemente, e riduce la quantità di dati da " +"scaricare per ogni operazione." #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Modifica come elenco" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Modifica come testo" @@ -1056,7 +1111,11 @@ msgstr "Modifica …" #: templates/backends/msgroup.html:3 msgid "Email address of the Office 365 group" -msgstr "" +msgstr "Indirizzo email del gruppo Office 365" + +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "Abilita controllo remoto" #: templates/export.html:22 msgid "Encrypt file" @@ -1076,15 +1135,17 @@ msgid "" "Encryption modules:

    {{item.Key}}

    " msgstr "" +"Moduli di crittografia:

    {{item.Key}}

    " -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Passphrase di crittografia" #: templates/backends/storj.html:30 msgid "Encryption passphrase (for verification)" -msgstr "" +msgstr "Passphrase di crittografia (per la verifica)" #: templates/backup-result/phases/compact.html:12 #: templates/backup-result/phases/delete.html:12 @@ -1104,7 +1165,7 @@ msgstr "Inserisci URL" #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 msgid "Enter a backup destination URL:" -msgstr "" +msgstr "Inserisci un URL di destinazione del backup:" #: templates/addoredit.html:339 msgid "" @@ -1123,7 +1184,7 @@ msgstr "" #: templates/commandline.html:14 msgid "Enter a url, or click the "Target URL >" link" -msgstr "" +msgstr "Inserisci un URL o fai clic sul "Target URL >" link" #: templates/restoredirect.html:61 msgid "Enter backup passphrase, if any" @@ -1149,10 +1210,13 @@ msgstr "Inserisci un argomento per riga senza virgolette, ad es. *.TXT" msgid "" "Enter one option per line in command-line format, e.g. --dblock-size=100MB" msgstr "" +"Inserisci un'opzione per riga nel formato della riga di comando, ad es. " +"--dblock-size=100MB" #: scripts/services/AppUtils.js:122 msgid "Enter one option per line in command-line format, e.g. {0}" msgstr "" +"Inserisci un'opzione per riga nel formato della riga di comando, ad es. {0}" #: templates/restore.html:90 msgid "Enter the destination path" @@ -1192,7 +1256,7 @@ msgstr "Errore" msgid "Error!" msgstr "Errore!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Errori e arresti anomali" @@ -1240,7 +1304,7 @@ msgstr "Escludi espressione regolare" msgid "Existing file found" msgstr "Trovato file esistente" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Sperimentale" @@ -1315,12 +1379,12 @@ msgstr "Impossibile trovare il backup:" #: scripts/directives/notificationArea.js:68 msgid "Failed to get bug report URL: {{message}}" -msgstr "" +msgstr "Impossibile ottenere l'URL di segnalazione del bug: {{message}}" #: scripts/controllers/ImportController.js:39 #: scripts/controllers/ImportController.js:43 msgid "Failed to import: {{message}}" -msgstr "" +msgstr "Impossibile importare: {{message}}" #: scripts/controllers/EditBackupController.js:707 msgid "Failed to read backup defaults:" @@ -1328,13 +1392,13 @@ msgstr "Lettura impostazioni predefinite backup fallita:" #: scripts/controllers/ImportController.js:49 msgid "Failed to read file: {{message}}" -msgstr "" +msgstr "Impossibile leggere il file: {{message}}" #: scripts/controllers/RestoreController.js:423 msgid "Failed to restore files: {{message}}" msgstr "Ripristino dei file fallito: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Salvataggio fallito:" @@ -1363,7 +1427,7 @@ msgstr "Filtri" msgid "Finished!" msgstr "Finito!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Impostazione prima esecuzione" @@ -1373,7 +1437,7 @@ msgstr "Cartella" #: templates/backends/storj.html:42 msgid "Folder in the bucket" -msgstr "" +msgstr "Cartella nel bucket" #: templates/backends/aliyunoss.html:30 templates/backends/b2.html:7 #: templates/backends/cos.html:30 templates/backends/e2.html:16 @@ -1387,7 +1451,7 @@ msgstr "Percorso cartella" #: templates/backends/mega.html:3 msgid "Folder path name" -msgstr "" +msgstr "Nome percorso cartella" #: scripts/services/AppUtils.js:108 msgid "Fri" @@ -1396,6 +1460,8 @@ msgstr "Ven" #: templates/backends/sharepoint.html:3 msgid "Full destination path, including the server name, but without https" msgstr "" +"Percorso di destinazione completo, compreso il nome del server, ma senza " +"https" #: scripts/services/AppUtils.js:84 msgid "GByte" @@ -1434,7 +1500,7 @@ msgstr "Genera criteri di accesso IAM" msgid "Getting file versions …" msgstr "Ottenere versioni di file ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Email gruppo" @@ -1459,7 +1525,7 @@ msgid "Hostnames" msgstr "Nomi host" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Ore" @@ -1489,13 +1555,13 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "Percorso cartella di sincronizzazione di IDrive" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" -msgstr "" +msgstr "ID chiave di accesso IDrive e2" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" -msgstr "" +msgstr "Chiave di accesso IDrive e2 segreta" #: templates/addoredit.html:261 msgid "If a date was missed, the job will run as soon as possible." @@ -1517,6 +1583,10 @@ msgid "" "that you perform a repair operation to synchronize the database. If the " "repair is unsuccessful, you can delete the local database and re-generate." msgstr "" +"Se il backup e l'archiviazione remote non sono sicronizzati, Duplicati " +"richiederà di eseguire un operazione di riparazione per sicronizzare il " +"database. Se la riparazione non ha successo, puoi eliminare il database " +"locale e rigenerarlo." #: templates/export.html:49 msgid "" @@ -1524,6 +1594,9 @@ msgid "" "href=\"{{DownloadURL}}\" target=\"_blank\">right click and choose "Save" " as …"." msgstr "" +"se il file di backup non è stato scaricato automaticamente tasto destro e scegli "Salva" +" come …" ." #: templates/notificationarea.html:7 msgid "" @@ -1531,6 +1604,9 @@ msgid "" "href=\"{{item.DownloadLink}}\" target=\"_blank\">right click and choose " ""Save as …"." msgstr "" +"Se il file di backup non è stato scaricato automaticamente tasto destro e scegli " +""Salva come …" ." #: scripts/services/EditUriBackendConfig.js:113 msgid "" @@ -1544,11 +1620,20 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Se non inserisci una Chiave API, è richiesto il nome del detentore" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" +"Se si mettono in pausa i trasferimenti, questi potrebbero andare in timeout " +"e causare tentativi o fallimenti." + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" " deleting it." msgstr "" +"Se vuoi usare il backup successivamente, puoi esportare la configurazione " +"prima di cancellarla." #: templates/import.html:29 msgid "Import" @@ -1561,7 +1646,7 @@ msgstr "Importa URL Destinazione" #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 msgid "Import URL" -msgstr "" +msgstr "Importa l'URL" #: templates/import.html:3 msgid "Import backup configuration" @@ -1595,7 +1680,7 @@ msgstr "Includi espressione regolare" msgid "Incorrect answer, try again" msgstr "Risposta errata, riprova" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1609,7 +1694,7 @@ msgstr "Informazioni" msgid "Interrupted, no statistics collected" msgstr "Interrotto, nessuna statistica raccolta" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caratteri non validi nel percorso" @@ -1619,7 +1704,7 @@ msgstr "Caratteri non validi nel percorso" msgid "Invalid retention time" msgstr "Tempo ritenzione non valido" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1647,7 +1732,7 @@ msgstr "Mantieni tutti i backup" msgid "Keystone API version" msgstr "Versione API Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Lingua interfaccia utente" @@ -1707,7 +1792,7 @@ msgstr "Carica dati precedenti" #: templates/delete.html:40 msgid "Loading remote storage usage …" -msgstr "" +msgstr "Caricamento utilizzo archiviazione remota…" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 @@ -1717,16 +1802,15 @@ msgstr "" msgid "Loading …" msgstr "Caricamento in corso …" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Repository locale" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}}…loading…" msgstr "" +"Database locale per {{Backup.Backup.Name}}...caricamento..." #: templates/localdatabase.html:23 msgid "Local database path:" @@ -1736,7 +1820,7 @@ msgstr "Percorso database locale:" msgid "Local repository" msgstr "Repository locale" -#: scripts/services/SystemInfo.js:84 +#: scripts/services/SystemInfo.js:85 msgid "Local storage" msgstr "Archivio locale" @@ -1758,7 +1842,7 @@ msgstr "Dati di log dal server" #: index.html:307 msgid "Log in" -msgstr "" +msgstr "Log in" #: index.html:227 msgid "Log out" @@ -1772,6 +1856,12 @@ msgstr "MByte" msgid "MByte/s" msgstr "MByte/s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" +"La macchina è ora registrata, apri questo link per aggiungerlo al tuo " +"account" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "Manutenzione" @@ -1781,6 +1871,8 @@ msgid "" "Make sure that rclone is in your path, or add the location to rclone via the" " advanced options." msgstr "" +"Assicurati che rclone è nel tuo percorso, oppure aggiungi la posizione di " +"rclone attraverso le opzioni avanzate" #: index.html:260 msgid "Manual" @@ -1820,12 +1912,12 @@ msgstr "Microsoft SQL Database" msgid "Minimum redundancy" msgstr "Ridondanza minima" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "Ridondanza minima è 1.0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minuti" @@ -1887,7 +1979,7 @@ msgstr "Mai" #: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" -msgstr "" +msgstr "Nuova password" #: templates/notificationarea.html:21 msgid "New update found: {{message}}" @@ -1895,7 +1987,7 @@ msgstr "" "Nuovo aggiornamento trovato: {{message}}" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1926,7 +2018,7 @@ msgstr "Prossima attività:" msgid "Next time" msgstr "Prossima volta" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1939,9 +2031,9 @@ msgstr "Prossima volta" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "No" @@ -1983,7 +2075,7 @@ msgstr "Nessuna attività pianificata" msgid "Non-matching passphrase" msgstr "Passphrase non corrispondente" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nessuno / disattivato" @@ -1997,6 +2089,9 @@ msgid "" "reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line " "is equivalent to 1 MByte/s." msgstr "" +"Nota che le velocità sono inserite in byte, mentre le velocità delle linee " +"sono tipicamente riportate in bit. Per la conversione utilizzare un fattore " +"8, in modo che una linea da 8 mbit/s equivalga a 1 MByte/s." #: templates/addoredit.html:328 msgid "Nothing will be deleted. The backup size will grow with each change." @@ -2004,23 +2099,23 @@ msgstr "" "Niente sarà eliminato. La dimensione del backup crescerà con ogni " "cambiamento." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" #: templates/backends/aliyunoss.html:10 templates/backends/aliyunoss.html:8 msgid "OSS Access Key ID" -msgstr "" +msgstr "ID chiave d'accesso OSS" #: templates/backends/aliyunoss.html:14 templates/backends/aliyunoss.html:16 msgid "OSS Access Key Secret" @@ -2032,7 +2127,7 @@ msgstr "Regione del bucket OSS" #: templates/backends/aliyunoss.html:25 templates/backends/aliyunoss.html:26 msgid "OSS Bucket name" -msgstr "" +msgstr "OSS nome del Bucket" #: templates/backends/aliyunoss.html:2 msgid "OSS Endpoint" @@ -2046,9 +2141,9 @@ msgstr "Percorso OSS o sottocartella nel bucket" msgid "OSS Region" msgstr "Regione dell'OSS" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" -msgstr "" +msgstr "Rilasci ufficiali" #: templates/addoredit.html:346 msgid "" @@ -2070,9 +2165,9 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Aperto" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" -msgstr "" +msgstr "La chiave API Openstack non è supportata nell'API keystone v3" #: scripts/services/AppUtils.js:197 msgid "Operating System" @@ -2088,7 +2183,7 @@ msgstr "Operazioni:" #: templates/backends/openstack.html:45 msgid "Optional API key" -msgstr "" +msgstr "Chiave API opzionale" #: templates/backends/file.html:34 msgid "Optional authentication password" @@ -2107,21 +2202,23 @@ msgid "Optional tenant name" msgstr "Nome detentore facoltativo" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opzioni" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." msgstr "" +"Le opzioni aggiunte qui sono applicate a tutti i backup, ma possono essere " +"sovrascritte in ogni singolo backup." #: templates/restore.html:81 msgid "Original location" msgstr "Percorso originale" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Altri" @@ -2160,7 +2257,7 @@ msgstr "Passphrase non corrispondenti" msgid "Passphrases do not match" msgstr "Le passphrase non corrispondono" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2171,8 +2268,8 @@ msgstr "Password" msgid "Patching files with local blocks …" msgstr "Patch di file con blocchi locali ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Percorso" @@ -2181,7 +2278,7 @@ msgid "Path not found" msgstr "Percorso non trovato" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Percorso sul server" @@ -2190,11 +2287,11 @@ msgstr "Percorso sul server" msgid "Path or subfolder in the bucket" msgstr "Percorso o sottocartella bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pausa" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausa dopo avvio o ibernazione" @@ -2212,7 +2309,7 @@ msgstr "Scegli posizione" #: scripts/controllers/ImportController.js:17 msgid "Please select a file to import" -msgstr "" +msgstr "Seleziona un file da importare" #: templates/restorewizard.html:10 msgid "Point to your backup files and restore from there" @@ -2233,6 +2330,10 @@ msgstr "" msgid "Previous" msgstr "Precedente" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "Elaborazione dei file per il backup ..." + #: templates/home.html:78 msgid "Progress:" msgstr "Avanzamento:" @@ -2241,7 +2342,7 @@ msgstr "Avanzamento:" msgid "ProjectID is optional if the bucket exist" msgstr "ID Progetto è opzionale se esiste un bucket" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietario" @@ -2277,10 +2378,30 @@ msgstr "Ricreazione del database ..." msgid "Region" msgstr "Regione" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "Registrazione per il controllo remoto" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "Registrato, in attesa di accettazione" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "Registro macchina..." + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrazione backup temporaneo ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "URL di registrazione" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "Registrazione fallita" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Percorsi relativi non consentiti" @@ -2293,14 +2414,34 @@ msgstr "Ricarica" msgid "Remote" msgstr "Remoto" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Percorso remoto" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Repository remoto" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "Controllo dell'accesso remoto" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "Controllo remoto è configurato ma non è abilitato" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "Controllo remoto è connesso" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "Controllo remoto è abilitato ma non connesso" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "Controllo remoto non è configurato" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Percorso remoto" @@ -2379,7 +2520,7 @@ msgstr "Ripristino dalla configurazione backup" #: templates/restorewizard.html:15 msgid "Restore from configuration …" -msgstr "" +msgstr "Ripristina dalla configurazione…" #: templates/restore.html:24 templates/restore.html:39 #: templates/restore.html:76 templates/restoredirect.html:24 @@ -2438,12 +2579,14 @@ msgstr "In esecuzione …" #: templates/commandline.html:54 msgid "Running … stop now" msgstr "" +"In " +"esecuzione...ferma ora" #: scripts/services/SystemInfo.js:51 msgid "S3 Compatible" msgstr "Compatibile S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Come la versione di base installata: {{channelname}}" @@ -2491,7 +2634,7 @@ msgstr "Cerca" msgid "Search for files" msgstr "Cerca per file" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Secondi" @@ -2504,8 +2647,8 @@ msgstr "Selezionare un livello di log e visiona i messaggi che avvengono:" msgid "Select files" msgstr "Seleziona file" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2526,6 +2669,8 @@ msgid "" "Server is currently paused, resume now" msgstr "" +"Il server è attualmente in pausa, riprendi adesso" #: scripts/controllers/HomeController.js:7 msgid "Server is currently paused, do you want to resume now?" @@ -2543,6 +2688,10 @@ msgstr "Server in pausa" msgid "Server state properties" msgstr "Proprietà stato del server" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "Imposta il fuso orario predefinito" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Impostazioni" @@ -2614,18 +2763,18 @@ msgstr "Cartella sorgente" #: templates/home.html:67 msgid "Source:" -msgstr "Dimensione sorgente:" +msgstr "Sorgente:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Build specifiche per soli sviluppatori. Non utilizzare con dati importanti." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" -msgstr "" +msgstr "Stabile" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocolli standard" @@ -2652,19 +2801,15 @@ msgstr "Avvio ripristino ..." msgid "Starting the restore process …" msgstr "Avvio del processo di ripristino ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stop dopo il file corrente" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "Stato: {{getRemoteControlStatusText()}}" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Ferma dopo il file corrente" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Ferma adesso" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Ferma esecuzione backup" @@ -2718,7 +2863,7 @@ msgstr "Link simbolico" msgid "System Files" msgstr "File di Sistema" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Sistema predefinito ({{levelname}})" @@ -2744,11 +2889,11 @@ msgstr "TByte/s" #: templates/commandline.html:13 msgid "Target URL >" -msgstr "" +msgstr "URL di destinazione >" #: templates/backends/sia.html:7 msgid "Target path. Example: /backup" -msgstr "" +msgstr "Percorso di destinazione. Esempio: /backup" #: templates/waitarea.html:5 msgid "Task is running" @@ -2764,15 +2909,20 @@ msgstr "File temporanei" #: templates/backends/openstack.html:39 msgid "Tenant name" -msgstr "" +msgstr "Nome detentore" #: templates/backends/cos.html:4 msgid "Tencent Cloud Account APPID" -msgstr "" +msgstr "APPID dell'account Tencent Cloud" #: templates/backends/cos.html:35 msgid "Tencent Cloud COS documents and resources" -msgstr "" +msgstr "Documentazione e risorse di Tencent Cloud COS" + +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "Termina" #: templates/backup-result/phases/test.html:3 msgid "Test Phase" @@ -2784,9 +2934,9 @@ msgstr "Prova connessione" #: scripts/directives/backupEditUri.js:43 msgid "Testing connection …" -msgstr "" +msgstr "Verifica della connessione..." -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Test delle autorizzazioni ..." @@ -2794,10 +2944,10 @@ msgstr "Test delle autorizzazioni ..." msgid "Testing …" msgstr "Test in corso..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2823,8 +2973,12 @@ msgid "" "link=\"'https://www.duplicati.com/articles/Choosing-Sizes/#remote-volume-" "size'\">See this page for more information." msgstr "" +"I backup saranno suddivisi in più file chiamati volumi. Qui è possibile " +"impostare la dimensione massima dei singoli file di volume. Consulta questa pagina per ulteriori informazioni." -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Il nome del bucket dovrebbe essere tutto minuscolo, convertirlo " @@ -2841,16 +2995,17 @@ msgstr "" #: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" +"La connessione al server è stata persa, nuovo tentativo tra {{time}} …" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tema scuro (da Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Predefinito - Tema blu su bianco (da Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "Le passphrase di crittografia non corrispondono" @@ -2882,7 +3037,7 @@ msgstr "" "Vuoi SOSTITUIRE la chiave host CORRENTE \"{{prev}}\" con la chiave host SEGNALATA: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Le password non corrispondono" @@ -2910,11 +3065,11 @@ msgstr "" #: templates/backends/s3.html:33 msgid "The region parameter is only applied when creating a new bucket" -msgstr "Il parametro area è applicato solo quando si crea un nuovo bucket" +msgstr "Il parametro regione è applicato solo quando si crea un nuovo bucket" #: templates/backends/openstack.html:50 msgid "The region parameter is only used when creating a bucket" -msgstr "Il parametro area è utilizzato solo quando si crea un bucket" +msgstr "Il parametro regione è utilizzato solo quando si crea un bucket" #: scripts/directives/backupEditUri.js:86 msgid "" @@ -2938,7 +3093,7 @@ msgstr "" "La cartella di destinazione contiene file criptati, per favore fornisci la " "passphrase" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2966,7 +3121,7 @@ msgstr "Questo mese" msgid "This week" msgstr "Questa settimana" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Impostazioni limitazione" @@ -2979,6 +3134,10 @@ msgstr "Mar" msgid "Time" msgstr "Tempo" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "Fuso orario" + #: templates/export.html:14 msgid "To File" msgstr "Al File" @@ -2996,11 +3155,13 @@ msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" "Per esportare senza una passphrase, deselezionare la casella \"Cripta file\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" msgstr "" +"Per evitare conflitti di denominazione dei bucket, è consigliabile anteporre" +" l'ID dell'account al nome del bucket. Anteporre automaticamente?" #: templates/settings.html:26 msgid "" @@ -3031,7 +3192,7 @@ msgstr "Certificato host affidabile?" msgid "Trust server certificate?" msgstr "Certificato server affidabile?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3067,8 +3228,11 @@ msgid "" "click=\"doShowUpdateChangelog()\">{{state.updatedVersion}} is available." " Download now" msgstr "" +"L'aggiornamento {{state.updatedVersion}} è " +"disponibile. Scaricalo ora" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canale di aggiornamento" @@ -3088,18 +3252,22 @@ msgstr "File caricati" msgid "Uploading verification file …" msgstr "Caricamento file di verifica ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" +"I rapporti di utilizzo ci aiutano a migliorare l'esperienza dell'utente e a " +"valutare l'impatto di nuove funzionalità. Li utilizziamo per generare " +"statistiche " +"di utilizzo pubblico." -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistiche di utilizzo" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistiche di utilizzo, avvisi, errori e arresti anomali" @@ -3127,17 +3295,17 @@ msgstr "Dati utente" msgid "User domain name" msgstr "Nome dominio utente" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "L'utente ha troppe autorizzazioni" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Impostazioni interfaccia utente" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3210,6 +3378,8 @@ msgid "" "WARNING: The remote database is found to be in use by the commandline " "library." msgstr "" +"WARNING: The remote database is found to be in use by the commandline " +"library." #: templates/delete.html:44 msgid "WARNING: This will prevent you from restoring the data in the future." @@ -3221,13 +3391,13 @@ msgstr "In attesa dell'attività per iniziare" #: templates/commandline.html:51 msgid "Waiting for task to start …" -msgstr "" +msgstr "In attesa dell'avvio dell'attività..." #: scripts/services/ServerStatus.js:41 msgid "Waiting for upload to finish …" msgstr "In attesa del completamento del caricamento ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avvisi, errori e arresti anomali" @@ -3265,7 +3435,7 @@ msgstr "Dove vuoi ripristinare i files?" msgid "Years" msgstr "Anni" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3278,9 +3448,9 @@ msgstr "Anni" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Si" @@ -3319,18 +3489,22 @@ msgstr "Attualmente stai eseguendo {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"È possibile interrompere il backup al termine di eventuali caricamenti di " -"file attualmente in corso." +"È possibile interrompere il backup al termine del caricamento dei file in " +"corso. Se si interrompe il backup, l'esecuzione successiva dovrà " +"ripristinare un backup non riuscito." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"È possibile interrompere l'operazione immediatamente, o consentire il " -"processo di continuare il suo file corrente e poi fermarsi." +"È possibile interrompere immediatamente l'attività o consentire al processo " +"di continuare il suo file corrente e quindi interromperlo. Se si termina " +"l'attività, il backup potrebbe rimanere in uno stato inconsistente." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3376,7 +3550,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Devi scegliere almeno una cartella sorgente" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Devi inserire un nome di dominio per utilizzare l'API v3" @@ -3388,7 +3562,7 @@ msgstr "Devi inserire un nome per il backup" msgid "You must enter a passphrase or disable encryption" msgstr "Devi inserire una passphrase o disattivare la crittografia" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Devi inserire una password per utilizzare l'API v3" @@ -3396,13 +3570,14 @@ msgstr "Devi inserire una password per utilizzare l'API v3" msgid "You must enter a positive number of backups to keep" msgstr "Devi inserire un numero positivo di backup da mantenere" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Devi inserire un detentore (aka progetto) per utilizzare l'API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" +"È necessario inserire un nome detentore se non si fornisce una chiave API" #: scripts/controllers/EditBackupController.js:289 msgid "You must enter a valid duration for the time to keep backups" @@ -3412,13 +3587,13 @@ msgstr "Devi inserire un periodo di tempo valido in cui mantenere i backup" msgid "You must enter a valid retention policy string" msgstr "Devi inserire una stringa di criteri di conservazione valida" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" -msgstr "" +msgstr "È necessario inserire una password o una chiave API" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" -msgstr "" +msgstr "È necessario inserire una password o una chiave API, non entrambe" #: scripts/services/EditUriBackendConfig.js:122 msgid "You must fill in the password" @@ -3437,11 +3612,11 @@ msgstr "Devi compilare in nome utente" msgid "You must fill in {{field}}" msgstr "Devi compilare in {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Devi selezionare o compilare in AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Devi selezionare o compilare in server" @@ -3451,7 +3626,7 @@ msgstr "Devi specificare un percorso" #: scripts/services/EditUriBackendConfig.js:92 msgid "You should fill in {{field}} {{reason}}" -msgstr "" +msgstr "Dovresti compilare {{field}} {{reason}}" #: templates/restore.html:144 msgid "Your files and folders have been restored successfully." @@ -3474,25 +3649,25 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" -msgstr "" +msgstr "cos_app_id" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" -msgstr "" +msgstr "cos_bucket" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" -msgstr "" +msgstr "cos_region" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" -msgstr "" +msgstr "cos_secret_id" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" -msgstr "" +msgstr "cos_secret_key" #: templates/addoredit.html:272 templates/addoredit.html:355 #: templates/advancedoptionseditor.html:29 @@ -3505,46 +3680,54 @@ msgid "failed" msgstr "fallito" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" -msgstr "" +msgid "local repository, leave empty for local" +msgstr "repository locale, lascia vuoto per il locale" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" -msgstr "" +msgstr "oss_access_key_id" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" -msgstr "" +msgstr "oss_access_key_secret" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" -msgstr "" +msgstr "oss_bucket_name" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" -msgstr "" +msgstr "oss_endpoint" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" -msgstr "" +msgstr "oss_region" + +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "pCloud EU (eapi.pcloud.com)" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "pCloud Global (api.pcloud.com)" #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" -msgstr "" +msgstr "percorso remoto, ad es. backup" #: templates/backends/rclone.html:7 msgid "remote repository, e.g. remote" -msgstr "" +msgstr "repository remoto, ad es. remoto" #: templates/restoredirect.html:95 templates/waitarea.html:15 msgid "resume now" msgstr "riprendi ora" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" -msgstr "" +msgstr "storj_shared_access" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "a meno che tu non stia specificando esplicitamente --group-id" @@ -3565,6 +3748,7 @@ msgstr "" msgid "" "{{brandingService.appName}} is using the following third party libraries:" msgstr "" +"{{brandingService.appName}} utilizza le seguenti librerie di terze parti:" #: scripts/controllers/StateController.js:53 msgid "{{files}} files ({{size}}) to go {{speed_txt}}" diff --git a/Localizations/webroot/localization_webroot-ja_JP.po b/Localizations/webroot/localization_webroot-ja_JP.po index 0dc61033a..ad0c46776 100644 --- a/Localizations/webroot/localization_webroot-ja_JP.po +++ b/Localizations/webroot/localization_webroot-ja_JP.po @@ -1,7 +1,7 @@ # # Translators: # AlbireoGT, 2017 -# TAKAHASHI Shuuji , 2018 +# Shuuji TAKAHASHI (shuuji3) , 2018 # yu hi , 2019 # あわしろいくや , 2020 # Suguru Hirahara, 2024 @@ -41,7 +41,7 @@ msgstr "(中断されました)" msgid "- pick an option -" msgstr "- オプションを選択してください -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "…読み込んでいます…" @@ -67,23 +67,23 @@ msgstr "" "

    不正認証のためサーバーへの接続は拒否されました。

    \n" "

    再度ログインするか、トレイのアイコンからページを再度開いてください(該当する場合)。

    " -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "APIキー" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWSのアクセスID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWSのアクセスキー" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWSのIAMポリシー" @@ -95,7 +95,7 @@ msgstr "概要" msgid "About {{appname}}" msgstr "{{appname}}について" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "アクセスキー" @@ -123,7 +123,7 @@ msgstr "アクセスキー" msgid "Access to user interface" msgstr "ユーザーインターフェースへのアクセス" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "アカウント名" @@ -156,8 +156,8 @@ msgstr "パスを追加" msgid "Added" msgstr "追加済" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "バケットの名称を変更しますか?" @@ -190,7 +190,7 @@ msgstr "全てのHyper-Vマシン" msgid "All Microsoft SQL Databases" msgstr "全てのMicrosoft SQLデータベース" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -208,6 +208,10 @@ msgstr "リモートアクセスを許可(要再起動)" msgid "Allowed days" msgstr "実行を許可する日" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "既存のファイルが新しい場所で見つかりました" @@ -231,7 +235,7 @@ msgstr "" "\n" "ローカルに存在するデータベースを使用しますか?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "使用状況に関する匿名の報告" @@ -239,20 +243,26 @@ msgstr "使用状況に関する匿名の報告" msgid "Applications" msgstr "アプリケーション" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "コマンドライン" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "認証ID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "認証方法" @@ -284,7 +294,7 @@ msgstr "B2 アプリケーションのID" msgid "B2 Application Key" msgstr "B2 アプリケーションのキー" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 クラウドストレージのアカウントのID" @@ -292,7 +302,7 @@ msgstr "B2 クラウドストレージのアカウントのID" msgid "B2 Cloud Storage Application ID" msgstr "B2 クラウドストレージのアプリケーションのID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 クラウドストレージのアプリケーションのキー" @@ -337,7 +347,7 @@ msgstr "バックアップの保持期間" msgid "Backup:" msgstr "バックアップ:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "ベータ版" @@ -349,7 +359,7 @@ msgstr "アクセスが壊れています" msgid "Browse" msgstr "参照" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "ブラウザ設定" @@ -357,14 +367,14 @@ msgstr "ブラウザ設定" msgid "Bucket create location" msgstr "バケットを作成する場所" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -373,7 +383,7 @@ msgstr "バケットを作成する場所" msgid "Bucket name" msgstr "バケット名" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -446,13 +456,13 @@ msgstr "COSの秘密鍵" msgid "Cache Files" msgstr "キャッシュファイル" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "実験的(カナリア)" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -467,14 +477,18 @@ msgstr "実験的(カナリア)" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "キャンセル" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "「{{text}}」を含めることはできません" @@ -491,9 +505,9 @@ msgstr "追加のオプションに、含めたり除外したりするフィル msgid "Change server passphrase" msgstr "サーバーのパスフレーズを変更" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" -msgstr "" +msgstr "サーバーのパスワードを変更" #: templates/about.html:5 msgid "Changelog" @@ -531,7 +545,7 @@ msgstr "初めにストレージの種類を選択してください" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "認証IDのリンクをクリックして作成してください" @@ -662,7 +676,7 @@ msgstr "切断しました" msgid "Connection worked!" msgstr "接続できました!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "コンテナ名" @@ -704,7 +718,7 @@ msgstr "コピーできませんでした。URLを手動でコピーしてくだ msgid "Copy log" msgstr "ログをコピー" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "中心のオプション" @@ -712,7 +726,7 @@ msgstr "中心のオプション" msgid "Counting ({{files}} files found, {{size}})" msgstr "計測中({{files}}個のファイルが見つかりました。サイズは{{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "クラッシュのみ" @@ -724,7 +738,7 @@ msgstr "バグレポートを作成…" msgid "Create folder?" msgstr "フォルダーを作成しますか?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "新規の制限ユーザーを作成しました" @@ -732,7 +746,7 @@ msgstr "新規の制限ユーザーを作成しました" msgid "Creating bug report …" msgstr "バグレポートを作成しています…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "アクセスが制限されている新規ユーザーを作成しています…" @@ -744,7 +758,7 @@ msgstr "バックアップ先のフォルダーを作成しています…" msgid "Creating temporary backup …" msgstr "一時的なバックアップを作成しています…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "ユーザーを作成しています…" @@ -764,7 +778,7 @@ msgstr "現在のバージョンは {{versionname}}({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "ユーザー定義のS3エンドポイント" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "ユーザー定義のサテライト" @@ -816,11 +830,11 @@ msgstr "データベース…" msgid "Days" msgstr "日" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "初期設定" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "既定({{channelname}})" @@ -828,7 +842,7 @@ msgstr "既定({{channelname}})" msgid "Default excludes" msgstr "既定で除外するアイテム" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "既定のオプション" @@ -856,6 +870,10 @@ msgstr "古いバックアップから削除" msgid "Delete local database" msgstr "ローカルデータベースを削除" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "リモートファイルを削除" @@ -910,7 +928,8 @@ msgstr "バックアップ先" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "バックアップ先のパス" @@ -922,6 +941,10 @@ msgstr "バックアップファイルから直接復元…" msgid "Directory path" msgstr "ディレクトリーのパス" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "無効" @@ -935,7 +958,7 @@ msgstr "表示しない" msgid "Dismiss all" msgstr "すべて表示しない" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "テーマカラー" @@ -984,7 +1007,7 @@ msgstr "Duplicatiのウェブサイト" msgid "Duplicati forum" msgstr "Duplicatiのフォーラム" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" @@ -994,7 +1017,7 @@ msgstr "" "Duplicatiをトレイアイコンから開く場合はパスフレーズは必要ありませんが、別の場所から開くにはパスフレーズを入力する必要があります。\n" "パスフレーズを設定しますか?" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1032,12 +1055,12 @@ msgstr "" "それぞれのバックアップには、ローカルのコンピューターに保存されるデータベースがあります。このデータベースには、リモートバックアップに関する情報が保存されており、操作の速度を改善したり、その都度の操作でダウンロードするデータ量を減らしたりする効果があります。" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "一覧で編集" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "テキストで編集" @@ -1049,6 +1072,10 @@ msgstr "編集..." msgid "Email address of the Office 365 group" msgstr "Office 365グループのメールアドレス" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "ファイルを暗号化" @@ -1070,7 +1097,7 @@ msgstr "" "暗号化モジュール:

    {{item.Key}}

    " -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "暗号化用のパスフレーズ" @@ -1180,7 +1207,7 @@ msgstr "エラー" msgid "Error!" msgstr "エラー!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "エラーとクラッシュ" @@ -1228,7 +1255,7 @@ msgstr "正規表現で除外" msgid "Existing file found" msgstr "既存のファイルが見つかりました" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "実験的" @@ -1322,7 +1349,7 @@ msgstr "ファイルを読み込めませんでした:{{message}}" msgid "Failed to restore files: {{message}}" msgstr "ファイルを復元できませんでした:{{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "保存できませんでした:" @@ -1351,7 +1378,7 @@ msgstr "フィルター" msgid "Finished!" msgstr "完了しました!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "初回実行セットアップ" @@ -1422,7 +1449,7 @@ msgstr "IAMアクセスポリシーを生成" msgid "Getting file versions …" msgstr "ファイルのバージョンを取得しています…" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "グループの電子メール" @@ -1447,7 +1474,7 @@ msgid "Hostnames" msgstr "ホスト名" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "時間" @@ -1477,11 +1504,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "IDrive Syncのディレクトリーのパス" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "IDrive e2のアクセスキーのID" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "IDrive e2のアクセスキーのシークレット" @@ -1533,6 +1560,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "APIを入力しない場合、テナント名が必要です" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1584,7 +1616,7 @@ msgstr "次の正規表現を含む" msgid "Incorrect answer, try again" msgstr "答えが正しくありません。もう一度試してください" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "開発者用の個別のビルドです。重要なデータのバックアップには使用しないでください。" @@ -1597,7 +1629,7 @@ msgstr "情報" msgid "Interrupted, no statistics collected" msgstr "中断されました。統計は収集されていません" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "無効な文字がパスに含まれています" @@ -1607,7 +1639,7 @@ msgstr "無効な文字がパスに含まれています" msgid "Invalid retention time" msgstr "無効な保持期間が設定されています" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1635,7 +1667,7 @@ msgstr "全てのバックアップを保存" msgid "Keystone API version" msgstr "Keystone APIのバージョン" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "言語設定" @@ -1700,10 +1732,6 @@ msgstr "リモートストレージの使用量を読み込んでいます…" msgid "Loading …" msgstr "読み込んでいます…" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "ローカルのリポジトリー" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}{{message}}" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1910,7 +1942,7 @@ msgstr "次のタスク:" msgid "Next time" msgstr "次回" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1923,9 +1955,9 @@ msgstr "次回" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "いいえ" @@ -1967,7 +1999,7 @@ msgstr "予定されているタスクはありません" msgid "Non-matching passphrase" msgstr "パスフレーズが一致しません" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "なし / 無効" @@ -1987,17 +2019,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "バックアップは削除されません。バックアップのサイズはその都度の変更に従って大きくなります。" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2029,7 +2061,7 @@ msgstr "OSSのパスあるいはバケットのサブフォルダー" msgid "OSS Region" msgstr "OSSのリージョン" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "公式リリース版" @@ -2051,7 +2083,7 @@ msgstr "OpenStack オブジェクトストレージ / Swift" msgid "Opened" msgstr "展開済" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "OpenstackのAPIキーは、バージョン3のkeystone APIではサポートされていません。" @@ -2088,11 +2120,11 @@ msgid "Optional tenant name" msgstr "テナント名(オプション)" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "オプション" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2102,7 +2134,7 @@ msgstr "ここで追加したオプションは全てのバックアップに適 msgid "Original location" msgstr "元の場所" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "その他" @@ -2139,7 +2171,7 @@ msgstr "パスフレーズが一致しません" msgid "Passphrases do not match" msgstr "パスフレーズが一致しません" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2150,8 +2182,8 @@ msgstr "パスワード" msgid "Patching files with local blocks …" msgstr "ファイルをローカルのブロックで修復しています…" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "パス" @@ -2160,7 +2192,7 @@ msgid "Path not found" msgstr "パスが見つかりません" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "サーバー上のパス" @@ -2169,11 +2201,11 @@ msgstr "サーバー上のパス" msgid "Path or subfolder in the bucket" msgstr "パスまたはバケットのサブフォルダー" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "一時停止" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "起動時またはハイバネート時に一時停止" @@ -2211,6 +2243,10 @@ msgstr "トレイアイコンの自動ログインを行わない" msgid "Previous" msgstr "前へ" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "進行度:" @@ -2219,7 +2255,7 @@ msgstr "進行度:" msgid "ProjectID is optional if the bucket exist" msgstr "バケットが存在する場合、ProjectIDはオプションです" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "独自プロトコル" @@ -2255,10 +2291,30 @@ msgstr "データベースを改めて作成しています…" msgid "Region" msgstr "リージョン" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "一時的なバックアップを登録しています…" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "相対パスは許可されていません" @@ -2271,14 +2327,34 @@ msgstr "更新" msgid "Remote" msgstr "リモート" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "リモートのパス" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "リモートのリポジトリー" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "リモートのパス" @@ -2421,7 +2497,7 @@ msgstr "実行しています … 詳細についてはこのページを確認してください。" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "バケット名には小文字のみが使用できます。自動的に変換しますか?" @@ -2813,15 +2894,15 @@ msgstr "設定ファイルは安全に保存すべきです。ファイルには msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "サーバーとの接続が失われました。{{time}}後に再試行します…" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "ダークテーマ(by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "既定の白地に青テーマ(by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "暗号化用のパスフレーズが一致しません" @@ -2851,7 +2932,7 @@ msgstr "" "現在のホストの鍵「{{prev}}」を、報告されたホストの鍵「{{key}}」で置き換えますか?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "パスワードが一致しません" @@ -2900,7 +2981,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "バックアップ先のフォルダーには暗号化されているファイルがあります。パスフレーズを指定してください。" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2923,7 +3004,7 @@ msgstr "当月" msgid "This week" msgstr "この週" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "速度制限の設定" @@ -2936,6 +3017,10 @@ msgstr "木曜日" msgid "Time" msgstr "時間" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "ファイルへ" @@ -2950,7 +3035,7 @@ msgstr "「{{name}}」の全てのリモートファイルを本当に削除し msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "パスフレーズなしでエクスポートするには、「ファイルを暗号化」のチェックを外してください" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2979,7 +3064,7 @@ msgstr "ホストの証明書を信用しますか?" msgid "Trust server certificate?" msgstr "サーバーの証明書を信用しますか?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3017,7 +3102,7 @@ msgstr "" "click=\"doShowUpdateChangelog()\">{{state.updatedVersion}} が利用できます。ダウンロード" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "アップデートチャンネル" @@ -3037,18 +3122,21 @@ msgstr "アップロードされたファイル" msgid "Uploading verification file …" msgstr "検証用ファイルをアップロードしています…" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" +"使用状況に関する報告は、ソフトウェアの使い勝手を改善したり、新しい機能の効果を評価したりする際に参照されます。また、私達はこの報告を用いて、使用状況に関する公開の統計を作成しています。" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "使用状況に関する統計" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "使用状況に関する統計、警告、エラー、クラッシュ" @@ -3076,17 +3164,17 @@ msgstr "ユーザーデータ" msgid "User domain name" msgstr "ユーザーのドメイン名" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "ユーザーに付与されている権限が多すぎます" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "インターフェースの設定" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3176,7 +3264,7 @@ msgstr "タスクの開始を待機しています…" msgid "Waiting for upload to finish …" msgstr "アップロードの完了を待機しています…" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "警告、エラー、クラッシュ" @@ -3212,7 +3300,7 @@ msgstr "復元したファイルはどこに保存しますか?" msgid "Years" msgstr "年" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3225,9 +3313,9 @@ msgstr "年" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "はい" @@ -3266,14 +3354,16 @@ msgstr "あなたは現在 {{appname}} {{version}}を使用しています。" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." -msgstr "現在実行中のファイルのアップロードが終了してからバックアップを停止することができます。" +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." +msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." -msgstr "タスクを即座に停止するか、あるいは、現在のファイルの処理を続行してから停止することができます。" +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." +msgstr "" #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3308,7 +3398,7 @@ msgstr "強力なパスフレーズを生成しました。パスフレーズの msgid "You must choose at least one source folder" msgstr "最低1つのバックアップ元のフォルダーを選択してください" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "バージョン3のAPIを使用するにはドメイン名を入力してください" @@ -3320,7 +3410,7 @@ msgstr "バックアップの名称を入力してください" msgid "You must enter a passphrase or disable encryption" msgstr "パスフレーズを入力するか、暗号化を無効にしてください" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "バージョン3のAPIを使用するにはパスワードを入力してください" @@ -3328,11 +3418,11 @@ msgstr "バージョン3のAPIを使用するにはパスワードを入力し msgid "You must enter a positive number of backups to keep" msgstr "保存するバックアップの数を入力してください" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "バージョン3のAPIを使用するにはテナント(プロジェクト)名を入力してください" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "APIキーを指定しない場合はテナント名の入力が必要です" @@ -3344,11 +3434,11 @@ msgstr "バックアップを保持する期間を正しく指定してくださ msgid "You must enter a valid retention policy string" msgstr "保持期間のポリシーを正しく入力してください" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "パスワードかAPIキーを入力してください" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "パスワードまたはAPIキーのどちらかを入力してください" @@ -3369,11 +3459,11 @@ msgstr "ユーザー名を入力してください" msgid "You must fill in {{field}}" msgstr "{{field}}を入力してください" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "AuthURIを選択または入力してください" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "サーバーを選択または入力してください" @@ -3405,23 +3495,23 @@ msgstr "バイト" msgid "byte/s" msgstr "バイト秒" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "COS AppのID" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "バケット名" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "リージョン" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "COSのシークレットのID" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "COSの秘密鍵" @@ -3436,29 +3526,37 @@ msgid "failed" msgstr "失敗しました" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" -msgstr "ローカルのリポジトリー名(例:local)" +msgid "local repository, leave empty for local" +msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "OSSのアクセスキーのID" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "OSSのアクセスキーのシークレット" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "OSSのバケット名" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "OSSのエンドポイント" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "OSSのリージョン" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "リモートのパス(例:backup)" @@ -3471,11 +3569,11 @@ msgstr "リモートのリポジトリー名(例:remote)" msgid "resume now" msgstr "再開" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "アクセス権" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "--group-idを明示的に指定しているのでない限り、" diff --git a/Localizations/webroot/localization_webroot-ko.po b/Localizations/webroot/localization_webroot-ko.po index c4ca4ebb0..edcc1a978 100644 --- a/Localizations/webroot/localization_webroot-ko.po +++ b/Localizations/webroot/localization_webroot-ko.po @@ -37,7 +37,7 @@ msgstr "" msgid "- pick an option -" msgstr "- 옵션을 선택하십시오 -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...로딩..." @@ -61,23 +61,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "" @@ -89,7 +89,7 @@ msgstr "정보" msgid "About {{appname}}" msgstr "{{appname}} 정보" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "접근 키" @@ -117,7 +117,7 @@ msgstr "" msgid "Access to user interface" msgstr "액세스 설정" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "계정 이름" @@ -150,8 +150,8 @@ msgstr "경로 추가" msgid "Added" msgstr "추가됨" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "버켓 이름을 적용 하시겠습니까?" @@ -184,7 +184,7 @@ msgstr "" msgid "All Microsoft SQL Databases" msgstr "" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -204,6 +204,10 @@ msgstr "원격 액세스 허용 (다시 시작 필요)" msgid "Allowed days" msgstr "허용된 요일" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "" @@ -222,7 +226,7 @@ msgid "" " Do you wish to use the existing database?" msgstr "" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "익명 사용 보고서" @@ -230,20 +234,26 @@ msgstr "익명 사용 보고서" msgid "Applications" msgstr "" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -275,7 +285,7 @@ msgstr "" msgid "B2 Application Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "" @@ -283,7 +293,7 @@ msgstr "" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "" @@ -325,7 +335,7 @@ msgstr "백업 보존" msgid "Backup:" msgstr "백업:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -337,7 +347,7 @@ msgstr "" msgid "Browse" msgstr "찾아보기" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "" @@ -345,14 +355,14 @@ msgstr "" msgid "Bucket create location" msgstr "" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -361,7 +371,7 @@ msgstr "" msgid "Bucket name" msgstr "Bucket 이름" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -436,13 +446,13 @@ msgstr "" msgid "Cache Files" msgstr "" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -457,14 +467,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "취소" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -481,7 +495,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -521,7 +535,7 @@ msgstr "시작할 저장소 유형을 선택하세요" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "" @@ -650,7 +664,7 @@ msgstr "연결이 끊어짐" msgid "Connection worked!" msgstr "연결되었습니다!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "" @@ -692,7 +706,7 @@ msgstr "" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "핵심 옵션" @@ -700,7 +714,7 @@ msgstr "핵심 옵션" msgid "Counting ({{files}} files found, {{size}})" msgstr "" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "충돌만" @@ -712,7 +726,7 @@ msgstr "버그 리포트 생성 …" msgid "Create folder?" msgstr "폴더를 생성하시겠습니까?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "" @@ -720,7 +734,7 @@ msgstr "" msgid "Creating bug report …" msgstr "버그 리포트 생성 중 …" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -732,7 +746,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -752,7 +766,7 @@ msgstr "" msgid "Custom S3 endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -804,11 +818,11 @@ msgstr "데이터베이스 …" msgid "Days" msgstr "일" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "기본값" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "기본값 ({{channelname}})" @@ -816,7 +830,7 @@ msgstr "기본값 ({{channelname}})" msgid "Default excludes" msgstr "" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "기본 옵션" @@ -844,6 +858,10 @@ msgstr "이전 백업 삭제" msgid "Delete local database" msgstr "로컬 데이터베이스 삭제" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "원격 파일 삭제" @@ -898,7 +916,8 @@ msgstr "대상" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "대상 경로" @@ -910,6 +929,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "비활성화" @@ -923,7 +946,7 @@ msgstr "닫기" msgid "Dismiss all" msgstr "모두 닫기" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "인터페이스 테마" @@ -972,14 +995,14 @@ msgstr "Duplicati Website" msgid "Duplicati forum" msgstr "Duplicati 포럼" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1014,12 +1037,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "목록으로 편집" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "텍스트로 편집" @@ -1031,6 +1054,10 @@ msgstr "편집 …" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "파일 암호화" @@ -1050,7 +1077,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1162,7 +1189,7 @@ msgstr "오류" msgid "Error!" msgstr "오류!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "오류 및 충돌" @@ -1210,7 +1237,7 @@ msgstr "" msgid "Existing file found" msgstr "" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1304,7 +1331,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "" @@ -1333,7 +1360,7 @@ msgstr "필터" msgid "Finished!" msgstr "" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "" @@ -1404,7 +1431,7 @@ msgstr "" msgid "Getting file versions …" msgstr "파일 버전을 구하는 중 ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "" @@ -1429,7 +1456,7 @@ msgid "Hostnames" msgstr "" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "시" @@ -1459,11 +1486,11 @@ msgstr "" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1508,6 +1535,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1559,7 +1591,7 @@ msgstr "" msgid "Incorrect answer, try again" msgstr "" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "개발자 전용 개별 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오." @@ -1572,7 +1604,7 @@ msgstr "" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "" @@ -1582,7 +1614,7 @@ msgstr "" msgid "Invalid retention time" msgstr "" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1608,7 +1640,7 @@ msgstr "모든 백업 유지" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "인터페이스 언어" @@ -1673,10 +1705,6 @@ msgstr "" msgid "Loading …" msgstr "로딩 …" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1878,7 +1910,7 @@ msgstr "" msgid "Next time" msgstr "시작" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1891,9 +1923,9 @@ msgstr "시작" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "아니오" @@ -1932,7 +1964,7 @@ msgstr "스케줄링된 작업 없음" msgid "Non-matching passphrase" msgstr "" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "비활성화" @@ -1951,17 +1983,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "아무 것도 삭제되지 않습니다. 백업 크기는 변경될 때마다 커집니다." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "확인" @@ -1993,7 +2025,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2015,7 +2047,7 @@ msgstr "" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2052,11 +2084,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "옵션" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2066,7 +2098,7 @@ msgstr "" msgid "Original location" msgstr "원래 위치" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "기타" @@ -2104,7 +2136,7 @@ msgstr "" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2115,8 +2147,8 @@ msgstr "암호" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "" @@ -2125,7 +2157,7 @@ msgid "Path not found" msgstr "" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "서버의 경로" @@ -2134,11 +2166,11 @@ msgstr "서버의 경로" msgid "Path or subfolder in the bucket" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "일시 중지" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "부팅 또는 최대 절전 모드 후 일시 중지" @@ -2176,6 +2208,10 @@ msgstr "트레이 아이콘 자동 로그인 방지" msgid "Previous" msgstr "이전" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "진행률:" @@ -2184,7 +2220,7 @@ msgstr "진행률:" msgid "ProjectID is optional if the bucket exist" msgstr "" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "독점" @@ -2220,10 +2256,30 @@ msgstr "데이터베이스를 다시 만드는 중 …" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "" @@ -2236,14 +2292,34 @@ msgstr "" msgid "Remote" msgstr "원격" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "원격 경로" @@ -2386,7 +2462,7 @@ msgstr "" msgid "S3 Compatible" msgstr "" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "기본 설치 버전과 동일: {{channelname}}" @@ -2434,7 +2510,7 @@ msgstr "검색" msgid "Search for files" msgstr "파일 검색" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "초" @@ -2447,8 +2523,8 @@ msgstr "로그 레벨을 선택하고 발생하는 메시지를 확인하십시 msgid "Select files" msgstr "파일 선택" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "" @@ -2486,6 +2562,10 @@ msgstr "" msgid "Server state properties" msgstr "서버 상태 속성" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "설정" @@ -2555,15 +2635,15 @@ msgstr "원본 폴더" msgid "Source:" msgstr "대상:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "개발자 전용 특정 빌드입니다. 중요한 데이터와 함께 사용하지 마십시오." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "표준 프로토콜" @@ -2590,19 +2670,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "현재 파일까지 진행 후 중지" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "현재 파일까지 진행 후 중지" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "지금 중지" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "백업 실행 중지" @@ -2656,7 +2732,7 @@ msgstr "" msgid "System Files" msgstr "" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "" @@ -2712,6 +2788,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2724,7 +2805,7 @@ msgstr "연결 테스트" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2732,10 +2813,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2758,7 +2839,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" @@ -2772,15 +2853,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "어두운 테마 (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "파란색의 밝은 테마 (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2804,7 +2885,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "암호가 일치하지 않음" @@ -2848,7 +2929,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2870,7 +2951,7 @@ msgstr "이번 달" msgid "This week" msgstr "이번 주" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "속도 제한 설정" @@ -2883,6 +2964,10 @@ msgstr "목요일" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "" @@ -2897,7 +2982,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2925,7 +3010,7 @@ msgstr "" msgid "Trust server certificate?" msgstr "" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -2961,7 +3046,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "업데이트 채널" @@ -2981,18 +3066,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "사용 통계" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "사용 통계, 경고, 오류 및 충돌" @@ -3020,17 +3105,17 @@ msgstr "사용자 데이터" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "인터페이스 설정" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3120,7 +3205,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "업로드가 완료되기를 기다리는 중 …" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "경고, 오류 및 충돌" @@ -3156,7 +3241,7 @@ msgstr "파일을 어디에 복원하시겠습니까?" msgid "Years" msgstr "년" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3169,9 +3254,9 @@ msgstr "년" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "예" @@ -3208,13 +3293,15 @@ msgstr "현재 사용 중: {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." -msgstr "현재 진행 중인 파일 업로드가 완료된 후 백업을 중지할 수 있습니다." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." +msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3250,7 +3337,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3262,7 +3349,7 @@ msgstr "백업 이름을 입력해야 합니다" msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3270,11 +3357,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3286,11 +3373,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3311,11 +3398,11 @@ msgstr "사용자 이름을 채워야합니다." msgid "You must fill in {{field}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "" @@ -3347,23 +3434,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3378,29 +3465,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3413,11 +3508,11 @@ msgstr "" msgid "resume now" msgstr "지금 다시 시작" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-lt.po b/Localizations/webroot/localization_webroot-lt.po index 22bee8f45..5d41f1738 100644 --- a/Localizations/webroot/localization_webroot-lt.po +++ b/Localizations/webroot/localization_webroot-lt.po @@ -41,7 +41,7 @@ msgstr "" msgid "- pick an option -" msgstr "- pasirinkite parametrą -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...įkeliama..." @@ -59,33 +59,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API raktas" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS prieigos ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS prieigos raktas" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM politika" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "Apie" @@ -93,7 +93,7 @@ msgstr "Apie" msgid "About {{appname}}" msgstr "Apie {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Prieigos raktas" @@ -121,7 +121,7 @@ msgstr "" msgid "Access to user interface" msgstr "Pasiekti vartotojo sąsają" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Paskyros vardas" @@ -138,7 +138,7 @@ msgstr "Pridėti kelią tiesiiogiai" msgid "Add advanced option" msgstr "Pridėti papildomą parametrą" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Pridėti kopiją" @@ -154,8 +154,8 @@ msgstr "Pridėti kelią" msgid "Added" msgstr "Pridėta" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Keisti saugyklos pavadinimą?" @@ -188,7 +188,7 @@ msgstr "Visos Hyper-V mašinos" msgid "All Microsoft SQL Databases" msgstr "Visos Microsoft SQL duombazės" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -210,6 +210,10 @@ msgstr "Leisti nuotolinę prieigą (reikia paleisti iš naujo)" msgid "Allowed days" msgstr "Leidžiamos dienos" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Naujoje vietoje rasti jau esantys failai" @@ -234,7 +238,7 @@ msgstr "" "\n" " Ar norite naudoti esamą duomenų bazę?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anoniminės naudojimo ataskaitos" @@ -242,20 +246,26 @@ msgstr "Anoniminės naudojimo ataskaitos" msgid "Applications" msgstr "Programos" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Kaip komandinę eilutę" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Autorizacijos metodas" @@ -287,7 +297,7 @@ msgstr "B2 programos ID" msgid "B2 Application Key" msgstr "B2 programos raktas" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 debesų saugyklos paskyros ID" @@ -295,7 +305,7 @@ msgstr "B2 debesų saugyklos paskyros ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 debesų saugyklos programos ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 debesų saugyklos programos raktas" @@ -337,7 +347,7 @@ msgstr "Atsarginės kopijos saugojimo laikas" msgid "Backup:" msgstr "Kopija:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -349,7 +359,7 @@ msgstr "Sugadinta prieiga" msgid "Browse" msgstr "Naršyti" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Naršyklės numatyta reišmė" @@ -357,14 +367,14 @@ msgstr "Naršyklės numatyta reišmė" msgid "Bucket create location" msgstr "Sukurti saugyklos vietą" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -373,7 +383,7 @@ msgstr "Sukurti saugyklos vietą" msgid "Bucket name" msgstr "Saugyklos pavadinimas" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -449,12 +459,13 @@ msgstr "" msgid "Cache Files" msgstr "Talpyklos failai" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -469,14 +480,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Atšaukti" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -493,6 +508,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Pakeitimų žurnalas" @@ -513,7 +532,7 @@ msgstr "Ieškoti atnaujinimų dabar" msgid "Checking for updates …" msgstr "" -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -529,11 +548,11 @@ msgstr "Norėdami pradėti pasirinkite saugyklos tipą" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Norėdami sukurti AuthID paspauskite AuthID nuorodą" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Spustelėkite, kad nustatyti akceleratoriaus parametrus" @@ -617,7 +636,7 @@ msgstr "Patvirtinkite tryminą" msgid "Confirm encryption passphrase" msgstr "" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -633,11 +652,11 @@ msgstr "Reikalingas patvirtinimas" msgid "Connect" msgstr "Prisijungti" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Prisijungti dabar" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "" @@ -645,11 +664,11 @@ msgstr "" msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "Prisijungimas nutrūko" @@ -658,7 +677,7 @@ msgstr "Prisijungimas nutrūko" msgid "Connection worked!" msgstr "Prisijungti pavyko!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Konteinerio pavadinimas" @@ -700,7 +719,7 @@ msgstr "Kopijavimas nepavyko. Nukopijuokite URL rankiniu būdu" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Pagrindiniai parametrai" @@ -708,7 +727,7 @@ msgstr "Pagrindiniai parametrai" msgid "Counting ({{files}} files found, {{size}})" msgstr "Skaičiuojama, rasta failų: ({{files}}, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Tik lūžimai" @@ -720,7 +739,7 @@ msgstr "" msgid "Create folder?" msgstr "Sukurti aplanką?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Sukurtas naujas ribotas vartotojas" @@ -728,7 +747,7 @@ msgstr "Sukurtas naujas ribotas vartotojas" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -740,7 +759,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -760,7 +779,7 @@ msgstr "Dabartinė versija: {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Nestandartinė S3 saugykla" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -812,11 +831,11 @@ msgstr "" msgid "Days" msgstr "Dienos" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Numatyta" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Numatytas ({{channelname}})" @@ -824,7 +843,7 @@ msgstr "Numatytas ({{channelname}})" msgid "Default excludes" msgstr "Numatytos išimtys" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Numatyti parametrai" @@ -852,6 +871,10 @@ msgstr "Ištrinti kopijas, kurios senesnės nei" msgid "Delete local database" msgstr "Ištrinti lokalią duombazę" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Ištrinti nutolusius failus" @@ -906,7 +929,8 @@ msgstr "Paskirtis" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Kelias iki paskirties" @@ -918,6 +942,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Išjungta" @@ -931,7 +959,7 @@ msgstr "Neberodyti" msgid "Dismiss all" msgstr "Neberodyti visko" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Vaizdo ir spalvų tema" @@ -972,22 +1000,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "Pasikartojantis parametras {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Duplicati svetainė" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Duplicati forumas" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1023,12 +1051,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Taisyti kaip sąrašą" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Taisyti kaip tekstą" @@ -1040,6 +1068,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Šifruoti failą" @@ -1059,7 +1091,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1173,7 +1205,7 @@ msgstr "Klaida" msgid "Error!" msgstr "Klaida!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Klaidos ir lūžimai" @@ -1221,7 +1253,7 @@ msgstr "Neįtraukti standartinės išraiškos" msgid "Existing file found" msgstr "Rastas esamas failas" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Eksperimentinis" @@ -1315,7 +1347,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Failų atkūrimas nepavyko: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Išsaugoti nepavyko:" @@ -1344,7 +1376,7 @@ msgstr "Filtrai" msgid "Finished!" msgstr "Baigta!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Pirmojo paleidimo sąranka" @@ -1415,7 +1447,7 @@ msgstr "Generuoti IAM prieigos politiką" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Grupės el. paštas" @@ -1431,7 +1463,7 @@ msgstr "Paslepti" msgid "Hide hidden folders" msgstr "Nerodyti paslėptų aplankų" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Pradžia" @@ -1440,7 +1472,7 @@ msgid "Hostnames" msgstr "Serverio vardas" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Valandos" @@ -1470,11 +1502,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1525,6 +1557,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Jei nurodysite API raktą, būtina nurodyti savininką" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1572,11 +1609,11 @@ msgstr "Įtraukti išraišką" msgid "Include regular expression" msgstr "Įtraukti standartinę išraišką" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "Atsakymas neteisingas, bandykite dar kartą" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1591,7 +1628,7 @@ msgstr "Informacija" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Kelio pavadinime yra netinkamų simbolių" @@ -1601,7 +1638,7 @@ msgstr "Kelio pavadinime yra netinkamų simbolių" msgid "Invalid retention time" msgstr "Netinkamas saugojimo laikas" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1629,7 +1666,7 @@ msgstr "Saugoti visas kopijas" msgid "Keystone API version" msgstr "Keystone API versija" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Kalba vartotojo interfeise" @@ -1693,15 +1730,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Vietinė saugykla" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}}žurnalo duomenys" msgid "Log data from the server" msgstr "Žurnalo duomenys iš serverio" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "Atsijungti" @@ -1753,6 +1786,10 @@ msgstr "MB" msgid "MByte/s" msgstr "MB/s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "Priežiūra" @@ -1763,7 +1800,7 @@ msgid "" " advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1783,7 +1820,7 @@ msgstr "Maksimalus atsisiuntimo greitis" msgid "Max upload speed" msgstr "Maksimalus įkėlimo greitis" -#: index.html:144 templates/addoredit.html:127 templates/addoredit.html:169 +#: index.html:145 templates/addoredit.html:127 templates/addoredit.html:169 #: templates/addoredit.html:382 templates/addoredit.html:95 #: templates/edituri.html:34 templates/restoredirect.html:34 msgid "Menu" @@ -1801,12 +1838,12 @@ msgstr "Microsoft SQL duomenų bazės" msgid "Minimum redundancy" msgstr "Minimalus perteklinių kopijų kiekis" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "Minimalus perteklinių kopijų skaičius yra 1.0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minutės" @@ -1866,7 +1903,7 @@ msgstr "Vardas" msgid "Never" msgstr "Niekada" -#: templates/settings.html:11 +#: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" msgstr "" @@ -1874,7 +1911,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1893,11 +1930,11 @@ msgstr "Kitas" msgid "Next scheduled run:" msgstr "Kitas planuojamas paleidimas:" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Kita planuojama užduotis:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Kita užduotis" @@ -1905,7 +1942,7 @@ msgstr "Kita užduotis" msgid "Next time" msgstr "Kitą kartą" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1918,9 +1955,9 @@ msgstr "Kitą kartą" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Ne" @@ -1954,7 +1991,7 @@ msgstr "Nėra ko atkurti, pasirinkite vieną ar kelis elementus" msgid "No passphrase entered" msgstr "Neįvesta slapta frazė" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "Nėra planinių užduočių" @@ -1962,7 +1999,7 @@ msgstr "Nėra planinių užduočių" msgid "Non-matching passphrase" msgstr "Netinkama slapta frazė" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nieko / išjungta" @@ -1981,16 +2018,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Niekas nebus trinama. Kopijos dydis didės su kiekvienu pasikeitimu." -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2022,7 +2060,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2045,7 +2083,7 @@ msgstr "OpenStack objekto saugykla / Swift" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2082,11 +2120,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Parametrai" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2096,7 +2134,7 @@ msgstr "" msgid "Original location" msgstr "Originali vieta" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Kiti" @@ -2135,7 +2173,7 @@ msgstr "Slaptos frazės nesutampa" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2146,8 +2184,8 @@ msgstr "Slaptažodis" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Kelias" @@ -2156,7 +2194,7 @@ msgid "Path not found" msgstr "Kelias nerastas" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Kelias iki serverio" @@ -2165,15 +2203,15 @@ msgstr "Kelias iki serverio" msgid "Path or subfolder in the bucket" msgstr "Kelias arba pakatalogis saugykloje" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pauzė" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pauzė po paleidimo ar ramybės būsenos" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Pauzės parametrai" @@ -2207,6 +2245,10 @@ msgstr "Neleisti automatinio prisijungimo per dėklo piktogramą" msgid "Previous" msgstr "Ankstesnis" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progresas:" @@ -2215,7 +2257,7 @@ msgstr "Progresas:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID yra neprivalomas, jei egzistuoja saugykla" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Patentuota" @@ -2251,15 +2293,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Santykiniai keliai neleidžiami" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Užkrauti iš naujo" @@ -2267,14 +2329,34 @@ msgstr "Užkrauti iš naujo" msgid "Remote" msgstr "Nuotolinis" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Kelias iki nutolusio serverio" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Nutolusi saugykla" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Kelias iki nutolusio serverio" @@ -2323,7 +2405,7 @@ msgstr "Ataskaitų teikimas:" msgid "Reset" msgstr "Atstatyti" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Atkurti" @@ -2381,7 +2463,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Tęsti" @@ -2401,7 +2483,7 @@ msgstr "Vykdyti dabar" msgid "Running commandline entry" msgstr "Vykdoma komandų eilutės komanda" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "Vykdoma užduotis:" @@ -2417,7 +2499,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Suderinamas su S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Ta pati, kaip pagrindinė diegimo versija: {{channelname}}" @@ -2465,7 +2547,7 @@ msgstr "Paieška" msgid "Search for files" msgstr "Failų paieška" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekundės" @@ -2479,8 +2561,8 @@ msgstr "" msgid "Select files" msgstr "Pasirinkite failus" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Serveris" @@ -2518,7 +2600,11 @@ msgstr "Serveris pristabdytas" msgid "Server state properties" msgstr "Serverio būsenos parametrai" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Nustatymai" @@ -2589,17 +2675,17 @@ msgstr "Šaltinio aplankai" msgid "Source:" msgstr "Šaltinis:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Specifinės versijos skirtos tik programuotojams. Netinkamos naudoti su " "svarbiais duomenimis." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standartiniai protokolai" @@ -2626,19 +2712,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Stabdyti po dabartinio failo" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Stabdyti dabar" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Stabdyti vykdomą atsarginę kopiją" @@ -2647,11 +2729,11 @@ msgstr "Stabdyti vykdomą atsarginę kopiją" msgid "Stop running task" msgstr "Stabdyti vykdomą užduotį" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "Stabdoma užduotis:" @@ -2692,7 +2774,7 @@ msgstr "Simbolinė nuoroda" msgid "System Files" msgstr "Sisteminiai failai" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Sistemos numatytasis ({{levelname}})" @@ -2748,6 +2830,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2760,7 +2847,7 @@ msgstr "Patikrinti prisijungimą" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2768,10 +2855,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2796,7 +2883,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Saugyklos pavadinimas turi būti iš mažųjų raidžių, konvertuoti automatiškai?" @@ -2807,19 +2894,19 @@ msgid "" "unencrypted file containing your passwords?" msgstr "" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tamsi tema (nuo Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Numatyta mėlyna ant balto tema (nuo Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2848,7 +2935,7 @@ msgstr "" "Ar norite PAKEISTI jūsų DABARTINĮ serverio raktą \"{{prev}}\" PATEIKTU serverio raktu: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "" @@ -2897,7 +2984,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "Paskirties duomenys užšifruoti, pateikite slaptą frazę" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2924,7 +3011,7 @@ msgstr "Šį mėnesį" msgid "This week" msgstr "Šią savaitę" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Greičio nustatymai" @@ -2937,6 +3024,10 @@ msgstr "Ket" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Į failą" @@ -2955,7 +3046,7 @@ msgstr "" "Kad eksportuoti be slaptos frazės, palikite nepažymėtą varnelę \"Šifruoti " "failą\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2988,7 +3079,7 @@ msgstr "Pasitikite saito sertifikatu?" msgid "Trust server certificate?" msgstr "Pasitikite serverio sertifikatu?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3025,7 +3116,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Atnaujinimų kanalas" @@ -3045,19 +3136,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Naudojimo statistika" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Naudojimo statistika, įspėjimai, klaidos ir lūžimai" @@ -3085,17 +3175,17 @@ msgstr "Naudotojo duomenys" msgid "User domain name" msgstr "Naudotojo domeno vardas" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Naudotojas turi per daug teisių" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Naudotojo aplinkos nustatymai" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3159,7 +3249,7 @@ msgstr "Labai stiprus" msgid "Very weak" msgstr "Labai silpnas" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "Aplankykite mus" @@ -3185,7 +3275,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Įspėjimai, klaidos ir lūžimai" @@ -3222,7 +3312,7 @@ msgstr "Kur norite atkurti failus?" msgid "Years" msgstr "Metai" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3235,9 +3325,9 @@ msgstr "Metai" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Taip" @@ -3274,13 +3364,15 @@ msgstr "" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3316,7 +3408,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3328,7 +3420,7 @@ msgstr "" msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3336,11 +3428,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3352,11 +3444,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3377,11 +3469,11 @@ msgstr "" msgid "You must fill in {{field}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "" @@ -3413,23 +3505,23 @@ msgstr "" msgid "byte/s" msgstr "" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3444,31 +3536,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3483,11 +3579,11 @@ msgstr "" msgid "resume now" msgstr "" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-lv.po b/Localizations/webroot/localization_webroot-lv.po index 10f7dac0b..d3a967309 100644 --- a/Localizations/webroot/localization_webroot-lv.po +++ b/Localizations/webroot/localization_webroot-lv.po @@ -39,7 +39,7 @@ msgstr "" msgid "- pick an option -" msgstr "- izvēlieties iestatījumu -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...notiek ielāde..." @@ -57,33 +57,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Piekļuves ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Piekļuves atslēga" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Politika" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "Par" @@ -91,7 +91,7 @@ msgstr "Par" msgid "About {{appname}}" msgstr "Par {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Piekļuves atslēga" @@ -119,7 +119,7 @@ msgstr "" msgid "Access to user interface" msgstr "Piekļuve lietotāja saskarnei" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Konta nosaukums" @@ -136,7 +136,7 @@ msgstr "Pievienot tiešo ceļu" msgid "Add advanced option" msgstr "Pievienot pielāgotu iestatījumu" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Pievienot dublējumkopiju" @@ -152,8 +152,8 @@ msgstr "Pievienot ceļu" msgid "Added" msgstr "Pievienots" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Precizēt spaiņa iestatījumu?" @@ -186,7 +186,7 @@ msgstr "Visas Hyper-V Mašīnas" msgid "All Microsoft SQL Databases" msgstr "Visas Microsoft SQL Datubāzes" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -203,6 +203,10 @@ msgstr "Atļaut attālinātu piekļuvi (nepieciešams restartēt programmu)" msgid "Allowed days" msgstr "Atļautās dienas" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Tika atrasts jau esošs fails jaunajā atrašanās vietā" @@ -223,7 +227,7 @@ msgid "" " Do you wish to use the existing database?" msgstr "" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonīmas lietošanas atskaites" @@ -231,20 +235,26 @@ msgstr "Anonīmas lietošanas atskaites" msgid "Applications" msgstr "Lietotnes" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Kā Komand-rinda" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -276,7 +286,7 @@ msgstr "" msgid "B2 Application Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "" @@ -284,7 +294,7 @@ msgstr "" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "" @@ -326,7 +336,7 @@ msgstr "Dublējumkopiju saglabāšanas ilgums" msgid "Backup:" msgstr "Dublējumkopija:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta versija" @@ -338,7 +348,7 @@ msgstr "" msgid "Browse" msgstr "Pārlūkot" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Pārlūka noklusējums" @@ -346,14 +356,14 @@ msgstr "Pārlūka noklusējums" msgid "Bucket create location" msgstr "" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -362,7 +372,7 @@ msgstr "" msgid "Bucket name" msgstr "Spaiņa nosaukums" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -432,12 +442,13 @@ msgstr "" msgid "Cache Files" msgstr "" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -452,14 +463,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Atcelt" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -476,6 +491,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Izmaiņu žurnāls" @@ -496,7 +515,7 @@ msgstr "Pārbaudīt atjauninājumus tagad" msgid "Checking for updates …" msgstr "" -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -512,11 +531,11 @@ msgstr "" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Uzklikšķiniet, lai uzstādītu ierobežojumus" @@ -600,7 +619,7 @@ msgstr "Apstiprināt dzēšanu" msgid "Confirm encryption passphrase" msgstr "" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -616,11 +635,11 @@ msgstr "Nepieciešams apstiprinājums" msgid "Connect" msgstr "Pieslēgties" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Pieslēgties tagad" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "Pieslēdzas serverim..." @@ -628,11 +647,11 @@ msgstr "Pieslēdzas serverim..." msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "Savienojums ir zudis" @@ -641,7 +660,7 @@ msgstr "Savienojums ir zudis" msgid "Connection worked!" msgstr "Savienojums strādā!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "" @@ -683,7 +702,7 @@ msgstr "" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Pamata opcijas" @@ -691,7 +710,7 @@ msgstr "Pamata opcijas" msgid "Counting ({{files}} files found, {{size}})" msgstr "" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Tikai avārijas" @@ -703,7 +722,7 @@ msgstr "" msgid "Create folder?" msgstr "Izveidot mapi?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "" @@ -711,7 +730,7 @@ msgstr "" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -723,7 +742,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -743,7 +762,7 @@ msgstr "" msgid "Custom S3 endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -795,11 +814,11 @@ msgstr "" msgid "Days" msgstr "Dienas" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Noklusējums" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "" @@ -807,7 +826,7 @@ msgstr "" msgid "Default excludes" msgstr "" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Noklusējuma iestatījumi" @@ -835,6 +854,10 @@ msgstr "" msgid "Delete local database" msgstr "Izdzēst lokālo datubāzi" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Dzēst attālinātos failus" @@ -889,7 +912,8 @@ msgstr "Mērķis" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "" @@ -901,6 +925,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Atspējots" @@ -914,7 +942,7 @@ msgstr "Atmest" msgid "Dismiss all" msgstr "" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Displeja un krāsu motīvs" @@ -955,22 +983,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Duplicati tīmekļa vietne" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Duplicati forums" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1003,12 +1031,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Rediģēt kā sarakstu" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Rediģēt kā tekstu" @@ -1020,6 +1048,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Šifrēt failu" @@ -1039,7 +1071,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1148,7 +1180,7 @@ msgstr "Kļūda" msgid "Error!" msgstr "Kļūda!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Kļūdas un avārijas" @@ -1196,7 +1228,7 @@ msgstr "" msgid "Existing file found" msgstr "" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Eksperimentāls" @@ -1290,7 +1322,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "" @@ -1319,7 +1351,7 @@ msgstr "Filtrs" msgid "Finished!" msgstr "Pabeigts!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "" @@ -1390,7 +1422,7 @@ msgstr "" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "" @@ -1406,7 +1438,7 @@ msgstr "Paslēpt" msgid "Hide hidden folders" msgstr "Paslēpt paslēptās mapes" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Mājas" @@ -1415,7 +1447,7 @@ msgid "Hostnames" msgstr "" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Stundas" @@ -1445,11 +1477,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1495,6 +1527,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1542,11 +1579,11 @@ msgstr "" msgid "Include regular expression" msgstr "" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "Nepareiza atbilde, mēģiniet vēlreiz" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1559,7 +1596,7 @@ msgstr "Informācija" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "" @@ -1569,7 +1606,7 @@ msgstr "" msgid "Invalid retention time" msgstr "" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1597,7 +1634,7 @@ msgstr "" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Lietotāja saskarnes valoda:" @@ -1657,15 +1694,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1855,11 +1892,11 @@ msgstr "Nākamais" msgid "Next scheduled run:" msgstr "Nākamā plānotā norise" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Nākamais plānotais uzdevums:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Nākamais uzdevums:" @@ -1867,7 +1904,7 @@ msgstr "Nākamais uzdevums:" msgid "Next time" msgstr "Nākamreiz" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1880,9 +1917,9 @@ msgstr "Nākamreiz" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nē" @@ -1914,7 +1951,7 @@ msgstr "" msgid "No passphrase entered" msgstr "Pieejas frāze nav ievadīta" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "Nav ieplānotu uzdevumu" @@ -1922,7 +1959,7 @@ msgstr "Nav ieplānotu uzdevumu" msgid "Non-matching passphrase" msgstr "Nesakrītoša pieejas frāze" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nav / Atspējots" @@ -1941,16 +1978,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "Labi" @@ -1982,7 +2020,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2004,7 +2042,7 @@ msgstr "" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2041,11 +2079,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Iestatījumi" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2055,7 +2093,7 @@ msgstr "" msgid "Original location" msgstr "Sākotnējā atrašanās vieta" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Citi" @@ -2091,7 +2129,7 @@ msgstr "Pieejas frāzes nesakrīt" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2102,8 +2140,8 @@ msgstr "Parole" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "" @@ -2112,7 +2150,7 @@ msgid "Path not found" msgstr "Ceļš nav atrasts" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Ceļs uz servera" @@ -2121,15 +2159,15 @@ msgstr "Ceļs uz servera" msgid "Path or subfolder in the bucket" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pauzēt" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Pauzēt opcijas" @@ -2163,6 +2201,10 @@ msgstr "" msgid "Previous" msgstr "" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "" @@ -2171,7 +2213,7 @@ msgstr "" msgid "ProjectID is optional if the bucket exist" msgstr "" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "" @@ -2207,15 +2249,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Pārlādēt" @@ -2223,14 +2285,34 @@ msgstr "Pārlādēt" msgid "Remote" msgstr "Attālināts" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "" @@ -2279,7 +2361,7 @@ msgstr "" msgid "Reset" msgstr "Attiestatīt" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Atgūt" @@ -2337,7 +2419,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Turpināt" @@ -2357,7 +2439,7 @@ msgstr "Palaist tagad" msgid "Running commandline entry" msgstr "" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "" @@ -2373,7 +2455,7 @@ msgstr "" msgid "S3 Compatible" msgstr "" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "" @@ -2421,7 +2503,7 @@ msgstr "Meklēt" msgid "Search for files" msgstr "Meklēt failus" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "sekundes" @@ -2434,8 +2516,8 @@ msgstr "" msgid "Select files" msgstr "Izvēlēties failus" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Serveris" @@ -2473,7 +2555,11 @@ msgstr "" msgid "Server state properties" msgstr "" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Iestatījumi" @@ -2542,15 +2628,15 @@ msgstr "Avota mapes" msgid "Source:" msgstr "Avots:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "" @@ -2577,19 +2663,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Pātraukt tagad" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "" @@ -2598,11 +2680,11 @@ msgstr "" msgid "Stop running task" msgstr "Pārtraukt uzdevuma izpildi" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "Aptur uzdevumu:" @@ -2643,7 +2725,7 @@ msgstr "Simboliskā saite" msgid "System Files" msgstr "" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "" @@ -2699,6 +2781,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2711,7 +2798,7 @@ msgstr "Pārbaudīt savienojumu" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2719,10 +2806,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2745,7 +2832,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" @@ -2755,19 +2842,19 @@ msgid "" "unencrypted file containing your passwords?" msgstr "" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tumšais motīvs (veidoja Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Noklusējuma zils uz balta motīvs (veidoja Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2791,7 +2878,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "" @@ -2835,7 +2922,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2857,7 +2944,7 @@ msgstr "Šis mēnesis" msgid "This week" msgstr "Šī diena" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "" @@ -2870,6 +2957,10 @@ msgstr "Cetr" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "" @@ -2884,7 +2975,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2912,7 +3003,7 @@ msgstr "" msgid "Trust server certificate?" msgstr "" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -2946,7 +3037,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Atjauninājumu kanāls" @@ -2966,19 +3057,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Izmantošanas statistika" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "" @@ -3006,17 +3096,17 @@ msgstr "Lietotāja dati" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Lietotāja saskarnes iestatījumi" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3080,7 +3170,7 @@ msgstr "Ļoti stiprs" msgid "Very weak" msgstr "Ļoti vājš" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "" @@ -3106,7 +3196,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Brīdinājumi, kļūdas un avārijas" @@ -3144,7 +3234,7 @@ msgstr "" msgid "Years" msgstr "Gadi" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3157,9 +3247,9 @@ msgstr "Gadi" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Jā" @@ -3196,13 +3286,15 @@ msgstr "" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3238,7 +3330,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3250,7 +3342,7 @@ msgstr "Nepieciešams ievadīt dublējumkopijas nosaukumu" msgid "You must enter a passphrase or disable encryption" msgstr "Jums nepieciešams ievadīt pieejas frāzi vai atspējot šifrēšanu" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3258,11 +3350,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3274,11 +3366,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3299,11 +3391,11 @@ msgstr "" msgid "You must fill in {{field}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "" @@ -3336,23 +3428,23 @@ msgstr "baits" msgid "byte/s" msgstr "baiti/sekundē" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3367,31 +3459,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3406,11 +3502,11 @@ msgstr "" msgid "resume now" msgstr "turpināt tagad" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-nl_NL.po b/Localizations/webroot/localization_webroot-nl_NL.po index bdf5096aa..135fad511 100644 --- a/Localizations/webroot/localization_webroot-nl_NL.po +++ b/Localizations/webroot/localization_webroot-nl_NL.po @@ -45,7 +45,7 @@ msgstr "(onderbroken)" msgid "- pick an option -" msgstr " - kies een optie -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...laden..." @@ -73,23 +73,23 @@ msgstr "" "

    Verbinding met server is afgewezen vanwege ongeldige authenticatie.

    \n" "

    Meld u opnieuw aan of open de pagina opnieuw vanuit het Systeemvak (indien van toepassing)

    " -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API sleutel" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Toegangs ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Toegangssleutel" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Beleid" @@ -101,7 +101,7 @@ msgstr "Over" msgid "About {{appname}}" msgstr "Over {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Toegangssleutel" @@ -129,7 +129,7 @@ msgstr "Toegangssleutel" msgid "Access to user interface" msgstr "Toegang tot gebruikersomgeving" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Accountnaam" @@ -162,8 +162,8 @@ msgstr "Voeg pad toe" msgid "Added" msgstr "Toegevoegd" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Bucket naam aanpassen?" @@ -196,7 +196,7 @@ msgstr "Alle Hyper-V Machines" msgid "All Microsoft SQL Databases" msgstr "Alle Microsoft SQL Databases" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -219,6 +219,10 @@ msgstr "Remote toegang toestaan (herstart vereist)" msgid "Allowed days" msgstr "Alleen op deze dagen" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Een bestaand bestand was gevonden op de nieuwe locatie" @@ -243,7 +247,7 @@ msgstr "" "\n" "Wilt u de bestaande database gebruiken?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonieme gebruiksrapporten" @@ -251,20 +255,26 @@ msgstr "Anonieme gebruiksrapporten" msgid "Applications" msgstr "Toepassingen" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Als Opdrachtregel" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Authenticatiemethode" @@ -296,7 +306,7 @@ msgstr "B2 Applicatie ID" msgid "B2 Application Key" msgstr "B2 Applicatiesleutel" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -304,7 +314,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Applicatie ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Applicatiesleutel" @@ -353,7 +363,7 @@ msgstr "Back-up retentie" msgid "Backup:" msgstr "Back-up:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -365,7 +375,7 @@ msgstr "Verbroken toegang" msgid "Browse" msgstr "Bladeren" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browser standaard" @@ -373,14 +383,14 @@ msgstr "Browser standaard" msgid "Bucket create location" msgstr "Bucket aanmaaklocatie" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -389,7 +399,7 @@ msgstr "Bucket aanmaaklocatie" msgid "Bucket name" msgstr "Bucketnaam" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -471,13 +481,13 @@ msgstr "COS Geheime Sleutel" msgid "Cache Files" msgstr "Cache bestanden" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -492,14 +502,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Annuleren" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "Mag \"{{text}}\" niet bevatten" @@ -516,9 +530,9 @@ msgstr "Kan geen in- of uitsluitingsfilters opnemen in extra opties" msgid "Change server passphrase" msgstr "Wijzig server wachtwoordzin" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" -msgstr "" +msgstr "Wijzig serverwachtwoord" #: templates/about.html:5 msgid "Changelog" @@ -558,7 +572,7 @@ msgstr "Kies een opslagtype om aan de slag te gaan" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Klik op de AuthID link om een AuthID aan te maken" @@ -689,7 +703,7 @@ msgstr "Verbinding verbroken" msgid "Connection worked!" msgstr "Verbinding werkt!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Containernaam" @@ -731,7 +745,7 @@ msgstr "Kopiëren mislukt. Kopieer de URL handmatig" msgid "Copy log" msgstr "Kopie log" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Kern-opties" @@ -739,7 +753,7 @@ msgstr "Kern-opties" msgid "Counting ({{files}} files found, {{size}})" msgstr "Tellen ({{files}} bestanden gevonden, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Alleen crashes" @@ -751,7 +765,7 @@ msgstr "Bug rapport maken ..." msgid "Create folder?" msgstr "Map aanmaken?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Nieuwe beperkte gebruiker aangemaakt" @@ -759,7 +773,7 @@ msgstr "Nieuwe beperkte gebruiker aangemaakt" msgid "Creating bug report …" msgstr "Bug rapport maken ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Nieuwe gebruiker met beperkte toegang aanmaken ..." @@ -771,7 +785,7 @@ msgstr "Doelmappen aanmaken ..." msgid "Creating temporary backup …" msgstr "Tijdelijke back-up aanmaken ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "Gebruiker aanmaken …" @@ -791,7 +805,7 @@ msgstr "Huidige versie is {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Aangepaste S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Aangepaste Satellite" @@ -843,11 +857,11 @@ msgstr "Database ..." msgid "Days" msgstr "Dagen" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Standaard" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Standaard ({{channelname}})" @@ -855,7 +869,7 @@ msgstr "Standaard ({{channelname}})" msgid "Default excludes" msgstr "Standaard uitsluitingen" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Standaard opties" @@ -883,6 +897,10 @@ msgstr "Verwijder back-ups die ouder zijn dan" msgid "Delete local database" msgstr "Verwijder lokale database" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Verwijder remote bestanden" @@ -938,7 +956,8 @@ msgstr "Doel" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Doelpad" @@ -950,6 +969,10 @@ msgstr "Direct herstellen vanuit back-upbestanden …" msgid "Directory path" msgstr "Directory-pad" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Uitgeschakeld" @@ -963,7 +986,7 @@ msgstr "Afwijzen" msgid "Dismiss all" msgstr "Alles afwijzen" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Weergave en kleurenschema" @@ -1012,7 +1035,7 @@ msgstr "Duplicati Website" msgid "Duplicati forum" msgstr "Duplicati forum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" @@ -1022,7 +1045,7 @@ msgstr "" "Als u Duplicati opent via het systeemvakpictogram, heeft u geen wachtwoordzin nodig, maar als u van plan bent het te openen vanaf een andere locatie moet u een wachtwoordzin instellen die u kent.\n" "Wilt u nu een wachtwoordzin instellen?" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1065,12 +1088,12 @@ msgstr "" "gegevens die voor elke bewerking moet worden gedownload." #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Bewerk als lijst" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Bewerk als tekst" @@ -1082,6 +1105,10 @@ msgstr "Bewerken ..." msgid "Email address of the Office 365 group" msgstr "E-mailadres van de Office 365-groep" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Versleutel bestand" @@ -1103,7 +1130,7 @@ msgstr "" "Coderingsmodules:

    {{item.Key}}

    " -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Encryptie wachtwoordzin" @@ -1218,7 +1245,7 @@ msgstr "Fout" msgid "Error!" msgstr "Fout!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Fouten en crashes" @@ -1266,7 +1293,7 @@ msgstr "Sluit reguliere expressie uit" msgid "Existing file found" msgstr "Bestaand bestand gevonden" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimenteel" @@ -1360,7 +1387,7 @@ msgstr "Kan bestand niet lezen: {{message}}" msgid "Failed to restore files: {{message}}" msgstr "Herstellen bestanden mislukt: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Opslaan mislukt:" @@ -1389,7 +1416,7 @@ msgstr "Filters" msgid "Finished!" msgstr "Klaar!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Instellen voor eerste gebruik" @@ -1460,7 +1487,7 @@ msgstr "Genereer IAM toegangsbeleid" msgid "Getting file versions …" msgstr "Bestandsversies ophalen ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Groep e-mail" @@ -1485,7 +1512,7 @@ msgid "Hostnames" msgstr "hostnamen" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Uur" @@ -1515,11 +1542,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "IDrive Sync directory-pad" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "IDrive e2 Toegangssleutel-ID" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "IDrive e2 Toegangssleutel-geheim" @@ -1580,6 +1607,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Als u geen API sleutel ingeeft, is een tenant naam vereist" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1633,7 +1665,7 @@ msgstr "Reguliere expressie opnemen" msgid "Incorrect answer, try again" msgstr "Incorrect antwoord, probeer opnieuw" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1648,7 +1680,7 @@ msgstr "Informatie" msgid "Interrupted, no statistics collected" msgstr "Onderbroken, geen statistieken verzameld" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Ongeldige tekens in pad" @@ -1658,7 +1690,7 @@ msgstr "Ongeldige tekens in pad" msgid "Invalid retention time" msgstr "Ongeldige retentietijd" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1686,7 +1718,7 @@ msgstr "Behoud alle back-ups" msgid "Keystone API version" msgstr "Keystone API versie" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Taal in gebruikersomgeving" @@ -1754,10 +1786,6 @@ msgstr "Gebruik van externe opslag laden …" msgid "Loading …" msgstr "Laden ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Lokale Opslagplaats" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1968,7 +2000,7 @@ msgstr "Volgende taak:" msgid "Next time" msgstr "Volgende keer" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1981,9 +2013,9 @@ msgstr "Volgende keer" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nee" @@ -2027,7 +2059,7 @@ msgstr "Geen geplande taken" msgid "Non-matching passphrase" msgstr "Niet-bijbehorende wachtwoordzin" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Geen / uitgeschakeld" @@ -2052,17 +2084,17 @@ msgstr "" "Er zal niets verwijderd worden. De back-upgrootte zal toenemen met iedere " "verandering." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2094,7 +2126,7 @@ msgstr "OSS Pad of submap in de bucket" msgid "OSS Region" msgstr "OSS-Regio" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "Officiële releases" @@ -2118,7 +2150,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Geopend" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "Openstack API Sleutels worden niet ondersteund in v3 keystone API" @@ -2155,11 +2187,11 @@ msgid "Optional tenant name" msgstr "Optionele tenant-naam" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opties" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2171,7 +2203,7 @@ msgstr "" msgid "Original location" msgstr "Originele locatie" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Anderen" @@ -2211,7 +2243,7 @@ msgstr "Wachtwoordzinnen komen niet overeen" msgid "Passphrases do not match" msgstr "Wachtwoordzinnen komen niet overeen" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2222,8 +2254,8 @@ msgstr "Wachtwoord" msgid "Patching files with local blocks …" msgstr "Bestanden bijwerken met lokale blokken ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Pad" @@ -2232,7 +2264,7 @@ msgid "Path not found" msgstr "Pad niet gevonden" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Pad op server" @@ -2241,11 +2273,11 @@ msgstr "Pad op server" msgid "Path or subfolder in the bucket" msgstr "Pad of submap in de bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pauze" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pauzeer na opstarten of slaapmodus" @@ -2283,6 +2315,10 @@ msgstr "Voorkom automatisch inloggen door systeemvak-pictogram" msgid "Previous" msgstr "Vorige" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Voortgang:" @@ -2291,7 +2327,7 @@ msgstr "Voortgang:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID is optioneel als de bucket bestaat" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Fabrikantgebonden" @@ -2327,10 +2363,30 @@ msgstr "Opnieuw aanmaken van de database ..." msgid "Region" msgstr "Regio" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registreren tijdelijke back-up ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relatieve paden zijn niet toegestaan" @@ -2343,14 +2399,34 @@ msgstr "Andere code" msgid "Remote" msgstr "Remote" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Remote Pad" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Remote Opslagplaats" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Remote pad" @@ -2495,7 +2571,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Compatible" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Zelfde als de basis installatie versie: {{channelname}}" @@ -2543,7 +2619,7 @@ msgstr "Zoek" msgid "Search for files" msgstr "Zoek bestanden" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Seconden" @@ -2556,8 +2632,8 @@ msgstr "Selecteer een logniveau en bekijk meldingen zodra ze zich voordoen:" msgid "Select files" msgstr "Selecteer bestanden" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2597,6 +2673,10 @@ msgstr "Server gepauzeerd" msgid "Server state properties" msgstr "Server status eigenschappen" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Instellingen" @@ -2670,17 +2750,17 @@ msgstr "Bronmappen" msgid "Source:" msgstr "Bron:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Specifieke builds alleen voor ontwikkelaars. Niet voor gebruik met " "belangrijke gegevens." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "Stabiel" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standaard protocollen" @@ -2707,19 +2787,15 @@ msgstr "Herstellen wordt gestart ..." msgid "Starting the restore process …" msgstr "Starten van het herstelproces ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stop na het huidige bestand" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Stop na het huidige bestand" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Nu stoppen" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Stop de back-up in uitvoering" @@ -2773,7 +2849,7 @@ msgstr "Symbolische link" msgid "System Files" msgstr "Systeembestanden" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Systeem standaard ({{levelname}})" @@ -2829,6 +2905,11 @@ msgstr "Tencent Cloud Account APPID" msgid "Tencent Cloud COS documents and resources" msgstr "Tencent Cloud COS documenten en bronnen" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Testen Subtaak" @@ -2841,7 +2922,7 @@ msgstr "Test verbinding" msgid "Testing connection …" msgstr "Testen van de verbinding …" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Testen van de permissies ..." @@ -2849,10 +2930,10 @@ msgstr "Testen van de permissies ..." msgid "Testing …" msgstr "Testen ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2884,7 +2965,7 @@ msgstr "" "link=\"'https://www.duplicati.com/articles/Choosing-Sizes/#remote-volume-" "size'\">Zie deze pagina voor meer informatie." -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "De bucket-naam hoort in kleine letters te zijn, automatisch converteren?" @@ -2902,15 +2983,15 @@ msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" "De verbinding met de server is verbroken, opnieuw proberen over {{time}} …" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Het donkere thema (door Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Het standaard blauw op wit thema (door Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "De coderings-wachtwoordzinnen komen niet overeen" @@ -2942,7 +3023,7 @@ msgstr "" "Wilt u de HUIDIGE host sleutel \"{prev}\" VERVANGEN door de GERAPPORTEERDE host sleutel: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "De wachtwoorden komen niet overeen" @@ -2998,7 +3079,7 @@ msgid "" msgstr "" "De doelmap bevat versleutelde bestanden, geef alstublieft de wachtwoordzin" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -3026,7 +3107,7 @@ msgstr "Afgelopen maand" msgid "This week" msgstr "Afgelopen week" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Bandbreedte-instellingen" @@ -3039,6 +3120,10 @@ msgstr "Donderdag" msgid "Time" msgstr "Tijd" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Naar Bestand" @@ -3057,7 +3142,7 @@ msgstr "" "Om te exporteren zonder een wachtwoordzin, deselecteer het \"Versleutel " "bestand\" vakje" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3095,7 +3180,7 @@ msgstr "Vertrouw host certificaat?" msgid "Trust server certificate?" msgstr "Vertrouw server certificaat?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3135,7 +3220,7 @@ msgstr "" "click=\"doShowUpdateChangelog()\">{{state.updatedVersion}} is " "beschikbaar. Download nu" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Updatekanaal" @@ -3155,18 +3240,22 @@ msgstr "Geüploade bestanden" msgid "Uploading verification file …" msgstr "Uploaden controlebestand ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" +"Gebruiksrapporten helpen ons de gebruikerservaring te verbeteren en de " +"impact van nieuwe mogelijkheden te evalueren. We gebruiken ze om openbare " +"gebruikstatistieken te genereren." -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Gebruikstatistieken" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Gebruikstatistieken, waarschuwingen, fouten en crashes" @@ -3194,17 +3283,17 @@ msgstr "Gebruikersgegevens" msgid "User domain name" msgstr "Gebruikers domeinnaam" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Gebruiker heeft teveel permissies" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Gebruikersomgeving-instellingen" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3298,7 +3387,7 @@ msgstr "Wachten tot een taak begint …" msgid "Waiting for upload to finish …" msgstr "Wachten op voltooien van upload ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Waarschuwingen, fouten en crashes" @@ -3336,7 +3425,7 @@ msgstr "Waarheen wilt u de bestanden herstellen?" msgid "Years" msgstr "Jaren" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3349,9 +3438,9 @@ msgstr "Jaren" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Ja" @@ -3390,18 +3479,16 @@ msgstr "U werkt momenteel met {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"De back-up kan worden gestopt nadat de upload van alle bestanden die " -"momenteel in behandeling zijn, is voltooid." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"De taak kan onmiddellijk worden gestopt, of het proces toestaan om door te " -"gaan met het huidige bestand en dan stoppen." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3447,7 +3534,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "U moet tenminste één bronmap kiezen" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Een domeinnaam moet worden opgegeven om v3 API te gebruiken" @@ -3459,7 +3546,7 @@ msgstr "U moet een naam ingeven voor de back-up" msgid "You must enter a passphrase or disable encryption" msgstr "U moet een wachtwoordzin ingeven of versleuteling uitschakelen" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Een wachtwoord moet worden opgegeven om v3 API te gebruiken" @@ -3468,13 +3555,13 @@ msgid "You must enter a positive number of backups to keep" msgstr "" "U moet een positief getal opgeven voor de hoeveelheid te bewaren back-ups" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Een tenant (ofwel project) naam moet worden opgegeven om v3 API te gebruiken" " " -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "U moet een tenant naam ingeven als u de API sleutel niet verstrekt" @@ -3488,11 +3575,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Er moet een geldige waarde voor retentiebeleid worden opgegeven" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "U moet òf een wachtwoord, òf een API sleutel ingeven" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "U moet òf een wachtwoord, òf een API sleutel ingeven, niet beide" @@ -3513,11 +3600,11 @@ msgstr "U moet de gebruikersnaam invullen" msgid "You must fill in {{field}}" msgstr "U moet {{field}} invullen" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "U moet de AuthURI selecteren of invullen" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "U moet de server selecteren of invullen" @@ -3551,23 +3638,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "cos_app_id" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "cos_bucket" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "cos_region" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "cos_secret_id" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "cos_secret_key" @@ -3582,29 +3669,37 @@ msgid "failed" msgstr "mislukt" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" -msgstr "lokale opslagplaats, bijv. local" +msgid "local repository, leave empty for local" +msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "oss_access_key_id" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "oss_access_key_secret" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "oss_bucket_name" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "oss_endpoint" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "oss_region" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "extern pad, bijv. backup" @@ -3617,11 +3712,11 @@ msgstr "externe opslagplaats, bijv. remote" msgid "resume now" msgstr "nu hervatten" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "storj_shared_access" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "tenzij u expliciet --group-id opgeeft" diff --git a/Localizations/webroot/localization_webroot-pl.po b/Localizations/webroot/localization_webroot-pl.po index 56b898f38..ec07e476d 100644 --- a/Localizations/webroot/localization_webroot-pl.po +++ b/Localizations/webroot/localization_webroot-pl.po @@ -47,7 +47,7 @@ msgstr "" msgid "- pick an option -" msgstr "- wybierz opcję -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...ładowanie..." @@ -71,23 +71,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "klucz API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "Identyfikator dostępu AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Klucz dostepu AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Polityka AWS IAM" @@ -99,7 +99,7 @@ msgstr "O programie" msgid "About {{appname}}" msgstr "O programie {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Klucz dostępu" @@ -127,7 +127,7 @@ msgstr "" msgid "Access to user interface" msgstr "Dostęp do interfejsu użytkownika" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nazwa konta" @@ -160,8 +160,8 @@ msgstr "Dodaj ścieżkę" msgid "Added" msgstr "Dodano" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Poprawić nazwę zasobnika?" @@ -194,7 +194,7 @@ msgstr "Wszystkie Maszyny Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Wszystkie Bazy Danych Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -216,6 +216,10 @@ msgstr "Zezwalaj na dostęp zdalny (wymaga restartu)" msgid "Allowed days" msgstr "Dozwolone dni" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Znaleziono istniejący plik w nowym położeniu" @@ -240,7 +244,7 @@ msgstr "" "\n" "Czy chcesz użyć istniejącej bazy danych?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonimowy raport użycia" @@ -248,20 +252,26 @@ msgstr "Anonimowy raport użycia" msgid "Applications" msgstr "Aplikacje" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Jako Linia poleceń" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Metoda uwierzytelnienia" @@ -293,7 +303,7 @@ msgstr "ID aplikacji B2" msgid "B2 Application Key" msgstr "Klucz aplikacji B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "ID konta magazynu w chmurze B2" @@ -301,7 +311,7 @@ msgstr "ID konta magazynu w chmurze B2" msgid "B2 Cloud Storage Application ID" msgstr "ID aplikacji magazynu w chmurze B2" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Klucz aplikacji B2 magazynu w chmurze" @@ -343,7 +353,7 @@ msgstr "Retencja kopii zapasowej" msgid "Backup:" msgstr "Kopia:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -355,7 +365,7 @@ msgstr "Przerwany dostęp" msgid "Browse" msgstr "Przeglądaj" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Domyślna przeglądarka" @@ -363,14 +373,14 @@ msgstr "Domyślna przeglądarka" msgid "Bucket create location" msgstr "Miejsce tworzenia zasobnika" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -379,7 +389,7 @@ msgstr "Miejsce tworzenia zasobnika" msgid "Bucket name" msgstr "Nazwa zasobnika" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -458,13 +468,13 @@ msgstr "" msgid "Cache Files" msgstr "Pliki pamięci podręcznej" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Robocze" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -479,14 +489,18 @@ msgstr "Robocze" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Anuluj" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -503,7 +517,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -543,7 +557,7 @@ msgstr "Wybierz typ magazynu by rozpocząć" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Kliknij link AuthID by utworzyć AuthID" @@ -672,7 +686,7 @@ msgstr "Utracono połączenie" msgid "Connection worked!" msgstr "Połączenie działa!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nazwa zasobnika" @@ -714,7 +728,7 @@ msgstr "Niepowodzenie kopiowania. Proszę skopiować URL ręcznie" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opcje podstawowe" @@ -722,7 +736,7 @@ msgstr "Opcje podstawowe" msgid "Counting ({{files}} files found, {{size}})" msgstr "Liczenie ({{files}} znaleziono plików, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Tylko awarie" @@ -734,7 +748,7 @@ msgstr "Utwórz raport o błędach ..." msgid "Create folder?" msgstr "Utworzyć folder" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Utwórz nowego użytkownika z ograniczeniami" @@ -742,7 +756,7 @@ msgstr "Utwórz nowego użytkownika z ograniczeniami" msgid "Creating bug report …" msgstr "Tworzenie raportu o błędach ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Tworzenie nowego użytkownika z ograniczonym dostępem ..." @@ -754,7 +768,7 @@ msgstr "Tworzenie folderów docelowych ..." msgid "Creating temporary backup …" msgstr "Tworzenie kopii tymczasowej ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -774,7 +788,7 @@ msgstr "Bieżąca wersja to {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Niestandardowy węzeł końcowy S3" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Niestandardowy satelita" @@ -826,11 +840,11 @@ msgstr "Baza danych ..." msgid "Days" msgstr "Dni" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Domyślny" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Domyślny ({{channelname}})" @@ -838,7 +852,7 @@ msgstr "Domyślny ({{channelname}})" msgid "Default excludes" msgstr "Domyślne wykluczenia" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opcje domyślne" @@ -866,6 +880,10 @@ msgstr "Usuń kopie zapasowe starsze niż" msgid "Delete local database" msgstr "Usuń lokalną bazę danych" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Usuń zdalne pliki" @@ -920,7 +938,8 @@ msgstr "Lokalizacja docelowa" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Ścieżka docelowa" @@ -932,6 +951,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Wyłączone" @@ -945,7 +968,7 @@ msgstr "Odrzuć" msgid "Dismiss all" msgstr "Odrzucić wszystkie" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Schemat ekranu i kolorystyki" @@ -994,14 +1017,14 @@ msgstr "Strona Duplicati" msgid "Duplicati forum" msgstr "Forum Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1040,12 +1063,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Edytuj jako listę" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Edytuj jako tekst" @@ -1057,6 +1080,10 @@ msgstr "Edycja ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Zaszyfruj plik" @@ -1076,7 +1103,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Hasło szyfrowania" @@ -1190,7 +1217,7 @@ msgstr "Błąd" msgid "Error!" msgstr "Błąd!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Błędy i awarie" @@ -1238,7 +1265,7 @@ msgstr "Wyklucz wyrażenie regularne" msgid "Existing file found" msgstr "Znaleziono istniejący plik" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Eksperymentalne" @@ -1332,7 +1359,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Nie udało się odtworzyć plików: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Nie udało się zapisać:" @@ -1361,7 +1388,7 @@ msgstr "Filtry" msgid "Finished!" msgstr "Zakończono!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Konfiguracja początkowa" @@ -1432,7 +1459,7 @@ msgstr "Wygeneruj politykę dostępu IAM" msgid "Getting file versions …" msgstr "Pobieranie wersji plików ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "E-mail grupowy" @@ -1457,7 +1484,7 @@ msgid "Hostnames" msgstr "Nazwy hostów" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Godziny" @@ -1487,11 +1514,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1541,6 +1568,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Jeśli nie podasz Klucza API, nawa dzierżawcy jest wymagana" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1592,7 +1624,7 @@ msgstr "Dołącz wyrażenie regularne" msgid "Incorrect answer, try again" msgstr "Nieprawidłowa odpowiedź, spróbuj ponownie" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1607,7 +1639,7 @@ msgstr "Informacja" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Nieprawidłowe znaki w ścieżce" @@ -1617,7 +1649,7 @@ msgstr "Nieprawidłowe znaki w ścieżce" msgid "Invalid retention time" msgstr "Nieprawidłowy czas przechowywania" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1645,7 +1677,7 @@ msgstr "Zachowaj wszystkie kopie" msgid "Keystone API version" msgstr "Wersja Keystone API" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Język w interfejsie użytkownika" @@ -1711,10 +1743,6 @@ msgstr "" msgid "Loading …" msgstr "Ładowanie ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Magazyn lokalny" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1918,7 +1950,7 @@ msgstr "Następne zadanie" msgid "Next time" msgstr "Następny raz" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1931,9 +1963,9 @@ msgstr "Następny raz" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nie" @@ -1975,7 +2007,7 @@ msgstr "Brak zaplanowanych zadań" msgid "Non-matching passphrase" msgstr "Niepasujące długie hasła" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Żaden / wyłączone" @@ -1995,17 +2027,17 @@ msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" "Nic nie będzie kasowane. Kopia będzie zwiększała rozmiar z każdą zmianą." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2037,7 +2069,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2061,7 +2093,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Otwarto" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2098,11 +2130,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opcje" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2112,7 +2144,7 @@ msgstr "" msgid "Original location" msgstr "Położenie oryginalne" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Inne" @@ -2151,7 +2183,7 @@ msgstr "Hasła różnią się od siebie" msgid "Passphrases do not match" msgstr "Hasła różnią się od siebie" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2162,8 +2194,8 @@ msgstr "Hasło" msgid "Patching files with local blocks …" msgstr "Poprawianie plików za pomocą lokalnych bloków ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Ścieżka" @@ -2172,7 +2204,7 @@ msgid "Path not found" msgstr "Ścieżka nie znaleziona" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Ścieżka na serwerze" @@ -2181,11 +2213,11 @@ msgstr "Ścieżka na serwerze" msgid "Path or subfolder in the bucket" msgstr "Ścieżka lub podkatalog w zasobniku" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Wstrzymaj" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Wstrzymaj po uruchomieniu lub hibernacji" @@ -2223,6 +2255,10 @@ msgstr "Zapobiegaj automatycznemu logowaniu z ikony w trayu" msgid "Previous" msgstr "Poprzedni" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Postęp:" @@ -2231,7 +2267,7 @@ msgstr "Postęp:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID jest opcjonalne jeśli zasobnik istnieje" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Własny" @@ -2267,10 +2303,30 @@ msgstr "Odtwarzanie bazy danych ..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Rejestrowanie kopii tymczasowej ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Ścieżki względne nie są dopuszczalne" @@ -2283,14 +2339,34 @@ msgstr "Przeładuj" msgid "Remote" msgstr "Zdalny" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Ścieżka zdalna" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Magazyn zdalny" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Ścieżka zdalna" @@ -2433,7 +2509,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Kompatybilny z S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Zgodny z bazową wersją instalacji: {{channelname}}" @@ -2481,7 +2557,7 @@ msgstr "Szukaj" msgid "Search for files" msgstr "Szukaj plików" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekundy" @@ -2494,8 +2570,8 @@ msgstr "Wybierz zakres dziennika i zobacz co się wydarzyło:" msgid "Select files" msgstr "Wybierz pliki" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Serwer" @@ -2533,6 +2609,10 @@ msgstr "Serwer wstrzymany" msgid "Server state properties" msgstr "Właściwości stanu serwera" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Ustawienia" @@ -2605,17 +2685,17 @@ msgstr "Foldery źródłowe" msgid "Source:" msgstr "Źródło:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Szczególne kompilacje tylko dla programistów. Nie do użytku z ważnymi " "danymi." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protokoły standardowe" @@ -2642,19 +2722,15 @@ msgstr "Uruchamianie odzyskiwania ..." msgid "Starting the restore process …" msgstr "Uruchamianie procesu odzyskiwania ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Zatrzymaj po bieżącym pliku" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Zatrzymaj po bieżącym pliku" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Zatrzymaj teraz" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Zatrzymaj wykonywaną kopię" @@ -2708,7 +2784,7 @@ msgstr "Link symboliczny" msgid "System Files" msgstr "Pliki systemowe" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "System domyślny ({{levelname}})" @@ -2764,6 +2840,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Faza testu" @@ -2776,7 +2857,7 @@ msgstr "Sprawdź połączenie" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Sprawdzanie uprawnień ..." @@ -2784,10 +2865,10 @@ msgstr "Sprawdzanie uprawnień ..." msgid "Testing …" msgstr "Testowanie ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2812,7 +2893,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Nazwa zasobnika powinna być pisana wersalikami, zmienić automatycznie ?" @@ -2829,15 +2910,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Ciemny schemat (wyk. Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Domyślny schemat niebieski na białym (wyk. Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2866,7 +2947,7 @@ msgstr "" "Czy chcesz ZASTĄPIĆ twój BIEŻĄCY klucz komputera \"{{prev}}\" na PODANY klucz: {{klucz}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Hasła różnią się od siebie" @@ -2919,7 +3000,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "Docelowy folder zawiera zaszyfrowane pliki, proszę podać długie hasło" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2947,7 +3028,7 @@ msgstr "Bieżący miesiąc" msgid "This week" msgstr "Bieżący tydzień" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Limity prędkości" @@ -2960,6 +3041,10 @@ msgstr "Czw" msgid "Time" msgstr "Czas" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Do Pliku" @@ -2976,7 +3061,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Aby wyeksportować bez hasła, odznacz pole \"Szyfruj plik\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3010,7 +3095,7 @@ msgstr "Certyfikat zaufanego hosta?" msgid "Trust server certificate?" msgstr "Certyfikat zaufanego serwera?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3047,7 +3132,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Kanał uaktualnień" @@ -3067,18 +3152,18 @@ msgstr "Przesłane pliki" msgid "Uploading verification file …" msgstr "Przesyłanie pliku weryfikującego ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statystyki użycia" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statystyki użycia , ostrzeżenia, błędy i awarie" @@ -3106,17 +3191,17 @@ msgstr "Dane użytkownika" msgid "User domain name" msgstr "Nazwa domeny użytkownika" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Użytkownik ma za duże uprawnienia" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Ustawienia interfejsu użytkownika" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3206,7 +3291,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Oczekiwanie na zakończenie przesyłania ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Ostrzeżenia, błędy i awarie" @@ -3243,7 +3328,7 @@ msgstr "Gdzie chcesz odtworzyć pliki?" msgid "Years" msgstr "Lata" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3256,9 +3341,9 @@ msgstr "Lata" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Tak" @@ -3297,18 +3382,16 @@ msgstr "Aktualnie używasz {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Możesz zatrzymać wykonywanie kopii po zakończeniu wysyłania dowolnego " -"aktualnie przetwarzanego pliku." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Możesz przerwać zadanie natychmiast lub pozwolić kontynuować z bieżącym " -"plikiem i następnie przerwać." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3352,7 +3435,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Musisz wybrać co najmniej jeden folder źródłowy" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Musisz podać domenę aby użyć v3 API" @@ -3364,7 +3447,7 @@ msgstr "Musisz podać nazwę kopii zapasowej" msgid "You must enter a passphrase or disable encryption" msgstr "Musisz podać długie hasło lub wyłączyć szyfrowanie" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Musisz podać hasło aby użyć v3 API" @@ -3372,11 +3455,11 @@ msgstr "Musisz podać hasło aby użyć v3 API" msgid "You must enter a positive number of backups to keep" msgstr "Musisz podać dodatnią liczbę kopii do zachowania" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Musisz podać nazwę dzierżawcy (znanego jako projekt) aby użyć v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3388,11 +3471,11 @@ msgstr "Musisz podać prawidłowy okres przechowywania kopii zapasowych" msgid "You must enter a valid retention policy string" msgstr "Musisz wprowadzić prawidłowy ciąg zasad przechowywania" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3413,11 +3496,11 @@ msgstr "Musisz wypełnić pole użytkownik" msgid "You must fill in {{field}}" msgstr "Musisz wypełnić pole {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Musisz wybrać lub wypełnić pole AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Musisz wybrać lub wypełnić pole serwer" @@ -3450,23 +3533,23 @@ msgstr "bajtów" msgid "byte/s" msgstr "bajtów/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3481,29 +3564,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3516,11 +3607,11 @@ msgstr "" msgid "resume now" msgstr "wznów teraz" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "chyba że wyraźnie określisz --group-id" diff --git a/Localizations/webroot/localization_webroot-pt.po b/Localizations/webroot/localization_webroot-pt.po index 2a4678cba..ff65f2bd6 100644 --- a/Localizations/webroot/localization_webroot-pt.po +++ b/Localizations/webroot/localization_webroot-pt.po @@ -42,7 +42,7 @@ msgstr "" msgid "- pick an option -" msgstr "- escolha uma opção -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...a carregar..." @@ -66,23 +66,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Chave API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "ID do acesso AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Chave do acesso AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Política de acesso e identidade AWS" @@ -94,7 +94,7 @@ msgstr "Sobre" msgid "About {{appname}}" msgstr "Sobre o {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Chave de acesso" @@ -122,7 +122,7 @@ msgstr "" msgid "Access to user interface" msgstr "Acesso à interface" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nome da conta" @@ -155,8 +155,8 @@ msgstr "Adicionar caminho" msgid "Added" msgstr "Adicionado" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Ajustar nome do 'bucket'?" @@ -189,7 +189,7 @@ msgstr "Todas as máquinas Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Todas as bases de dados Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -211,6 +211,10 @@ msgstr "Permitir acesso remoto (tem que reiniciar)" msgid "Allowed days" msgstr "Dias permitidos" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Encontrado um ficheiro na nova localização" @@ -235,7 +239,7 @@ msgstr "" "\n" "Deseja reutilizar a base de dados existente?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Relatório anónimos de utilização" @@ -243,20 +247,26 @@ msgstr "Relatório anónimos de utilização" msgid "Applications" msgstr "Aplicações" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Como linha de comandos" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Método de autenticação" @@ -288,7 +298,7 @@ msgstr "ID Aplicação B2" msgid "B2 Application Key" msgstr "Chave da aplicação B2" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "ID da conta B2 Cloud Storage" @@ -296,7 +306,7 @@ msgstr "ID da conta B2 Cloud Storage" msgid "B2 Cloud Storage Application ID" msgstr "ID Aplicação B2 Cloud Storage" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "Chave da aplicação B2 Cloud Storage" @@ -338,7 +348,7 @@ msgstr "Retenção de cópias de segurança" msgid "Backup:" msgstr "Cópia de segurança:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -350,7 +360,7 @@ msgstr "Acesso danificado" msgid "Browse" msgstr "Explorar" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Navegador padrão" @@ -358,14 +368,14 @@ msgstr "Navegador padrão" msgid "Bucket create location" msgstr "Localização de criação do 'bucket'" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -374,7 +384,7 @@ msgstr "Localização de criação do 'bucket'" msgid "Bucket name" msgstr "Nome do 'bucket'" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -453,13 +463,13 @@ msgstr "" msgid "Cache Files" msgstr "Ficheiros em cache" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -474,14 +484,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancelar" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -498,7 +512,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -538,7 +552,7 @@ msgstr "Escolha o tipo de armazenamento para iniciar" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Clique na ligação para criar uma AuthID" @@ -667,7 +681,7 @@ msgstr "Ligação perdida" msgid "Connection worked!" msgstr "Ligação funcional!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nome do 'container'" @@ -709,7 +723,7 @@ msgstr "Falha ao copiar. Copie o URL manualmente." msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opções de core" @@ -717,7 +731,7 @@ msgstr "Opções de core" msgid "Counting ({{files}} files found, {{size}})" msgstr "Encontrados ({{files}} ficheiros, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Apenas términos" @@ -729,7 +743,7 @@ msgstr "Criar relatório de erros ..." msgid "Create folder?" msgstr "Criar pasta?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Criar utilizador com restrições" @@ -737,7 +751,7 @@ msgstr "Criar utilizador com restrições" msgid "Creating bug report …" msgstr "A criar relatório de erros ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "A criar novo utilizador com acesso limitado ..." @@ -749,7 +763,7 @@ msgstr "A criar pastas de destino ..." msgid "Creating temporary backup …" msgstr "A criar cópia de segurança temporária ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -769,7 +783,7 @@ msgstr "A versão atual é a {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "URL S3 personalizado" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Satélite personalizado" @@ -821,11 +835,11 @@ msgstr "Base de dados ..." msgid "Days" msgstr "Dias" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Padrão" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Padrão ({{channelname}})" @@ -833,7 +847,7 @@ msgstr "Padrão ({{channelname}})" msgid "Default excludes" msgstr "Exclusões padrão" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opções padrão" @@ -861,6 +875,10 @@ msgstr "Eliminar cópias de segurança mais antigas do que" msgid "Delete local database" msgstr "Eliminar base de dados local" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Eliminar ficheiros remotos" @@ -916,7 +934,8 @@ msgstr "Destino" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Caminho de destino" @@ -928,6 +947,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Desativada" @@ -941,7 +964,7 @@ msgstr "Descartar" msgid "Dismiss all" msgstr "Descartar tudo" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Visualização e cor do tema" @@ -991,14 +1014,14 @@ msgstr "Site do Duplicati" msgid "Duplicati forum" msgstr "Fórum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1037,12 +1060,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Editar como lista..." #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Editar como texto" @@ -1054,6 +1077,10 @@ msgstr "Editar ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Encriptar ficheiro" @@ -1073,7 +1100,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Frase-passe de encriptação" @@ -1187,7 +1214,7 @@ msgstr "Erro" msgid "Error!" msgstr "Erro!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Erros e términos" @@ -1235,7 +1262,7 @@ msgstr "Expressão regular de exclusão" msgid "Existing file found" msgstr "Encontrado ficheiro" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1329,7 +1356,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Falha ao restaurar os ficheiros: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Falha ao guardar:" @@ -1358,7 +1385,7 @@ msgstr "Filtros" msgid "Finished!" msgstr "Terminado!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Configuração de primeira utilização" @@ -1429,7 +1456,7 @@ msgstr "Gerar política de acesso IAM" msgid "Getting file versions …" msgstr "A obter versão dos ficheiros ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "E-mail do grupo" @@ -1454,7 +1481,7 @@ msgid "Hostnames" msgstr "Nomes de hosts" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Horas" @@ -1484,11 +1511,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1538,6 +1565,11 @@ msgid "If you do not enter an API Key, the tenant name is required" msgstr "" "Se não digitar a chave API, será necessário o nome do 'tenant' (projeto)." +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1589,7 +1621,7 @@ msgstr "Expressão regular de exclusão" msgid "Incorrect answer, try again" msgstr "Resposta errada, tente novamente." -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1604,7 +1636,7 @@ msgstr "Informação" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caracteres inválidos no caminho" @@ -1614,7 +1646,7 @@ msgstr "Caracteres inválidos no caminho" msgid "Invalid retention time" msgstr "Tempo de retenção inválido" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1642,7 +1674,7 @@ msgstr "Manter todas as cópias de segurança" msgid "Keystone API version" msgstr "Versão da API Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Idioma da interface de utilizador" @@ -1713,10 +1745,6 @@ msgstr "" msgid "Loading …" msgstr "A carregar ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Repositório local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1920,7 +1952,7 @@ msgstr "Próxima tarefa:" msgid "Next time" msgstr "Próxima hora" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1933,9 +1965,9 @@ msgstr "Próxima hora" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Não" @@ -1979,7 +2011,7 @@ msgstr "Nenhuma tarefa agendada" msgid "Non-matching passphrase" msgstr "Disparidade de frases-passe" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nenhum / desativado" @@ -2000,17 +2032,17 @@ msgstr "" "Nada será eliminado. O tamanho da cópia de segurança crescerá com cada " "alteração." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "Aceitar" @@ -2042,7 +2074,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2066,7 +2098,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Aberto" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2103,11 +2135,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opções" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2117,7 +2149,7 @@ msgstr "" msgid "Original location" msgstr "Localização original" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Outras" @@ -2157,7 +2189,7 @@ msgstr "Disparidade de frases-passe" msgid "Passphrases do not match" msgstr "As frases-passe não coincidem" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2168,8 +2200,8 @@ msgstr "Palavra-passe" msgid "Patching files with local blocks …" msgstr "A aplicar correcções aos ficheiros com blocos locais ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Caminho" @@ -2178,7 +2210,7 @@ msgid "Path not found" msgstr "Caminho não encontrado" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Caminho no servidor" @@ -2187,11 +2219,11 @@ msgstr "Caminho no servidor" msgid "Path or subfolder in the bucket" msgstr "Caminho ou sub-pasta no 'bucket'" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pausa" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausa após o arranque ou hibernação" @@ -2230,6 +2262,10 @@ msgstr "Impedir autenticação automática com o ícone da barra de tarefas" msgid "Previous" msgstr "Anterior" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progresso:" @@ -2238,7 +2274,7 @@ msgstr "Progresso:" msgid "ProjectID is optional if the bucket exist" msgstr "ID do projeto é opcional se o 'bucket' já existir" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietário" @@ -2274,10 +2310,30 @@ msgstr "A recriar a base de dados" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "A registar a cópia de segurança emporária ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Caminhos relativos não são permitidos" @@ -2290,14 +2346,34 @@ msgstr "Recarregar" msgid "Remote" msgstr "Remoto" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Caminho remoto" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Repositório remoto" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Caminho remoto" @@ -2440,7 +2516,7 @@ msgstr "" msgid "S3 Compatible" msgstr "Compatível com S3" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Igual à versão de instalação base: {{channelname}}" @@ -2488,7 +2564,7 @@ msgstr "Pesquisa" msgid "Search for files" msgstr "Pesquisar ficheiros" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Segundos" @@ -2502,8 +2578,8 @@ msgstr "" msgid "Select files" msgstr "Selecionar ficheiros" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Servidor" @@ -2541,6 +2617,10 @@ msgstr "Servidor em pausa" msgid "Server state properties" msgstr "Propriedades do estado do servidor" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Definições" @@ -2615,17 +2695,17 @@ msgstr "Pastas de origem" msgid "Source:" msgstr "Origem:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Versões específicas apenas para programadores. Não destinadas a serem " "utilizadas com dados importantes." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocolos padrão" @@ -2652,19 +2732,15 @@ msgstr "A iniciar o restauro ..." msgid "Starting the restore process …" msgstr "A iniciar o processo de restauro ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Parar após o ficheiro atual" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Parar após o ficheiro atual" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Parar agora" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Parar cópia de segurança em execução" @@ -2718,7 +2794,7 @@ msgstr "Ligação simbólica" msgid "System Files" msgstr "Ficheiros de sistema" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Predefinição ({{levelname}})" @@ -2774,6 +2850,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Fase de teste" @@ -2786,7 +2867,7 @@ msgstr "Testar ligação" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "A verificar permissões ..." @@ -2794,10 +2875,10 @@ msgstr "A verificar permissões ..." msgid "Testing …" msgstr "A verificar ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2824,7 +2905,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "O nome do 'bucket' deve ser todo em minúsculas. Converter automaticamente?" @@ -2841,15 +2922,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tema escuro (por Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Azul em tema claro (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2878,7 +2959,7 @@ msgstr "" "Deseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" pela chave do host REPORTADA: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "As palavras-passe não coincidem" @@ -2932,7 +3013,7 @@ msgid "" msgstr "" "A pasta de destino contém ficheiros encriptados. Forneça a frase-passe" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2960,7 +3041,7 @@ msgstr "Este mês" msgid "This week" msgstr "Esta semana" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Definições de velocidade" @@ -2973,6 +3054,10 @@ msgstr "Qui" msgid "Time" msgstr "Hora" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Para ficheiro" @@ -2990,7 +3075,7 @@ msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" "Para exportar sem uma frase-passe, desmarque a caixa \"Encriptar ficheiro\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3025,7 +3110,7 @@ msgstr "Confiar no certificado do host?" msgid "Trust server certificate?" msgstr "Confiar no certificado do servidor?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3062,7 +3147,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal de atualização" @@ -3082,18 +3167,18 @@ msgstr "Ficheiros enviados" msgid "Uploading verification file …" msgstr "A enviar ficheiro de verificação ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Estatísticas de utilização" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Estatísticas de utilização, avisos e erros" @@ -3121,17 +3206,17 @@ msgstr "Dados do utilizador" msgid "User domain name" msgstr "Nome do domínio do utilizador" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Utilizador com demasiadas permissões" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Definições da interface" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3221,7 +3306,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "A aguardar que o envio termine ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avisos e erros" @@ -3259,7 +3344,7 @@ msgstr "Para onde quer restaurar os ficheiros?" msgid "Years" msgstr "Anos" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3272,9 +3357,9 @@ msgstr "Anos" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Sim" @@ -3313,18 +3398,16 @@ msgstr "Está a executar o {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Pode parar a cópia de segurança após o envio de qualquer ficheiro em curso " -"terminar." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Pode parar a tarefa imediatamente ou parar a tarefa após o processo do " -"ficheiro atual." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3368,7 +3451,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Tem que escolher, pelo menos, uma pasta de origem" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Tem de introduzir um nome de domínio para usar a API v3" @@ -3380,7 +3463,7 @@ msgstr "Tem que introduzir o nome para a cópia de segurança" msgid "You must enter a passphrase or disable encryption" msgstr "Tem de introduzir uma frase-passe ou desativar a encriptação" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Tem de introduzir uma palavra-passe para usar a API v3" @@ -3389,11 +3472,11 @@ msgid "You must enter a positive number of backups to keep" msgstr "" "Tem que introduzir um número positivo para as cópias de segurança a manter" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Te de introduzir um tenant (ou seja projeto) para usar a API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3407,11 +3490,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Tem de inserir uma cadeia de política de retenção válida" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3432,11 +3515,11 @@ msgstr "Tem que preencher o nome de utilizador" msgid "You must fill in {{field}}" msgstr "Tem que preencher {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Tem que selecionar ou preencher o AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Tem que selecionar ou preencher o servidor" @@ -3468,23 +3551,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3499,29 +3582,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3534,11 +3625,11 @@ msgstr "" msgid "resume now" msgstr "retomar agora" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "a não ser que esteja a especificar explicitamente --group-id" diff --git a/Localizations/webroot/localization_webroot-pt_BR.po b/Localizations/webroot/localization_webroot-pt_BR.po index 73b276e93..317a8657d 100644 --- a/Localizations/webroot/localization_webroot-pt_BR.po +++ b/Localizations/webroot/localization_webroot-pt_BR.po @@ -50,7 +50,7 @@ msgstr "" msgid "- pick an option -" msgstr "- selecione uma opção -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...carregando..." @@ -74,23 +74,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Chave API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "ID de acesso do AWS" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "Chave de acesso do AWS" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Política de IAM do AWS" @@ -102,7 +102,7 @@ msgstr "Sobre" msgid "About {{appname}}" msgstr "Sobre {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Chave de acesso" @@ -130,7 +130,7 @@ msgstr "" msgid "Access to user interface" msgstr "Acesso à interface do usuário" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nome do usuário" @@ -163,8 +163,8 @@ msgstr "Adicionar caminho" msgid "Added" msgstr "Adicionado" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Ajustar o nome do bucket?" @@ -197,7 +197,7 @@ msgstr "Todas as máquinas Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Todas as bases Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -219,6 +219,10 @@ msgstr "Permitir acesso remoto (restart necessário)" msgid "Allowed days" msgstr "Dias permitidos" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Um arquivo foi encontrado no local escolhido" @@ -242,7 +246,7 @@ msgstr "" "Reutilizar a basa permitirá que as ferramentas de linha de comando e as instâncias trabalhem no mesmo armazenamento remoto.\n" "Gostaria de utilizar a base existente?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Relatório anônimo de uso" @@ -250,20 +254,26 @@ msgstr "Relatório anônimo de uso" msgid "Applications" msgstr "Aplicações" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Como linha de comando" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Método de autenticação" @@ -295,7 +305,7 @@ msgstr "ID da aplicação B2" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -303,7 +313,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "ID da aplicação B2 armazenagem em nuvem" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -345,7 +355,7 @@ msgstr "Retenção de backup" msgid "Backup:" msgstr "Backup:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -357,7 +367,7 @@ msgstr "Acesso quebrado" msgid "Browse" msgstr "Navegar" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Navegador padrão" @@ -365,14 +375,14 @@ msgstr "Navegador padrão" msgid "Bucket create location" msgstr "Localização do Bucket" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -381,7 +391,7 @@ msgstr "Localização do Bucket" msgid "Bucket name" msgstr "Nome do Bucket" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -459,13 +469,13 @@ msgstr "" msgid "Cache Files" msgstr "Arquivos de Cache" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -480,14 +490,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancelar" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -504,7 +518,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -544,7 +558,7 @@ msgstr "Para iniciar, escolha o tipo de armazenamento" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Clique no link AuthID para criar uma AuthID" @@ -673,7 +687,7 @@ msgstr "Conexão perdida" msgid "Connection worked!" msgstr "Conexão estabelecida!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Nome do Container" @@ -715,7 +729,7 @@ msgstr "Falha na cópia. Copie a URL manualmente" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opções básicas" @@ -723,7 +737,7 @@ msgstr "Opções básicas" msgid "Counting ({{files}} files found, {{size}})" msgstr "Contabilizando ({{files}} arquivos encontrados, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Somente falhas" @@ -735,7 +749,7 @@ msgstr "Criar relatório de errors ..." msgid "Create folder?" msgstr "Criar diretório?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Criar novo usuário com limitações no acesso" @@ -743,7 +757,7 @@ msgstr "Criar novo usuário com limitações no acesso" msgid "Creating bug report …" msgstr "Criando relatório de erros ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Criando novo usuário com acesso limitado ..." @@ -755,7 +769,7 @@ msgstr "Criando diretórios de destino…" msgid "Creating temporary backup …" msgstr "Criando backup temporário ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -775,7 +789,7 @@ msgstr "A versão atual é {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Endpoint S3 modificado" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Satélite customizado" @@ -827,11 +841,11 @@ msgstr "Banco de dados" msgid "Days" msgstr "Dias" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Padrão" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Padrão ({{channelname}})" @@ -839,7 +853,7 @@ msgstr "Padrão ({{channelname}})" msgid "Default excludes" msgstr "Exclusões padrão" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opções padrão" @@ -867,6 +881,10 @@ msgstr "Excluir backups mais antigos que" msgid "Delete local database" msgstr "Remover base local" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Remover arquivos remotos" @@ -922,7 +940,8 @@ msgstr "Destino" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Caminho de destino" @@ -934,6 +953,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Desabilitado" @@ -947,7 +970,7 @@ msgstr "Ok" msgid "Dismiss all" msgstr "Ignorar tudo" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Tela e cores do tema" @@ -996,14 +1019,14 @@ msgstr "Site do Duplicati" msgid "Duplicati forum" msgstr "Fórum do Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1042,12 +1065,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Editar como lista" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Editar como texto" @@ -1059,6 +1082,10 @@ msgstr "Editar ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Criptografar arquivo" @@ -1078,7 +1105,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Frase-senha de criptografia " @@ -1192,7 +1219,7 @@ msgstr "Erro" msgid "Error!" msgstr "Erro!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Erros e problemas" @@ -1240,7 +1267,7 @@ msgstr "Excluir utilizando expressão regular" msgid "Existing file found" msgstr "Excluir arquivo encontrado" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1334,7 +1361,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Falha ao restaurar arquivos: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Falha ao salvar:" @@ -1363,7 +1390,7 @@ msgstr "Filtros" msgid "Finished!" msgstr "Finalizado!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Configuração inicial" @@ -1434,7 +1461,7 @@ msgstr "Gerar política de acesso IAM" msgid "Getting file versions …" msgstr "Obtendo versões do arquivo ... " -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "E-mail do grupo" @@ -1459,7 +1486,7 @@ msgid "Hostnames" msgstr "Hostnames" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Horas" @@ -1489,11 +1516,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1544,6 +1571,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Se você não inserir uma chave de API, o nome do projeto é necessário" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1595,7 +1627,7 @@ msgstr "Incluir expressão regular" msgid "Incorrect answer, try again" msgstr "Resposta incorreta, tente novamente" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1609,7 +1641,7 @@ msgstr "Informação" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caracteres inválidos no caminho" @@ -1619,7 +1651,7 @@ msgstr "Caracteres inválidos no caminho" msgid "Invalid retention time" msgstr "Tempo de retenção inválido" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1647,7 +1679,7 @@ msgstr "Manter todos os backups" msgid "Keystone API version" msgstr "Versão da API Keystone" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Idioma da interface do usuário" @@ -1718,10 +1750,6 @@ msgstr "" msgid "Loading …" msgstr "Carregando …" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Repositório Local" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1925,7 +1957,7 @@ msgstr "Próxima tarefa:" msgid "Next time" msgstr "Próxima vez" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1938,9 +1970,9 @@ msgstr "Próxima vez" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Não" @@ -1983,7 +2015,7 @@ msgstr "Sem tarefas agendadas" msgid "Non-matching passphrase" msgstr "Senha não correspondente" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nenhum / desabilitado" @@ -2002,17 +2034,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "Nada será excluído. O tamanho do backup crescerá com cada mudança." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2044,7 +2076,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2068,7 +2100,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Aberto" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2105,11 +2137,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opções" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2119,7 +2151,7 @@ msgstr "" msgid "Original location" msgstr "Localização original" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Outros" @@ -2158,7 +2190,7 @@ msgstr "Senhas não correspondem" msgid "Passphrases do not match" msgstr "As senhas não correspondem" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2169,8 +2201,8 @@ msgstr "Senha" msgid "Patching files with local blocks …" msgstr "Aplicando patch nos arquivos com blocos locais ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Caminho" @@ -2179,7 +2211,7 @@ msgid "Path not found" msgstr "Caminho não encontrado" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Caminho do servidor" @@ -2188,11 +2220,11 @@ msgstr "Caminho do servidor" msgid "Path or subfolder in the bucket" msgstr "Caminho ou subpasta no bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Parar" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausa após a inicialização ou a hibernação" @@ -2230,6 +2262,10 @@ msgstr "Impedir login automático no ícone da bandeja" msgid "Previous" msgstr "Anterior" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Progresso:" @@ -2238,7 +2274,7 @@ msgstr "Progresso:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID é opcional se o bucket já existe" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietário" @@ -2274,10 +2310,30 @@ msgstr "Recriaando banco de dados ..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrando backup temporário ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Caminhos relativos não são permitidos" @@ -2290,14 +2346,34 @@ msgstr "Recarregar" msgid "Remote" msgstr "Remoto" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Caminho remoto" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Repositório remoto" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Caminho remoto" @@ -2440,7 +2516,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Compatível" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Igual à versão de instalação base: {{channelname}}" @@ -2488,7 +2564,7 @@ msgstr "Buscar" msgid "Search for files" msgstr "Procurar por arquivos" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Segundos" @@ -2501,8 +2577,8 @@ msgstr "Selecione um nível de log e veja as mensagens conforme elas aparecem:" msgid "Select files" msgstr "Selecionar arquivos" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Servidor" @@ -2540,6 +2616,10 @@ msgstr "Servidor parado" msgid "Server state properties" msgstr "Propriedades do estado do servidor" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Configurações" @@ -2613,16 +2693,16 @@ msgstr "Pasta de origem" msgid "Source:" msgstr "Origem:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Versão apenas para desenvolvedores. Não para uso com dados importantes." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocolos padrão" @@ -2649,19 +2729,15 @@ msgstr "Iniciando restauração ..." msgid "Starting the restore process …" msgstr "Iniciando o processo de restauração ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Parar após o arquivo atual" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Parar após o arquivo atual" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Parar agora" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Parar de executar o backup" @@ -2715,7 +2791,7 @@ msgstr "Link simbólico" msgid "System Files" msgstr "Arquivos do sistema" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Sistema padrão ({{levelname}})" @@ -2771,6 +2847,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Fase de teste" @@ -2783,7 +2864,7 @@ msgstr "Teste de conexão" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Testando permissões ..." @@ -2791,10 +2872,10 @@ msgstr "Testando permissões ..." msgid "Testing …" msgstr "Testando ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2821,7 +2902,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "O nome do bucket deve ser todo em minúsculas. Converter automaticamente?" @@ -2838,15 +2919,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "O tema escuro (por Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "O tema padrão azul sobre branco (por Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2875,7 +2956,7 @@ msgstr "" "Deseja SUBSTITUIR a sua chave do host ATUAL \"{{prev}}\" com a chave do host REPORTADA: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Senhas não conferem" @@ -2929,7 +3010,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "A pasta de destino contém arquivos criptografados. Forneça a senha" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2957,7 +3038,7 @@ msgstr "Este mês" msgid "This week" msgstr "Esta semana" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Configurações de limitação" @@ -2970,6 +3051,10 @@ msgstr "Qui" msgid "Time" msgstr "Tempo" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Para o arquivo" @@ -2986,7 +3071,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Para exportar sem uma senha, desmarque a caixa \"Criptografar arquivo\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3021,7 +3106,7 @@ msgstr "Confiar no certificado de host?" msgid "Trust server certificate?" msgstr "Confiar no certificado de servidor?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3058,7 +3143,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Canal de atualização" @@ -3078,18 +3163,18 @@ msgstr "Arquivos enviados" msgid "Uploading verification file …" msgstr "Enviando arquivo de verificação ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Estatísticas de uso" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Estatísticas de uso, avisos, erros e falhas" @@ -3117,17 +3202,17 @@ msgstr "Dados do usuário" msgid "User domain name" msgstr "Nome de domínio do usuário" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "O usuário tem muitas permissões" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Configurações da interface do usuário" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3217,7 +3302,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Aguardando o upload terminar ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avisos, erros e falhas" @@ -3255,7 +3340,7 @@ msgstr "Para onde você deseja restaurar os arquivos?" msgid "Years" msgstr "Anos" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3268,9 +3353,9 @@ msgstr "Anos" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Sim" @@ -3309,18 +3394,16 @@ msgstr "Você está atualmente executando {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Você pode interromper o backup após a conclusão de qualquer upload de " -"arquivo em andamento." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Você pode interromper a tarefa imediatamente ou permitir que o processo " -"continue seu arquivo atual e então pare." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3363,7 +3446,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Você deve escolher pelo menos uma pasta de origem" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Você deve inserir um nome de domínio para usar a API v3" @@ -3375,7 +3458,7 @@ msgstr "Você deve inserir um nome para o backup" msgid "You must enter a passphrase or disable encryption" msgstr "Você deve inserir uma senha ou desativar a criptografia" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Você deve digitar uma senha para usar a API v3" @@ -3383,12 +3466,12 @@ msgstr "Você deve digitar uma senha para usar a API v3" msgid "You must enter a positive number of backups to keep" msgstr "Você deve inserir um número positivo de backups para manter." -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Você deve inserir um nome de inquilino (aka project) para usar a API v3" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3400,11 +3483,11 @@ msgstr "Você deve inserir uma duração válida de tempo para manter os backups msgid "You must enter a valid retention policy string" msgstr "Você tem que inserir uma string de política de retenção válida" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3425,11 +3508,11 @@ msgstr "Você deve preencher o usuário" msgid "You must fill in {{field}}" msgstr "Você deve preencher {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Você deve selecionar ou preencher a AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Você deve selecionar ou preencher o servidor" @@ -3461,23 +3544,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3492,29 +3575,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3527,11 +3618,11 @@ msgstr "" msgid "resume now" msgstr "continuar agora" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "a menos que você esteja explicitamente especificando --group-id" diff --git a/Localizations/webroot/localization_webroot-ro.po b/Localizations/webroot/localization_webroot-ro.po index 01eda811b..db1435258 100644 --- a/Localizations/webroot/localization_webroot-ro.po +++ b/Localizations/webroot/localization_webroot-ro.po @@ -40,7 +40,7 @@ msgstr "" msgid "- pick an option -" msgstr "- alegeți o opțiune -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...se încarcă..." @@ -58,33 +58,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "Politica AWS IAM" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "Despre" @@ -92,7 +92,7 @@ msgstr "Despre" msgid "About {{appname}}" msgstr "Despre {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Cheie de acces" @@ -120,7 +120,7 @@ msgstr "" msgid "Access to user interface" msgstr "Accesul la interfața cu utilizatorul" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Nume de cont" @@ -137,7 +137,7 @@ msgstr "Adăugați direct o cale" msgid "Add advanced option" msgstr "Adăugați opțiunea avansată" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "Adăugați o copie de rezervă" @@ -153,8 +153,8 @@ msgstr "Adaugă calea" msgid "Added" msgstr "Adăugat" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Modificați numele găleții?" @@ -187,7 +187,7 @@ msgstr "Toate mașinile Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Toate bazele de date Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -209,6 +209,10 @@ msgstr "Permiteți accesul de la distanță (necesită repornire)" msgid "Allowed days" msgstr "Zile permise" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Un fișier existent a fost găsit la noua locație" @@ -233,7 +237,7 @@ msgstr "" "\n" " Doriți să utilizați baza de date existentă?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Rapoarte de utilizare anonime" @@ -241,20 +245,26 @@ msgstr "Rapoarte de utilizare anonime" msgid "Applications" msgstr "Aplicații" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Ca linie de comandă" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "authId" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -286,7 +296,7 @@ msgstr "" msgid "B2 Application Key" msgstr "B2 cheie de aplicație" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 ID-ul contului de stocare în cloud" @@ -294,7 +304,7 @@ msgstr "B2 ID-ul contului de stocare în cloud" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cheia aplicației de stocare cloud" @@ -336,7 +346,7 @@ msgstr "" msgid "Backup:" msgstr "Copie de rezervă:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -348,7 +358,7 @@ msgstr "Accesul spart" msgid "Browse" msgstr "Naviga" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Browser default" @@ -356,14 +366,14 @@ msgstr "Browser default" msgid "Bucket create location" msgstr "Locația unde va fi creată găleata" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -372,7 +382,7 @@ msgstr "Locația unde va fi creată găleata" msgid "Bucket name" msgstr "Numele găleții" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -446,12 +456,13 @@ msgstr "" msgid "Cache Files" msgstr "Încarcă fișierele în avans" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canar" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -466,14 +477,18 @@ msgstr "Canar" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Anulare" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -490,6 +505,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "Jurnal de modificări" @@ -510,7 +529,7 @@ msgstr "Verifică acum actualizările" msgid "Checking for updates …" msgstr "Caut versiuni noi ..." -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -526,11 +545,11 @@ msgstr "Alege un tip de stocare pentru a începe" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Faceți clic pe linkul AuthID pentru a crea un AuthID" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "Faceți clic pentru a seta opțiunile de accelerație" @@ -614,7 +633,7 @@ msgstr "Confirmă ștergerea" msgid "Confirm encryption passphrase" msgstr "Confirmă parola de criptare" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -630,11 +649,11 @@ msgstr "Confirmare Necesară" msgid "Connect" msgstr "Conectează" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "Conectează acum" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "Se conectează la server ..." @@ -642,11 +661,11 @@ msgstr "Se conectează la server ..." msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "Conexiunea a fost pierdută" @@ -655,7 +674,7 @@ msgstr "Conexiunea a fost pierdută" msgid "Connection worked!" msgstr "Conexiunea a funcționat!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Numele containerului" @@ -697,7 +716,7 @@ msgstr "Copierea a eșuat. Copiați manual adresa URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Opțiuni centrale" @@ -705,7 +724,7 @@ msgstr "Opțiuni centrale" msgid "Counting ({{files}} files found, {{size}})" msgstr "Numărătoare ({{fișiere}} fișiere găsite, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Doar eșecuri" @@ -717,7 +736,7 @@ msgstr "Creează un raport de defecțiune" msgid "Create folder?" msgstr "Creează director?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "S-a creat un nou utilizator cu drepturi limitate" @@ -725,7 +744,7 @@ msgstr "S-a creat un nou utilizator cu drepturi limitate" msgid "Creating bug report …" msgstr "Se creează un raport de defecțiuni ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Se creează un nou utilizator cu acces limitat ..." @@ -737,7 +756,7 @@ msgstr "Se creează directoarele destinație ..." msgid "Creating temporary backup …" msgstr "Se creează o copie de rezervă temporară ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -757,7 +776,7 @@ msgstr "Versiunea curentă este {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Conector S3 personalizat" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -809,11 +828,11 @@ msgstr "Bază de date ..." msgid "Days" msgstr "Zile" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Mod implicit" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Implicit ({{nume_canal}})" @@ -821,7 +840,7 @@ msgstr "Implicit ({{nume_canal}})" msgid "Default excludes" msgstr "Excluderi implicite" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Opțiunile prestabilite" @@ -849,6 +868,10 @@ msgstr "Șterge copiile de rezervă mai vechi de:" msgid "Delete local database" msgstr "Șterge baza de date locală" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Șterge fișierele la distanță" @@ -905,7 +928,8 @@ msgstr "Destinaţie" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Calea destinație" @@ -917,6 +941,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Inactiv" @@ -930,7 +958,7 @@ msgstr "Închide" msgid "Dismiss all" msgstr "Închide tot" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Afișare și temă de culoare" @@ -971,22 +999,22 @@ msgstr "Se descarcă actualizarea ..." msgid "Duplicate option {{opt}}" msgstr "Opțiunea de duplicare {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Site-ul web al Duplicati" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Forum-ul Duplicati" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1025,12 +1053,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Editați ca listă" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Editați ca text" @@ -1042,6 +1070,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Criptați fișierul" @@ -1061,7 +1093,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1175,7 +1207,7 @@ msgstr "Eroare" msgid "Error!" msgstr "Eroare!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Erori și accidente" @@ -1223,7 +1255,7 @@ msgstr "Excludeți expresia regulată" msgid "Existing file found" msgstr "Fișierul existent găsit" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1317,7 +1349,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Nu sa reușit restaurarea fișierelor: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Salvarea nu a reușit:" @@ -1346,7 +1378,7 @@ msgstr "Filtre" msgid "Finished!" msgstr "Terminat!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Prima configurare" @@ -1417,7 +1449,7 @@ msgstr "" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "" @@ -1433,7 +1465,7 @@ msgstr "Ascunde" msgid "Hide hidden folders" msgstr "Ascundeți folderele ascunse" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "Acasă" @@ -1442,7 +1474,7 @@ msgid "Hostnames" msgstr "" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "ore" @@ -1472,11 +1504,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1524,6 +1556,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Dacă nu introduceți o cheie API, este necesar numele locatarului" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1571,11 +1608,11 @@ msgstr "Includeți expresia" msgid "Include regular expression" msgstr "Includeți expresia regulată" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "Răspuns incorect, încercați din nou" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1588,7 +1625,7 @@ msgstr "informație" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Caractere nevalide în cale" @@ -1598,7 +1635,7 @@ msgstr "Caractere nevalide în cale" msgid "Invalid retention time" msgstr "Timp de retenție nevalid" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1626,7 +1663,7 @@ msgstr "" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Limba în interfața cu utilizatorul" @@ -1690,15 +1727,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "" "Local database for {{Backup.Backup.Name}} " msgid "Log data from the server" msgstr "Datele din jurnal de pe server" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "Deconectați-vă" @@ -1750,6 +1783,10 @@ msgstr "MByte" msgid "MByte/s" msgstr "MByte / s" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "întreținere" @@ -1760,7 +1797,7 @@ msgid "" " advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1780,7 +1817,7 @@ msgstr "Viteză maximă de descărcare" msgid "Max upload speed" msgstr "Viteză maximă de încărcare" -#: index.html:144 templates/addoredit.html:127 templates/addoredit.html:169 +#: index.html:145 templates/addoredit.html:127 templates/addoredit.html:169 #: templates/addoredit.html:382 templates/addoredit.html:95 #: templates/edituri.html:34 templates/restoredirect.html:34 msgid "Menu" @@ -1798,12 +1835,12 @@ msgstr "Baze de date Microsoft SQL" msgid "Minimum redundancy" msgstr "Redundanță minimă" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "Redundanța minimă este de 1,0" #: scripts/services/AppUtils.js:89 scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "Minute" @@ -1863,7 +1900,7 @@ msgstr "Nume" msgid "Never" msgstr "Nu" -#: templates/settings.html:11 +#: templates/changepassword.html:5 templates/settings.html:11 msgid "New Password" msgstr "" @@ -1871,7 +1908,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1890,11 +1927,11 @@ msgstr "Următor →" msgid "Next scheduled run:" msgstr "Următorul programat:" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "Următoarea sarcină programată:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "Următoarea sarcină:" @@ -1902,7 +1939,7 @@ msgstr "Următoarea sarcină:" msgid "Next time" msgstr "Data viitoare" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1915,9 +1952,9 @@ msgstr "Data viitoare" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nu" @@ -1954,7 +1991,7 @@ msgstr "" msgid "No passphrase entered" msgstr "Nu a fost introdusă nici o expresie de acces" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "Nu există sarcini programate" @@ -1962,7 +1999,7 @@ msgstr "Nu există sarcini programate" msgid "Non-matching passphrase" msgstr "Fraza de acces fără potrivire" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Nici unul / dezactivat" @@ -1981,16 +2018,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "O.K" @@ -2022,7 +2060,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2044,7 +2082,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2081,11 +2119,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opțiuni" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2095,7 +2133,7 @@ msgstr "" msgid "Original location" msgstr "Locația originală" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Alții" @@ -2131,7 +2169,7 @@ msgstr "Frazele de acces nu se potrivesc" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2142,8 +2180,8 @@ msgstr "Parola" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "" @@ -2152,7 +2190,7 @@ msgid "Path not found" msgstr "Calea nu a fost găsită" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Cale pe server" @@ -2161,15 +2199,15 @@ msgstr "Cale pe server" msgid "Path or subfolder in the bucket" msgstr "Cale sau subfolder în găleată" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pauză" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Întrerupeți după pornire sau hibernare" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "Opțiunile de întrerupere" @@ -2203,6 +2241,10 @@ msgstr "" msgid "Previous" msgstr "Anterior" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "" @@ -2211,7 +2253,7 @@ msgstr "" msgid "ProjectID is optional if the bucket exist" msgstr "ID-ul proiectului este opțional dacă există o cupă" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietate" @@ -2247,15 +2289,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Căile relative nu sunt permise" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "Reîncarcă" @@ -2263,14 +2325,34 @@ msgstr "Reîncarcă" msgid "Remote" msgstr "la distanta" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "" @@ -2319,7 +2401,7 @@ msgstr "Raportarea:" msgid "Reset" msgstr "restabili" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "Restabili" @@ -2377,7 +2459,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "Relua" @@ -2397,7 +2479,7 @@ msgstr "Fugiți acum" msgid "Running commandline entry" msgstr "Rulează intrarea în linia de comandă" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "Sarcina de funcționare:" @@ -2413,7 +2495,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Compatibil" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "La fel ca versiunea de instalare de bază: {{channelname}}" @@ -2461,7 +2543,7 @@ msgstr "Căutare" msgid "Search for files" msgstr "Căutați fișiere" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "secunde" @@ -2474,8 +2556,8 @@ msgstr "Selectați un nivel de jurnal și vedeți mesajele așa cum se întâmpl msgid "Select files" msgstr "Selectati fisierele" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2513,7 +2595,11 @@ msgstr "Serverul a fost întrerupt" msgid "Server state properties" msgstr "Proprietăți stare server" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Setări" @@ -2584,15 +2670,15 @@ msgstr "Sursă de directoare" msgid "Source:" msgstr "Sursă:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Protocoale standard" @@ -2619,19 +2705,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Opriți după fișierul curent" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Opreste-te acum" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Nu mai rulați backupul" @@ -2640,11 +2722,11 @@ msgstr "Nu mai rulați backupul" msgid "Stop running task" msgstr "Opriți executarea sarcinii" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "Oprire:" @@ -2685,7 +2767,7 @@ msgstr "Link-uri simbolice" msgid "System Files" msgstr "" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Implicit în sistem ({{levelname}})" @@ -2741,6 +2823,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2753,7 +2840,7 @@ msgstr "Test de conexiune" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2761,10 +2848,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2787,7 +2874,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Numele găleții ar trebui să fie toate literele mici, să se convertească " @@ -2799,19 +2886,19 @@ msgid "" "unencrypted file containing your passwords?" msgstr "" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tema intunecata (de Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Culoarea albastră implicită pe alb (de Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2838,7 +2925,7 @@ msgstr "" "Doriți să ÎNLOCUIți cheia gazdă CURRENT \"{{prev}}\" cu cheia gazdă REPORTED: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "" @@ -2890,7 +2977,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "Dosarul țintă conține fișiere criptate, furnizați expresia de acces" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2918,7 +3005,7 @@ msgstr "Luna aceasta" msgid "This week" msgstr "Săptămâna aceasta" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Setările clapetei" @@ -2931,6 +3018,10 @@ msgstr "Thu" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "La dosar" @@ -2949,7 +3040,7 @@ msgstr "" "Pentru a exporta fără o expresie de acces, debifați caseta \"Criptare " "fișier\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2977,7 +3068,7 @@ msgstr "Trust gazdă certificat?" msgid "Trust server certificate?" msgstr "Certificat de server de încredere?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3011,7 +3102,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Actualizați canalul" @@ -3031,19 +3122,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistica utilizării" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistici de utilizare, avertismente, erori și accidente" @@ -3071,17 +3161,17 @@ msgstr "Datele utilizatorului" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Utilizatorul are prea multe permisiuni" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Setările interfeței utilizatorului" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3145,7 +3235,7 @@ msgstr "Foarte puternic" msgid "Very weak" msgstr "Foarte slab" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "Vizitați-ne" @@ -3172,7 +3262,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Avertizări, erori și accidente" @@ -3210,7 +3300,7 @@ msgstr "Unde doriți să restaurați fișierele?" msgid "Years" msgstr "Ani" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3223,9 +3313,9 @@ msgstr "Ani" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "da" @@ -3264,13 +3354,15 @@ msgstr "În prezent, executați {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3315,7 +3407,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Trebuie să alegeți cel puțin un dosar sursă" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3328,7 +3420,7 @@ msgid "You must enter a passphrase or disable encryption" msgstr "" "Trebuie să introduceți o expresie de acces sau să dezactivați criptarea" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3338,11 +3430,11 @@ msgstr "" "Trebuie să introduceți un număr pozitiv de copii de rezervă pe care să le " "păstrați" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3356,11 +3448,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3381,11 +3473,11 @@ msgstr "Trebuie să completați numele de utilizator" msgid "You must fill in {{field}}" msgstr "Trebuie să completați {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Trebuie să selectați sau să completați AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Trebuie să selectați sau să completați serverul" @@ -3419,23 +3511,23 @@ msgstr "octet" msgid "byte/s" msgstr "byte / s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3450,31 +3542,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3489,11 +3585,11 @@ msgstr "" msgid "resume now" msgstr "reluați acum" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-ru.po b/Localizations/webroot/localization_webroot-ru.po index 0a2524976..6c4245ea8 100644 --- a/Localizations/webroot/localization_webroot-ru.po +++ b/Localizations/webroot/localization_webroot-ru.po @@ -51,7 +51,7 @@ msgstr "" msgid "- pick an option -" msgstr "- выберите параметр -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...загрузка..." @@ -75,23 +75,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "Ключ API" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -103,7 +103,7 @@ msgstr "О программе" msgid "About {{appname}}" msgstr "О {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Ключ доступа" @@ -131,7 +131,7 @@ msgstr "" msgid "Access to user interface" msgstr "Доступ в веб-интерфейс" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Имя учётной записи" @@ -164,8 +164,8 @@ msgstr "Добавить путь" msgid "Added" msgstr "Добавлено" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Изменить имя блока?" @@ -198,7 +198,7 @@ msgstr "Все виртуальные машины Hyper-V" msgid "All Microsoft SQL Databases" msgstr "Все базы данных Microsoft SQL" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -221,6 +221,10 @@ msgstr "Разрешить удалённый доступ (потребуетс msgid "Allowed days" msgstr "Разрешенные дни" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Существующий файл был найден по новому пути" @@ -245,7 +249,7 @@ msgstr "" "\n" " Вы хотите использовать существующую базу данных?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Анонимные отчёты об использовании" @@ -253,20 +257,26 @@ msgstr "Анонимные отчёты об использовании" msgid "Applications" msgstr "Приложения" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Как командная строка" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Метод аутентификации" @@ -298,7 +308,7 @@ msgstr "B2 Application ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -306,7 +316,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -348,7 +358,7 @@ msgstr "Хранение копий" msgid "Backup:" msgstr "Резервная копия:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -360,7 +370,7 @@ msgstr "Битый доступ" msgid "Browse" msgstr "Обзор" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Браузер по-умолчанию" @@ -368,14 +378,14 @@ msgstr "Браузер по-умолчанию" msgid "Bucket create location" msgstr "Место создания блока" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -384,7 +394,7 @@ msgstr "Место создания блока" msgid "Bucket name" msgstr "Имя блока" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -462,13 +472,13 @@ msgstr "" msgid "Cache Files" msgstr "Кеш файлы" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -483,14 +493,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Отмена" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -507,7 +521,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -547,7 +561,7 @@ msgstr "Для начала выберите тип хранилища" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Нажмите на ссылку AuthID для создания AuthID" @@ -676,7 +690,7 @@ msgstr "Потеряно соединение" msgid "Connection worked!" msgstr "Подключение работает!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Имя контейнера" @@ -718,7 +732,7 @@ msgstr "Копирование не удалось. Скопируйте URL-а msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Основные параметры" @@ -726,7 +740,7 @@ msgstr "Основные параметры" msgid "Counting ({{files}} files found, {{size}})" msgstr "Сканирование (найдено {{files}} файлов, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Только падения" @@ -738,7 +752,7 @@ msgstr "Создать отчет об ошибке…" msgid "Create folder?" msgstr "Создать папку?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Создан новый ограниченный пользователь" @@ -746,7 +760,7 @@ msgstr "Создан новый ограниченный пользовател msgid "Creating bug report …" msgstr "Создание отчета об ошибке…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Создание нового пользователя с ограниченным доступом…" @@ -758,7 +772,7 @@ msgstr "Создание целевых папок…" msgid "Creating temporary backup …" msgstr "Создание временной резервной копии…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -778,7 +792,7 @@ msgstr "Текущая версия — {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Пользовательский S3 endpoint" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Пользовательский спутник" @@ -830,11 +844,11 @@ msgstr "База данных…" msgid "Days" msgstr "Дней" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "По умолчанию" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "По умолчанию ({{channelname}})" @@ -842,7 +856,7 @@ msgstr "По умолчанию ({{channelname}})" msgid "Default excludes" msgstr "Исключения по-умолчанию" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Параметры по умолчанию" @@ -870,6 +884,10 @@ msgstr "Удалить копии старше" msgid "Delete local database" msgstr "Удалить локальную базу данных" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Удалить файлы с диска" @@ -924,7 +942,8 @@ msgstr "Хранение" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Путь назначения" @@ -936,6 +955,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Отключено" @@ -949,7 +972,7 @@ msgstr "Скрыть" msgid "Dismiss all" msgstr "Отклонить все" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Отображение и цветовая тема" @@ -998,14 +1021,14 @@ msgstr "Сайт Duplicati " msgid "Duplicati forum" msgstr "Форум Duplicati" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1047,12 +1070,12 @@ msgstr "" "передаваемых данных с удалённых серверов." #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Редактировать как список" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Редактировать как текст" @@ -1064,6 +1087,10 @@ msgstr "Изменить... " msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Шифровать файл" @@ -1083,7 +1110,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Кодовая фраза для шифрования" @@ -1194,7 +1221,7 @@ msgstr "Ошибка" msgid "Error!" msgstr "Ошибка!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Ошибки и падения" @@ -1242,7 +1269,7 @@ msgstr "Регулярное выражение для исключения" msgid "Existing file found" msgstr "Найден существующий файл" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1260,7 +1287,7 @@ msgstr "Экспорт конфигурации" #: templates/export.html:18 msgid "Export passwords" -msgstr "Экспорт паролей" +msgstr "Экспортировать пароли" #: templates/home.html:30 msgid "Export …" @@ -1336,7 +1363,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Не удалось восстановить файлы: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Не удалось сохранить:" @@ -1365,7 +1392,7 @@ msgstr "Фильтры" msgid "Finished!" msgstr "Готово!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Настройка при первом запуске" @@ -1436,7 +1463,7 @@ msgstr "Сгенерировать политики доступа IAM" msgid "Getting file versions …" msgstr "Получение версий файлов…" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Электронная почта группы" @@ -1461,7 +1488,7 @@ msgid "Hostnames" msgstr "Имя хоста" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "часов" @@ -1491,11 +1518,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1544,6 +1571,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Если вы не вводите ключ API, требуется имя арендатора" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1595,7 +1627,7 @@ msgstr "Регулярное выражение для включения" msgid "Incorrect answer, try again" msgstr "Неправильный ответ, попробуйте еще раз" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1610,7 +1642,7 @@ msgstr "Информация" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Недопустимые символы в пути" @@ -1620,7 +1652,7 @@ msgstr "Недопустимые символы в пути" msgid "Invalid retention time" msgstr "Недопустимое время хранения" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1648,7 +1680,7 @@ msgstr "Хранить все копии" msgid "Keystone API version" msgstr "Версия Keystone API" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Язык пользовательского интерфейса" @@ -1717,10 +1749,6 @@ msgstr "" msgid "Loading …" msgstr "Загрузка..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Локальный репозиторий" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1924,7 +1956,7 @@ msgstr "Следующая задача:" msgid "Next time" msgstr "В следующий раз" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1937,9 +1969,9 @@ msgstr "В следующий раз" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Нет" @@ -1982,7 +2014,7 @@ msgstr "Нет запланированных задач" msgid "Non-matching passphrase" msgstr "Кодовые фразы не совпадают" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Нет / отключено" @@ -2003,17 +2035,17 @@ msgstr "" "Ничего не будет удалено. Размер резервной копии будет расти с каждым " "изменением." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2045,7 +2077,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2069,7 +2101,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Открыто" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2106,11 +2138,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Параметры" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2122,7 +2154,7 @@ msgstr "" msgid "Original location" msgstr "Исходное местоположение" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Другие" @@ -2162,7 +2194,7 @@ msgstr "Кодовые фразы не совпадают" msgid "Passphrases do not match" msgstr "Парольные фразы не совпадают" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2173,8 +2205,8 @@ msgstr "Пароль" msgid "Patching files with local blocks …" msgstr "Исправление файлов локальными блоками…" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Путь" @@ -2183,7 +2215,7 @@ msgid "Path not found" msgstr "Путь не найден" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Путь на сервере" @@ -2192,11 +2224,11 @@ msgstr "Путь на сервере" msgid "Path or subfolder in the bucket" msgstr "Путь или подпапка в bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Пауза" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Отложенный запуск после включения или выхода из спящего режима" @@ -2234,6 +2266,10 @@ msgstr "Запретить автоматический вход из значк msgid "Previous" msgstr "Назад" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Прогресс:" @@ -2242,7 +2278,7 @@ msgstr "Прогресс:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID необязателен, если существует bucket" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Проприетарное" @@ -2278,10 +2314,30 @@ msgstr "Восстановление базы данных…" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Регистрация временной резервной копии…" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Относительные пути не допускаются" @@ -2294,14 +2350,34 @@ msgstr "Обновить" msgid "Remote" msgstr "Удаленный" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Удаленный путь" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Удаленный Репозиторий" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Удаленный путь" @@ -2444,7 +2520,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 совместимый" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Такой же как в базовой версии: {{channelname}}" @@ -2492,7 +2568,7 @@ msgstr "Поиск" msgid "Search for files" msgstr "Поиск файлов" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Секунд" @@ -2507,8 +2583,8 @@ msgstr "" msgid "Select files" msgstr "Выбор файлов" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Сервер" @@ -2546,6 +2622,10 @@ msgstr "Сервер приостановлен" msgid "Server state properties" msgstr "Свойства состояния сервера" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Настройки" @@ -2619,17 +2699,17 @@ msgstr "Исходные папки" msgid "Source:" msgstr "Источник:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Специальные сборки только для разработчиков. Не рекомендуется использовать " "для сохранения важных данных." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Стандартные протоколы" @@ -2656,19 +2736,15 @@ msgstr "Начало восстановления…" msgid "Starting the restore process …" msgstr "Запуск процесса восстановления…" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Остановить после текущего файла" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Остановиться после текущего файла" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Остановить сейчас" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Остановить резервное копирование" @@ -2722,7 +2798,7 @@ msgstr "Символическая ссылка" msgid "System Files" msgstr "Системные Файлы" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "По умолчанию ({{levelname}})" @@ -2778,6 +2854,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Этап проверки" @@ -2790,7 +2871,7 @@ msgstr "Проверить доступ" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Проверка разрешений…" @@ -2798,10 +2879,10 @@ msgstr "Проверка разрешений…" msgid "Testing …" msgstr "Тестирование…" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2828,7 +2909,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Имя bucket должно быть строчным, преобразовать автоматически?" @@ -2844,15 +2925,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Тёмная тема (от Michael)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Стандартная тема синий на белом (от Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2881,7 +2962,7 @@ msgstr "" "Вы хотите ЗАМЕНИТЬ ваш ТЕКУЩИЙ ключ узла «{{prev}}» ПОЛУЧЕННЫМ ключом хоста: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Пароли не совпадают" @@ -2933,7 +3014,7 @@ msgstr "" "Целевая папка содержит зашифрованные файлы, пожалуйста, укажите кодовую " "фразу" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2961,7 +3042,7 @@ msgstr "В этом месяце" msgid "This week" msgstr "На этой неделе" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Параметры ограничения скорости" @@ -2974,6 +3055,10 @@ msgstr "Чт" msgid "Time" msgstr "Время" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "В файл" @@ -2991,7 +3076,7 @@ msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" "Чтобы экспортировать без кодовой фразы, снимите флажок «Зашифровать файл»" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3024,7 +3109,7 @@ msgstr "Доверять сертификату хоста?" msgid "Trust server certificate?" msgstr "Доверять сертификату сервера?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3061,7 +3146,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Канал обновлений" @@ -3081,18 +3166,18 @@ msgstr "Загруженные файлы" msgid "Uploading verification file …" msgstr "Загрузить проверочный файл…" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Статистика использования" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Статистика использования, предупреждения, ошибки и падения" @@ -3120,17 +3205,17 @@ msgstr "Данные пользователя" msgid "User domain name" msgstr "Доменное имя пользователя" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Пользователь имеет слишком много разрешений" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Настройки интерфейса" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3220,7 +3305,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Ожидание завершения выгрузки…" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Предупреждения, ошибки и падения" @@ -3257,7 +3342,7 @@ msgstr "Куда вы хотите восстановить файлы?" msgid "Years" msgstr "Лет" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3270,9 +3355,9 @@ msgstr "Лет" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Да" @@ -3311,18 +3396,16 @@ msgstr "Вы используете {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Вы можете остановить резервное копирование после завершения загрузки всех " -"текущих файлов." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Вы можете немедленно остановить задачу или позволить процессу продолжить " -"работу с текущим файлом, а затем остановить." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3366,7 +3449,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Вы должны выбрать по крайней мере одну исходную папку" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Вы должны ввести доменное имя, чтобы использовать v3 API" @@ -3378,7 +3461,7 @@ msgstr "Вам необходимо ввести имя резервной ко msgid "You must enter a passphrase or disable encryption" msgstr "Вы должны ввести кодовую фразу или отключить шифрование" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Вы должны ввести пароль, чтобы использовать v3 API" @@ -3386,11 +3469,11 @@ msgstr "Вы должны ввести пароль, чтобы использо msgid "You must enter a positive number of backups to keep" msgstr "Необходимо ввести положительное число резервных копий для хранения" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Вы должны ввести имя проекта, чтобы использовать v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3402,11 +3485,11 @@ msgstr "Необходимо ввести допустимый срок врем msgid "You must enter a valid retention policy string" msgstr "Необходимо ввести допустимое значение политики хранения" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3427,11 +3510,11 @@ msgstr "Вы должны заполнить имя пользователя" msgid "You must fill in {{field}}" msgstr "Вы должны заполнить {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Вы должны выбрать или заполнить AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Вы должны выбрать или заполнить сервер" @@ -3464,23 +3547,23 @@ msgstr "байт" msgid "byte/s" msgstr "байт/сек" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3495,29 +3578,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3530,11 +3621,11 @@ msgstr "" msgid "resume now" msgstr "возобновить сейчас" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "если вы явно не указываете --group-id" diff --git a/Localizations/webroot/localization_webroot-sr_RS.po b/Localizations/webroot/localization_webroot-sr_RS.po index 9a3fec953..48db73571 100644 --- a/Localizations/webroot/localization_webroot-sr_RS.po +++ b/Localizations/webroot/localization_webroot-sr_RS.po @@ -41,7 +41,7 @@ msgstr "" msgid "- pick an option -" msgstr "- odaberite opciju -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...učitavanje..." @@ -65,23 +65,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API ključ" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -93,7 +93,7 @@ msgstr "O nama" msgid "About {{appname}}" msgstr "O aplikaciji {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Pristupni ključ - access key" @@ -121,7 +121,7 @@ msgstr "" msgid "Access to user interface" msgstr "Pristup korisničkom interfejsu" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Korisničko ime" @@ -154,8 +154,8 @@ msgstr "Dodaj putanju" msgid "Added" msgstr "Dodato" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Prilagodi ime segment-a?" @@ -188,7 +188,7 @@ msgstr "Sve Hyper-V mašine" msgid "All Microsoft SQL Databases" msgstr "Sve Microsoft SQL baze podataka" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -210,6 +210,10 @@ msgstr "Dozvoli udaljeni pristup (zahteva restartovanje)" msgid "Allowed days" msgstr "Dozvoljeni dani" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "Postojeća datoteka je pronađena na novoj lokaciji" @@ -234,7 +238,7 @@ msgstr "" "\n" "Da li želite da koristite postojeću bazu?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonimni izveštaj o korišćenju" @@ -242,20 +246,26 @@ msgstr "Anonimni izveštaj o korišćenju" msgid "Applications" msgstr "Aplikacije" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Kao komandna linija" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Metoda autentifikacije" @@ -287,7 +297,7 @@ msgstr "B2 ID aplikacije" msgid "B2 Application Key" msgstr "B2 aplikacioni ključ" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 ID naloga za skladište u oblaku" @@ -295,7 +305,7 @@ msgstr "B2 ID naloga za skladište u oblaku" msgid "B2 Cloud Storage Application ID" msgstr "B2 ID aplikacije za skladište u oblaku" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 ključ aplikacije za skladište u oblaku" @@ -337,7 +347,7 @@ msgstr "Čuvanje rezervne kopije" msgid "Backup:" msgstr "Rezervna kopija:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -349,7 +359,7 @@ msgstr "Neispravan pristup" msgid "Browse" msgstr "Pregledaj" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Podrazumvani pretraživač" @@ -357,14 +367,14 @@ msgstr "Podrazumvani pretraživač" msgid "Bucket create location" msgstr "Segment kreira lokaciju" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -373,7 +383,7 @@ msgstr "Segment kreira lokaciju" msgid "Bucket name" msgstr "Ime segmenta" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -451,13 +461,13 @@ msgstr "" msgid "Cache Files" msgstr "Keš fajlovi" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -472,14 +482,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Otkaži" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -496,7 +510,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -536,7 +550,7 @@ msgstr "Izaberite tip skladištenja da biste započeli" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Kliknite na vezu AuthID da biste kreirali AuthID" @@ -665,7 +679,7 @@ msgstr "Veza izgubljena" msgid "Connection worked!" msgstr "Veza je radila!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Naziv kontejnera" @@ -707,7 +721,7 @@ msgstr "Kopiranje nije uspelo. Molimo ručno kopirajte URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Osnovne opcije" @@ -715,7 +729,7 @@ msgstr "Osnovne opcije" msgid "Counting ({{files}} files found, {{size}})" msgstr "Brojanjem ({{files}} fajlova pronađeno, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Samo srušeni" @@ -727,7 +741,7 @@ msgstr "Kreira se izveštaj o greškama ..." msgid "Create folder?" msgstr "Napraviti fasciklu?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Napravljen novi korisnik sa ograničenjima" @@ -735,7 +749,7 @@ msgstr "Napravljen novi korisnik sa ograničenjima" msgid "Creating bug report …" msgstr "Kreira se izveštaj o greškama ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Pravljenje novog korisnika sa ograničenim pristupom …" @@ -747,7 +761,7 @@ msgstr "Pravljenje ciljnih foldera ..." msgid "Creating temporary backup …" msgstr "Pravljenje privremene rezervne kopije …" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -767,7 +781,7 @@ msgstr "Trenutna verzija je {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Prilagođena krajnja tačka S3" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Prilagođeni satelit" @@ -819,11 +833,11 @@ msgstr "Baza podataka ..." msgid "Days" msgstr "Dana" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Podrazumevano" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Podrazumevano ({{channelname}})" @@ -831,7 +845,7 @@ msgstr "Podrazumevano ({{channelname}})" msgid "Default excludes" msgstr "Podrazumevano isključuje" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Podrazumevane opcije" @@ -859,6 +873,10 @@ msgstr "Izbrisati rezervne kopije koje su starije od" msgid "Delete local database" msgstr "Obriši lokalnu bazu podataka" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Obriši udaljene datoteke" @@ -914,7 +932,8 @@ msgstr "Odredište" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Putanja odredišta" @@ -926,6 +945,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Onemogućeno" @@ -939,7 +962,7 @@ msgstr "Odbaci" msgid "Dismiss all" msgstr "Odbaci sve" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Ekran i tema boja" @@ -988,14 +1011,14 @@ msgstr "Duplicati veb sajt" msgid "Duplicati forum" msgstr "Duplicati forum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1034,12 +1057,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Izmeni kao listu" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Izmeni kao tekst" @@ -1051,6 +1074,10 @@ msgstr "Izmeni ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Šifrujte fajl" @@ -1070,7 +1097,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Šifrovanje pristupne fraze" @@ -1184,7 +1211,7 @@ msgstr "Greška" msgid "Error!" msgstr "Greška!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Greške i rušenja" @@ -1232,7 +1259,7 @@ msgstr "Isključi regularni izraz" msgid "Existing file found" msgstr "Pronađen je postojeći fajl" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Eksperimentalno" @@ -1326,7 +1353,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Vraćanje fajlova nije uspelo: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Čuvanje nije uspelo:" @@ -1355,7 +1382,7 @@ msgstr "Filteri" msgid "Finished!" msgstr "Završeno!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Podešavanje za prvo pokretanje" @@ -1426,7 +1453,7 @@ msgstr "" msgid "Getting file versions …" msgstr "Dohvatanje verzija fajla ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Grupna e-pošta" @@ -1451,7 +1478,7 @@ msgid "Hostnames" msgstr "Imena hostova" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Sati" @@ -1481,11 +1508,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1535,6 +1562,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Ako ne unesete API ključ, potrebno je ime zakupca" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1586,7 +1618,7 @@ msgstr "Uključite regularni izraz" msgid "Incorrect answer, try again" msgstr "Netačan odgovor, pokušajte ponovo" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1600,7 +1632,7 @@ msgstr "Informacije" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Nevažeći znakovi u putanji" @@ -1610,7 +1642,7 @@ msgstr "Nevažeći znakovi u putanji" msgid "Invalid retention time" msgstr "Nevažeće vreme zadržavanja" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1638,7 +1670,7 @@ msgstr "Čuvajte sve rezervne kopije" msgid "Keystone API version" msgstr "Keystone API verzija" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Jezik u korisničkom interfejsu" @@ -1705,10 +1737,6 @@ msgstr "" msgid "Loading …" msgstr "Učitavanje ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Lokalno spremište" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1912,7 +1944,7 @@ msgstr "Sledeći zadatak:" msgid "Next time" msgstr "Sledeći put" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1925,9 +1957,9 @@ msgstr "Sledeći put" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Ne" @@ -1970,7 +2002,7 @@ msgstr "Nema zakazanih zadataka" msgid "Non-matching passphrase" msgstr "Pristupna fraza koja se ne podudara" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Ništa / onemogućeno" @@ -1991,17 +2023,17 @@ msgstr "" "Ništa neće biti izbrisano. Veličina rezervne kopije će rasti sa svakom " "promenom." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "U redu" @@ -2033,7 +2065,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2057,7 +2089,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Otvoren" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2094,11 +2126,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Opcije" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2108,7 +2140,7 @@ msgstr "" msgid "Original location" msgstr "Originalna lokacija" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Ostalo" @@ -2148,7 +2180,7 @@ msgstr "Lozinke se ne poklapaju" msgid "Passphrases do not match" msgstr "Pristupne fraze se ne podudaraju" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2159,8 +2191,8 @@ msgstr "Lozinka" msgid "Patching files with local blocks …" msgstr "Zakrpa fajlova sa lokalnim blokovima …" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Putanja" @@ -2169,7 +2201,7 @@ msgid "Path not found" msgstr "Putanja nije pronađena" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Putanja na serveru" @@ -2178,11 +2210,11 @@ msgstr "Putanja na serveru" msgid "Path or subfolder in the bucket" msgstr "Putanja ili podfascikla u segment-u" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Pauza" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pauziraj nakon pokretanja ili hibernacije" @@ -2221,6 +2253,10 @@ msgstr "Sprečite automatsko prijavljivanje ikonom na traci" msgid "Previous" msgstr "Prethodno" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Napredak:" @@ -2229,7 +2265,7 @@ msgstr "Napredak:" msgid "ProjectID is optional if the bucket exist" msgstr "ID projekta je opcioni ako segment postoji" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Vlasnički" @@ -2265,10 +2301,30 @@ msgstr "Ponovo kreiranje baze podataka …" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrovanje privremene rezervne kopije …" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relativne putanje nisu dozvoljene" @@ -2281,14 +2337,34 @@ msgstr "Učitaj ponovo" msgid "Remote" msgstr "Udaljeno" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Udaljena putanja" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Udaljeno spremište" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Udaljena putanja" @@ -2431,7 +2507,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 kompatibilno" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Isto kao i verzija osnovne instalacije: {{channelname}}" @@ -2479,7 +2555,7 @@ msgstr "Pretraga" msgid "Search for files" msgstr "Pretraga fajlova" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekunde" @@ -2492,8 +2568,8 @@ msgstr "Izaberite nivo dnevnika i pogledajte poruke kako se dešavaju:" msgid "Select files" msgstr "Izaberite fajlove" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2531,6 +2607,10 @@ msgstr "Server je pauziran" msgid "Server state properties" msgstr "Opcije stanja servera" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Podešavanja" @@ -2604,16 +2684,16 @@ msgstr "Izvorne fascikle" msgid "Source:" msgstr "Izvor:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Posebne verzije samo za programere. Nije za upotrebu sa važnim podacima." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standardni protokoli" @@ -2640,19 +2720,15 @@ msgstr "Startujem obnavljanje ..." msgid "Starting the restore process …" msgstr "Startujem proces obnavljanja ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Zaustavi nakon trenutnog fajla" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Zaustavi nakon trenutnog fajla" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Zaustavi odmah" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Zaustavi pokrenutu rezervnu kopiju" @@ -2706,7 +2782,7 @@ msgstr "Simbolička veza" msgid "System Files" msgstr "Sistemski fajlovi" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Podrazumevani sistem ({{levelname}})" @@ -2762,6 +2838,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Faza testiranje" @@ -2774,7 +2855,7 @@ msgstr "Ispitaj vezu" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Ispitivanje dozvola ..." @@ -2782,10 +2863,10 @@ msgstr "Ispitivanje dozvola ..." msgid "Testing …" msgstr "Ispitivanje ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2812,7 +2893,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" "Naziv segmenta treba da bude malim slovima, da li da se automatski " @@ -2830,15 +2911,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Tamna tema (napravio Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Podrazumevana tema plavo na belom (napravio Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2867,7 +2948,7 @@ msgstr "" "Da li želite da ZAMENITE svoj TRENUTNI ključ hosta \"{{prev}}\" sa PRIJAVLJENIM ključem hosta: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Lozinke se ne poklapaju" @@ -2919,7 +3000,7 @@ msgid "" msgstr "" "Ciljana fasckla sadrži šifrovane fajlove, molimo unesite pristupnu frazu" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2947,7 +3028,7 @@ msgstr "Ovog meseca" msgid "This week" msgstr "Ove sedmice" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Podešavanja regulacije" @@ -2960,6 +3041,10 @@ msgstr "Čet" msgid "Time" msgstr "Vreme" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "U datoteku" @@ -2976,7 +3061,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "Za izvoz bez lozinke, polje \"Šifruj datoteku\" ne treba da bude označeno" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3011,7 +3096,7 @@ msgstr "Verujete sertifikatu hosta?" msgid "Trust server certificate?" msgstr "Veruj sertifikatu servera?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3048,7 +3133,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Ažurirajte kanal" @@ -3068,18 +3153,18 @@ msgstr "Otpremanje fajlova" msgid "Uploading verification file …" msgstr "Otpremanje fajla za verifikaciju …" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Statistika upotrebe" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Statistika korišćenja, upozorenja, greške i rušenja" @@ -3107,17 +3192,17 @@ msgstr "Podaci o korisniku" msgid "User domain name" msgstr "Ime korisničkog domena" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Korisnik ima previše dozvola" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Podešavanja korisničkog interfejsa" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3207,7 +3292,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Čeka se da se otpremanje završi …" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Upozorenja, greške i padovi" @@ -3244,7 +3329,7 @@ msgstr "Gde želite da vratite fajlove?" msgid "Years" msgstr "Godina" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3257,9 +3342,9 @@ msgstr "Godina" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Da" @@ -3298,18 +3383,16 @@ msgstr "Trenutno koristite {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Možete da zaustavite pravljenje rezervne kopije nakon što se završi bilo " -"koji fajl koji je trenutno u toku." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Možete odmah zaustaviti zadatak ili dozvoliti procesu da nastavi sa " -"trenutnim fajlom, a zatim ga zaustavi." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3353,7 +3436,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Morate odabrati najmanje jednu izvornu fasciklu" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Morate uneti naziv domena da biste koristili v3 API" @@ -3365,7 +3448,7 @@ msgstr "Morate uneti naziv za rezervnu kopiju" msgid "You must enter a passphrase or disable encryption" msgstr "Morate uneti lozinku ili isključiti šifrovanje" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Morate uneti lozinku da biste koristili v3 API" @@ -3373,12 +3456,12 @@ msgstr "Morate uneti lozinku da biste koristili v3 API" msgid "You must enter a positive number of backups to keep" msgstr "Morate da unesete važeće vreme trajanje za čuvanje rezervnih kopija" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" "Morate da unesete ime zakupca (aka projekta) da biste koristili v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3390,11 +3473,11 @@ msgstr "Morate da unesete važeće vreme trajanja za čuvanja rezervnih kopija" msgid "You must enter a valid retention policy string" msgstr "Morate da unesete važeći niz politike retencije" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3415,11 +3498,11 @@ msgstr "Morate uneti korisničko ime" msgid "You must fill in {{field}}" msgstr "Morate uneti {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Morate izabrati ili uneti AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Morate izabrati ili uneti server" @@ -3451,23 +3534,23 @@ msgstr "bajt" msgid "byte/s" msgstr "bajt/ova" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3482,29 +3565,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3517,11 +3608,11 @@ msgstr "" msgid "resume now" msgstr "nastavi odmah" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "osim ako izričito ne navedete --group-id" diff --git a/Localizations/webroot/localization_webroot-sv_SE.po b/Localizations/webroot/localization_webroot-sv_SE.po index 019a4112c..e119641bf 100644 --- a/Localizations/webroot/localization_webroot-sv_SE.po +++ b/Localizations/webroot/localization_webroot-sv_SE.po @@ -40,7 +40,7 @@ msgstr "" msgid "- pick an option -" msgstr "- välj ett alternativ -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...laddar..." @@ -64,23 +64,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API-nyckel" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -92,7 +92,7 @@ msgstr "Om" msgid "About {{appname}}" msgstr "Om {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Åtkomstnyckel" @@ -120,7 +120,7 @@ msgstr "" msgid "Access to user interface" msgstr "Access till användarinterface" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "Kontonamn" @@ -153,8 +153,8 @@ msgstr "Lägg till sökväg" msgid "Added" msgstr "Sparad" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "Justera \"bucket name\"?" @@ -187,7 +187,7 @@ msgstr "Alla Hyper-V datorer" msgid "All Microsoft SQL Databases" msgstr "Alla Microsoft SQL-databaser" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -209,6 +209,10 @@ msgstr "Tillåt fjärrstyrning (kräver omstart)" msgid "Allowed days" msgstr "Tillåtna dagar" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "En existerande fil hittades på den nya platsen" @@ -233,7 +237,7 @@ msgstr "" "\n" "Vill du använda den befintliga databasen?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "Anonym användarrapport" @@ -241,20 +245,26 @@ msgstr "Anonym användarrapport" msgid "Applications" msgstr "Applikationer" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "Som kommandorad" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "Autentiseringsmetod" @@ -286,7 +296,7 @@ msgstr "B2 Application ID" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage Account ID" @@ -294,7 +304,7 @@ msgstr "B2 Cloud Storage Account ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 Cloud Storage Application ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -336,7 +346,7 @@ msgstr "Backup-bibehållning" msgid "Backup:" msgstr "Säkerhetskopia:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -348,7 +358,7 @@ msgstr "Trasig åtkomst" msgid "Browse" msgstr "Bläddra" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "Webbläsarens standard" @@ -356,14 +366,14 @@ msgstr "Webbläsarens standard" msgid "Bucket create location" msgstr "Bucket skapa plats" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -372,7 +382,7 @@ msgstr "Bucket skapa plats" msgid "Bucket name" msgstr "Bucket namn" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -451,13 +461,13 @@ msgstr "" msgid "Cache Files" msgstr "Cachefiler" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Kanariefågel" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -472,14 +482,18 @@ msgstr "Kanariefågel" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Avbryt" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -496,7 +510,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -536,7 +550,7 @@ msgstr "Välj en lagringstyp för att börja" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "Klicka på AuthID-länken för att skapa ett AuthID" @@ -665,7 +679,7 @@ msgstr "Anslutning avbruten" msgid "Connection worked!" msgstr "Anslutning OK!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "Behållarnamn" @@ -707,7 +721,7 @@ msgstr "Kopering misslyckades, var vänlig kopiera URLen manuellt" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "Kärnalternativ" @@ -715,7 +729,7 @@ msgstr "Kärnalternativ" msgid "Counting ({{files}} files found, {{size}})" msgstr "Beräknar ({{files}} filer hittade, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "Endast kraschar" @@ -727,7 +741,7 @@ msgstr "Skapa buggrapport" msgid "Create folder?" msgstr "Skapa mapp?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "Skapa ny begränsad användare" @@ -735,7 +749,7 @@ msgstr "Skapa ny begränsad användare" msgid "Creating bug report …" msgstr "Skapar felrapport ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "Skapar ny användare med begränsad åtkomst …" @@ -747,7 +761,7 @@ msgstr "Skapar målmappar …" msgid "Creating temporary backup …" msgstr "Skapar temporär säkerhetskopia ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -767,7 +781,7 @@ msgstr "Aktuell version är {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "Anpassad S3-slutpunkt" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "Anpassad Satellit" @@ -819,11 +833,11 @@ msgstr "Databas ..." msgid "Days" msgstr "Dagar" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "Standard" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "Standard ({{channelname}})" @@ -831,7 +845,7 @@ msgstr "Standard ({{channelname}})" msgid "Default excludes" msgstr "Standard exkluderingar" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "Standardalternativ" @@ -859,6 +873,10 @@ msgstr "Radera säkerhetskopior äldre än" msgid "Delete local database" msgstr "Radera lokal databas" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "Radera målfiler" @@ -913,7 +931,8 @@ msgstr "Destination" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "Målsökväg" @@ -925,6 +944,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "Avstängd" @@ -938,7 +961,7 @@ msgstr "Avfärda" msgid "Dismiss all" msgstr "Avfärda allt" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "Visnings- och färgtema" @@ -987,14 +1010,14 @@ msgstr "Duplicatis webbsida" msgid "Duplicati forum" msgstr "Duplicatis forum" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1033,12 +1056,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "Ändra som lista" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "Ändra som text" @@ -1050,6 +1073,10 @@ msgstr "Ändra ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "Kryptera fil" @@ -1069,7 +1096,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "Ange krypteringslösenord" @@ -1183,7 +1210,7 @@ msgstr "Fel" msgid "Error!" msgstr "Fel!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "Fel och kraschar" @@ -1231,7 +1258,7 @@ msgstr "Uteslut enligt reguljärt uttryck" msgid "Existing file found" msgstr "Filen existerar redan" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimentell" @@ -1325,7 +1352,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "Misslyckades med att återställa filer: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "Misslyckades med att spara:" @@ -1354,7 +1381,7 @@ msgstr "Filter" msgid "Finished!" msgstr "Klar!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "Nyinstallationsinställningar" @@ -1425,7 +1452,7 @@ msgstr "" msgid "Getting file versions …" msgstr "Hämtar filversioner ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "Grupp-epost" @@ -1450,7 +1477,7 @@ msgid "Hostnames" msgstr "Värdnamn" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "Timmar" @@ -1480,11 +1507,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1533,6 +1560,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "Om du inte anger en API-nyckel krävs \"tenant name\"" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1584,7 +1616,7 @@ msgstr "Inkludera enligt reguljärt uttryck" msgid "Incorrect answer, try again" msgstr "Felaktigt svar, försök igen" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1599,7 +1631,7 @@ msgstr "Information" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "Ogiltiga tecken i sökvägen" @@ -1609,7 +1641,7 @@ msgstr "Ogiltiga tecken i sökvägen" msgid "Invalid retention time" msgstr "Ogiltig bibehållningstid" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1637,7 +1669,7 @@ msgstr "Behåll alla säkerhetskopior" msgid "Keystone API version" msgstr "Keystone API-version" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "Språk i användargränssnittet" @@ -1703,10 +1735,6 @@ msgstr "" msgid "Loading …" msgstr "Laddar ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "Lokalt arkiv" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1910,7 +1942,7 @@ msgstr "Nästa uppgift:" msgid "Next time" msgstr "Nästa gång" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1923,9 +1955,9 @@ msgstr "Nästa gång" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "Nej" @@ -1967,7 +1999,7 @@ msgstr "Inga schemalagda uppgifter" msgid "Non-matching passphrase" msgstr "Lösenfras som inte matchar" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "Ingen / inaktiverad" @@ -1988,17 +2020,17 @@ msgstr "" "Ingenting kommer att raderas. Storleken på säkerhetskopieringen kommer att " "växa med varje ändring." -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "OK" @@ -2030,7 +2062,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2054,7 +2086,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "Öppnad" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2091,11 +2123,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "Alternativ" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2105,7 +2137,7 @@ msgstr "" msgid "Original location" msgstr "Ursprunglig plats" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Andra" @@ -2145,7 +2177,7 @@ msgstr "Lösenfraser matchar inte" msgid "Passphrases do not match" msgstr "Lösenfraser matchar inte" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2156,8 +2188,8 @@ msgstr "Lösenord" msgid "Patching files with local blocks …" msgstr "Patchar filer med lokala block..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "Sökväg" @@ -2166,7 +2198,7 @@ msgid "Path not found" msgstr "Sökvägen hittades inte" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "Sökväg på servern" @@ -2175,11 +2207,11 @@ msgstr "Sökväg på servern" msgid "Path or subfolder in the bucket" msgstr "Sökväg eller undermapp i bucket" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "Paus" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "Pausa efter uppstart eller viloläge" @@ -2217,6 +2249,10 @@ msgstr "Förhindra att tray-icon automatiskt loggar in" msgid "Previous" msgstr "Tidigare" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "Framsteg:" @@ -2225,7 +2261,7 @@ msgstr "Framsteg:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID är valfritt om bucket finns" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "Proprietär" @@ -2261,10 +2297,30 @@ msgstr "Återskapar databas..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "Registrerar tillfällig säkerhetskopia …" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "Relativa sökvägar är inte tillåtna" @@ -2277,14 +2333,34 @@ msgstr "Ladda om" msgid "Remote" msgstr "Fjärr" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "Fjärrsökväg " -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "Fjärr Repository" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "Fjärrsökväg " @@ -2427,7 +2503,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 Kompatibel" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "Samma som basinstallationsversionen: {{channelname}}" @@ -2475,7 +2551,7 @@ msgstr "Sök" msgid "Search for files" msgstr "Sök efter filer" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "Sekunder" @@ -2488,8 +2564,8 @@ msgstr "Välj en logg-nivå och se meddelanden när de händer:" msgid "Select files" msgstr "Välj filer" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "Server" @@ -2527,6 +2603,10 @@ msgstr "Servern pausad" msgid "Server state properties" msgstr "Serverstatusegenskaper" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "Inställningar" @@ -2600,17 +2680,17 @@ msgstr "Källmappar" msgid "Source:" msgstr "Källa:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" "Specifika versioner endast för utvecklare. Ej för användning med viktig " "data." -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "Standardprotokoll" @@ -2637,19 +2717,15 @@ msgstr "Startar återställning ..." msgid "Starting the restore process …" msgstr "Startar återställningsprocessen ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "Stoppa efter aktuell fil" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "Stoppa efter den aktuella filen" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "Stoppa nu" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "Avsluta säkerhetskopiering" @@ -2703,7 +2779,7 @@ msgstr "Symbolisk länk" msgid "System Files" msgstr "Systemfiler" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "Systemstandard ({{levelname}})" @@ -2759,6 +2835,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "Test Fas" @@ -2771,7 +2852,7 @@ msgstr "Testa anslutningen" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "Testar behörigheter..." @@ -2779,10 +2860,10 @@ msgstr "Testar behörigheter..." msgid "Testing …" msgstr "Testar ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2809,7 +2890,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Namnet på bucket borde vara gemener, konvertera automatiskt?" @@ -2825,15 +2906,15 @@ msgstr "" msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "Det mörka temat (av Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "Standardtemat för blått på vitt (av Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2862,7 +2943,7 @@ msgstr "" "Vill du ERSÄTTA din AKTUELLA värdnyckel \"{{prev}}\" med den RAPPORTERADE värdnyckeln: {{key}}?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "Lösenorden matchar inte" @@ -2915,7 +2996,7 @@ msgid "" msgstr "" "Målmappen innehåller redan krypterade filer, vänligen ange lösenfrasen" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2942,7 +3023,7 @@ msgstr "Denna månad" msgid "This week" msgstr "Denna vecka" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "Inställningar för Hastighetsbegränsningar " @@ -2955,6 +3036,10 @@ msgstr "Tors" msgid "Time" msgstr "Tid" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "Till Arkiv" @@ -2971,7 +3056,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "För att exportera utan en lösenordsfras, avmarkera rutan \"Kryptera fil\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -3005,7 +3090,7 @@ msgstr "Lita på värdcertifikat?" msgid "Trust server certificate?" msgstr "Lita på servercertifikat?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3042,7 +3127,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "Uppdatera kanal" @@ -3062,18 +3147,18 @@ msgstr "Uppladdade filer" msgid "Uploading verification file …" msgstr "Laddar upp verifieringsfil …" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "Användningsstatistik" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "Användningsstatistik, varningar, fel och krascher" @@ -3101,17 +3186,17 @@ msgstr "Användardata" msgid "User domain name" msgstr "Användardomännamn" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "Användaren har för många behörigheter" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "Användargränssnittet inställningar" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3203,7 +3288,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "Väntar på att uppladdningen ska slutföras ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "Varningar, fel och krascher" @@ -3241,7 +3326,7 @@ msgstr "Var vill du återställa filerna?" msgid "Years" msgstr "År" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3254,9 +3339,9 @@ msgstr "År" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "Ja" @@ -3295,18 +3380,16 @@ msgstr "Du kör för närvarande {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" -"Du kan stoppa säkerhetskopieringen efter att alla pågående filuppladdningar " -"har slutförts." #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" -"Du kan stoppa uppgiften omedelbart eller tillåta processen att fortsätta sin" -" nuvarande fil och sedan stoppa." #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3350,7 +3433,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "Du måste välja minst en källmapp" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "Du måste ange ett domännamn för att använda v3 API" @@ -3362,7 +3445,7 @@ msgstr "Du måste ange ett namn för säkerhetskopian" msgid "You must enter a passphrase or disable encryption" msgstr "Du måste ange en lösenfras eller inaktivera kryptering" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "Du måste ange ett lösenord för att använda v3 API" @@ -3370,11 +3453,11 @@ msgstr "Du måste ange ett lösenord för att använda v3 API" msgid "You must enter a positive number of backups to keep" msgstr "Du måste ange ett positivt antal säkerhetskopior för att behålla" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "Du måste ange ett tenant (aka project) för att använda v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3387,11 +3470,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "Du måste ange en giltig lagrings-policysträng" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3412,11 +3495,11 @@ msgstr "Du måste fylla i användarnamnet" msgid "You must fill in {{field}}" msgstr "Du måste fylla i {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "Du måste välja eller fylla i AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "Du måste välja eller fylla i uppgifterna för servern" @@ -3448,23 +3531,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3479,29 +3562,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3514,11 +3605,11 @@ msgstr "" msgid "resume now" msgstr "återuppta nu" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "om du inte uttryckligen anger --group-id" diff --git a/Localizations/webroot/localization_webroot-zh_CN.po b/Localizations/webroot/localization_webroot-zh_CN.po index dfa26b39e..ca560a813 100644 --- a/Localizations/webroot/localization_webroot-zh_CN.po +++ b/Localizations/webroot/localization_webroot-zh_CN.po @@ -4,11 +4,12 @@ # Hoilc , 2024 # mays_wind , 2024 # vishun nadir, 2024 +# Chisato Niskikigi, 2024 # msgid "" msgstr "" "Project-Id-Version: \n" -"Last-Translator: vishun nadir, 2024\n" +"Last-Translator: Chisato Niskikigi, 2024\n" "Language-Team: Chinese (China) (https://app.transifex.com/duplicati/teams/67655/zh_CN/)\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -38,7 +39,7 @@ msgstr "(中断)" msgid "- pick an option -" msgstr "- 选择一个选项 -" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "…正在加载…" @@ -64,23 +65,23 @@ msgstr "" "

    由于无效的身份验证,连接服务器被拒绝。

    \n" "

    重新登录,或者从托盘图标重新打开页面(如果适用)。

    " -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "API 密钥" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS 访问 ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS 访问密钥" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM 策略" @@ -92,9 +93,9 @@ msgstr "关于" msgid "About {{appname}}" msgstr "关于 {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" -msgstr "访问密钥" +msgstr "Access Key" #: templates/backends/e2.html:2 msgid "Access Key ID" @@ -120,7 +121,7 @@ msgstr "" msgid "Access to user interface" msgstr "用户界面访问" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "帐户名" @@ -153,8 +154,8 @@ msgstr "添加路径" msgid "Added" msgstr "已添加" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "调整 bucket 名称?" @@ -187,7 +188,7 @@ msgstr "所有 Hyper-V 机器" msgid "All Microsoft SQL Databases" msgstr "所有 Microsoft SQL 数据库" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -206,6 +207,10 @@ msgstr "允许远程访问 (需要重启)" msgid "Allowed days" msgstr "允许的日期" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "新位置已经存在文件" @@ -229,7 +234,7 @@ msgstr "" "重新使用该数据库将使用命令行或服务器实例工作在相同的存储\n" "您希望使用已有的数据库吗?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "匿名使用报告" @@ -237,20 +242,26 @@ msgstr "匿名使用报告" msgid "Applications" msgstr "应用" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "导出为命令行" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "授权 ID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "认证方法" @@ -282,7 +293,7 @@ msgstr "B2 应用 ID" msgid "B2 Application Key" msgstr "B2 应用密钥" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 云存储帐户 ID" @@ -290,7 +301,7 @@ msgstr "B2 云存储帐户 ID" msgid "B2 Cloud Storage Application ID" msgstr "B2 云存储应用 ID" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 云存储应用密钥" @@ -335,7 +346,7 @@ msgstr "备份保留策略" msgid "Backup:" msgstr "备份数据:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -347,7 +358,7 @@ msgstr "访问中断" msgid "Browse" msgstr "浏览" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "浏览器默认" @@ -355,14 +366,14 @@ msgstr "浏览器默认" msgid "Bucket create location" msgstr "Bucket 创建位置" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -371,7 +382,7 @@ msgstr "Bucket 创建位置" msgid "Bucket name" msgstr "Bucket 名称" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -442,13 +453,13 @@ msgstr "" msgid "Cache Files" msgstr "缓存文件" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -463,14 +474,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "取消" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "不能包含 \"{{text}}\"" @@ -487,7 +502,7 @@ msgstr "不能在额外选项中指定过滤器的包含或排除" msgid "Change server passphrase" msgstr "更改服务器密码短语" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -527,7 +542,7 @@ msgstr "选择存储类型以开始" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "点击\"授权 ID\"链接来创建一个授权 ID" @@ -658,7 +673,7 @@ msgstr "连接中断" msgid "Connection worked!" msgstr "连接正常!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "容器名称" @@ -700,7 +715,7 @@ msgstr "复制失败,请手动复制该地址" msgid "Copy log" msgstr "复制日志" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "核心选项" @@ -708,7 +723,7 @@ msgstr "核心选项" msgid "Counting ({{files}} files found, {{size}})" msgstr "正在计算 (已找到 {{files}} 个文件,{{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "仅崩溃" @@ -720,7 +735,7 @@ msgstr "创建问题报告…" msgid "Create folder?" msgstr "创建文件夹?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "受限用户已创建" @@ -728,7 +743,7 @@ msgstr "受限用户已创建" msgid "Creating bug report …" msgstr "正在创建问题报告…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "正在创建受限用户…" @@ -740,7 +755,7 @@ msgstr "正在创建目标文件夹…" msgid "Creating temporary backup …" msgstr "正在创建临时备份…" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "正在创建用户…" @@ -760,7 +775,7 @@ msgstr "当前版本为 {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "自定义 S3 端点" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "自定义卫星" @@ -812,11 +827,11 @@ msgstr "数据库…" msgid "Days" msgstr "天" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "默认" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "默认 ({{channelname}})" @@ -824,7 +839,7 @@ msgstr "默认 ({{channelname}})" msgid "Default excludes" msgstr "默认排除规则" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "默认选项" @@ -852,6 +867,10 @@ msgstr "删除早于条件的备份" msgid "Delete local database" msgstr "删除本地数据库" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "删除远程文件" @@ -906,7 +925,8 @@ msgstr "目标位置" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "目标路径" @@ -918,6 +938,10 @@ msgstr "从备份文件直接恢复…" msgid "Directory path" msgstr "目录路径" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "已禁用" @@ -931,7 +955,7 @@ msgstr "忽略" msgid "Dismiss all" msgstr "忽略所有" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "显示和颜色主题" @@ -980,7 +1004,7 @@ msgstr "Duplicati 网站" msgid "Duplicati forum" msgstr "Duplicati 论坛" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" @@ -990,7 +1014,7 @@ msgstr "" "如果您从托盘图标打开Duplicati,则不需要密码短语,但如果您计划从其他位置打开它,则需要设置一个您知道的密码短语。\n" "您现在想要设置一个密码短语吗?" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1027,12 +1051,12 @@ msgstr "" "每个备份都有一个与之关联的本地数据库,该数据库存储了有关本地机器上远程备份的信息。这使得执行许多操作变得更快,并减少了每次操作需要下载的数据量。" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "以列表形式编辑" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "以文本形式编辑" @@ -1044,6 +1068,10 @@ msgstr "编辑…" msgid "Email address of the Office 365 group" msgstr "Office 365群组的电子邮件地址" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "加密文件" @@ -1065,7 +1093,7 @@ msgstr "" "加密模块:

    {{item.Key}}

    " -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "加密密码" @@ -1177,7 +1205,7 @@ msgstr "错误" msgid "Error!" msgstr "错误!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "错误和崩溃" @@ -1225,7 +1253,7 @@ msgstr "排除正则表达式" msgid "Existing file found" msgstr "发现已存在文件" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "Experimental" @@ -1319,7 +1347,7 @@ msgstr "读取文件失败: {{message}}" msgid "Failed to restore files: {{message}}" msgstr "恢复文件失败: {{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "保存失败:" @@ -1348,7 +1376,7 @@ msgstr "过滤条件" msgid "Finished!" msgstr "已完成!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "初始配置" @@ -1419,7 +1447,7 @@ msgstr "生成 IAM 访问策略" msgid "Getting file versions …" msgstr "正在获取文件版本..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "群组邮箱" @@ -1444,7 +1472,7 @@ msgid "Hostnames" msgstr "主机名" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "小时" @@ -1474,11 +1502,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1529,6 +1557,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "如果您不输入 API 密钥,则需要输入租户名称" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1580,7 +1613,7 @@ msgstr "包含正则表达式" msgid "Incorrect answer, try again" msgstr "验证失败,请重试" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "面向开发者的单个构建,不适用于重要数据" @@ -1593,7 +1626,7 @@ msgstr "信息" msgid "Interrupted, no statistics collected" msgstr "中断,未收集统计信息" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "路径中包含无效字符" @@ -1603,7 +1636,7 @@ msgstr "路径中包含无效字符" msgid "Invalid retention time" msgstr "无效的保留时间" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1631,7 +1664,7 @@ msgstr "永久保留" msgid "Keystone API version" msgstr "Keystone API 版本" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "界面语言" @@ -1696,10 +1729,6 @@ msgstr "正在加载远程存储使用情况…" msgid "Loading …" msgstr "正在加载…" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "本地仓库" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "新的更新: {{message}}" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1905,7 +1938,7 @@ msgstr "下一次任务:" msgid "Next time" msgstr "下一次运行时间:" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1918,9 +1951,9 @@ msgstr "下一次运行时间:" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "否" @@ -1962,7 +1995,7 @@ msgstr "暂无计划任务" msgid "Non-matching passphrase" msgstr "密码不匹配" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "无 / 禁用" @@ -1976,23 +2009,24 @@ msgid "" "reported in bits. Use a factor of 8 to convert, such that an 8 mbit/s line " "is equivalent to 1 MByte/s." msgstr "" -"请注意,速度是以bytes为单位输入的,而线路速度通常以bits为蛋王。两者使用 8 的倍数进行转换,这样8 mbit/s的线路相当于1 MByte/s" +"注意:速度是以bytes为单位输入的,而线路速度通常以bits为单位。两者使用 8 的倍数进行转换。换言之,8 mbit/s的线路相当于1 " +"MByte/s" #: templates/addoredit.html:328 msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "不会清理任何备份,备份大小将持续增长" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "确定" @@ -2024,7 +2058,7 @@ msgstr "Aliyun OSS路径或bucket的子文件夹" msgid "OSS Region" msgstr "Aliyun OSS 区域" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "官方发布" @@ -2046,7 +2080,7 @@ msgstr "OpenStack 对象存储 / Swift" msgid "Opened" msgstr "已打开" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "Openstack API key 在 v3 keystone API 中不受支持" @@ -2083,11 +2117,11 @@ msgid "Optional tenant name" msgstr "租户名称(可选)" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "选项" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2097,7 +2131,7 @@ msgstr "在此添加的选项适用于所有备份,但每个备份中可以单 msgid "Original location" msgstr "原位置" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "其它" @@ -2133,7 +2167,7 @@ msgstr "密码不匹配" msgid "Passphrases do not match" msgstr "密码不匹配" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2144,8 +2178,8 @@ msgstr "密码" msgid "Patching files with local blocks …" msgstr "正在使用本地块修补文件…" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "路径" @@ -2154,7 +2188,7 @@ msgid "Path not found" msgstr "路径未找到" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "服务器上路径" @@ -2163,11 +2197,11 @@ msgstr "服务器上路径" msgid "Path or subfolder in the bucket" msgstr "Bucket 中路径或子文件夹" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "暂停" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "开机或休眠后暂停" @@ -2205,6 +2239,10 @@ msgstr "保持托盘图标自动登录" msgid "Previous" msgstr "上一步" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "进度:" @@ -2213,7 +2251,7 @@ msgstr "进度:" msgid "ProjectID is optional if the bucket exist" msgstr "若 Bucket 存在, 则项目ID 可选" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "专有" @@ -2249,10 +2287,30 @@ msgstr "正在重建数据库…" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "正在注册临时备份…" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "不允许相对路径" @@ -2265,14 +2323,34 @@ msgstr "重新加载" msgid "Remote" msgstr "远程" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "远程路径" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "远程仓库" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "远程路径" @@ -2415,7 +2493,7 @@ msgstr "正在运行… 立 msgid "S3 Compatible" msgstr "S3 兼容" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "与当前安装版本一致:{{channelname}}" @@ -2463,7 +2541,7 @@ msgstr "搜索" msgid "Search for files" msgstr "搜索文件" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "秒" @@ -2476,8 +2554,8 @@ msgstr "选择日志级别并实时查看" msgid "Select files" msgstr "选择文件" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "服务器" @@ -2517,6 +2595,10 @@ msgstr "服务器已暂停" msgid "Server state properties" msgstr "服务器状态" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "设置" @@ -2586,15 +2668,15 @@ msgstr "源文件夹" msgid "Source:" msgstr "源数据:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "面向开发者的特定构建,不适用于重要数据" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "标准协议" @@ -2621,19 +2703,15 @@ msgstr "准备开始恢复…" msgid "Starting the restore process …" msgstr "正在开始恢复操作…" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "当前文件完成后停止" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "当前文件完成后停止" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "立即停止" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "停止正在运行的备份" @@ -2687,7 +2765,7 @@ msgstr "符号链接" msgid "System Files" msgstr "系统文件" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "默认 ({{levelname}})" @@ -2743,6 +2821,11 @@ msgstr "腾讯云账号APPID" msgid "Tencent Cloud COS documents and resources" msgstr "腾讯云COS文档和资源" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "测试阶段" @@ -2755,7 +2838,7 @@ msgstr "测试连接" msgid "Testing connection …" msgstr "测试连接中…" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "正在测试权限…" @@ -2763,10 +2846,10 @@ msgstr "正在测试权限…" msgid "Testing …" msgstr "正在测试…" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2792,7 +2875,7 @@ msgstr "" "link=\"'https://www.duplicati.com/articles/Choosing-Sizes/#remote-volume-" "size'\">更多信息,请参见此页面。" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Bucket 名称应当是全小写,需要自动转换吗?" @@ -2806,15 +2889,15 @@ msgstr "配置应该注意安全。您确定要将含有您密码的配置保存 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "与服务器的连接丢失,将在{{time}}后再次尝试…" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "黑色主题 (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "默认蓝白主题 (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "加密密码不匹配" @@ -2843,7 +2926,7 @@ msgstr "" "您想要把现有主机密钥 \"{{prev}}\" 替换为 {{key}} 吗?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "密码不匹配" @@ -2891,7 +2974,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "目标文件夹包含加密文件,请提供密码" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2913,7 +2996,7 @@ msgstr "本月" msgid "This week" msgstr "本周" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "限流设置" @@ -2926,6 +3009,10 @@ msgstr "周四" msgid "Time" msgstr "时间 " +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "导出为文件" @@ -2940,7 +3027,7 @@ msgstr "为确认您要删除 \"{{name}}\" 的所有远程文件,请输入以 msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "如果不想使用密码加密导出的文件,请去除勾选\"加密文件\"" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2970,7 +3057,7 @@ msgstr "信任主机证书?" msgid "Trust server certificate?" msgstr "信任服务器证书?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -3006,7 +3093,7 @@ msgstr "" "更新 {{state.updatedVersion}}" " 可用。立即下载" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "更新分支" @@ -3026,18 +3113,20 @@ msgstr "已上传文件" msgid "Uploading verification file …" msgstr "正在上传校验文件…" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" +"使用报告帮助我们改善用户体验并评估新功能的影响。我们使用它们来生成public usage statistics。" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "使用情况统计" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "使用情况统计、警告、错误和崩溃" @@ -3065,17 +3154,17 @@ msgstr "用户数据" msgid "User domain name" msgstr "用户域名称" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "用户权限太多" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "界面设置" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3165,7 +3254,7 @@ msgstr "等待任务启动…" msgid "Waiting for upload to finish …" msgstr "等待上传完成…" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "警告、错误和崩溃" @@ -3201,7 +3290,7 @@ msgstr "您想把文件恢复到哪里?" msgid "Years" msgstr "年" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3214,9 +3303,9 @@ msgstr "年" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "是" @@ -3255,14 +3344,16 @@ msgstr "当前正在运行 {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." -msgstr "您可以立即停止备份,将在当前上传的任意文件完成后停止。" +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." +msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." -msgstr "您可以立即停止任务,或在当前文件处理完成后停止。" +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." +msgstr "" #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3297,7 +3388,7 @@ msgstr "您已经生成了一个强密码。确保您已经安全记录下了该 msgid "You must choose at least one source folder" msgstr "您必须至少一个源文件夹" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "您必须输入域名称以使用 v3 API" @@ -3309,7 +3400,7 @@ msgstr "您必须输入备份名称" msgid "You must enter a passphrase or disable encryption" msgstr "您必须输入加密密码或禁用加密" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "您必须输入密码以使用 v3 API" @@ -3317,11 +3408,11 @@ msgstr "您必须输入密码以使用 v3 API" msgid "You must enter a positive number of backups to keep" msgstr "您输入要保留的版本数必须为正数" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "您必须输入租户名称(即项目)以使用 v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "如果您不提供API key,则必须输入租户名称" @@ -3333,11 +3424,11 @@ msgstr "您必须输入有效的期限来保留备份" msgid "You must enter a valid retention policy string" msgstr "您必须输入一个有效的保留策略" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "您必须输入密码或API key" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "您必须输入密码或API key,两者不能同时都输入" @@ -3358,11 +3449,11 @@ msgstr "您必须填写用户名" msgid "You must fill in {{field}}" msgstr "您必须填写 {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "您必须选择或填写认证地址" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "您必须选择或填写服务器" @@ -3394,23 +3485,23 @@ msgstr "B" msgid "byte/s" msgstr "B/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3425,29 +3516,37 @@ msgid "failed" msgstr "失败" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" -msgstr "本地仓库,例如:local" +msgid "local repository, leave empty for local" +msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "远程路径,例如:backup" @@ -3460,11 +3559,11 @@ msgstr "远程仓库,例如:remote" msgid "resume now" msgstr "立即恢复运行" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "除非您明确指定 --group-id" diff --git a/Localizations/webroot/localization_webroot-zh_HK.po b/Localizations/webroot/localization_webroot-zh_HK.po index 7bc65980f..f196170ff 100644 --- a/Localizations/webroot/localization_webroot-zh_HK.po +++ b/Localizations/webroot/localization_webroot-zh_HK.po @@ -35,7 +35,7 @@ msgstr "" msgid "- pick an option -" msgstr "選擇一個選項" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...載入中..." @@ -53,33 +53,33 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM 原則" -#: index.html:223 index.html:239 +#: index.html:224 index.html:240 msgid "About" msgstr "關於" @@ -87,7 +87,7 @@ msgstr "關於" msgid "About {{appname}}" msgstr "關於 {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "" @@ -115,7 +115,7 @@ msgstr "" msgid "Access to user interface" msgstr "" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "用戶名" @@ -132,7 +132,7 @@ msgstr "直接加入路徑" msgid "Add advanced option" msgstr "新增進階選項" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "新增備份" @@ -148,8 +148,8 @@ msgstr "加入路徑" msgid "Added" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "" @@ -182,7 +182,7 @@ msgstr "所有Hyper-V機器" msgid "All Microsoft SQL Databases" msgstr "所有Microsoft SQL數據庫" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -199,6 +199,10 @@ msgstr "允許遠端存取(需要重新啟動)" msgid "Allowed days" msgstr "允許日子" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "在新的位置上發現有檔案存在" @@ -217,7 +221,7 @@ msgid "" " Do you wish to use the existing database?" msgstr "" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "匿名使用報告" @@ -225,20 +229,26 @@ msgstr "匿名使用報告" msgid "Applications" msgstr "" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -270,7 +280,7 @@ msgstr "" msgid "B2 Application Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "" @@ -278,7 +288,7 @@ msgstr "" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "" @@ -320,7 +330,7 @@ msgstr "" msgid "Backup:" msgstr "備份:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "Beta" @@ -332,7 +342,7 @@ msgstr "" msgid "Browse" msgstr "瀏覽" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "瀏覽預設" @@ -340,14 +350,14 @@ msgstr "瀏覽預設" msgid "Bucket create location" msgstr "Bucket 建立位置" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -356,7 +366,7 @@ msgstr "Bucket 建立位置" msgid "Bucket name" msgstr "Bucket 名稱" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -426,12 +436,13 @@ msgstr "" msgid "Cache Files" msgstr "" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -446,14 +457,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "Cancel" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -470,6 +485,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "更新日誌" @@ -490,7 +509,7 @@ msgstr "立即檢查更新" msgid "Checking for updates …" msgstr "" -#: templates/captcha.html:10 +#: templates/captcha.html:11 msgid "Checking …" msgstr "" @@ -506,11 +525,11 @@ msgstr "" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "" -#: index.html:148 index.html:199 +#: index.html:149 index.html:200 msgid "Click to set throttle options" msgstr "" @@ -594,7 +613,7 @@ msgstr "確認刪除" msgid "Confirm encryption passphrase" msgstr "確認加密密碼" -#: templates/settings.html:15 +#: templates/changepassword.html:14 templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -610,11 +629,11 @@ msgstr "需要確認" msgid "Connect" msgstr "連接" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "立即連接" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "" @@ -622,11 +641,11 @@ msgstr "" msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "連接中斷" @@ -635,7 +654,7 @@ msgstr "連接中斷" msgid "Connection worked!" msgstr "連接成功!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "容器名稱" @@ -677,7 +696,7 @@ msgstr "複製失敗。請手動複製網址" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "" @@ -685,7 +704,7 @@ msgstr "" msgid "Counting ({{files}} files found, {{size}})" msgstr "點算中(找到 {{files}} 個檔案,{{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "" @@ -697,7 +716,7 @@ msgstr "" msgid "Create folder?" msgstr "建立資料夾?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "已建立受限制的使用者" @@ -705,7 +724,7 @@ msgstr "已建立受限制的使用者" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -717,7 +736,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -737,7 +756,7 @@ msgstr "現時版本 {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -789,11 +808,11 @@ msgstr "" msgid "Days" msgstr "Days" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "預設" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "預設 ({{channelname}})" @@ -801,7 +820,7 @@ msgstr "預設 ({{channelname}})" msgid "Default excludes" msgstr "" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "預設選項" @@ -829,6 +848,10 @@ msgstr "" msgid "Delete local database" msgstr "刪除本地資料庫" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "刪除遠端檔案" @@ -883,7 +906,8 @@ msgstr "目的地" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "" @@ -895,6 +919,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "已停用" @@ -908,7 +936,7 @@ msgstr "略過" msgid "Dismiss all" msgstr "" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "顯示及顏色主題" @@ -949,22 +977,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "Duplicati 選項 {{opt}}" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "Duplicati 網站" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "Duplicati 討論區" -#: scripts/controllers/AppController.js:181 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -997,12 +1025,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "" @@ -1014,6 +1042,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "加密檔案" @@ -1033,7 +1065,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1142,7 +1174,7 @@ msgstr "錯誤" msgid "Error!" msgstr "錯誤!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "" @@ -1190,7 +1222,7 @@ msgstr "排除正規表達式" msgid "Existing file found" msgstr "找到已存在的檔案" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "實驗性" @@ -1284,7 +1316,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "還原檔案失敗:{{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "儲存失敗:" @@ -1313,7 +1345,7 @@ msgstr "過濾器" msgid "Finished!" msgstr "已完成!" -#: scripts/controllers/AppController.js:180 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "" @@ -1384,7 +1416,7 @@ msgstr "產生 IAM 存取原則" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "" @@ -1400,7 +1432,7 @@ msgstr "隱藏" msgid "Hide hidden folders" msgstr "不顯示隱藏的資料夾" -#: index.html:208 scripts/services/AppUtils.js:62 +#: index.html:209 scripts/services/AppUtils.js:62 msgid "Home" msgstr "首頁" @@ -1409,7 +1441,7 @@ msgid "Hostnames" msgstr "" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "小時" @@ -1439,11 +1471,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1488,6 +1520,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1535,11 +1572,11 @@ msgstr "包括表達式" msgid "Include regular expression" msgstr "包括正規表達式" -#: templates/captcha.html:11 +#: templates/captcha.html:12 msgid "Incorrect answer, try again" msgstr "答案錯誤,請重試" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1552,7 +1589,7 @@ msgstr "訊息" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "路徑中有無效的字符" @@ -1562,7 +1599,7 @@ msgstr "路徑中有無效的字符" msgid "Invalid retention time" msgstr "無效的保留時間" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1588,7 +1625,7 @@ msgstr "" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "界面語言" @@ -1648,15 +1685,11 @@ msgstr "" #: templates/about.html:43 templates/about.html:49 templates/about.html:55 #: templates/backuplog.html:12 templates/backuplog.html:22 #: templates/backuplog.html:29 templates/backuplog.html:40 -#: templates/captcha.html:14 templates/log.html:12 templates/log.html:23 +#: templates/captcha.html:15 templates/log.html:12 templates/log.html:23 #: templates/updatechangelog.html:7 msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1848,11 +1885,11 @@ msgstr "下一步" msgid "Next scheduled run:" msgstr "下次預定報行的時間:" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "下次預定報行的工作:" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "下次的工作:" @@ -1860,7 +1897,7 @@ msgstr "下次的工作:" msgid "Next time" msgstr "下次執行時間:" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1873,9 +1910,9 @@ msgstr "下次執行時間:" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "否" @@ -1909,7 +1946,7 @@ msgstr "沒有需要還原的項目,請擇一個或以上的項目" msgid "No passphrase entered" msgstr "沒有輸入密碼" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "沒有預定的工作" @@ -1917,7 +1954,7 @@ msgstr "沒有預定的工作" msgid "Non-matching passphrase" msgstr "密碼不正確" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "沒有/已停用" @@ -1936,16 +1973,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" -#: scripts/controllers/AppController.js:52 -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:197 +#: scripts/controllers/AppController.js:54 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "確定" @@ -1977,7 +2015,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -1999,7 +2037,7 @@ msgstr "" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2036,11 +2074,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "選項" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2050,7 +2088,7 @@ msgstr "" msgid "Original location" msgstr "" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "Others" @@ -2086,7 +2124,7 @@ msgstr "密碼不相同" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2097,8 +2135,8 @@ msgstr "密碼" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "路徑" @@ -2107,7 +2145,7 @@ msgid "Path not found" msgstr "找不到路徑" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "伺服器上路徑" @@ -2116,15 +2154,15 @@ msgstr "伺服器上路徑" msgid "Path or subfolder in the bucket" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "暫停" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "啟動或休眠後暫停" -#: scripts/controllers/AppController.js:50 +#: scripts/controllers/AppController.js:52 msgid "Pause options" msgstr "暫停選項" @@ -2158,6 +2196,10 @@ msgstr "" msgid "Previous" msgstr "Previous" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "" @@ -2166,7 +2208,7 @@ msgstr "" msgid "ProjectID is optional if the bucket exist" msgstr "" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "" @@ -2202,15 +2244,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "" -#: index.html:305 templates/captcha.html:7 +#: index.html:306 templates/captcha.html:8 msgid "Reload" msgstr "" @@ -2218,14 +2280,34 @@ msgstr "" msgid "Remote" msgstr "遠端" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "" @@ -2274,7 +2356,7 @@ msgstr "報告︰" msgid "Reset" msgstr "重設" -#: index.html:214 templates/restore.html:137 +#: index.html:215 templates/restore.html:137 msgid "Restore" msgstr "還原" @@ -2332,7 +2414,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "繼續" @@ -2352,7 +2434,7 @@ msgstr "立即執行" msgid "Running commandline entry" msgstr "" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "正在執行工作:" @@ -2368,7 +2450,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 相容" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "" @@ -2416,7 +2498,7 @@ msgstr "搜尋" msgid "Search for files" msgstr "搜尋檔案" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "秒" @@ -2429,8 +2511,8 @@ msgstr "" msgid "Select files" msgstr "選擇檔案" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "伺服器" @@ -2468,7 +2550,11 @@ msgstr "伺服器已暫停" msgid "Server state properties" msgstr "伺服器狀態" -#: index.html:220 templates/settings.html:2 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "設定" @@ -2537,15 +2623,15 @@ msgstr "來源資料夾" msgid "Source:" msgstr "來源:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "標準通訊協定" @@ -2572,19 +2658,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "現時檔案完成後停止" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "立即停止" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "停止正在進行的備份" @@ -2593,11 +2675,11 @@ msgstr "停止正在進行的備份" msgid "Stop running task" msgstr "停止正在進行的工作" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "停止工作中:" @@ -2638,7 +2720,7 @@ msgstr "符號連結" msgid "System Files" msgstr "" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "系統預設({{levelname}})" @@ -2694,6 +2776,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2706,7 +2793,7 @@ msgstr "測試連線" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2714,10 +2801,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2740,7 +2827,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" @@ -2750,19 +2837,19 @@ msgid "" "unencrypted file containing your passwords?" msgstr "" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "深色主題(Michai設計)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "預設的藍白色主題(Alexi設計)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2786,7 +2873,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "" @@ -2830,7 +2917,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2852,7 +2939,7 @@ msgstr "本月" msgid "This week" msgstr "本週" -#: scripts/controllers/AppController.js:65 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "" @@ -2865,6 +2952,10 @@ msgstr "星期四" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "到檔案" @@ -2879,7 +2970,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2907,7 +2998,7 @@ msgstr "" msgid "Trust server certificate?" msgstr "信任伺服器證書?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -2941,7 +3032,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "更新頻道" @@ -2961,19 +3052,18 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate {{'public usage statistics' | " -"translate}}." +"reporter.duplicati.com/'\">public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "" @@ -3001,17 +3091,17 @@ msgstr "" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3075,7 +3165,7 @@ msgstr "十分強" msgid "Very weak" msgstr "十分弱" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "" @@ -3101,7 +3191,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "" @@ -3137,7 +3227,7 @@ msgstr "" msgid "Years" msgstr "年" -#: scripts/controllers/AppController.js:182 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3150,9 +3240,9 @@ msgstr "年" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "是" @@ -3189,13 +3279,15 @@ msgstr "" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3231,7 +3323,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3243,7 +3335,7 @@ msgstr "" msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3251,11 +3343,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3267,11 +3359,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3292,11 +3384,11 @@ msgstr "" msgid "You must fill in {{field}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "您必須選擇或填寫伺服器" @@ -3328,23 +3420,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3359,31 +3451,35 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" -#: templates/settings.html:125 -msgid "public usage statistics" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" msgstr "" #: templates/backends/rclone.html:11 @@ -3398,11 +3494,11 @@ msgstr "" msgid "resume now" msgstr "立即繼續" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/Localizations/webroot/localization_webroot-zh_TW.po b/Localizations/webroot/localization_webroot-zh_TW.po index 687594494..0609737eb 100644 --- a/Localizations/webroot/localization_webroot-zh_TW.po +++ b/Localizations/webroot/localization_webroot-zh_TW.po @@ -35,7 +35,7 @@ msgstr "" msgid "- pick an option -" msgstr "選擇一個項目" -#: templates/delete.html:7 +#: scripts/controllers/SystemSettingsController.js:217 templates/delete.html:7 msgid "...loading..." msgstr "...載入中..." @@ -59,23 +59,23 @@ msgid "" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 templates/backends/s3.html:66 +#: scripts/services/EditUriBuiltins.js:1086 templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "AWS Access ID" -#: scripts/services/EditUriBuiltins.js:1042 templates/backends/s3.html:71 +#: scripts/services/EditUriBuiltins.js:1087 templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "AWS Access Key" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "AWS IAM Policy" @@ -87,7 +87,7 @@ msgstr "關於" msgid "About {{appname}}" msgstr "關於 {{appname}}" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "Access Key" @@ -115,7 +115,7 @@ msgstr "" msgid "Access to user interface" msgstr "進入使用者介面" -#: scripts/services/EditUriBuiltins.js:989 templates/backends/azure.html:7 +#: scripts/services/EditUriBuiltins.js:1034 templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" msgstr "帳號名稱" @@ -148,8 +148,8 @@ msgstr "加入路徑" msgid "Added" msgstr "已加入" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "調整 bucket 名稱?" @@ -182,7 +182,7 @@ msgstr "全部 Hyper-V 主機" msgid "All Microsoft SQL Databases" msgstr "全部 Microsoft SQL 資料庫" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "" "All usage reports are sent anonymously and do not contain any personal " "information. They contain information about hardware and operating system, " @@ -200,6 +200,10 @@ msgstr "允許遠端存取(需要重新啟動)" msgid "Allowed days" msgstr "允許日" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "新的位置發現已既有檔案存在" @@ -222,7 +226,7 @@ msgstr "" "\n" "您希望使用既有的資料庫嗎?" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "匿名使用報告" @@ -230,20 +234,26 @@ msgstr "匿名使用報告" msgid "Applications" msgstr "Applications" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "顯示為 Command-Line" -#: scripts/services/EditUriBuiltins.js:958 templates/backends/gcs.html:8 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 templates/backends/jottacloud.html:8 #: templates/backends/jottacloud.html:9 templates/backends/msgroup.html:13 #: templates/backends/msgroup.html:14 templates/backends/oauth.html:8 -#: templates/backends/oauth.html:9 templates/backends/sharepoint.html:8 +#: templates/backends/oauth.html:9 templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "AuthID" -#: scripts/services/EditUriBuiltins.js:1158 templates/backends/storj.html:2 +#: scripts/services/EditUriBuiltins.js:1203 templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -275,7 +285,7 @@ msgstr "" msgid "B2 Application Key" msgstr "B2 Application Key" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "B2 Cloud Storage 帳號 ID" @@ -283,7 +293,7 @@ msgstr "B2 Cloud Storage 帳號 ID" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 templates/backends/b2.html:17 +#: scripts/services/EditUriBuiltins.js:1133 templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "B2 Cloud Storage Application Key" @@ -325,7 +335,7 @@ msgstr "保留備份數目" msgid "Backup:" msgstr "備份:" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "測試版 (Beta)" @@ -337,7 +347,7 @@ msgstr "故障連線" msgid "Browse" msgstr "瀏覽" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "瀏覽器預設" @@ -345,14 +355,14 @@ msgstr "瀏覽器預設" msgid "Bucket create location" msgstr "Bucket 建立位置" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 templates/backends/b2.html:2 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 templates/backends/b2.html:2 #: templates/backends/b2.html:3 templates/backends/cos.html:25 #: templates/backends/e2.html:11 templates/backends/e2.html:12 #: templates/backends/gcs.html:2 templates/backends/openstack.html:2 @@ -361,7 +371,7 @@ msgstr "Bucket 建立位置" msgid "Bucket name" msgstr "Bucket 名稱" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "" "Bucket name can only be between 3 and 63 characters long and contain only " "lower-case characters, numbers, periods and dashes" @@ -433,13 +443,13 @@ msgstr "" msgid "Cache Files" msgstr "快取檔案" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "Canary" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -454,14 +464,18 @@ msgstr "Canary" #: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 templates/delete.html:54 -#: templates/export.html:34 templates/settings.html:157 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 templates/delete.html:54 +#: templates/export.html:34 templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "取消" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -478,7 +492,7 @@ msgstr "" msgid "Change server passphrase" msgstr "" -#: scripts/controllers/AppController.js:194 +#: scripts/controllers/AppController.js:195 msgid "Change server password" msgstr "" @@ -518,7 +532,7 @@ msgstr "選擇儲存區類型,然後開始" #: templates/backends/gcs.html:11 templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 templates/backends/oauth.html:11 -#: templates/backends/sharepoint.html:11 +#: templates/backends/pcloud.html:24 templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "按下 AuthID 連結來建立一組 AuthID" @@ -647,7 +661,7 @@ msgstr "連線失敗" msgid "Connection worked!" msgstr "連線已建立!" -#: scripts/services/EditUriBuiltins.js:991 templates/backends/azure.html:2 +#: scripts/services/EditUriBuiltins.js:1036 templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" msgstr "容器名稱" @@ -689,7 +703,7 @@ msgstr "複製失敗。請手動複製 URL" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "核心選項" @@ -697,7 +711,7 @@ msgstr "核心選項" msgid "Counting ({{files}} files found, {{size}})" msgstr "正在計算 ({{files}} 個檔案, {{size}})" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "只有當機" @@ -709,7 +723,7 @@ msgstr "建立問題報告 ..." msgid "Create folder?" msgstr "建立資料夾?" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "建立新的受限使用者" @@ -717,7 +731,7 @@ msgstr "建立新的受限使用者" msgid "Creating bug report …" msgstr "正在建立問題報告 ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "正在建立有限制存取的新使用者 ..." @@ -729,7 +743,7 @@ msgstr "正在建立目標資料夾 ..." msgid "Creating temporary backup …" msgstr "正在建立暫存備份 ..." -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -749,7 +763,7 @@ msgstr "目前版本 {{versionname}} ({{versionnumber}})" msgid "Custom S3 endpoint" msgstr "自訂 S3 進入點" -#: scripts/services/EditUriBuiltins.js:1173 templates/backends/storj.html:18 +#: scripts/services/EditUriBuiltins.js:1218 templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -801,11 +815,11 @@ msgstr "資料庫 ..." msgid "Days" msgstr "日" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "預設" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "預設 ({{channelname}})" @@ -813,7 +827,7 @@ msgstr "預設 ({{channelname}})" msgid "Default excludes" msgstr "預設排除" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "預設選項" @@ -841,6 +855,10 @@ msgstr "刪除指定條件以前的備份" msgid "Delete local database" msgstr "刪除本機資料庫" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 templates/delete.html:47 msgid "Delete remote files" msgstr "刪除遠端檔案" @@ -895,7 +913,8 @@ msgstr "目的地" #: templates/backends/file.html:7 templates/backends/generic.html:14 #: templates/backends/jottacloud.html:3 templates/backends/msgroup.html:8 -#: templates/backends/oauth.html:3 templates/backends/sharepoint.html:2 +#: templates/backends/oauth.html:3 templates/backends/pcloud.html:16 +#: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "目的路徑" @@ -907,6 +926,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "取消" @@ -920,7 +943,7 @@ msgstr "忽略" msgid "Dismiss all" msgstr "全部忽略" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "佈景主題設定" @@ -969,14 +992,14 @@ msgstr "Duplicati 官方網站" msgid "Duplicati forum" msgstr "Duplicati 論壇" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "" "Duplicati will run when started, but will remain in a paused state for the " "duration. Duplicati will occupy minimal system resources and no backups will" @@ -1013,12 +1036,12 @@ msgid "" msgstr "" #: templates/addoredit.html:174 templates/addoredit.html:387 -#: templates/edituri.html:39 templates/settings.html:146 +#: templates/edituri.html:39 templates/settings.html:194 msgid "Edit as list" msgstr "編輯清單" #: templates/addoredit.html:177 templates/addoredit.html:390 -#: templates/edituri.html:42 templates/settings.html:152 +#: templates/edituri.html:42 templates/settings.html:200 msgid "Edit as text" msgstr "編輯文字內容" @@ -1030,6 +1053,10 @@ msgstr "編輯 ..." msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "加密檔案" @@ -1049,7 +1076,7 @@ msgid "" "sysinfo.EncryptionModules\">{{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 templates/backends/storj.html:25 +#: scripts/services/EditUriBuiltins.js:1213 templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" msgstr "" @@ -1161,7 +1188,7 @@ msgstr "錯誤" msgid "Error!" msgstr "錯誤!" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "錯誤與當機" @@ -1209,7 +1236,7 @@ msgstr "排除的正規表示式" msgid "Existing file found" msgstr "檔案已存在" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "實驗版 (Experimental)" @@ -1303,7 +1330,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "還原檔案失敗:{{message}}" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "儲存失敗:" @@ -1332,7 +1359,7 @@ msgstr "篩選" msgid "Finished!" msgstr "已完成!" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "執行初始化設定" @@ -1403,7 +1430,7 @@ msgstr "產生 IAM access policy" msgid "Getting file versions …" msgstr "正在取得檔案版本 ..." -#: scripts/services/EditUriBuiltins.js:980 templates/backends/msgroup.html:2 +#: scripts/services/EditUriBuiltins.js:1025 templates/backends/msgroup.html:2 msgid "Group email" msgstr "群組郵件" @@ -1428,7 +1455,7 @@ msgid "Hostnames" msgstr "主機名稱" #: scripts/services/AppUtils.js:100 scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "小時" @@ -1458,11 +1485,11 @@ msgstr "ID:" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 templates/backends/e2.html:3 +#: scripts/services/EditUriBuiltins.js:1268 templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 templates/backends/e2.html:7 +#: scripts/services/EditUriBuiltins.js:1269 templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1509,6 +1536,11 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "If you do not enter an API Key, the tenant name is required" +#: templates/pause.html:53 +msgid "" +"If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "" "If you want to use the backup later, you can export the configuration before" @@ -1560,7 +1592,7 @@ msgstr "包含正則表示式" msgid "Incorrect answer, try again" msgstr "回應不正確,請重試一次" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "" "Individual builds for developers only. Not for use with important data." msgstr "僅針對開發人員的個別組建版本,請不要使用在重要資料上。" @@ -1573,7 +1605,7 @@ msgstr "資訊" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "路徑有無法使用的字元" @@ -1583,7 +1615,7 @@ msgstr "路徑有無法使用的字元" msgid "Invalid retention time" msgstr "保留時間無效" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1611,7 +1643,7 @@ msgstr "保留所有備份" msgid "Keystone API version" msgstr "Keystone API 版本" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "使用者介面語言" @@ -1676,10 +1708,6 @@ msgstr "" msgid "Loading …" msgstr "載入中 ..." -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "本機 Repository" - #: templates/localdatabase.html:2 msgid "" "Local database for {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1883,7 +1915,7 @@ msgstr "下一個工作:" msgid "Next time" msgstr "下一次" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1896,9 +1928,9 @@ msgstr "下一次" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "否" @@ -1940,7 +1972,7 @@ msgstr "沒有排程工作" msgid "Non-matching passphrase" msgstr "密碼不相符" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "無 / 取消" @@ -1959,17 +1991,17 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "什麼都不刪除。備份大小將隨著每次異動而持續增長。" -#: scripts/controllers/AppController.js:196 +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 #: scripts/controllers/RestoreDirectController.js:33 #: scripts/services/CaptchaService.js:22 scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 templates/restore.html:147 -#: templates/settings.html:158 +#: scripts/services/EditUriBuiltins.js:137 templates/restore.html:147 +#: templates/settings.html:206 msgid "OK" msgstr "確定" @@ -2001,7 +2033,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2023,7 +2055,7 @@ msgstr "OpenStack Object Storage / Swift" msgid "Opened" msgstr "已開啟" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2060,11 +2092,11 @@ msgid "Optional tenant name" msgstr "" #: templates/addoredit.html:28 templates/edituri.html:51 -#: templates/settings.html:145 templates/settings.html:151 +#: templates/settings.html:193 templates/settings.html:199 msgid "Options" msgstr "選項" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "" "Options added here are applied to all backups, but can be overridden in each" " individual backup." @@ -2074,7 +2106,7 @@ msgstr "" msgid "Original location" msgstr "原始位置" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "其它" @@ -2110,7 +2142,7 @@ msgstr "密碼不相符" msgid "Passphrases do not match" msgstr "密碼不相符" -#: scripts/services/EditUriBuiltins.js:1132 templates/backends/file.html:33 +#: scripts/services/EditUriBuiltins.js:1177 templates/backends/file.html:33 #: templates/backends/generic.html:22 templates/backends/idrive.html:11 #: templates/backends/idrive.html:12 templates/backends/mega.html:11 #: templates/backends/mega.html:12 templates/backends/openstack.html:34 @@ -2121,8 +2153,8 @@ msgstr "密碼" msgid "Patching files with local blocks …" msgstr "使用本機區塊修復檔案中 ..." -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "路徑" @@ -2131,7 +2163,7 @@ msgid "Path not found" msgstr "找不到路徑" #: templates/backends/generic.html:13 templates/backends/msgroup.html:7 -#: templates/backends/oauth.html:2 +#: templates/backends/oauth.html:2 templates/backends/pcloud.html:15 msgid "Path on server" msgstr "伺服器路徑" @@ -2140,11 +2172,11 @@ msgstr "伺服器路徑" msgid "Path or subfolder in the bucket" msgstr "Bucket 裡的路徑或子資料夾" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "暫停" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "當啟動或休眠後暫停" @@ -2182,6 +2214,10 @@ msgstr "關閉從系統列 (Tray) 圖示自動登入" msgid "Previous" msgstr "上一頁" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "正在處理:" @@ -2190,7 +2226,7 @@ msgstr "正在處理:" msgid "ProjectID is optional if the bucket exist" msgstr "ProjectID is optional if the bucket exist" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "雲端服務" @@ -2226,10 +2262,30 @@ msgstr "正在重建資料庫 ..." msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "正在註冊暫時備份 ..." +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "不允許使用相對路徑" @@ -2242,14 +2298,34 @@ msgstr "重新載入" msgid "Remote" msgstr "遠端" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "遠端 Path" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "遠端 Repository" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "遠端 path" @@ -2392,7 +2468,7 @@ msgstr "" msgid "S3 Compatible" msgstr "S3 相容" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "與目前已安裝版本相同: {{channelname}}" @@ -2440,7 +2516,7 @@ msgstr "搜尋" msgid "Search for files" msgstr "搜尋檔案" -#: scripts/services/AppUtils.js:98 templates/settings.html:51 +#: scripts/services/AppUtils.js:98 templates/settings.html:89 msgid "Seconds" msgstr "秒" @@ -2453,8 +2529,8 @@ msgstr "選擇一個記錄等級以查看訊息︰" msgid "Select files" msgstr "選擇檔案" -#: scripts/services/EditUriBuiltins.js:1140 templates/backends/s3.html:8 -#: templates/backends/sia.html:2 +#: scripts/services/EditUriBuiltins.js:1185 templates/backends/pcloud.html:2 +#: templates/backends/s3.html:8 templates/backends/sia.html:2 msgid "Server" msgstr "伺服器" @@ -2492,6 +2568,10 @@ msgstr "伺服器目前已暫停" msgid "Server state properties" msgstr "伺服器狀態屬性" +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + #: index.html:221 templates/settings.html:2 msgid "Settings" msgstr "設定" @@ -2561,15 +2641,15 @@ msgstr "來源資料夾" msgid "Source:" msgstr "來源:" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "僅針對開發人員的特定組建版本,請不要使用在重要資料上。" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "標準通訊協定" @@ -2596,19 +2676,15 @@ msgstr "正在開始還原..." msgid "Starting the restore process …" msgstr "正在開始還原程序 ..." -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" -msgstr "這個檔案完成後停止" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" +msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "這個檔案完成後停止" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "立即停止" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "停止正在進行的備份" @@ -2662,7 +2738,7 @@ msgstr "符號連結" msgid "System Files" msgstr "系統檔案" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "系統預設 ({{levelname}})" @@ -2718,6 +2794,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "測試階段" @@ -2730,7 +2811,7 @@ msgstr "測試連線" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "正在測試權限 ..." @@ -2738,10 +2819,10 @@ msgstr "正在測試權限 ..." msgid "Testing …" msgstr "測試中 ..." -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "" "The '{{fieldname}}' field contains an invalid character: {{character}} " "(value: {{value}}, index: {{pos}})" @@ -2766,7 +2847,7 @@ msgid "" "size'\">See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "Bucket 名稱應該全部小寫,要自動轉換嗎?" @@ -2780,15 +2861,15 @@ msgstr "設定應該注意安全,您確定將含有密碼的設定儲存為不 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "深色主題 (by Michal)" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "預設白色主題 (by Alex)" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2815,7 +2896,7 @@ msgstr "" "你想要更換原先的主機金鑰 \"{{prev}}\" 到 {{key}} 嗎?" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "密碼不符" @@ -2864,7 +2945,7 @@ msgid "" "The target folder contains encrypted files, please supply the passphrase" msgstr "目的資料夾中包含加密檔案,請提供密碼" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "" "The user has too many permissions. Do you want to create a new limited user," " with only permissions to the selected path?" @@ -2887,7 +2968,7 @@ msgstr "本月" msgid "This week" msgstr "本週" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "頻寬限制設定" @@ -2900,6 +2981,10 @@ msgstr "週四" msgid "Time" msgstr "時間" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "到檔案" @@ -2914,7 +2999,7 @@ msgstr "確認要刪除所有的遠端檔案 \"{{name}}\",請輸入下面的 msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "若要無密碼匯出,請不要勾選\"加密檔案\"核取方塊" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "" "To prevent bucket naming conflicts, it is recommended to prepend your " "account ID to the bucket name. Prepend automatically?" @@ -2945,7 +3030,7 @@ msgstr "信任主機憑證?" msgid "Trust server certificate?" msgstr "信任伺服器憑證?" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "" "Try out the new features that we are working on. Currently the most stable " "version available. Test Restore data before using this in production " @@ -2979,7 +3064,7 @@ msgid "" " Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "更新頻道" @@ -2999,18 +3084,18 @@ msgstr "已上傳檔案" msgid "Uploading verification file …" msgstr "正在上傳驗證檔案 ..." -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "" "Usage reports help us improve the user experience and evaluate impact of new" " features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "使用統計" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "使用統計、警告、錯誤與當機" @@ -3038,17 +3123,17 @@ msgstr "使用者資料" msgid "User domain name" msgstr "使用者網域名稱" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "使用者有太多權限" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "使用者介面設定" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 templates/backends/file.html:29 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 templates/backends/file.html:29 #: templates/backends/generic.html:18 templates/backends/idrive.html:7 #: templates/backends/idrive.html:8 templates/backends/mega.html:7 #: templates/backends/mega.html:8 templates/backends/openstack.html:30 @@ -3138,7 +3223,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "等待上傳完成中 ..." -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "警告、錯誤與當機" @@ -3174,7 +3259,7 @@ msgstr "您要還原檔案到哪裡?" msgid "Years" msgstr "年" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3187,9 +3272,9 @@ msgstr "年" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "是" @@ -3228,14 +3313,16 @@ msgstr "您正在執行 {{appname}} {{version}}" #: scripts/controllers/StateController.js:107 msgid "" "You can stop the backup after any file uploads currently in progress have " -"finished." -msgstr "您可以立即停止備份,將在目前檔案上傳完成後停止。" +"finished. If you terminate the backup, the next run will need to recover " +"from a failed backup." +msgstr "" #: scripts/controllers/StateController.js:116 msgid "" "You can stop the task immediately, or allow the process to continue its " -"current file and then stop." -msgstr "您可以立即停止備份作業,或是讓備份作業進行至目前檔案完成後再停止。" +"current file and then stop. If you terminate the task, the backup could be " +"left in an inconsistent state." +msgstr "" #: scripts/controllers/EditBackupController.js:403 msgid "" @@ -3270,7 +3357,7 @@ msgstr "您已經產生足夠強度的密碼。請確保您已經另外備份好 msgid "You must choose at least one source folder" msgstr "您至少要選擇一個來源資料夾" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "您必須輸入網域名稱以使用 v3 API" @@ -3282,7 +3369,7 @@ msgstr "您必須輸入備份名稱" msgid "You must enter a passphrase or disable encryption" msgstr "您必須輸入密碼或取消加密" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "您必須輸入密碼以使用 v3 API" @@ -3290,11 +3377,11 @@ msgstr "您必須輸入密碼以使用 v3 API" msgid "You must enter a positive number of backups to keep" msgstr "您必須輸入正數,備份才能保存" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "您必須輸入 tenant (或 project) 名稱以使用 v3 API" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3306,11 +3393,11 @@ msgstr "您必須輸入有效的起迄時間來保留備份" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3331,11 +3418,11 @@ msgstr "您必須填寫使用者名稱" msgid "You must fill in {{field}}" msgstr "您必須填寫 {{field}}" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "您必須選擇或填寫 AuthURI" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "您必須選擇或填寫伺服器" @@ -3367,23 +3454,23 @@ msgstr "byte" msgid "byte/s" msgstr "byte/s" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3398,29 +3485,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3433,11 +3528,11 @@ msgstr "" msgid "resume now" msgstr "立即繼續" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "除非您明確的指定 --group-id" diff --git a/Localizations/webroot/localization_webroot.pot b/Localizations/webroot/localization_webroot.pot index db7a0d527..fbf7a2c4e 100644 --- a/Localizations/webroot/localization_webroot.pot +++ b/Localizations/webroot/localization_webroot.pot @@ -24,6 +24,7 @@ msgstr "" msgid "- pick an option -" msgstr "" +#: scripts/controllers/SystemSettingsController.js:217 #: templates/delete.html:7 msgid "...loading..." msgstr "" @@ -40,37 +41,37 @@ msgstr "" msgid " Edit as text" msgstr "" -#: index.html:294 +#: index.html:295 msgid "" "

    Connection to server was rejected due to invalid authentication.

    \n" "

    Log in again, or re-open the page from the TrayIcon (if applicable)

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1167 +#: scripts/services/EditUriBuiltins.js:1212 #: templates/backends/openstack.html:44 #: templates/backends/storj.html:21 #: templates/backends/storj.html:22 msgid "API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1041 +#: scripts/services/EditUriBuiltins.js:1086 #: templates/backends/s3.html:66 #: templates/backends/s3.html:68 msgid "AWS Access ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1042 +#: scripts/services/EditUriBuiltins.js:1087 #: templates/backends/s3.html:71 #: templates/backends/s3.html:73 msgid "AWS Access Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:153 +#: scripts/services/EditUriBuiltins.js:154 msgid "AWS IAM Policy" msgstr "" -#: index.html:223 -#: index.html:239 +#: index.html:224 +#: index.html:240 msgid "About" msgstr "" @@ -78,7 +79,7 @@ msgstr "" msgid "About {{appname}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:990 +#: scripts/services/EditUriBuiltins.js:1035 msgid "Access Key" msgstr "" @@ -108,7 +109,7 @@ msgstr "" msgid "Access to user interface" msgstr "" -#: scripts/services/EditUriBuiltins.js:989 +#: scripts/services/EditUriBuiltins.js:1034 #: templates/backends/azure.html:7 #: templates/backends/azure.html:8 msgid "Account name" @@ -126,7 +127,7 @@ msgstr "" msgid "Add advanced option" msgstr "" -#: index.html:211 +#: index.html:212 msgid "Add backup" msgstr "" @@ -142,8 +143,8 @@ msgstr "" msgid "Added" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 msgid "Adjust bucket name?" msgstr "" @@ -179,7 +180,7 @@ msgstr "" msgid "All Microsoft SQL Databases" msgstr "" -#: templates/settings.html:127 +#: templates/settings.html:175 msgid "All usage reports are sent anonymously and do not contain any personal information. They contain information about hardware and operating system, the type of backend, backup duration, overall size of source data and similar data. They do not contain paths, filenames, usernames, passwords or similar sensitive information." msgstr "" @@ -191,6 +192,10 @@ msgstr "" msgid "Allowed days" msgstr "" +#: templates/pause.html:52 +msgid "Also pause transfers" +msgstr "" + #: scripts/controllers/LocalDatabaseController.js:103 msgid "An existing file was found at the new location" msgstr "" @@ -209,7 +214,7 @@ msgid "" " Do you wish to use the existing database?" msgstr "" -#: templates/settings.html:115 +#: templates/settings.html:163 msgid "Anonymous usage reports" msgstr "" @@ -217,11 +222,16 @@ msgstr "" msgid "Applications" msgstr "" +#: scripts/controllers/SystemSettingsController.js:194 +msgid "Are you sure you want to delete the remote control registration?" +msgstr "" + #: templates/export.html:8 msgid "As Command-line" msgstr "" -#: scripts/services/EditUriBuiltins.js:958 +#: scripts/services/EditUriBuiltins.js:1002 +#: scripts/services/EditUriBuiltins.js:992 #: templates/backends/gcs.html:8 #: templates/backends/gcs.html:9 #: templates/backends/jottacloud.html:8 @@ -230,12 +240,14 @@ msgstr "" #: templates/backends/msgroup.html:14 #: templates/backends/oauth.html:8 #: templates/backends/oauth.html:9 +#: templates/backends/pcloud.html:21 +#: templates/backends/pcloud.html:22 #: templates/backends/sharepoint.html:8 #: templates/backends/sharepoint.html:9 msgid "AuthID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1158 +#: scripts/services/EditUriBuiltins.js:1203 #: templates/backends/storj.html:2 msgid "Authentication method" msgstr "" @@ -270,7 +282,7 @@ msgstr "" msgid "B2 Application Key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1087 +#: scripts/services/EditUriBuiltins.js:1132 msgid "B2 Cloud Storage Account ID" msgstr "" @@ -278,7 +290,7 @@ msgstr "" msgid "B2 Cloud Storage Application ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1088 +#: scripts/services/EditUriBuiltins.js:1133 #: templates/backends/b2.html:17 msgid "B2 Cloud Storage Application Key" msgstr "" @@ -318,7 +330,7 @@ msgstr "" msgid "Backup:" msgstr "" -#: templates/settings.html:92 +#: templates/settings.html:140 msgid "Beta" msgstr "" @@ -331,7 +343,7 @@ msgstr "" msgid "Browse" msgstr "" -#: scripts/controllers/SystemSettingsController.js:33 +#: scripts/controllers/SystemSettingsController.js:39 msgid "Browser default" msgstr "" @@ -339,14 +351,14 @@ msgstr "" msgid "Bucket create location" msgstr "" -#: scripts/services/EditUriBuiltins.js:1000 -#: scripts/services/EditUriBuiltins.js:1040 -#: scripts/services/EditUriBuiltins.js:1086 -#: scripts/services/EditUriBuiltins.js:1100 -#: scripts/services/EditUriBuiltins.js:1163 -#: scripts/services/EditUriBuiltins.js:1169 -#: scripts/services/EditUriBuiltins.js:1226 -#: scripts/services/EditUriBuiltins.js:1238 +#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1085 +#: scripts/services/EditUriBuiltins.js:1131 +#: scripts/services/EditUriBuiltins.js:1145 +#: scripts/services/EditUriBuiltins.js:1208 +#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1270 +#: scripts/services/EditUriBuiltins.js:1282 #: templates/backends/b2.html:2 #: templates/backends/b2.html:3 #: templates/backends/cos.html:25 @@ -361,7 +373,7 @@ msgstr "" msgid "Bucket name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1181 +#: scripts/services/EditUriBuiltins.js:1226 msgid "Bucket name can only be between 3 and 63 characters long and contain only lower-case characters, numbers, periods and dashes" msgstr "" @@ -421,12 +433,13 @@ msgstr "" msgid "Cache Files" msgstr "" -#: templates/settings.html:102 +#: templates/settings.html:150 msgid "Canary" msgstr "" +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:345 #: scripts/controllers/EditBackupController.js:360 #: scripts/controllers/EditBackupController.js:394 @@ -442,16 +455,20 @@ msgstr "" #: scripts/controllers/StateController.js:117 #: scripts/services/CaptchaService.js:22 #: scripts/services/DialogService.js:50 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 #: templates/delete.html:54 #: templates/export.html:34 -#: templates/settings.html:157 +#: templates/settings.html:205 #: templates/waitarea.html:19 msgid "Cancel" msgstr "" +#: templates/settings.html:56 +msgid "Cancel registration" +msgstr "" + #: scripts/directives/sourceFolderPicker.js:409 msgid "Cannot include \"{{text}}\"" msgstr "" @@ -468,6 +485,10 @@ msgstr "" msgid "Change server passphrase" msgstr "" +#: scripts/controllers/AppController.js:195 +msgid "Change server password" +msgstr "" + #: templates/about.html:5 msgid "Changelog" msgstr "" @@ -504,12 +525,13 @@ msgstr "" #: templates/backends/jottacloud.html:11 #: templates/backends/msgroup.html:16 #: templates/backends/oauth.html:11 +#: templates/backends/pcloud.html:24 #: templates/backends/sharepoint.html:11 msgid "Click the AuthID link to create an AuthID" msgstr "" -#: index.html:148 -#: index.html:199 +#: index.html:149 +#: index.html:200 msgid "Click to set throttle options" msgstr "" @@ -592,6 +614,7 @@ msgstr "" msgid "Confirm encryption passphrase" msgstr "" +#: templates/changepassword.html:14 #: templates/settings.html:15 msgid "Confirm new password" msgstr "" @@ -608,11 +631,11 @@ msgstr "" msgid "Connect" msgstr "" -#: index.html:307 +#: index.html:308 msgid "Connect now" msgstr "" -#: index.html:301 +#: index.html:302 msgid "Connecting to server …" msgstr "" @@ -620,11 +643,11 @@ msgstr "" msgid "Connecting to task …" msgstr "" -#: index.html:308 +#: index.html:309 msgid "Connecting …" msgstr "" -#: index.html:293 +#: index.html:294 msgid "Connection lost" msgstr "" @@ -633,7 +656,7 @@ msgstr "" msgid "Connection worked!" msgstr "" -#: scripts/services/EditUriBuiltins.js:991 +#: scripts/services/EditUriBuiltins.js:1036 #: templates/backends/azure.html:2 #: templates/backends/azure.html:3 msgid "Container name" @@ -677,7 +700,7 @@ msgstr "" msgid "Copy log" msgstr "" -#: scripts/services/AppUtils.js:724 +#: scripts/services/AppUtils.js:735 msgid "Core options" msgstr "" @@ -685,7 +708,7 @@ msgstr "" msgid "Counting ({{files}} files found, {{size}})" msgstr "" -#: templates/settings.html:122 +#: templates/settings.html:170 msgid "Crashes only" msgstr "" @@ -697,7 +720,7 @@ msgstr "" msgid "Create folder?" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "Created new limited user" msgstr "" @@ -705,7 +728,7 @@ msgstr "" msgid "Creating bug report …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating new user with limited access …" msgstr "" @@ -717,7 +740,7 @@ msgstr "" msgid "Creating temporary backup …" msgstr "" -#: scripts/services/EditUriBuiltins.js:122 +#: scripts/services/EditUriBuiltins.js:123 msgid "Creating user …" msgstr "" @@ -737,7 +760,7 @@ msgstr "" msgid "Custom S3 endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1173 +#: scripts/services/EditUriBuiltins.js:1218 #: templates/backends/storj.html:18 msgid "Custom Satellite" msgstr "" @@ -793,11 +816,11 @@ msgstr "" msgid "Days" msgstr "" -#: scripts/controllers/SystemSettingsController.js:34 +#: scripts/controllers/SystemSettingsController.js:40 msgid "Default" msgstr "" -#: templates/settings.html:81 +#: templates/settings.html:129 msgid "Default ({{channelname}})" msgstr "" @@ -805,7 +828,7 @@ msgstr "" msgid "Default excludes" msgstr "" -#: templates/settings.html:142 +#: templates/settings.html:190 msgid "Default options" msgstr "" @@ -834,6 +857,10 @@ msgstr "" msgid "Delete local database" msgstr "" +#: templates/settings.html:68 +msgid "Delete remote control setup" +msgstr "" + #: templates/delete.html:38 #: templates/delete.html:47 msgid "Delete remote files" @@ -892,6 +919,7 @@ msgstr "" #: templates/backends/jottacloud.html:3 #: templates/backends/msgroup.html:8 #: templates/backends/oauth.html:3 +#: templates/backends/pcloud.html:16 #: templates/backends/sharepoint.html:2 msgid "Destination path" msgstr "" @@ -904,6 +932,10 @@ msgstr "" msgid "Directory path" msgstr "" +#: templates/settings.html:60 +msgid "Disable remote control" +msgstr "" + #: templates/log.html:32 msgid "Disabled" msgstr "" @@ -918,7 +950,7 @@ msgstr "" msgid "Dismiss all" msgstr "" -#: templates/settings.html:70 +#: templates/settings.html:108 msgid "Display and color theme" msgstr "" @@ -960,22 +992,22 @@ msgstr "" msgid "Duplicate option {{opt}}" msgstr "" -#: index.html:254 +#: index.html:255 msgid "Duplicati Website" msgstr "" -#: index.html:248 +#: index.html:249 msgid "Duplicati forum" msgstr "" -#: scripts/controllers/AppController.js:184 +#: scripts/controllers/AppController.js:185 msgid "" "Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\n" "If you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\n" "Do you want to set a passphrase now?" msgstr "" -#: templates/settings.html:55 +#: templates/settings.html:93 msgid "Duplicati will run when started, but will remain in a paused state for the duration. Duplicati will occupy minimal system resources and no backups will be run." msgstr "" @@ -1003,14 +1035,14 @@ msgstr "" #: templates/addoredit.html:174 #: templates/addoredit.html:387 #: templates/edituri.html:39 -#: templates/settings.html:146 +#: templates/settings.html:194 msgid "Edit as list" msgstr "" #: templates/addoredit.html:177 #: templates/addoredit.html:390 #: templates/edituri.html:42 -#: templates/settings.html:152 +#: templates/settings.html:200 msgid "Edit as text" msgstr "" @@ -1022,6 +1054,10 @@ msgstr "" msgid "Email address of the Office 365 group" msgstr "" +#: templates/settings.html:64 +msgid "Enable remote control" +msgstr "" + #: templates/export.html:22 msgid "Encrypt file" msgstr "" @@ -1041,7 +1077,7 @@ msgstr "" msgid "Encryption modules:

    {{item.Key}}

    " msgstr "" -#: scripts/services/EditUriBuiltins.js:1168 +#: scripts/services/EditUriBuiltins.js:1213 #: templates/backends/storj.html:25 #: templates/backends/storj.html:26 msgid "Encryption passphrase" @@ -1147,7 +1183,7 @@ msgstr "" msgid "Error!" msgstr "" -#: templates/settings.html:121 +#: templates/settings.html:169 msgid "Errors and crashes" msgstr "" @@ -1195,7 +1231,7 @@ msgstr "" msgid "Existing file found" msgstr "" -#: templates/settings.html:97 +#: templates/settings.html:145 msgid "Experimental" msgstr "" @@ -1290,7 +1326,7 @@ msgstr "" msgid "Failed to restore files: {{message}}" msgstr "" -#: scripts/controllers/SystemSettingsController.js:112 +#: scripts/controllers/SystemSettingsController.js:263 msgid "Failed to save:" msgstr "" @@ -1319,7 +1355,7 @@ msgstr "" msgid "Finished!" msgstr "" -#: scripts/controllers/AppController.js:183 +#: scripts/controllers/AppController.js:184 msgid "First run setup" msgstr "" @@ -1397,7 +1433,7 @@ msgstr "" msgid "Getting file versions …" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 #: templates/backends/msgroup.html:2 msgid "Group email" msgstr "" @@ -1415,7 +1451,7 @@ msgstr "" msgid "Hide hidden folders" msgstr "" -#: index.html:208 +#: index.html:209 #: scripts/services/AppUtils.js:62 msgid "Home" msgstr "" @@ -1426,7 +1462,7 @@ msgstr "" #: scripts/services/AppUtils.js:100 #: scripts/services/AppUtils.js:90 -#: templates/settings.html:53 +#: templates/settings.html:91 msgid "Hours" msgstr "" @@ -1456,12 +1492,12 @@ msgstr "" msgid "IDrive Sync directory path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1224 +#: scripts/services/EditUriBuiltins.js:1268 #: templates/backends/e2.html:3 msgid "IDrive e2 Access Key ID" msgstr "" -#: scripts/services/EditUriBuiltins.js:1225 +#: scripts/services/EditUriBuiltins.js:1269 #: templates/backends/e2.html:7 msgid "IDrive e2 Access Key Secret" msgstr "" @@ -1496,6 +1532,10 @@ msgstr "" msgid "If you do not enter an API Key, the tenant name is required" msgstr "" +#: templates/pause.html:53 +msgid "If you pause transfers they could time out and cause retries or failures." +msgstr "" + #: templates/delete.html:32 msgid "If you want to use the backup later, you can export the configuration before deleting it." msgstr "" @@ -1546,7 +1586,7 @@ msgstr "" msgid "Incorrect answer, try again" msgstr "" -#: templates/settings.html:103 +#: templates/settings.html:151 msgid "Individual builds for developers only. Not for use with important data." msgstr "" @@ -1558,7 +1598,7 @@ msgstr "" msgid "Interrupted, no statistics collected" msgstr "" -#: scripts/services/EditUriBuiltins.js:1144 +#: scripts/services/EditUriBuiltins.js:1189 msgid "Invalid characters in path" msgstr "" @@ -1568,7 +1608,7 @@ msgstr "" msgid "Invalid retention time" msgstr "" -#: scripts/services/EditUriBuiltins.js:934 +#: scripts/services/EditUriBuiltins.js:968 msgid "" "It is possible to connect to some FTP without a password.\n" "Are you sure your FTP server supports password-less logins?" @@ -1594,7 +1634,7 @@ msgstr "" msgid "Keystone API version" msgstr "" -#: templates/settings.html:60 +#: templates/settings.html:98 msgid "Language in user interface" msgstr "" @@ -1666,10 +1706,6 @@ msgstr "" msgid "Loading …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1191 -msgid "Local Repository" -msgstr "" - #: templates/localdatabase.html:2 msgid "Local database for {{Backup.Backup.Name}}…loading…" msgstr "" @@ -1682,7 +1718,7 @@ msgstr "" msgid "Local repository" msgstr "" -#: scripts/services/SystemInfo.js:84 +#: scripts/services/SystemInfo.js:85 msgid "Local storage" msgstr "" @@ -1702,11 +1738,11 @@ msgstr "" msgid "Log data from the server" msgstr "" -#: index.html:306 +#: index.html:307 msgid "Log in" msgstr "" -#: index.html:226 +#: index.html:227 msgid "Log out" msgstr "" @@ -1718,6 +1754,10 @@ msgstr "" msgid "MByte/s" msgstr "" +#: templates/settings.html:51 +msgid "Machine is now registered, open this link to add it to your account:" +msgstr "" + #: templates/localdatabase.html:8 msgid "Maintenance" msgstr "" @@ -1726,7 +1766,7 @@ msgstr "" msgid "Make sure that rclone is in your path, or add the location to rclone via the advanced options." msgstr "" -#: index.html:259 +#: index.html:260 msgid "Manual" msgstr "" @@ -1747,7 +1787,7 @@ msgstr "" msgid "Max upload speed" msgstr "" -#: index.html:144 +#: index.html:145 #: templates/addoredit.html:127 #: templates/addoredit.html:169 #: templates/addoredit.html:382 @@ -1769,13 +1809,13 @@ msgstr "" msgid "Minimum redundancy" msgstr "" -#: scripts/services/EditUriBuiltins.js:1148 +#: scripts/services/EditUriBuiltins.js:1193 msgid "Minimum redundancy is 1.0" msgstr "" #: scripts/services/AppUtils.js:89 #: scripts/services/AppUtils.js:99 -#: templates/settings.html:52 +#: templates/settings.html:90 msgid "Minutes" msgstr "" @@ -1836,6 +1876,7 @@ msgstr "" msgid "Never" msgstr "" +#: templates/changepassword.html:5 #: templates/settings.html:11 msgid "New Password" msgstr "" @@ -1844,7 +1885,7 @@ msgstr "" msgid "New update found: {{message}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 msgid "" "New user name is {{user}}.\n" "Updated credentials to use the new limited user" @@ -1864,11 +1905,11 @@ msgstr "" msgid "Next scheduled run:" msgstr "" -#: index.html:183 +#: index.html:184 msgid "Next scheduled task:" msgstr "" -#: index.html:180 +#: index.html:181 msgid "Next task:" msgstr "" @@ -1876,7 +1917,7 @@ msgstr "" msgid "Next time" msgstr "" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -1889,9 +1930,9 @@ msgstr "" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "No" msgstr "" @@ -1923,7 +1964,7 @@ msgstr "" msgid "No passphrase entered" msgstr "" -#: index.html:185 +#: index.html:186 msgid "No scheduled tasks" msgstr "" @@ -1931,7 +1972,7 @@ msgstr "" msgid "Non-matching passphrase" msgstr "" -#: templates/settings.html:123 +#: templates/settings.html:171 msgid "None / disabled" msgstr "" @@ -1947,8 +1988,9 @@ msgstr "" msgid "Nothing will be deleted. The backup size will grow with each change." msgstr "" +#: scripts/controllers/AppController.js:197 #: scripts/controllers/AppController.js:54 -#: scripts/controllers/AppController.js:69 +#: scripts/controllers/AppController.js:70 #: scripts/controllers/EditBackupController.js:104 #: scripts/controllers/EditBackupController.js:96 #: scripts/controllers/RestoreDirectController.js:24 @@ -1957,9 +1999,9 @@ msgstr "" #: scripts/services/DialogService.js:28 #: scripts/services/DialogService.js:50 #: scripts/services/DialogService.js:58 -#: scripts/services/EditUriBuiltins.js:136 +#: scripts/services/EditUriBuiltins.js:137 #: templates/restore.html:147 -#: templates/settings.html:158 +#: templates/settings.html:206 msgid "OK" msgstr "" @@ -1994,7 +2036,7 @@ msgstr "" msgid "OSS Region" msgstr "" -#: templates/settings.html:88 +#: templates/settings.html:136 msgid "Official releases" msgstr "" @@ -2014,7 +2056,7 @@ msgstr "" msgid "Opened" msgstr "" -#: scripts/services/EditUriBuiltins.js:1016 +#: scripts/services/EditUriBuiltins.js:1061 msgid "Openstack API key are not supported in v3 keystone API" msgstr "" @@ -2052,12 +2094,12 @@ msgstr "" #: templates/addoredit.html:28 #: templates/edituri.html:51 -#: templates/settings.html:145 -#: templates/settings.html:151 +#: templates/settings.html:193 +#: templates/settings.html:199 msgid "Options" msgstr "" -#: templates/settings.html:143 +#: templates/settings.html:191 msgid "Options added here are applied to all backups, but can be overridden in each individual backup." msgstr "" @@ -2065,7 +2107,7 @@ msgstr "" msgid "Original location" msgstr "" -#: scripts/services/SystemInfo.js:87 +#: scripts/services/SystemInfo.js:88 msgid "Others" msgstr "" @@ -2100,7 +2142,7 @@ msgstr "" msgid "Passphrases do not match" msgstr "" -#: scripts/services/EditUriBuiltins.js:1132 +#: scripts/services/EditUriBuiltins.js:1177 #: templates/backends/file.html:33 #: templates/backends/generic.html:22 #: templates/backends/idrive.html:11 @@ -2115,8 +2157,8 @@ msgstr "" msgid "Patching files with local blocks …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1116 -#: scripts/services/EditUriBuiltins.js:1254 +#: scripts/services/EditUriBuiltins.js:1161 +#: scripts/services/EditUriBuiltins.js:1298 msgid "Path" msgstr "" @@ -2127,6 +2169,7 @@ msgstr "" #: templates/backends/generic.html:13 #: templates/backends/msgroup.html:7 #: templates/backends/oauth.html:2 +#: templates/backends/pcloud.html:15 msgid "Path on server" msgstr "" @@ -2136,11 +2179,11 @@ msgstr "" msgid "Path or subfolder in the bucket" msgstr "" -#: templates/settings.html:37 +#: templates/settings.html:75 msgid "Pause" msgstr "" -#: templates/settings.html:35 +#: templates/settings.html:73 msgid "Pause after startup or hibernation" msgstr "" @@ -2180,6 +2223,10 @@ msgstr "" msgid "Previous" msgstr "" +#: scripts/services/ServerStatus.js:39 +msgid "Processing files to backup …" +msgstr "" + #: templates/home.html:78 msgid "Progress:" msgstr "" @@ -2188,7 +2235,7 @@ msgstr "" msgid "ProjectID is optional if the bucket exist" msgstr "" -#: scripts/services/SystemInfo.js:86 +#: scripts/services/SystemInfo.js:87 msgid "Proprietary" msgstr "" @@ -2224,15 +2271,35 @@ msgstr "" msgid "Region" msgstr "" +#: templates/settings.html:47 +msgid "Register for remote control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:213 +msgid "Registered, waiting for accept" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:209 +msgid "Registering machine..." +msgstr "" + #: scripts/controllers/RestoreDirectController.js:40 msgid "Registering temporary backup …" msgstr "" +#: templates/settings.html:42 +msgid "Registration URL" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:211 +msgid "Registration failed" +msgstr "" + #: scripts/controllers/EditBackupController.js:139 msgid "Relative paths not allowed" msgstr "" -#: index.html:305 +#: index.html:306 #: templates/captcha.html:8 msgid "Reload" msgstr "" @@ -2241,14 +2308,34 @@ msgstr "" msgid "Remote" msgstr "" -#: scripts/services/EditUriBuiltins.js:1192 +#: scripts/services/EditUriBuiltins.js:1236 msgid "Remote Path" msgstr "" -#: scripts/services/EditUriBuiltins.js:1190 +#: scripts/services/EditUriBuiltins.js:1235 msgid "Remote Repository" msgstr "" +#: templates/settings.html:34 +msgid "Remote access control" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:215 +msgid "Remote control is configured but not enabled" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:207 +msgid "Remote control is connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:205 +msgid "Remote control is enabled but not connected" +msgstr "" + +#: scripts/controllers/SystemSettingsController.js:219 +msgid "Remote control is not set up" +msgstr "" + #: templates/backends/rclone.html:10 msgid "Remote path" msgstr "" @@ -2298,7 +2385,7 @@ msgstr "" msgid "Reset" msgstr "" -#: index.html:214 +#: index.html:215 #: templates/restore.html:137 msgid "Restore" msgstr "" @@ -2359,7 +2446,7 @@ msgstr "" msgid "Restoring files …" msgstr "" -#: index.html:217 +#: index.html:218 msgid "Resume" msgstr "" @@ -2380,7 +2467,7 @@ msgstr "" msgid "Running commandline entry" msgstr "" -#: index.html:172 +#: index.html:173 msgid "Running task:" msgstr "" @@ -2396,7 +2483,7 @@ msgstr "" msgid "S3 Compatible" msgstr "" -#: templates/settings.html:82 +#: templates/settings.html:130 msgid "Same as the base install version: {{channelname}}" msgstr "" @@ -2447,7 +2534,7 @@ msgid "Search for files" msgstr "" #: scripts/services/AppUtils.js:98 -#: templates/settings.html:51 +#: templates/settings.html:89 msgid "Seconds" msgstr "" @@ -2461,7 +2548,8 @@ msgstr "" msgid "Select files" msgstr "" -#: scripts/services/EditUriBuiltins.js:1140 +#: scripts/services/EditUriBuiltins.js:1185 +#: templates/backends/pcloud.html:2 #: templates/backends/s3.html:8 #: templates/backends/sia.html:2 msgid "Server" @@ -2500,7 +2588,11 @@ msgstr "" msgid "Server state properties" msgstr "" -#: index.html:220 +#: templates/settings.html:121 +msgid "Set timezone to default" +msgstr "" + +#: index.html:221 #: templates/settings.html:2 msgid "Settings" msgstr "" @@ -2571,15 +2663,15 @@ msgstr "" msgid "Source:" msgstr "" -#: templates/settings.html:98 +#: templates/settings.html:146 msgid "Specific builds for developers only. Not for use with important data." msgstr "" -#: templates/settings.html:87 +#: templates/settings.html:135 msgid "Stable" msgstr "" -#: scripts/services/SystemInfo.js:85 +#: scripts/services/SystemInfo.js:86 msgid "Standard protocols" msgstr "" @@ -2606,19 +2698,15 @@ msgstr "" msgid "Starting the restore process …" msgstr "" -#: scripts/controllers/StateController.js:108 -msgid "Stop after current file" +#: templates/settings.html:36 +msgid "Status: {{getRemoteControlStatusText()}}" msgstr "" +#: scripts/controllers/StateController.js:108 #: scripts/controllers/StateController.js:117 msgid "Stop after the current file" msgstr "" -#: scripts/controllers/StateController.js:108 -#: scripts/controllers/StateController.js:117 -msgid "Stop now" -msgstr "" - #: scripts/controllers/StateController.js:106 msgid "Stop running backup" msgstr "" @@ -2627,11 +2715,11 @@ msgstr "" msgid "Stop running task" msgstr "" -#: index.html:168 +#: index.html:169 msgid "Stopping after the current file:" msgstr "" -#: index.html:173 +#: index.html:174 msgid "Stopping task:" msgstr "" @@ -2672,7 +2760,7 @@ msgstr "" msgid "System Files" msgstr "" -#: templates/settings.html:118 +#: templates/settings.html:166 msgid "System default ({{levelname}})" msgstr "" @@ -2728,6 +2816,11 @@ msgstr "" msgid "Tencent Cloud COS documents and resources" msgstr "" +#: scripts/controllers/StateController.js:108 +#: scripts/controllers/StateController.js:117 +msgid "Terminate" +msgstr "" + #: templates/backup-result/phases/test.html:3 msgid "Test Phase" msgstr "" @@ -2740,7 +2833,7 @@ msgstr "" msgid "Testing connection …" msgstr "" -#: scripts/services/EditUriBuiltins.js:48 +#: scripts/services/EditUriBuiltins.js:49 msgid "Testing permissions …" msgstr "" @@ -2749,10 +2842,10 @@ msgstr "" msgid "Testing …" msgstr "" -#: scripts/services/EditUriBuiltins.js:1096 -#: scripts/services/EditUriBuiltins.js:1112 -#: scripts/services/EditUriBuiltins.js:1234 -#: scripts/services/EditUriBuiltins.js:1250 +#: scripts/services/EditUriBuiltins.js:1141 +#: scripts/services/EditUriBuiltins.js:1157 +#: scripts/services/EditUriBuiltins.js:1278 +#: scripts/services/EditUriBuiltins.js:1294 msgid "The '{{fieldname}}' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})" msgstr "" @@ -2768,7 +2861,7 @@ msgstr "" msgid "The backups will be split up into multiple files called volumes. Here you can set the maximum size of the individual volume files. See this page for more information." msgstr "" -#: scripts/services/EditUriBuiltins.js:1068 +#: scripts/services/EditUriBuiltins.js:1113 msgid "The bucket name should be all lower-case, convert automatically?" msgstr "" @@ -2776,19 +2869,19 @@ msgstr "" msgid "The configuration should be kept safe. Are you sure you want to save an unencrypted file containing your passwords?" msgstr "" -#: index.html:298 +#: index.html:299 msgid "The connection to the server is lost, attempting again in {{time}} …" msgstr "" -#: templates/settings.html:74 +#: templates/settings.html:112 msgid "The dark theme (by Michal)" msgstr "" -#: templates/settings.html:73 +#: templates/settings.html:111 msgid "The default blue on white theme (by Alex)" msgstr "" -#: scripts/services/EditUriBuiltins.js:1177 +#: scripts/services/EditUriBuiltins.js:1222 msgid "The encryption passphrases do not match" msgstr "" @@ -2810,7 +2903,7 @@ msgid "" msgstr "" #: scripts/controllers/ExportController.js:70 -#: scripts/controllers/SystemSettingsController.js:91 +#: scripts/controllers/SystemSettingsController.js:242 msgid "The passwords do not match" msgstr "" @@ -2851,7 +2944,7 @@ msgstr "" msgid "The target folder contains encrypted files, please supply the passphrase" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "The user has too many permissions. Do you want to create a new limited user, with only permissions to the selected path?" msgstr "" @@ -2867,7 +2960,7 @@ msgstr "" msgid "This week" msgstr "" -#: scripts/controllers/AppController.js:67 +#: scripts/controllers/AppController.js:68 msgid "Throttle settings" msgstr "" @@ -2880,6 +2973,10 @@ msgstr "" msgid "Time" msgstr "" +#: templates/settings.html:117 +msgid "Time zone" +msgstr "" + #: templates/export.html:14 msgid "To File" msgstr "" @@ -2892,7 +2989,7 @@ msgstr "" msgid "To export without a passphrase, uncheck the \"Encrypt file\" box" msgstr "" -#: scripts/services/EditUriBuiltins.js:1052 +#: scripts/services/EditUriBuiltins.js:1097 msgid "To prevent bucket naming conflicts, it is recommended to prepend your account ID to the bucket name. Prepend automatically?" msgstr "" @@ -2912,7 +3009,7 @@ msgstr "" msgid "Trust server certificate?" msgstr "" -#: templates/settings.html:93 +#: templates/settings.html:141 msgid "Try out the new features that we are working on. Currently the most stable version available. Test Restore data before using this in production environments." msgstr "" @@ -2940,7 +3037,7 @@ msgstr "" msgid "Update {{state.updatedVersion}} is available. Download now" msgstr "" -#: templates/settings.html:78 +#: templates/settings.html:126 msgid "Update channel" msgstr "" @@ -2960,15 +3057,15 @@ msgstr "" msgid "Uploading verification file …" msgstr "" -#: templates/settings.html:125 +#: templates/settings.html:173 msgid "Usage reports help us improve the user experience and evaluate impact of new features. We use them to generate public usage statistics." msgstr "" -#: templates/settings.html:113 +#: templates/settings.html:161 msgid "Usage statistics" msgstr "" -#: templates/settings.html:119 +#: templates/settings.html:167 msgid "Usage statistics, warnings, errors, and crashes" msgstr "" @@ -2997,17 +3094,17 @@ msgstr "" msgid "User domain name" msgstr "" -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:58 msgid "User has too many permissions" msgstr "" -#: templates/settings.html:58 +#: templates/settings.html:96 msgid "User interface settings" msgstr "" -#: scripts/services/EditUriBuiltins.js:1131 -#: scripts/services/EditUriBuiltins.js:929 -#: scripts/services/EditUriBuiltins.js:999 +#: scripts/services/EditUriBuiltins.js:1044 +#: scripts/services/EditUriBuiltins.js:1176 +#: scripts/services/EditUriBuiltins.js:963 #: templates/backends/file.html:29 #: templates/backends/generic.html:18 #: templates/backends/idrive.html:7 @@ -3077,7 +3174,7 @@ msgstr "" msgid "Very weak" msgstr "" -#: index.html:245 +#: index.html:246 msgid "Visit us on" msgstr "" @@ -3101,7 +3198,7 @@ msgstr "" msgid "Waiting for upload to finish …" msgstr "" -#: templates/settings.html:120 +#: templates/settings.html:168 msgid "Warnings, errors and crashes" msgstr "" @@ -3139,7 +3236,7 @@ msgstr "" msgid "Years" msgstr "" -#: scripts/controllers/AppController.js:185 +#: scripts/controllers/AppController.js:186 #: scripts/controllers/DeleteController.js:77 #: scripts/controllers/EditBackupController.js:154 #: scripts/controllers/EditBackupController.js:164 @@ -3152,9 +3249,9 @@ msgstr "" #: scripts/directives/backupEditUri.js:172 #: scripts/directives/backupEditUri.js:86 #: scripts/services/EditUriBackendConfig.js:73 -#: scripts/services/EditUriBuiltins.js:1052 -#: scripts/services/EditUriBuiltins.js:1068 -#: scripts/services/EditUriBuiltins.js:57 +#: scripts/services/EditUriBuiltins.js:1097 +#: scripts/services/EditUriBuiltins.js:1113 +#: scripts/services/EditUriBuiltins.js:58 msgid "Yes" msgstr "" @@ -3189,11 +3286,11 @@ msgid "You are currently running {{appname}} {{version}}" msgstr "" #: scripts/controllers/StateController.js:107 -msgid "You can stop the backup after any file uploads currently in progress have finished." +msgid "You can stop the backup after any file uploads currently in progress have finished. If you terminate the backup, the next run will need to recover from a failed backup." msgstr "" #: scripts/controllers/StateController.js:116 -msgid "You can stop the task immediately, or allow the process to continue its current file and then stop." +msgid "You can stop the task immediately, or allow the process to continue its current file and then stop. If you terminate the task, the backup could be left in an inconsistent state." msgstr "" #: scripts/controllers/EditBackupController.js:403 @@ -3220,7 +3317,7 @@ msgstr "" msgid "You must choose at least one source folder" msgstr "" -#: scripts/services/EditUriBuiltins.js:1010 +#: scripts/services/EditUriBuiltins.js:1055 msgid "You must enter a domain name to use v3 API" msgstr "" @@ -3232,7 +3329,7 @@ msgstr "" msgid "You must enter a passphrase or disable encryption" msgstr "" -#: scripts/services/EditUriBuiltins.js:1007 +#: scripts/services/EditUriBuiltins.js:1052 msgid "You must enter a password to use v3 API" msgstr "" @@ -3240,11 +3337,11 @@ msgstr "" msgid "You must enter a positive number of backups to keep" msgstr "" -#: scripts/services/EditUriBuiltins.js:1013 +#: scripts/services/EditUriBuiltins.js:1058 msgid "You must enter a tenant (aka project) name to use v3 API" msgstr "" -#: scripts/services/EditUriBuiltins.js:1027 +#: scripts/services/EditUriBuiltins.js:1072 msgid "You must enter a tenant name if you do not provide an API key" msgstr "" @@ -3256,11 +3353,11 @@ msgstr "" msgid "You must enter a valid retention policy string" msgstr "" -#: scripts/services/EditUriBuiltins.js:1024 +#: scripts/services/EditUriBuiltins.js:1069 msgid "You must enter either a password or an API key" msgstr "" -#: scripts/services/EditUriBuiltins.js:1031 +#: scripts/services/EditUriBuiltins.js:1076 msgid "You must enter either a password or an API key, not both" msgstr "" @@ -3281,11 +3378,11 @@ msgstr "" msgid "You must fill in {{field}}" msgstr "" -#: scripts/services/EditUriBuiltins.js:1003 +#: scripts/services/EditUriBuiltins.js:1048 msgid "You must select or fill in the AuthURI" msgstr "" -#: scripts/services/EditUriBuiltins.js:1045 +#: scripts/services/EditUriBuiltins.js:1090 msgid "You must select or fill in the server" msgstr "" @@ -3318,23 +3415,23 @@ msgstr "" msgid "byte/s" msgstr "" -#: scripts/services/EditUriBuiltins.js:1200 +#: scripts/services/EditUriBuiltins.js:1244 msgid "cos_app_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1204 +#: scripts/services/EditUriBuiltins.js:1248 msgid "cos_bucket" msgstr "" -#: scripts/services/EditUriBuiltins.js:1203 +#: scripts/services/EditUriBuiltins.js:1247 msgid "cos_region" msgstr "" -#: scripts/services/EditUriBuiltins.js:1201 +#: scripts/services/EditUriBuiltins.js:1245 msgid "cos_secret_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1202 +#: scripts/services/EditUriBuiltins.js:1246 msgid "cos_secret_key" msgstr "" @@ -3350,29 +3447,37 @@ msgid "failed" msgstr "" #: templates/backends/rclone.html:3 -msgid "local repository, e.g. local" +msgid "local repository, leave empty for local" msgstr "" -#: scripts/services/EditUriBuiltins.js:1213 +#: scripts/services/EditUriBuiltins.js:1257 msgid "oss_access_key_id" msgstr "" -#: scripts/services/EditUriBuiltins.js:1214 +#: scripts/services/EditUriBuiltins.js:1258 msgid "oss_access_key_secret" msgstr "" -#: scripts/services/EditUriBuiltins.js:1216 +#: scripts/services/EditUriBuiltins.js:1260 msgid "oss_bucket_name" msgstr "" -#: scripts/services/EditUriBuiltins.js:1212 +#: scripts/services/EditUriBuiltins.js:1256 msgid "oss_endpoint" msgstr "" -#: scripts/services/EditUriBuiltins.js:1215 +#: scripts/services/EditUriBuiltins.js:1259 msgid "oss_region" msgstr "" +#: templates/backends/pcloud.html:10 +msgid "pCloud EU (eapi.pcloud.com)" +msgstr "" + +#: templates/backends/pcloud.html:7 +msgid "pCloud Global (api.pcloud.com)" +msgstr "" + #: templates/backends/rclone.html:11 msgid "remote path, e.g. backup" msgstr "" @@ -3386,11 +3491,11 @@ msgstr "" msgid "resume now" msgstr "" -#: scripts/services/EditUriBuiltins.js:1162 +#: scripts/services/EditUriBuiltins.js:1207 msgid "storj_shared_access" msgstr "" -#: scripts/services/EditUriBuiltins.js:980 +#: scripts/services/EditUriBuiltins.js:1025 msgid "unless you are explicitly specifying --group-id" msgstr "" diff --git a/README.ja-JP.md b/README.ja-JP.md index c44107140..1d24d280c 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -19,87 +19,81 @@ Gitterは削除済 [![カバレッジの状況](https://coveralls.io/repos/github/duplicati/duplicati/badge.svg?branch=HEAD)](https://coveralls.io/github/duplicati/duplicati?branch=HEAD) [![ライセンス](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/duplicati/duplicati/blob/master/LICENSE.txt) +Duplicati は、フリー(自由)でオープンソースのバックアップ用クライアントです。圧縮し、暗号化した増分バックアップを、クラウドストレージサービスや遠隔のファイルサーバー上に安全に保存できます。Duplicati は、主に以下のサービスやソフトウェアで使うことができます。 -Duplicatiは、フリー(自由)でオープンソースのバックアップ用クライアントです。圧縮し、暗号化した増分バックアップを、クラウドストレージサービスや遠隔のファイルサーバー上に安全に保存できます。Duplicatiは、主に以下のサービスやソフトウェアで使うことができます。 +   _Amazon S3、[IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage")、Box、Dropbox、FTP、Google クラウド、Google ドライブ、MEGA、Microsoft Azure、Microsoft OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Sia、Storj DCS、SSH (SFTP)、WebDAV、Tencent クラウドオブジェクトストレージ(COS)、Aliyun OSS、[その他にも対応しています!](https://docs.duplicati.com/backup-destinations/destination-overview)_ -   *Amazon S3、[IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage")、Box、Dropbox、FTP、Googleクラウド、Googleドライブ、MEGA、Microsoft Azure、Microsoft OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Sia、Storj DCS、SSH (SFTP)、WebDAV、Tencentクラウドオブジェクトストレージ(COS)、Aliyun OSS、[その他にも対応しています!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)* +Duplicati は MIT ライセンスで公開されており、Windows、OSX、Linux で利用できます(.NET 4.7.1 以上、または Mono 5.10.0 以上が必要です)。 -DuplicatiはMITライセンスで公開されており、Windows、OSX、Linuxで利用できます(.NET 4.7.1以上、またはMono 5.10.0以上が必要です)。 +# ダウンロード -ダウンロード -======== +Duplicati 2.0 のベータ版が Duplicati の最新バージョンとなります。 -Duplicati 2.0のベータ版がDuplicatiの最新バージョンとなります。 +[ここをクリックすると、Duplicati 2.0 のベータ版をダウンロードできます。](https://duplicati.com/download) -[ここをクリックすると、Duplicati 2.0のベータ版をダウンロードできます。](https://duplicati.com/download) - -ベータ版では、アップデートがある場合に自動的に通知を行い、1クリック(またはターミナルでのコマンド入力)でアップグレードできます。 +ベータ版では、アップデートがある場合に自動的に通知を行い、1 クリック(またはターミナルでのコマンド入力)でアップグレードできます。 より新しい[テスト版に関しては、最新のリリースを確認](https://github.com/duplicati/duplicati/releases)するか、ソフトウェア上の画面またはコマンドラインで、別のアップデートチャンネルを選択してください。 -全てのリリースは、GPGで署名されます。署名に使われる公開鍵は[3DAC703D](https://keys.openpgp.org/search?q=0xC20E90473DAC703D)となります。最新の署名ファイル(バイナリー版とASCII版)については、[Duplicatiのダウンロード用ページ](https://github.com/duplicati/duplicati/releases)から入手できます。 +全てのリリースは、GPG で署名されます。署名に使われる公開鍵は[3DAC703D](https://keys.openpgp.org/search?q=0xC20E90473DAC703D)となります。最新の署名ファイル(バイナリー版と ASCII 版)については、[Duplicati のダウンロード用ページ](https://github.com/duplicati/duplicati/releases)から入手できます。 -サポート -======= +# サポート -Duplicatiは、活発なコミュニティーによってサポートが行われています。コミュニティーには[フォーラム](https://forum.duplicati.com)からご参加ください。 +Duplicati は、活発なコミュニティーによってサポートが行われています。コミュニティーには[フォーラム](https://forum.duplicati.com)からご参加ください。 -[Duplicatiのマニュアル](https://docs.duplicati.com)もあります。マニュアルの作成や維持にぜひ[ご参加](https://github.com/kees-z/DuplicatiDocs)ください。 +[Duplicati のマニュアル](https://docs.duplicati.com)もあります。マニュアルの作成や維持にぜひ[ご参加](https://github.com/kees-z/DuplicatiDocs)ください。 -機能 -======== +# 機能 - * 全てのデータについて、アップロードする前にAES-256(またはGNU Privacy Guard)による暗号化を行い、データの安全性を確保します。 - * 最初に全体のフルバックアップを行い、その後、小さな増分のバックアップを送信することにより、回線の帯域幅と保存領域の使用量を節約します。 - * スケジュールの設定機能により、バックアップを最新のものに自動的に維持します。 - * 新しいリリースが公開された際に、通知を行います。 - * 暗号化したバックアップのファイルを、FTP、 Cloudfiles、WebDAV、SSH (SFTP)、Amazon S3などのサービスに送信します。 - * フォルダー、ドキュメントや画像などファイルの種類、ユーザー定義のフィルターを指定して、バックアップを実行できます。 - * 簡単に使える操作画面と、コマンドラインのツールを備えています。 - * Windowsのボリュームシャドウコピーサービス(VSS)や、Linuxの論理ボリューム管理(LVM)によって、プログラムによって開かれているファイルや、ロックされているファイルを適切にバックアップできます。これにより、Microsoft Outlookを使っている際に、OutlookのPSTファイルをバックアップできます。 - * フィルター、削除に関するルール、転送や帯域幅に関する設定などを行えます。 +- 全てのデータについて、アップロードする前に AES-256(または GNU Privacy Guard)による暗号化を行い、データの安全性を確保します。 +- 最初に全体のフルバックアップを行い、その後、小さな増分のバックアップを送信することにより、回線の帯域幅と保存領域の使用量を節約します。 +- スケジュールの設定機能により、バックアップを最新のものに自動的に維持します。 +- 新しいリリースが公開された際に、通知を行います。 +- 暗号化したバックアップのファイルを、FTP、 Cloudfiles、WebDAV、SSH (SFTP)、Amazon S3 などのサービスに送信します。 +- フォルダー、ドキュメントや画像などファイルの種類、ユーザー定義のフィルターを指定して、バックアップを実行できます。 +- 簡単に使える操作画面と、コマンドラインのツールを備えています。 +- Windows のボリュームシャドウコピーサービス(VSS)や、Linux の論理ボリューム管理(LVM)によって、プログラムによって開かれているファイルや、ロックされているファイルを適切にバックアップできます。これにより、Microsoft Outlook を使っている際に、Outlook の PST ファイルをバックアップできます。 +- フィルター、削除に関するルール、転送や帯域幅に関する設定などを行えます。 -Duplicatiの利点 -================== +# Duplicati の利点 データを安全に保つこと。離れたところに保管すること。バックアップを定期的に更新すること。 とてもシンプルなルールですが、今日の多くのバックアップ用サービスやソフトウェアは、これを達成していません。 -一方、Duplicatiでは、このルールを実践しています! +一方、Duplicati では、このルールを実践しています! -データを安全に保ちましょう! 悪意をもったインターネット上の人々は、興味を引くデータをあらゆるところで探し回っているようです。しかしユーザーは、自らのプライベートなデータが第三者に暴かれてもよいとは誰も思っていません。Duplicatiでは、強力な暗号を使うことで、あなたのデータが、自分以外には全く意味不明なものになっていることを保証します。よく検討されたパスワードを使うと、あなたのバックアップファイルは、公開されているウェブサーバー上に保管されている場合でも、あなたの自宅にあり、しかし暗号化されずに保管されているファイルと比べて、より安全なものとなります。 +データを安全に保ちましょう! 悪意をもったインターネット上の人々は、興味を引くデータをあらゆるところで探し回っているようです。しかしユーザーは、自らのプライベートなデータが第三者に暴かれてもよいとは誰も思っていません。Duplicati では、強力な暗号を使うことで、あなたのデータが、自分以外には全く意味不明なものになっていることを保証します。よく検討されたパスワードを使うと、あなたのバックアップファイルは、公開されているウェブサーバー上に保管されている場合でも、あなたの自宅にあり、しかし暗号化されずに保管されているファイルと比べて、より安全なものとなります。 -バックアップは、離れたところに保管しましょう! たとえバックアップが完璧だったとしても、それがバックアップ元のデータもろとも失われてしまっては何の意味もありません。職場で火事があった場合を想像してみてください。… バックアップは火事にも負けず生き残りますか? Duplicatiはバックアップを多様な遠隔のファイルサーバーに保存し、データの更新が必要な部分だけが転送されるよう、増分バックアップをサポートしています。これによって、バックアップ元のデータから遠く離れたところにバックアップを保管しやすくなっています。 +バックアップは、離れたところに保管しましょう! たとえバックアップが完璧だったとしても、それがバックアップ元のデータもろとも失われてしまっては何の意味もありません。職場で火事があった場合を想像してみてください。… バックアップは火事にも負けず生き残りますか?  Duplicati はバックアップを多様な遠隔のファイルサーバーに保存し、データの更新が必要な部分だけが転送されるよう、増分バックアップをサポートしています。これによって、バックアップ元のデータから遠く離れたところにバックアップを保管しやすくなっています。 -定期的にバックアップを行いましょう! 最悪のケースは、しかるべきときにバックアップを行うことをうっかり忘れていたために、バックアップが古くなってしまっていることです。Duplicatiにはスケジュールの設定機能が備わっているので、簡単に、最新の状態のバックアップを定期的に作成できます。また、Duplicatiはファイルの圧縮を実行し、増分バックアップを行えるため、保存領域と帯域幅を節約できます。 +定期的にバックアップを行いましょう! 最悪のケースは、しかるべきときにバックアップを行うことをうっかり忘れていたために、バックアップが古くなってしまっていることです。Duplicati にはスケジュールの設定機能が備わっているので、簡単に、最新の状態のバックアップを定期的に作成できます。また、Duplicati はファイルの圧縮を実行し、増分バックアップを行えるため、保存領域と帯域幅を節約できます。 -開発に参加 -================== +# 開発に参加 ## 不具合を報告 -バグの管理にはGitHubを使っています。不具合を発見した場合は https://github.com/duplicati/duplicati/issues で既存のIssueがないか検索して、もしまだ報告されていないようであれば、新しいIssueを作成してください。 + +バグの管理には GitHub を使っています。不具合を発見した場合は https://github.com/duplicati/duplicati/issues で既存の Issue がないか検索して、もしまだ報告されていないようであれば、新しい Issue を作成してください。 ## 翻訳に参加 -Duplicatiの翻訳に興味がある場合は、[Transifex](https://www.transifex.com/duplicati/duplicati/dashboard/)で翻訳作業にご参加ください。 + +Duplicati の翻訳に興味がある場合は、[Transifex](https://www.transifex.com/duplicati/duplicati/dashboard/)で翻訳作業にご参加ください。 ## 開発作業に参加 -開発環境を設定してDuplicatiをビルドする方法については、[ウィキ](https://github.com/duplicati/duplicati/wiki/How-to-build-from-source)をご覧ください。不具合を修正したり、Duplicatiを改善したりするプルリクエストについては、いつでも歓迎します。 -修正すべき問題を探している場合は、[minor change](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22minor+change%22)のIssueを確認してみてください。ウェブUIの開発に慣れている場合は、 [「UI」でタグ付けされたIssue](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22UI%22)を見てみてください。 +開発環境を設定して Duplicati をビルドする方法については、[ウィキ](https://docs.duplicati.com/installation-details/developer)をご覧ください。不具合を修正したり、Duplicati を改善したりするプルリクエストについては、いつでも歓迎します。 +修正すべき問題を探している場合は、[minor change](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22minor+change%22)の Issue を確認してみてください。ウェブ UI の開発に慣れている場合は、 [「UI」でタグ付けされた Issue](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22UI%22)を見てみてください。 貢献していただいた皆様に感謝いたします! - ## 後援 後援いただいている皆様に感謝いたします!🙏 - ## スポンサー -以下に、Duplicatiに寄付していただいたスポンサーを一覧でご紹介します。 +以下に、Duplicati に寄付していただいたスポンサーを一覧でご紹介します。 diff --git a/README.md b/README.md index 32a02c8a3..93d4d54be 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,15 @@ Removed Gitter [![Backers on Open Collective](https://opencollective.com/duplicati/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/duplicati/sponsors/badge.svg)](#sponsors) [![Build Status on Travis-CI](https://travis-ci.org/duplicati/duplicati.svg?branch=master)](https://travis-ci.org/duplicati/duplicati) [![Coverage Status](https://coveralls.io/repos/github/duplicati/duplicati/badge.svg?branch=HEAD)](https://coveralls.io/github/duplicati/duplicati?branch=HEAD) [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/duplicati/duplicati/blob/master/LICENSE.txt) - +[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Duplicati%20Guru-006BFF)](https://gurubase.io/g/duplicati) Duplicati is a free, open source, backup client that securely stores encrypted, incremental, compressed backups on cloud storage services and remote file servers. It works with: -   *Amazon S3, [IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2"), [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Storj DCS, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), Aliyun OSS, [and more!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)* +   _Amazon S3, [IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2"), [Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage"), Box, Dropbox, FTP, Google Cloud and Drive, MEGA, Microsoft Azure and OneDrive, Rackspace Cloud Files, OpenStack Storage (Swift), Sia, Storj DCS, SSH (SFTP), WebDAV, Tencent Cloud Object Storage (COS), Aliyun OSS, [and more!](https://docs.duplicati.com/backup-destinations/destination-overview)_ Duplicati is licensed under the MIT license and available for Windows, OSX and Linux (.NET 4.7.1+ or Mono 5.10.0+ required). -Download -======== +# Download The latest version of Duplicati is a beta version for the Duplicati 2.0 release. @@ -38,28 +37,25 @@ For even more [bleeding edge access, check the latest releases](https://github.c All releases are GPG signed with the public key [3DAC703D](https://keys.openpgp.org/search?q=0xC20E90473DAC703D). The latest signature file and latest ASCII signature file are also available from [the Duplicati download page](https://github.com/duplicati/duplicati/releases). -Support -======= +# Support Duplicati is supported by an [active community and you can reach them via our forum](https://forum.duplicati.com). We have a great [Duplicati manual](https://docs.duplicati.com) that you can also [contribute to](https://github.com/kees-z/DuplicatiDocs). -Features -======== +# Features - * Duplicati uses AES-256 encryption (or GNU Privacy Guard) to secure all data before it is uploaded. - * Duplicati uploads a full backup initially and stores smaller, incremental updates afterwards to save bandwidth and storage space. - * A scheduler keeps backups up-to-date automatically. - * Integrated updater notifies you when a new release is out - * Encrypted backup files are transferred to targets like FTP, Cloudfiles, WebDAV, SSH (SFTP), Amazon S3 and others. - * Duplicati allows backups of folders, document types like e.g. documents or images, or custom filter rules. - * Duplicati is available as application with an easy-to-use user interface and as command line tool. - * Duplicati can make proper backups of opened or locked files using the Volume Snapshot Service (VSS) under Windows or the Logical Volume Manager (LVM) under Linux. This allows Duplicati to back up the Microsoft Outlook PST file while Outlook is running. - * Filters, deletion rules, transfer and bandwidth options, etc +- Duplicati uses AES-256 encryption (or GNU Privacy Guard) to secure all data before it is uploaded. +- Duplicati uploads a full backup initially and stores smaller, incremental updates afterwards to save bandwidth and storage space. +- A scheduler keeps backups up-to-date automatically. +- Integrated updater notifies you when a new release is out +- Encrypted backup files are transferred to targets like FTP, Cloudfiles, WebDAV, SSH (SFTP), Amazon S3 and others. +- Duplicati allows backups of folders, document types like e.g. documents or images, or custom filter rules. +- Duplicati is available as application with an easy-to-use user interface and as command line tool. +- Duplicati can make proper backups of opened or locked files using the Volume Snapshot Service (VSS) under Windows or the Logical Volume Manager (LVM) under Linux. This allows Duplicati to back up the Microsoft Outlook PST file while Outlook is running. +- Filters, deletion rules, transfer and bandwidth options, etc -Why use Duplicati? -================== +# Why use Duplicati? Keep your data safe, store it far away, update your backup regularly! This is a simple rule but many backup solutions do not achieve that today. @@ -71,34 +67,33 @@ Store your backup far away! The best backup is useless when it is destroyed toge Backup regularly! The worst case is that your backup is outdated simply because someone forgot to make a backup at the right time. Duplicati has a built-in scheduler, so that it's easy to have a regular, up-to-date backup. Furthermore, Duplicati uses file compression and is able to store incremental backups to save storage space and bandwidth. -Contributing -================== +# Contributing ## Contributing Bug reports + We use GitHub for bug tracking. Please search the existing issues for your bug and create a new one if the issue is not yet tracked: https://github.com/duplicati/duplicati/issues ## Contributing Translations + Interested in helping to translate duplicati? Help is always welcome at transifex: https://www.transifex.com/duplicati/duplicati/dashboard/ ## Contributing Code -Instructions on how to setup your development environment and build duplicati can be found in the [wiki](https://github.com/duplicati/duplicati/wiki/How-to-build-from-source). Pull requests fixing bugs or improving duplicati in another way are appreciated. + +Instructions on how to setup your development environment and build duplicati can be found in the [documentation](https://docs.duplicati.com/installation-details/developer). Pull requests fixing bugs or improving duplicati in another way are appreciated. If you are looking for an issue to fix, try looking at one of the [minor change](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22minor+change%22) issues. If you are most used to web development, have a look at the [issues marked UI](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22UI%22). - Thank you to all our existing contributors: - ## Backers Thank you to all our backers! 🙏 - ## Sponsors The list below reflects the sponsors who donated to the open-source project. diff --git a/README.zh-CN.md b/README.zh-CN.md index 567e22c35..d7aa179c0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -23,15 +23,13 @@ [![覆盖率状态](https://coveralls.io/repos/github/duplicati/duplicati/badge.svg?branch=HEAD)](https://coveralls.io/github/duplicati/duplicati?branch=HEAD) [![许可](https://img.shields.io/github/license/duplicati/duplicati.svg)](https://github.com/duplicati/duplicati/blob/master/LICENSE.txt) - Duplicati 是一个免费、开源的备份客户端,可以安全地将加密、增量、压缩的备份存储在云存储服务和远程文件服务器上。它与以下服务兼容: -   *亚马逊 S3、[IDrive e2](https://www.idrive.com/e2/duplicati "使用 Duplicati 与 IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati 与 Backblaze B2 云存储")、Box、Dropbox、FTP、Google Cloud 和 Drive、MEGA、Microsoft Azure 和 OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Sia、Storj DCS、SSH (SFTP)、WebDAV、阿里云对象存储(OSS)、百度网盘、阿里云盘、腾讯云对象存储 (COS)、[以及更多!](https://duplicati.readthedocs.io/en/latest/01-introduction/#supported-backends)* +   _亚马逊 S3、[IDrive e2](https://www.idrive.com/e2/duplicati "使用 Duplicati 与 IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati 与 Backblaze B2 云存储")、Box、Dropbox、FTP、Google Cloud 和 Drive、MEGA、Microsoft Azure 和 OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Sia、Storj DCS、SSH (SFTP)、WebDAV、阿里云对象存储(OSS)、百度网盘、阿里云盘、腾讯云对象存储 (COS)、[以及更多!](https://docs.duplicati.com/backup-destinations/destination-overview)_ Duplicati 根据 LGPL 许可证授权,并可用于 Windows、OSX 和 Linux (.NET 4.7.1+ 或 Mono 5.10.0+ 需要)。 -下载 -======== +# 下载 Duplicati 的最新版本是 Duplicati 2.0 发布的测试版。 @@ -42,28 +40,25 @@ Duplicati 的最新版本是 Duplicati 2.0 发布的测试版。 所有发布版本都使用公钥 [3DAC703D](https://keys.openpgp.org/search?q=0xC20E90473DAC703D) 进行 GPG 签名。最新的签名文件和最新的 ASCII 签名文件也可以在 [Duplicati 下载页面](https://github.com/duplicati/duplicati/releases) 获取。 -支持 -======= +# 支持 Duplicati 由一个[活跃的社区支持,您可以通过我们的论坛与他们联系](https://forum.duplicati.com)。 我们有一个很棒的 [Duplicati 手册](https://docs.duplicati.com),您也可以[为其做出贡献](https://github.com/kees-z/DuplicatiDocs)。 -功能 -======== +# 功能 - * Duplicati 使用 AES-256 加密(或 GNU Privacy Guard)在上传之前加密所有数据。 - * Duplicati 最初上传完整备份,之后存储较小的增量更新,以节省带宽和存储空间。 - * 计划程序自动保持备份的最新状态。 - * 集成的更新器会通知您新版本发布 - * 加密的备份文件被传输到像 FTP、Cloudfiles、WebDAV、SSH (SFTP)、Amazon S3 等目标。 - * Duplicati 允许备份文件夹、文档类型(如文档或图像)或自定义过滤规则。 - * Duplicati 有易于使用的用户界面和命令行工具。 - * Duplicati 可以使用 Windows 下的卷快照服务 (VSS) 或 Linux 下的逻辑卷管理器 (LVM) 对打开或锁定的文件进行正确备份。这允许 Duplicati 在 Outlook 运行时备份 Microsoft Outlook PST 文件。 - * 过滤器、删除规则、传输和带宽选项等 +- Duplicati 使用 AES-256 加密(或 GNU Privacy Guard)在上传之前加密所有数据。 +- Duplicati 最初上传完整备份,之后存储较小的增量更新,以节省带宽和存储空间。 +- 计划程序自动保持备份的最新状态。 +- 集成的更新器会通知您新版本发布 +- 加密的备份文件被传输到像 FTP、Cloudfiles、WebDAV、SSH (SFTP)、Amazon S3 等目标。 +- Duplicati 允许备份文件夹、文档类型(如文档或图像)或自定义过滤规则。 +- Duplicati 有易于使用的用户界面和命令行工具。 +- Duplicati 可以使用 Windows 下的卷快照服务 (VSS) 或 Linux 下的逻辑卷管理器 (LVM) 对打开或锁定的文件进行正确备份。这允许 Duplicati 在 Outlook 运行时备份 Microsoft Outlook PST 文件。 +- 过滤器、删除规则、传输和带宽选项等 -为什么使用 Duplicati? -================== +# 为什么使用 Duplicati? 保护您的数据安全,将其存储在远处,定期更新您的备份! 这是一个简单的规则,但许多备份解决方案今天都无法做到。 @@ -75,33 +70,33 @@ Duplicati 由一个[活跃的社区支持,您可以通过我们的论坛与他 定期备份!最糟糕的情况是,您的备份过时了,仅仅是因为有人忘了在正确的时间备份。Duplicati 有内置的计划程序,因此很容易拥有定期、最新的备份。此外,Duplicati 使用文件压缩,并能够存储增量备份以节省存储空间和带宽。 -贡献 -================== +# 贡献 ## 贡献错误报告 + 我们使用 GitHub 进行错误跟踪。请在创建新问题前先搜索已有的问题,看看您的错误是否已被记录: https://github.com/duplicati/duplicati/issues ## 贡献翻译 + 对帮助翻译 duplicati 感兴趣吗?欢迎在 transifex 提供帮助: https://www.transifex.com/duplicati/duplicati/dashboard/ ## 贡献代码 -关于如何设置您的开发环境以及如何构建 duplicati 的说明可以在 [wiki](https://github.com/duplicati/duplicati/wiki/How-to-build-from-source) 中找到。我们欣赏任何修复错误或以其他方式改进 duplicati 的拉取请求。 + +关于如何设置您的开发环境以及如何构建 duplicati 的说明可以在 [docs](https://docs.duplicati.com/installation-details/developer) 中找到。我们欣赏任何修复错误或以其他方式改进 duplicati 的拉取请求。 如果您正在寻找一个问题来解决,请尝试查看其中一个标记为 [小改动](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22minor+change%22) 的问题。如果您最熟悉的是 Web 开发,请查看标记为 [UI](https://github.com/duplicati/duplicati/issues?q=is%3Aissue+is%3Aopen+label%3A%22UI%22) 的问题。 感谢我们所有的现有贡献者: - ## 赞助者 感谢所有的赞助者!🙏 [[成为赞助者](https://opencollective.com/duplicati#backer)] - ## 赞助商 通过成为赞助商来支持这个项目。您的标志将会出现在这里,附带链接到您的网站。[[成为赞助商](https://opencollective.com/duplicati#sponsor)] diff --git a/ReleaseBuilder/.vscode/launch.json b/ReleaseBuilder/.vscode/launch.json index 1aa621df8..ba09d54fb 100644 --- a/ReleaseBuilder/.vscode/launch.json +++ b/ReleaseBuilder/.vscode/launch.json @@ -27,12 +27,18 @@ "--targets", "win-x86-gui.msi", "--targets", "win-x64-gui.msi", "--targets", "win-x64-gui.zip", + "--targets", "win-x64-agent.msi", + "--targets", "win-arm64-agent.msi", "--targets", "linux-x64-gui.zip", "--targets", "linux-arm64-gui.zip", "--targets", "osx-x64-gui.dmg", "--targets", "osx-arm64-gui.dmg", "--targets", "osx-x64-gui.pkg", "--targets", "osx-arm64-gui.pkg", + "--targets", "osx-arm64-agent.pkg", + "--targets", "osx-x64-agent.pkg", + "--targets", "osx-arm64-cli.pkg", + "--targets", "osx-x64-cli.pkg", "--targets", "linux-x64-gui.deb", "--targets", "linux-x64-cli.deb", "--targets", "linux-arm64-gui.deb", diff --git a/ReleaseBuilder/Build/Command.Compile.Post.cs b/ReleaseBuilder/Build/Command.Compile.Post.cs index 5d3234a7b..07f430ea0 100644 --- a/ReleaseBuilder/Build/Command.Compile.Post.cs +++ b/ReleaseBuilder/Build/Command.Compile.Post.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text.RegularExpressions; namespace ReleaseBuilder.Build; @@ -14,29 +34,29 @@ public static partial class Command ///
    /// The source directory /// The output build directory to modify - /// The target operating system - /// The target architecture + /// The target to prepare for /// The build target string os-arch-interface /// The runtime config /// A flag that allows re-using existing builds /// An awaitable task - public static async Task PrepareTargetDirectory(string baseDir, string buildDir, OSType os, ArchType arch, string buildTargetString, RuntimeConfig rtcfg, bool keepBuilds) + public static async Task PrepareTargetDirectory(string baseDir, string buildDir, PackageTarget target, RuntimeConfig rtcfg, bool keepBuilds) { - await RemoveUnwantedFiles(os, buildDir); + await RemoveUnwantedFiles(target.OS, buildDir); - switch (os) + switch (target.OS) { case OSType.Windows: await SignWindowsExecutables(buildDir, rtcfg); break; case OSType.MacOS: - await BundleMacOSApplication(baseDir, buildDir, buildTargetString, rtcfg, keepBuilds); + if (target.Interface == InterfaceType.GUI) + await BundleMacOSApplication(baseDir, buildDir, target.BuildTargetString, rtcfg, keepBuilds); break; case OSType.Linux: - await ReplaceLibMonoUnix(baseDir, buildDir, arch); - await ReplaceSQLiteInterop(baseDir, buildDir, arch); + await ReplaceLibMonoUnix(baseDir, buildDir, target.Arch); + await ReplaceSQLiteInterop(baseDir, buildDir, target.Arch); break; default: @@ -94,8 +114,7 @@ public static partial class Command ///
    /// The operating system to get the unwanted filenames for /// The list of unwanted filenames - static IEnumerable UnwantedFileGlobExps(OSType os) - => new[] { + static IEnumerable UnwantedFileGlobExps(OSType os) => [ "Thumbs.db", "desktop.ini", ".DS_Store", @@ -103,8 +122,9 @@ public static partial class Command "*.pdb", "*.mdb", "._*", - os == OSType.Windows ? "*.sh" : "*.bat" - }; + os == OSType.Windows ? "*.sh" : "*.bat", + os == OSType.Windows ? "*.sh" : "*.ps1" + ]; /// /// Returns a regular expression mapping files that are not wanted in the build folders @@ -189,7 +209,7 @@ public static partial class Command EnvHelper.CopyDirectory(buildDir, binDir, recursive: true); // Patch the plist and place the icon from the resources - var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS"); + var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS", "AppBundle"); var plist = File.ReadAllText(Path.Combine(resourcesDir, "app-resources", "Info.plist")) .Replace("!LONG_VERSION!", rtcfg.ReleaseInfo.ReleaseName) @@ -228,28 +248,7 @@ public static partial class Command var licenseTarget = Path.Combine(tmpApp, "Contents", "Licenses"); Directory.Move(Path.Combine(binDir, "licenses"), licenseTarget); - if (rtcfg.UseCodeSignSigning) - { - Console.WriteLine("Performing MacOS code signing ..."); - - // Executables cannot be signed before their dependencies are signed - // So they are placed last in the list - var executables = ExecutableRenames.Values.Select(x => Path.Combine(binDir, x)) - .Where(File.Exists); - - var signtargets = Directory.EnumerateFiles(binDir, "*", SearchOption.AllDirectories) - .Except(executables) - .Concat(executables) - .Distinct() - .ToList(); - - var entitlementFile = Path.Combine(resourcesDir, "Entitlements.plist"); - foreach (var f in signtargets) - await rtcfg.Codesign(f, entitlementFile); - - await rtcfg.Codesign(Path.Combine(tmpApp), entitlementFile); - } - + // Make files executable if (!OperatingSystem.IsWindows()) foreach (var f in Directory.EnumerateFiles(binDir, "*.launchagent.plist", SearchOption.TopDirectoryOnly)) File.SetUnixFileMode(f, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead); @@ -310,7 +309,7 @@ public static partial class Command /// An awaitable task static async Task SignWindowsExecutables(string buildDir, RuntimeConfig rtcfg) { - var cfg = Program.Configuration; + var cfg = rtcfg.Configuration; if (!rtcfg.UseAuthenticodeSigning) return; diff --git a/ReleaseBuilder/Build/Command.Compile.Verify.cs b/ReleaseBuilder/Build/Command.Compile.Verify.cs new file mode 100644 index 000000000..038fbc335 --- /dev/null +++ b/ReleaseBuilder/Build/Command.Compile.Verify.cs @@ -0,0 +1,306 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Reflection; +using System.Text; +using System.Text.Json; + +namespace ReleaseBuilder.Build; + +public static partial class Command +{ + /// + /// Main compilation of projects + /// + private static class Verify + { + /// + /// Verify that some files that are expected to be present in the target directory are there + /// + /// The build directory to verify + /// The target to verify for + /// An awaitable task + public static Task VerifyTargetDirectory(string buildDir, PackageTarget target) + { + var rootFiles = Directory.EnumerateFiles(buildDir, "*", SearchOption.TopDirectoryOnly) + .Where(x => x.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) || x.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) + .Select(x => Path.GetFileName(x)) + .ToHashSet(Duplicati.Library.Utility.Utility.ClientFilenameStringComparer); + + // Random sample of files we expect + var probeFiles = new string[] { + "System.CommandLine.dll", + "System.CommandLine.NamingConventionBinder.dll", + "AWSSDK.S3.dll", + "CoCoL.dll", + "Duplicati.Library.Interface.dll", + "Google.Apis.Auth.dll", + "Google.Apis.Core.dll", + "SQLiteHelper.dll", + "SQLite.Interop.dll", + "Microsoft.IdentityModel.Abstractions.dll", + "System.Reactive.dll", + "System.Reactive.Linq.dll" + }; + + var missing = probeFiles.Where(x => !rootFiles.Contains(x)).ToArray(); + if (missing.Length > 0) + throw new Exception($"Expected files {string.Join(", ", missing)} for {target.BuildTargetString}, but were not found in build directory {buildDir}"); + + return Task.CompletedTask; + } + + /// + /// Verifies that all expected executables are in the output + /// + /// The build directory to verify + /// The project files to verify + /// The target to verify for + /// An awaitable task + public static Task VerifyExecutables(string buildDir, IEnumerable projectFiles, PackageTarget target) + { + var expected = projectFiles.Select(x => Path.GetFileNameWithoutExtension(x)) + .Select(x => target.OS == OSType.Windows ? $"{x}.exe" : x); + + var missing = expected.Where(x => !File.Exists(Path.Combine(buildDir, x))).ToArray(); + if (missing.Length > 0) + throw new Exception($"Expected files {string.Join(", ", missing)} for {target.BuildTargetString}, but were not found in build directory {buildDir}"); + + return Task.CompletedTask; + } + + /// + /// Root entry from the dotnet list output + /// + /// The version of the output format + /// The parameters used to generate the output + /// The list of projects found + public sealed record RootJson( + int Version, + string Parameters, + IEnumerable Projects + ); + + /// + /// A single project + /// + /// Full path to the csproj file + /// The frameworks found + public sealed record ProjectJson( + string Path, + IEnumerable Frameworks + ); + + /// + /// Contents of a framework + /// + /// The framework name + /// Directly included packages + /// Packages included due to the top-level packages + public sealed record FrameworkJson( + string Framework, + IEnumerable TopLevelPackages, + IEnumerable TransitivePackages + ); + + /// + /// A top-level package + /// + /// The package id + /// The version requested + /// The resolved version + public sealed record TopLevelJson( + string Id, + string RequestedVersion, + string ResolvedVersion + ); + + /// + /// A transitive package + /// + /// The package id + /// The resolved version + public sealed record PackageJson( + string Id, + string ResolvedVersion + ); + + /// + /// Executes the dotnet list command and parses the output + /// + /// The path to the sln file to analyze + /// The parsed output + public static async Task AnalyzeProject(string slnpath) + { + await ProcessHelper.ExecuteWithOutput([ + "dotnet", "restore", slnpath + ]).ConfigureAwait(false); + + var output = await ProcessHelper.ExecuteWithOutput([ + "dotnet", "list", + slnpath, "package", + "--include-transitive", + "--format", "json" + ]).ConfigureAwait(false); + + var root = JsonSerializer.Deserialize(output, new JsonSerializerOptions(JsonSerializerOptions.Default) { PropertyNameCaseInsensitive = true }) + ?? throw new Exception("Failed to parse JSON output from dotnet list"); + if (root.Version != 1) + throw new Exception($"Unexpected version {root.Version} from dotnet list"); + + return root; + } + + /// + /// Parses a version string into a Version object + /// + /// The nuget version string + /// A .NET version number + private static Version ParseVersion(string version) + { + var v = new Version(version.Split("-")[0]); + return new Version(v.Major, v.Minor, v.Build, Math.Max(0, v.Revision)); + } + + /// + /// A version that is duplicated in multiple projects + /// + /// The source project + /// The resolved nuget version string + /// The resolved parsed version + public sealed record DuplicatedVersion( + string Project, + string Version, + Version ParsedVersion + ); + + /// + /// Parses the output of the dotnet list command and returns a dictionary of duplicated versions + /// + /// The parsed output from the dotnet list command + /// A dictionary of duplicated versions, where the key is the package id and the value is a list of projects that use that version + public static Dictionary> GetDuplicatedVersions(RootJson input) + => input.Projects + .SelectMany(x => x.Frameworks.Select(y => new + { + Framework = y, + Project = x.Path + })) + .SelectMany(x => + (x.Framework.TopLevelPackages? + .Select(y => new + { + TopLevel = true, + y.Id, + y.ResolvedVersion, + x.Project + }) ?? []) + .Concat(x.Framework.TransitivePackages?.Select(y => new { TopLevel = false, y.Id, y.ResolvedVersion, x.Project }) ?? []) + ) + .GroupBy(x => x.Id, x => new DuplicatedVersion(x.Project, x.ResolvedVersion, ParseVersion(x.ResolvedVersion))) + .Where(x => x.DistinctBy(y => y.ParsedVersion).Count() > 1) + .ToDictionary( + x => x.Key, + x => x.ToList() + ); + + /// + /// Finds the maximum nuget versions of packages + /// + /// The parsed output from the dotnet list command + /// A list of nuget versions for each package + public static Dictionary FindMaxNugetVersions(RootJson input) + => input.Projects + .SelectMany(x => x.Frameworks) + .SelectMany(x => + (x.TopLevelPackages? + .Select(x => new { TopLevel = true, x.Id, x.ResolvedVersion }) ?? []) + .Concat(x.TransitivePackages?.Select(x => new { TopLevel = false, x.Id, x.ResolvedVersion }) ?? []) + ) + .Where(x => !x.TopLevel) + .GroupBy(x => x.Id, x => x.ResolvedVersion) + .Select(x => new { x.Key, Version = x.MaxBy(ParseVersion) }) + .ToDictionary( + x => x.Key, + x => ParseVersion(x.Version!) + ); + + /// + /// List of known wrong versions, where the assembly version is not the same as the nuget version + /// + private static Dictionary ManuallyFixedVersions = new Dictionary + { + // Using v3.3 for assembly, but 3.7 in nuget + { "AWSSDK.Core", new Version(3, 3, 0, 0) }, + + // Using the Framework version, not the package version + { "Microsoft.CSharp", new Version(8, 0, 0, 0) }, + { "System.Memory", new Version(8, 0, 0, 0) }, + { "System.Security.AccessControl", new Version(8, 0, 0, 0) }, + { "System.Security.Principal.Windows", new Version(8, 0, 0, 0) }, + { "System.Security.Cryptography.Algorithms", new Version(8, 0, 0, 0) }, + { "System.Security.Cryptography.Cng", new Version(8, 0, 0, 0) }, + + // Using v9.0 for assembly, but 9.0.2 in nuget + { "System.IO.Pipelines", new Version(9, 0, 0, 0) } + }; + + /// + /// Verifies that the versions of the assemblies in the output folder are the maximum versions + /// + /// The folder to check + /// The parsed output from the dotnet list command + /// An awaitable task + public static Task VerifyDuplicatedVersionsAreMaxVersions(string folder, RootJson input) + { + var duplicatedVersions = GetDuplicatedVersions(input) + .Select(x => + { + if (ManuallyFixedVersions.TryGetValue(x.Key, out var version)) + return new KeyValuePair>(x.Key, [new DuplicatedVersion(x.Value.First().Project, x.Value.First().Version, version)]); + return new KeyValuePair>(x.Key, x.Value); + }); + + var mismatches = new List<(string Path, Version Expected, Version Actual)>(); + foreach (var entry in duplicatedVersions) + { + var maxVersion = entry.Value.MaxBy(x => x.ParsedVersion) + ?? throw new Exception($"Failed to find max version for {entry.Key}"); + var filename = Path.Combine(folder, $"{entry.Key}.dll"); + if (!File.Exists(filename)) + continue; + + var assemblyVersion = AssemblyName.GetAssemblyName(filename).Version; + if (assemblyVersion != null && assemblyVersion != maxVersion.ParsedVersion) + mismatches.Add((filename, maxVersion.ParsedVersion, assemblyVersion)); + } + + if (mismatches.Count > 0) + { + var sb = new StringBuilder(); + foreach (var mismatch in mismatches) + sb.AppendLine($"File {mismatch.Path} has version {mismatch.Actual} but expected {mismatch.Expected}"); + throw new Exception(sb.ToString()); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/ReleaseBuilder/Build/Command.Compile.cs b/ReleaseBuilder/Build/Command.Compile.cs index 5b279b8a3..79c37e379 100644 --- a/ReleaseBuilder/Build/Command.Compile.cs +++ b/ReleaseBuilder/Build/Command.Compile.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder.Build; public static partial class Command @@ -7,6 +27,33 @@ public static partial class Command /// private static class Compile { + /// + /// Folders to remove from the build folder + /// + private static readonly IReadOnlySet TempBuildFolders = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "bin", + "obj" + }; + + /// + /// Removes all temporary build folders from the base folder + /// + /// The folder to clean + /// >A task that completes when the clean is done + private static Task RemoveAllBuildTempFolders(string basefolder) + { + // Remove all obj and bin folders + foreach (var folder in Directory.GetDirectories(basefolder, "*", SearchOption.AllDirectories)) + { + var name = Path.GetFileName(folder); + if (TempBuildFolders.Contains(name) && Directory.Exists(folder)) + Directory.Delete(folder, true); + } + + return Task.CompletedTask; + } + /// /// Builds the projects listed in for the distinct /// @@ -33,10 +80,78 @@ public static partial class Command else Console.WriteLine($"Building {buildArchTargets.Length} versions"); - foreach (var target in buildArchTargets) + var buildOutputFolders = buildArchTargets.ToDictionary(x => x, x => Path.Combine(buildDir, x.BuildTargetString)); + var temporarySolutionFiles = new Dictionary(); + var targetExecutables = new Dictionary>(); + var distinctSolutions = buildArchTargets.GroupBy(x => $"{x.InterfaceString}{(x.OS == OSType.Windows ? $"-{x.OSString}" : "")}").ToArray(); + Console.WriteLine($"Creating {distinctSolutions.Length} temporary solution files"); + foreach (var tk in distinctSolutions) { - var outputFolder = Path.Combine(buildDir, target.BuildTargetString); + // Don't create a solution if all the output folders exist + if (tk.All(x => Directory.Exists(buildOutputFolders[x]))) + continue; + var tmpslnfile = Path.Combine(buildDir, $"Duplicati-{tk.Key}.sln"); + + var target = tk.First(); + if (!sourceProjects.TryGetValue(target.Interface, out var buildProjects)) + throw new InvalidOperationException($"No projects found for {tk.Key}"); + + var actualBuildProjects = buildProjects + .Where(x => target.OS == OSType.Windows || !windowsOnlyProjects.Contains(x)) + .ToList(); + + // Faster debugging, keep the solution file + if (!File.Exists(tmpslnfile)) + { + var logOut = Path.Combine(logFolder, $"create-{Path.GetFileName(tmpslnfile)}.log"); + using var logStream = new FileStream(logOut, FileMode.Create, FileAccess.Write, FileShare.Read); + + var tmpslnfile2 = Path.Combine(buildDir, $"tmp-Duplicati-{tk.Key}.sln"); + if (File.Exists(tmpslnfile2)) + File.Delete(tmpslnfile2); + + await ProcessHelper.ExecuteWithOutput([ + "dotnet", "new", "sln", + "--name", Path.GetFileNameWithoutExtension(tmpslnfile2), + "--output", buildDir + ], logStream).ConfigureAwait(false); + + // Add the projects to the solution + foreach (var proj in actualBuildProjects) + { + var projpath = Path.Combine(baseDir, proj); + if (!File.Exists(projpath)) + throw new FileNotFoundException($"Project file {projpath} not found"); + + await ProcessHelper.ExecuteWithOutput([ + "dotnet", "sln", tmpslnfile2, + "add", projpath + ], logStream).ConfigureAwait(false); + } + + File.Move(tmpslnfile2, tmpslnfile, true); + } + + foreach (var s in tk) + { + temporarySolutionFiles[s] = tmpslnfile; + targetExecutables[s] = actualBuildProjects; + } + } + + var verifyRootJson = new Verify.RootJson(1, "", []); + + // Set up analysis for the projects, if we are building any + if (!keepBuilds || buildOutputFolders.Any(x => !Directory.Exists(x.Value))) + { + // Make sure there is no cache from previous builds + await RemoveAllBuildTempFolders(baseDir).ConfigureAwait(false); + await Verify.AnalyzeProject(Path.Combine(baseDir, "Duplicati.sln")).ConfigureAwait(false); + } + + foreach ((var target, var outputFolder) in buildOutputFolders) + { // Faster iteration for debugging is to keep the build folder if (keepBuilds && Directory.Exists(outputFolder)) { @@ -45,50 +160,60 @@ public static partial class Command else { var tmpfolder = Path.Combine(buildDir, target.BuildTargetString + "-tmp"); + if (Directory.Exists(tmpfolder)) + Directory.Delete(tmpfolder, true); + Console.WriteLine($"Building {target.BuildTargetString} ..."); - if (!sourceProjects.TryGetValue(target.Interface, out var buildProjects)) - throw new InvalidOperationException($"No projects found for {target.Interface}"); - - foreach (var proj in buildProjects) + // Fix any RIDs that differ from .NET SDK + var archstring = target.Arch switch { - if (target.OS != OSType.Windows && windowsOnlyProjects.Contains(proj)) - continue; + ArchType.Arm7 => $"{target.OSString}-arm", + _ => target.BuildArchString + }; - // TODO: Self contained builds are bloating the build size - // Alternative is to require the .NET runtime to be installed + var buildTime = $"{DateTime.Now:yyyyMMdd-HHmmss}"; + string logNameFn(int pid, bool isStdOut) + => $"{target.BuildTargetString}.{buildTime}.{(isStdOut ? "stdout" : "stderr")}.log"; - // Fix any RIDs that differ from .NET SDK - var archstring = target.Arch switch - { - ArchType.Arm7 => $"{target.OSString}-arm", - _ => target.BuildArchString - }; + // Make sure there is no cache from previous builds + await RemoveAllBuildTempFolders(baseDir).ConfigureAwait(false); - var command = new string[] { - "dotnet", "publish", proj, - "-c", "Release", - "-o", tmpfolder, - "-r", archstring, - $"/p:AssemblyVersion={releaseInfo.Version}", - $"/p:Version={releaseInfo.Version}-{releaseInfo.Channel}-{releaseInfo.Timestamp:yyyyMMdd}", - "--self-contained", useHostedBuilds ? "false" : "true" - }; + // TODO: Self contained builds are bloating the build size + // Alternative is to require the .NET runtime to be installed - try - { - await ProcessHelper.ExecuteWithLog(command, workingDirectory: tmpfolder, logFolder: logFolder, logFilename: (pid, isStdOut) => $"{Path.GetFileNameWithoutExtension(proj)}.{target.BuildTargetString}.{pid}.{(isStdOut ? "stdout" : "stderr")}.log"); - } - catch (Exception ex) - { - Console.WriteLine($"Error building {proj} for {target.BuildTargetString}: {ex.Message}"); - throw; - } + var command = new string[] { + "dotnet", "publish", temporarySolutionFiles[target], + "--configuration", "Release", + "--output", tmpfolder, + "--runtime", archstring, + "--self-contained", useHostedBuilds ? "false" : "true", + // $"-p:UseSharedCompilation=false", + $"-p:AssemblyVersion={releaseInfo.Version}", + $"-p:Version={releaseInfo.Version}-{releaseInfo.Channel}-{releaseInfo.Timestamp:yyyyMMdd}", + }; + + try + { + await ProcessHelper.ExecuteWithLog( + command, + workingDirectory: tmpfolder, + logFolder: logFolder, + logFilename: logNameFn).ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine($"Error building for {target.BuildTargetString}: {ex.Message}"); + throw; } // Perform any post-build steps, cleaning and signing as needed - await PostCompile.PrepareTargetDirectory(baseDir, tmpfolder, target.OS, target.Arch, target.BuildTargetString, rtcfg, keepBuilds); + await PostCompile.PrepareTargetDirectory(baseDir, tmpfolder, target, rtcfg, keepBuilds); + await Verify.VerifyTargetDirectory(tmpfolder, target); + await Verify.VerifyExecutables(tmpfolder, targetExecutables[target], target); + await Verify.VerifyDuplicatedVersionsAreMaxVersions(tmpfolder, verifyRootJson); + // Move the final build to the output folder Directory.Move(tmpfolder, outputFolder); } diff --git a/ReleaseBuilder/Build/Command.CreatePackage.cs b/ReleaseBuilder/Build/Command.CreatePackage.cs index 8f1a274a2..21232ab44 100644 --- a/ReleaseBuilder/Build/Command.CreatePackage.cs +++ b/ReleaseBuilder/Build/Command.CreatePackage.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Globalization; using System.IO.Compression; using System.Text; @@ -128,7 +148,10 @@ public static partial class Command break; case PackageType.MacPkg: - await BuildMacPkgPackage(baseDir, buildRoot, tempFile, target, rtcfg); + if (target.Interface != InterfaceType.GUI) + await BuildMacNonAppPkgPackage(baseDir, buildRoot, tempFile, target, rtcfg); + else + await BuildMacAppPkgPackage(baseDir, buildRoot, tempFile, target, rtcfg); break; case PackageType.Deb: @@ -152,7 +175,7 @@ public static partial class Command { // # Notarize and staple takes a while... Console.WriteLine($"Performing notarize and staple of {packageFile} ..."); - await ProcessHelper.Execute(["xcrun", "notarytool", "submit", tempFile, "--keychain-profile", Program.Configuration.ConfigFiles.NotarizeProfile, "--wait"]); + await ProcessHelper.Execute(["xcrun", "notarytool", "submit", tempFile, "--keychain-profile", rtcfg.Configuration.ConfigFiles.NotarizeProfile, "--wait"]); await ProcessHelper.Execute(["xcrun", "stapler", "staple", tempFile]); } @@ -241,7 +264,18 @@ public static partial class Command /// A task representing the asynchronous operation. static async Task BuildMsiPackage(string baseDir, string buildRoot, string msiFile, PackageTarget target, RuntimeConfig rtcfg) { + var isWindows = OperatingSystem.IsWindows(); + const string originalNamespace = "http://schemas.microsoft.com/wix/2006/wi"; + const string wixv4Namespace = "http://wixtoolset.org/schemas/v4/wxs"; + var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "Windows"); + var resourcesSubDir = Path.Combine(resourcesDir, + target.Interface switch + { + InterfaceType.GUI => "TrayIcon", + InterfaceType.Agent => "Agent", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }); var buildTmp = Path.Combine(buildRoot, "tmp-msi"); if (Directory.Exists(buildTmp)) @@ -254,11 +288,15 @@ public static partial class Command if (!sourceFiles.EndsWith(Path.DirectorySeparatorChar)) sourceFiles += Path.DirectorySeparatorChar; - var binFiles = Path.Combine(resourcesDir, "binfiles.wxs"); + var binFiles = Path.Combine(resourcesSubDir, "binfiles.wxs"); if (File.Exists(binFiles)) File.Delete(binFiles); - File.WriteAllText(binFiles, WixHeatBuilder.CreateWixFilelist(sourceFiles, version: rtcfg.ReleaseInfo.Version.ToString())); + File.WriteAllText(binFiles, WixHeatBuilder.CreateWixFilelist( + sourceFiles, + version: rtcfg.ReleaseInfo.Version.ToString(), + wixNs: isWindows ? wixv4Namespace : originalNamespace + )); var msiArch = target.Arch switch { @@ -268,17 +306,49 @@ public static partial class Command _ => throw new Exception($"Architeture not supported: {target.ArchString}") }; - await ProcessHelper.Execute([ - Program.Configuration.Commands.Wix!, - "--ext", "ui", - "--extdir", Path.Combine(resourcesDir, "WixUIExtension"), - "--define", $"HarvestPath={sourceFiles}", - "--arch", msiArch, - "--output", msiFile, - Path.Combine(resourcesDir, "Shortcuts.wxs"), - binFiles, - Path.Combine(resourcesDir, "Duplicati.wxs") - ], workingDirectory: buildRoot); + // Prepare the Wix arguments, different for WiX (Windows) and wixl (Linux/MacOS) + string[] wixArgs; + + if (isWindows) + { + // Update namespace in the .wxs files for WiX v4 + var shortcutsTempfile = Path.Combine(buildTmp, "Shortcuts.wxs"); + var entryTempfile = Path.Combine(buildTmp, "Duplicati.wxs"); + + File.WriteAllText(shortcutsTempfile, File.ReadAllText(Path.Combine(resourcesSubDir, "Shortcuts.wxs")) + .Replace(originalNamespace, wixv4Namespace)); + + File.WriteAllText(entryTempfile, File.ReadAllText(Path.Combine(resourcesSubDir, "Duplicati.wxs")) + .Replace(originalNamespace, wixv4Namespace)); + + wixArgs = [ + rtcfg.Configuration.Commands.Wix!, + "build", + "-define", $"HarvestPath={sourceFiles}", + "-arch", msiArch, + "-out", msiFile, + shortcutsTempfile, + binFiles, + entryTempfile + ]; + } + else + { + // wixl needs the UI extension + wixArgs = [ + rtcfg.Configuration.Commands.Wix!, + "--ext", "ui", + "--extdir", Path.Combine(resourcesDir, "WixUIExtension"), + "--define", $"HarvestPath={sourceFiles}", + "--arch", msiArch, + "--output", msiFile, + Path.Combine(resourcesSubDir, "Shortcuts.wxs"), + binFiles, + Path.Combine(resourcesSubDir, "Duplicati.wxs") + ]; + } + + await ProcessHelper.Execute(wixArgs, workingDirectory: buildRoot); if (rtcfg.UseAuthenticodeSigning) await rtcfg.AuthenticodeSign(msiFile); @@ -294,7 +364,7 @@ public static partial class Command /// The package target to create the file for /// The runtime config /// An awaitable task - static async Task PrepareAndReSignAppBundle(string appFolder, string installerDir, PackageTarget target, RuntimeConfig rtcfg) + static async Task PrepareAndSignAppBundle(string appFolder, string installerDir, PackageTarget target, RuntimeConfig rtcfg) { await PackageSupport.InstallPackageIdentifier(Path.Combine(appFolder, "Contents", "MacOS"), target); @@ -302,15 +372,10 @@ public static partial class Command if (rtcfg.UseCodeSignSigning) { var entitlementFile = Path.Combine(installerDir, "Entitlements.plist"); - var updates = new[] { Path.Combine(appFolder, "Contents", "MacOS", "package_type_id.txt") } - .Concat( - ExecutableRenames.Values.Select(x => Path.Combine(appFolder, "Contents", "MacOS", x)) - .Where(File.Exists) - ) - .Append(appFolder); - - foreach (var x in updates) - await rtcfg.Codesign(x, entitlementFile); + // In principle, it should be enough to deep sign the bundle, but the signtool is broken + await PackageSupport.SignMacOSBinaries(rtcfg, Path.Combine(appFolder, "Contents", "MacOS"), entitlementFile); + await rtcfg.Codesign(appFolder, false, entitlementFile); + await rtcfg.VerifyCodeSign(appFolder); } } @@ -328,15 +393,14 @@ public static partial class Command var mountDir = Path.Combine(buildRoot, "mount"); if (Directory.Exists(mountDir)) { - await ProcessHelper.Execute([ - "hdiutil", "detach", mountDir, "-quiet", "-force", - ], workingDirectory: buildRoot, codeIsError: _ => false); + await ProcessHelper.Execute(["hdiutil", "detach", mountDir, "-quiet", "-force",], + workingDirectory: buildRoot, codeIsError: _ => false); Directory.Delete(mountDir, false); } Directory.CreateDirectory(mountDir); - var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS"); + var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS", "AppBundle"); var compressedDmg = Path.Combine(resourcesDir, "template.dmg.bz2"); if (!File.Exists(compressedDmg)) throw new FileNotFoundException($"Compressed dmg template file not found: {compressedDmg}"); @@ -363,9 +427,7 @@ public static partial class Command // Change the dmg name var dmgname = $"Duplicati {rtcfg.ReleaseInfo.ReleaseName}"; Console.WriteLine($"Setting dmg name to {dmgname}"); - await ProcessHelper.Execute([ - "diskutil", "quiet", "rename", mountDir, dmgname - ], workingDirectory: mountDir); + await ProcessHelper.Execute(["diskutil", "quiet", "rename", mountDir, dmgname], workingDirectory: mountDir); // Make the Duplicati.app structure, root folder should exist var appFolder = Path.Combine(mountDir, rtcfg.MacOSAppName); @@ -374,7 +436,7 @@ public static partial class Command // Place the prepared folder EnvHelper.CopyDirectory(Path.Combine(buildRoot, $"{target.BuildTargetString}-{rtcfg.MacOSAppName}"), appFolder, recursive: true); - await PrepareAndReSignAppBundle(appFolder, resourcesDir, target, rtcfg); + await PrepareAndSignAppBundle(appFolder, resourcesDir, target, rtcfg); // Set permissions inside DMG file if (!OperatingSystem.IsWindows()) @@ -390,8 +452,8 @@ public static partial class Command File.Delete(templateDmg); Directory.Delete(mountDir, false); - if (rtcfg.UseCodeSignSigning) - await rtcfg.Codesign(dmgFile, Path.Combine(resourcesDir, "Entitlements.plist")); + await rtcfg.Codesign(dmgFile, false, Path.Combine(resourcesDir, "Entitlements.plist")); + await rtcfg.VerifyCodeSign(dmgFile); } /// @@ -403,14 +465,14 @@ public static partial class Command /// The package target. /// The runtime configuration. /// A task representing the asynchronous operation. - static async Task BuildMacPkgPackage(string baseDir, string buildRoot, string pkgFile, PackageTarget target, RuntimeConfig rtcfg) + static async Task BuildMacAppPkgPackage(string baseDir, string buildRoot, string pkgFile, PackageTarget target, RuntimeConfig rtcfg) { var tmpFolder = Path.Combine(buildRoot, "tmp-pkg"); if (Directory.Exists(tmpFolder)) Directory.Delete(tmpFolder, true); Directory.CreateDirectory(tmpFolder); - var installerDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS"); + var installerDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS", "AppBundle"); var appFolder = Path.Combine(tmpFolder, rtcfg.MacOSAppName); if (Directory.Exists(appFolder)) @@ -418,7 +480,7 @@ public static partial class Command // Place the prepared folder EnvHelper.CopyDirectory(Path.Combine(buildRoot, $"{target.BuildTargetString}-{rtcfg.MacOSAppName}"), appFolder, recursive: true); - await PrepareAndReSignAppBundle(appFolder, installerDir, target, rtcfg); + await PrepareAndSignAppBundle(appFolder, installerDir, target, rtcfg); // Copy the source script files var scripts = new[] { "daemon", "daemon-scripts", "app-scripts" }; @@ -449,11 +511,19 @@ public static partial class Command File.Delete(pkgDaemonFile); var distributionFile = Path.Combine(tmpFolder, "Distribution.xml"); + var hostArch = target.Arch switch + { + ArchType.x86 => "i386", + ArchType.x64 => "x86_64", + ArchType.Arm64 => "arm64", + _ => throw new Exception($"Unsupported architecture: {target.ArchString}") + }; File.WriteAllText(distributionFile, File.ReadAllText(Path.Combine(installerDir, "Distribution.xml")) .Replace("DuplicatiApp.pkg", Path.GetFileName(pkgAppFile)) .Replace("DuplicatiDaemon.pkg", Path.GetFileName(pkgDaemonFile)) + .Replace("$HOSTARCH", hostArch) ); // Make the pkg files @@ -472,6 +542,194 @@ public static partial class Command await rtcfg.Productsign(pkgFile); } + /// + /// Builds the Mac package asynchronously. + /// + /// The base directory. + /// The build root directory. + /// The package file path. + /// The package target. + /// The runtime configuration. + /// A task representing the asynchronous operation. + static async Task BuildMacNonAppPkgPackage(string baseDir, string buildRoot, string pkgFile, PackageTarget target, RuntimeConfig rtcfg) + { + var tmpFolder = Path.Combine(buildRoot, "tmp-pkg"); + if (Directory.Exists(tmpFolder)) + Directory.Delete(tmpFolder, true); + Directory.CreateDirectory(tmpFolder); + + var resFolder = target.Interface switch + { + InterfaceType.Agent => "Agent", + InterfaceType.Cli => "CLI", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + var installerDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "MacOS", resFolder); + + var binFolder = Path.Combine(tmpFolder, "bin"); + if (Directory.Exists(binFolder)) + Directory.Delete(binFolder, true); + + // Place the prepared folder + EnvHelper.CopyDirectory(Path.Combine(buildRoot, $"{target.BuildTargetString}"), binFolder, recursive: true); + + // Rename the executables + await PackageSupport.RenameExecutables(binFolder); + await PackageSupport.SetPermissionFlags(binFolder, rtcfg); + + // Copy the source script files + var scripts = new[] { "daemon", "daemon-scripts", "app-scripts" }; + + // Copy scripts + foreach (var s in scripts) + EnvHelper.CopyDirectory(Path.Combine(installerDir, s), Path.Combine(tmpFolder, s), recursive: true); + + // Inject the launch agent + EnvHelper.CopyDirectory( + Path.Combine(installerDir, "daemon"), + Path.Combine(binFolder), + recursive: true + ); + + // Inject the uninstall.sh script + File.Copy( + Path.Combine(installerDir, "uninstall.sh"), + Path.Combine(binFolder, "uninstall.sh"), + overwrite: true + ); + + // Create symlinks for the executables + if (target.Interface == InterfaceType.Cli) + { + var installedExecutables = ExecutableRenames.Values + .Select(x => Path.Combine(binFolder, x)) + .Where(File.Exists) + .Select(Path.GetFileName) + .ToList(); + + var installScript = Path.Combine(tmpFolder, "app-scripts", "postinstall"); + var installCommands = new StringBuilder(); + installCommands.Append(File.ReadAllText(installScript)); + installCommands.AppendLine(); + installCommands.AppendLine("echo 'Creating symbolic links for the Duplicati executables'"); + foreach (var x in installedExecutables) + installCommands.AppendLine($"ln -s /usr/local/duplicati/{x} /usr/local/bin/{x}"); + installCommands.AppendLine("echo 'Duplicati CLI has been installed'"); + installCommands.AppendLine("exit 0"); + File.WriteAllText(installScript, installCommands.ToString()); + + var uninstallScript = Path.Combine(binFolder, "uninstall.sh"); + var uninstallCommands = new StringBuilder(); + uninstallCommands.Append(File.ReadAllText(uninstallScript)); + uninstallCommands.AppendLine(); + uninstallCommands.AppendLine("echo 'Removing links for the Duplicati executables'"); + foreach (var x in installedExecutables) + uninstallCommands.AppendLine($"if [ -L /usr/local/bin/{x} ]; then rm /usr/local/bin/{x}; fi"); + uninstallCommands.AppendLine("echo 'Duplicati symlinks are removed'"); + uninstallCommands.AppendLine("exit 0"); + File.WriteAllText(uninstallScript, uninstallCommands.ToString()); + } + + // Copy the package identifier + await PackageSupport.InstallPackageIdentifier(binFolder, target); + + // Set permissions + if (!OperatingSystem.IsWindows()) + { + await EnvHelper.Chown(binFolder, "root", "admin", true); + foreach (var f in Directory.EnumerateFiles(Path.Combine(tmpFolder, "daemon"), "*.launchagent.plist", SearchOption.AllDirectories)) + await EnvHelper.Chown(f, "root", "wheel", false); + + var filemode = EnvHelper.GetUnixFileMode("+x"); + var allscripts = scripts + .Select(x => Path.Combine(tmpFolder, x)) + .Where(Directory.Exists) + .SelectMany(x => Directory.EnumerateFiles(x, "*", SearchOption.AllDirectories)) + .Append(Path.Combine(tmpFolder, "uninstall.sh")); + + foreach (var x in allscripts) + if (File.Exists(x)) + EnvHelper.AddFilemode(x, filemode); + } + + // Apply code signing, if requested + await PackageSupport.SignMacOSBinaries(rtcfg, binFolder, Path.Combine(installerDir, "Entitlements.plist")); + + var payloadPkgFile = target.Interface switch + { + InterfaceType.Agent => "DuplicatiAgent.pkg", + InterfaceType.Cli => "DuplicatiCLI.pkg", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + var daemonPkgFile = target.Interface switch + { + InterfaceType.Agent => "DuplicatiAgentDaemon.pkg", + InterfaceType.Cli => "DuplicatiServerDaemon.pkg", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + var pkgAppFile = Path.Combine(tmpFolder, $"{rtcfg.ReleaseInfo.ReleaseName}-{payloadPkgFile}"); + if (File.Exists(pkgAppFile)) + File.Delete(pkgAppFile); + var pkgDaemonFile = Path.Combine(tmpFolder, $"{rtcfg.ReleaseInfo.ReleaseName}-{daemonPkgFile}"); + if (File.Exists(pkgDaemonFile)) + File.Delete(pkgDaemonFile); + + var distributionFile = Path.Combine(tmpFolder, "Distribution.xml"); + var hostArch = target.Arch switch + { + ArchType.x86 => "i386", + ArchType.x64 => "x86_64", + ArchType.Arm64 => "arm64", + _ => throw new Exception($"Unsupported architecture: {target.ArchString}") + }; + + File.WriteAllText(distributionFile, + File.ReadAllText(Path.Combine(installerDir, "Distribution.xml")) + .Replace(payloadPkgFile, Path.GetFileName(pkgAppFile)) + .Replace(daemonPkgFile, Path.GetFileName(pkgDaemonFile)) + .Replace("$HOSTARCH", hostArch) + ); + + var installLocation = target.Interface switch + { + InterfaceType.Agent => "/usr/local/duplicati-agent", + InterfaceType.Cli => "/usr/local/duplicati", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + var appId = target.Interface switch + { + InterfaceType.Agent => "com.duplicati.agent", + InterfaceType.Cli => "com.duplicati.cli", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + var daemonId = target.Interface switch + { + InterfaceType.Agent => "com.duplicati.agent.daemon", + InterfaceType.Cli => "com.duplicati.server.daemon", + _ => throw new Exception($"Unsupported interface type: {target.Interface}") + }; + + // Make the pkg files + await ProcessHelper.ExecuteAll([ + ["pkgbuild", "--analyze", "--root", binFolder, "--install-location", installLocation, "InstallerComponent.plist"], + ["pkgbuild", "--scripts", Path.Combine(tmpFolder, "app-scripts"), "--identifier", appId, "--root", binFolder, "--install-location", installLocation, "--component-plist", "InstallerComponent.plist", pkgAppFile], + ["pkgbuild", "--scripts", Path.Combine(tmpFolder, "daemon-scripts"), "--identifier", daemonId, "--root", Path.Combine(tmpFolder, "daemon"), "--install-location", "/Library/LaunchAgents", pkgDaemonFile], + ["productbuild", "--distribution", distributionFile, "--package-path", ".", "--resources", ".", pkgFile] + ], workingDirectory: tmpFolder); + + // Clean up + Directory.Delete(tmpFolder, true); + + // Sign the pkg file + if (rtcfg.UseCodeSignSigning) + await rtcfg.Productsign(pkgFile); + } + /// /// Creates a GZip compressed file /// @@ -511,7 +769,7 @@ public static partial class Command { // The approach here is based on: // https://www.internalpointers.com/post/build-binary-deb-package-practical-guide - // + // // It is not the recommended way to build a package, // but since the build is from a pre-build binary, // it is easier than trying to hack debhelper. @@ -589,12 +847,19 @@ public static partial class Command _ => throw new Exception($"Architeture not supported: {target.ArchString}") }; + var packageType = target.Interface switch + { + InterfaceType.Agent => "-agent", + _ => "" + }; + // Write a custom control file File.WriteAllText( Path.Combine(pkgroot, "DEBIAN", "control"), File.ReadAllText(Path.Combine(resourcesDir, "control.template.txt")) .Replace("%VERSION%", rtcfg.ReleaseInfo.Version.ToString()) .Replace("%ARCH%", debArchString) + .Replace("%PACKAGE_TYPE%", packageType) .Replace("%DEPENDS%", string.Join(", ", target.Interface == InterfaceType.GUI ? DebianGUIDepends : DebianCLIDepends)) @@ -670,6 +935,37 @@ public static partial class Command string.Join("\n", conffiles) ); + if (target.Interface == InterfaceType.Agent) + { + // Write a custom postinst script + File.Copy( + Path.Combine(resourcesDir, $"postinst-agent"), + Path.Combine(pkgroot, "DEBIAN", "postinst"), + true + ); + + // Write a custom prerm script + File.Copy( + Path.Combine(resourcesDir, $"prerm-agent"), + Path.Combine(pkgroot, "DEBIAN", "prerm"), + true + ); + + if (!OperatingSystem.IsWindows()) + { + var filemode = UnixFileMode.UserRead + | UnixFileMode.UserWrite + | UnixFileMode.UserExecute + | UnixFileMode.GroupRead + | UnixFileMode.GroupExecute + | UnixFileMode.OtherRead + | UnixFileMode.OtherExecute; + + File.SetUnixFileMode(Path.Combine(pkgroot, "DEBIAN", "prerm"), filemode); + File.SetUnixFileMode(Path.Combine(pkgroot, "DEBIAN", "postinst"), filemode); + } + } + // Copy the Docker build file File.Copy( Path.Combine(resourcesDir, "Dockerfile.build"), @@ -764,6 +1060,7 @@ public static partial class Command { File.Copy(Path.Combine(resourcesDir, "systemd", "duplicati-agent.service"), Path.Combine(sources, "duplicati-agent.service")); File.Copy(Path.Combine(resourcesDir, "systemd", "duplicati-agent.default"), Path.Combine(sources, "duplicati-agent.default")); + File.Copy(Path.Combine(resourcesDir, "systemd", "duplicati-agent.preset"), Path.Combine(sources, "duplicati-agent.preset")); } // Write custom script to install executable files @@ -798,8 +1095,7 @@ public static partial class Command .Replace("%DEPENDS%", string.Join("\n", (target.Interface == InterfaceType.GUI ? FedoraGUIDepends - : FedoraCLIDepends).Select(x => $"Requires:\t{x}"))) - .Replace("#GUI_ONLY#", target.Interface == InterfaceType.Cli ? "# " : ""); + : FedoraCLIDepends).Select(x => $"Requires:\t{x}"))); // Remove comment markers, or remove lines if (target.Interface == InterfaceType.GUI) @@ -807,6 +1103,11 @@ public static partial class Command else specData = string.Join("\n", specData.Split('\n').Where(x => !x.StartsWith("#GUI_ONLY#"))); + if (target.Interface == InterfaceType.Agent) + specData = specData.Replace("#AGENT_ONLY#", ""); + else + specData = string.Join("\n", specData.Split('\n').Where(x => !x.StartsWith("#AGENT_ONLY#"))); + File.WriteAllText( Path.Combine(sources, "duplicati.spec"), specData @@ -867,11 +1168,11 @@ public static partial class Command private static async Task BuildDockerImages(string baseDir, string buildRoot, IEnumerable targets, RuntimeConfig rtcfg) { // Make sure any dangling buildx instances are removed - try { await ProcessHelper.Execute([Program.Configuration.Commands.Docker!, "buildx", "rm", "duplicati-builder"], codeIsError: _ => false, suppressStdErr: true); } + try { await ProcessHelper.Execute([rtcfg.Configuration.Commands.Docker!, "buildx", "rm", "duplicati-builder"], codeIsError: _ => false, suppressStdErr: true); } catch { } // Prepare multi-build - await ProcessHelper.Execute([Program.Configuration.Commands.Docker!, "buildx", "create", "--use", "--name", "duplicati-builder"]); + await ProcessHelper.Execute([rtcfg.Configuration.Commands.Docker!, "buildx", "create", "--use", "--name", "duplicati-builder"]); // Perform a distict build for each interface type, but keep the buildx instance foreach (var interfaceType in Enum.GetValues()) @@ -915,7 +1216,7 @@ public static partial class Command await PackageSupport.SetPermissionFlags(tgfolder, rtcfg); } - var tags = new List { rtcfg.ReleaseInfo.Channel.ToString(), rtcfg.ReleaseInfo.Version.ToString() }; + var tags = new List { rtcfg.ReleaseInfo.Channel.ToString(), rtcfg.ReleaseInfo.Version.ToString(), $"{rtcfg.ReleaseInfo.Version}-{rtcfg.ReleaseInfo.Channel}" }; if (rtcfg.ReleaseInfo.Channel == ReleaseChannel.Stable) tags.Add("latest"); @@ -926,7 +1227,7 @@ public static partial class Command var repo = $"{rtcfg.DockerRepo}{(interfaceType == InterfaceType.Agent ? "-agent" : "")}"; // Build the images - var args = new List { Program.Configuration.Commands.Docker!, "buildx", "build" }; + var args = new List { rtcfg.Configuration.Commands.Docker!, "buildx", "build" }; args.AddRange(tags.SelectMany(x => new[] { "-t", $"{repo}:{x.ToLowerInvariant()}" })); args.AddRange([ "--platform", string.Join(",", dockerArchs), @@ -945,6 +1246,6 @@ public static partial class Command } // Clean up - await ProcessHelper.Execute([Program.Configuration.Commands.Docker!, "buildx", "rm", "duplicati-builder"]); + await ProcessHelper.Execute([rtcfg.Configuration.Commands.Docker!, "buildx", "rm", "duplicati-builder"]); } } diff --git a/ReleaseBuilder/Build/Command.GitPush.cs b/ReleaseBuilder/Build/Command.GitPush.cs index 3c9214137..0ccbc207c 100644 --- a/ReleaseBuilder/Build/Command.GitPush.cs +++ b/ReleaseBuilder/Build/Command.GitPush.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder.Build; public static partial class Command @@ -19,28 +39,29 @@ public static partial class Command File.WriteAllText(Path.Combine(baseDir, "ReleaseBuilder", "build_version.txt"), releaseInfo.Version.ToString()); // Add modified files - await ProcessHelper.Execute(new[] { + await ProcessHelper.Execute([ "git", "add", "ReleaseBuilder/build_version.txt", "changelog.txt" - }, workingDirectory: baseDir); + ], workingDirectory: baseDir); // Make a commit - await ProcessHelper.Execute(new[] { + await ProcessHelper.Execute([ "git", "commit", "-m", $"Version bump to v{releaseInfo.Version}-{releaseInfo.ReleaseName}", "-m", "You can download this build from: ", - "-m", $"Binaries: https://updates.duplicati.com/{releaseInfo.Channel.ToString().ToLowerInvariant()}/", + "-m", $"Binaries: https://updates.duplicati.com/{releaseInfo.Channel.ToString().ToLowerInvariant()}/?version={releaseInfo.Version}", "-m", $"Signature file: https://updates.duplicati.com/{releaseInfo.Channel.ToString().ToLowerInvariant()}/duplicati-{releaseInfo.ReleaseName}.signatures.zip" - }, workingDirectory: baseDir); + ], workingDirectory: baseDir); // And tag the release - await ProcessHelper.Execute(new[] { + await ProcessHelper.Execute([ "git", "tag", $"v{releaseInfo.Version}-{releaseInfo.ReleaseName}", - }, workingDirectory: baseDir); + ], workingDirectory: baseDir); // Then push the release - await ProcessHelper.Execute(new[] { "git", "push", "--tags" }, workingDirectory: baseDir); + await ProcessHelper.Execute(["git", "push", "-u", "origin", "head"], workingDirectory: baseDir); + await ProcessHelper.Execute(["git", "push", "--tags"], workingDirectory: baseDir); } } } diff --git a/ReleaseBuilder/Build/Command.GpgSign.cs b/ReleaseBuilder/Build/Command.GpgSign.cs index 86705d601..4b203a014 100644 --- a/ReleaseBuilder/Build/Command.GpgSign.cs +++ b/ReleaseBuilder/Build/Command.GpgSign.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.IO.Compression; namespace ReleaseBuilder.Build; @@ -33,7 +53,7 @@ public static partial class Command await ProcessHelper.Execute( [ - Program.Configuration.Commands.Gpg!, + rtcfg.Configuration.Commands.Gpg!, "--pinentry-mode", "loopback", "--passphrase-fd", "0", "--batch", "--yes", @@ -66,7 +86,7 @@ public static partial class Command static (string GpgId, string GpgPassphrase) GetGpgIdAndPassphrase(RuntimeConfig rtcfg) { using var ms = new MemoryStream(); - using var fs = File.OpenRead(Program.Configuration.ConfigFiles.GpgKeyfile); + using var fs = File.OpenRead(rtcfg.Configuration.ConfigFiles.GpgKeyfile); SharpAESCrypt.SharpAESCrypt.Decrypt(rtcfg.KeyfilePassword, fs, ms); var parts = System.Text.Encoding.UTF8.GetString(ms.ToArray()).Split('\n', 2, StringSplitOptions.RemoveEmptyEntries); return (parts[0], parts[1]); diff --git a/ReleaseBuilder/Build/Command.PackageSupport.cs b/ReleaseBuilder/Build/Command.PackageSupport.cs index b5ad01ec8..39567a09b 100644 --- a/ReleaseBuilder/Build/Command.PackageSupport.cs +++ b/ReleaseBuilder/Build/Command.PackageSupport.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder.Build; public static partial class Command @@ -69,5 +89,34 @@ public static partial class Command /// An awaitable task public static Task InstallPackageIdentifier(string buildDir, PackageTarget target) => File.WriteAllTextAsync(Path.Combine(buildDir, "package_type_id.txt"), target.PackageTargetString); + + /// + /// Codesigns the binary files in the given directory + /// + /// The runtime configuration + /// The directory containing the binaries + /// The entitlement file to use + /// An awaitable task + public static async Task SignMacOSBinaries(RuntimeConfig rtcfg, string binDir, string entitlementFile) + { + if (rtcfg.UseCodeSignSigning) + { + Console.WriteLine("Performing MacOS code signing ..."); + + // Executables cannot be signed before their dependencies are signed + // So they are placed last in the list + var executables = ExecutableRenames.Values.Select(x => Path.Combine(binDir, x)) + .Where(File.Exists); + + var signtargets = Directory.EnumerateFiles(binDir, "*", SearchOption.AllDirectories) + .Except(executables) + .Concat(executables) + .Distinct() + .ToList(); + + foreach (var f in signtargets) + await rtcfg.Codesign(f, false, entitlementFile); + } + } } } \ No newline at end of file diff --git a/ReleaseBuilder/Build/Command.RuntimeConfig.cs b/ReleaseBuilder/Build/Command.RuntimeConfig.cs index aa947933d..b78a40504 100644 --- a/ReleaseBuilder/Build/Command.RuntimeConfig.cs +++ b/ReleaseBuilder/Build/Command.RuntimeConfig.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Security.Cryptography; namespace ReleaseBuilder.Build; @@ -7,24 +27,23 @@ public static partial class Command /// /// Setup of the current runtime information /// - private class RuntimeConfig + /// + /// Constructs a new + /// + /// The release info to use + /// The keyfile password to use + /// The sign keys + /// The changelog news + /// The command input + private class RuntimeConfig( + Configuration configuration, + ReleaseInfo releaseInfo, + IEnumerable signKeys, + string keyfilePassword, + string changelogNews, + CommandInput input + ) { - /// - /// Constructs a new - /// - /// The release info to use - /// The keyfile password to use - /// The sign keys - /// The changelog news - /// The command input - public RuntimeConfig(ReleaseInfo releaseInfo, IEnumerable signKeys, string keyfilePassword, string changelogNews, CommandInput input) - { - ReleaseInfo = releaseInfo; - SignKeys = signKeys; - KeyfilePassword = keyfilePassword; - ChangelogNews = changelogNews; - Input = input; - } /// /// The cached password for the pfx file @@ -34,27 +53,30 @@ public static partial class Command /// /// The commandline input /// - private CommandInput Input { get; } - + private CommandInput Input => input; + /// + /// The configuration to use + /// + public Configuration Configuration => configuration; /// /// The release info for this run /// - public ReleaseInfo ReleaseInfo { get; } + public ReleaseInfo ReleaseInfo => releaseInfo; /// /// The keyfile password for this run /// - public IEnumerable SignKeys { get; } + public IEnumerable SignKeys => signKeys; /// /// The primary password /// - public string KeyfilePassword { get; } + public string KeyfilePassword => keyfilePassword; /// /// The changelog news /// - public string ChangelogNews { get; } + public string ChangelogNews => changelogNews; /// /// Gets the PFX password and throws if not possible @@ -69,6 +91,11 @@ public static partial class Command /// private bool? _useAuthenticodeSigning; + /// + /// Cache value for checking if jsign tool should be used for authenticode signing + /// + private bool? _useJsignToolForAuthenticode; + /// /// Checks if Authenticode signing should be enabled /// @@ -82,18 +109,39 @@ public static partial class Command return; } - if (Program.Configuration.IsAuthenticodePossible()) - _useAuthenticodeSigning = true; - else + if (Configuration.IsAuthenticodePossibleWithSignTool()) { - if (ConsoleHelper.ReadInput("Configuration missing for osslsigncode, continue without signing executables?", "Y", "n") == "Y") + _useAuthenticodeSigning = true; + _useJsignToolForAuthenticode = false; + return; + } + + if (Configuration.IsAuthenticodePossibleWithJsignTool()) + { + if (string.IsNullOrWhiteSpace(Input.SignkeyPin)) { - _useAuthenticodeSigning = false; - return; + var signKeyPin = EnvHelper.GetEnvKey("SIGNKEY_PIN", ""); + if (string.IsNullOrWhiteSpace(signKeyPin)) + signKeyPin = ConsoleHelper.ReadPassword("Enter the pin for the signing key"); + input = input with { SignkeyPin = signKeyPin }; } - throw new Exception("Configuration is not set up for osslsigncode"); + if (!string.IsNullOrWhiteSpace(Input.SignkeyPin)) + { + _useAuthenticodeSigning = true; + _useJsignToolForAuthenticode = true; + return; + } } + + if (ConsoleHelper.ReadInput("Configuration missing for jsign/signtool/osslsigncode, continue without signing executables?", "Y", "n") == "Y") + { + _useAuthenticodeSigning = false; + _useJsignToolForAuthenticode = false; + return; + } + + throw new Exception("Configuration is not set up for jsign/signtool/osslsigncode"); } } @@ -117,7 +165,7 @@ public static partial class Command if (!OperatingSystem.IsMacOS()) _useCodeSignSigning = false; - else if (Program.Configuration.IsCodeSignPossible()) + else if (Configuration.IsCodeSignPossible()) _useCodeSignSigning = true; else { @@ -146,7 +194,7 @@ public static partial class Command { try { - var res = await ProcessHelper.ExecuteWithOutput([Program.Configuration.Commands.Docker!, "ps"], suppressStdErr: true); + var res = await ProcessHelper.ExecuteWithOutput([Configuration.Commands.Docker!, "ps"], suppressStdErr: true); _dockerBuild = true; } catch @@ -183,7 +231,7 @@ public static partial class Command if (!OperatingSystem.IsMacOS()) _useNotarizeSigning = false; - else if (Program.Configuration.IsNotarizePossible()) + else if (Configuration.IsNotarizePossible()) _useNotarizeSigning = true; else { @@ -216,7 +264,7 @@ public static partial class Command return; } - if (Program.Configuration.IsGpgPossible()) + if (Configuration.IsGpgPossible()) _useGpgSigning = true; else { @@ -249,7 +297,7 @@ public static partial class Command return; } - if (Program.Configuration.IsAwsUploadPossible()) + if (Configuration.IsAwsUploadPossible()) _useS3Upload = true; else { @@ -283,7 +331,7 @@ public static partial class Command return; } - if (Program.Configuration.IsGithubUploadPossible()) + if (Configuration.IsGithubUploadPossible()) _useGithubUpload = true; else { @@ -316,7 +364,7 @@ public static partial class Command return; } - if (Program.Configuration.IsUpdateServerReloadPossible()) + if (Configuration.IsUpdateServerReloadPossible()) _useUpdateServerReload = true; else { @@ -350,7 +398,7 @@ public static partial class Command return; } - if (Program.Configuration.IsDiscourseAnnouncePossible()) + if (Configuration.IsDiscourseAnnouncePossible()) _useDiscourseAnnounce = true; else { @@ -375,6 +423,11 @@ public static partial class Command /// public bool UseAuthenticodeSigning => _useAuthenticodeSigning!.Value; + /// + /// Returns a value indicating if authenticode signing is enabled + /// + public bool UseJsingToolForAuthenticode => _useJsignToolForAuthenticode!.Value; + /// /// Returns a value indicating if notarize is enabled /// @@ -431,7 +484,7 @@ public static partial class Command /// Password for the password file /// The Authenticode password private string GetAuthenticodePassword(string keyfilepassword) - => EncryptionHelper.DecryptPasswordFile(Program.Configuration.ConfigFiles.AuthenticodePasswordFile, keyfilepassword).Trim(); + => EncryptionHelper.DecryptPasswordFile(Configuration.ConfigFiles.AuthenticodePasswordFile, keyfilepassword).Trim(); /// /// Performs authenticode signing if enabled @@ -439,26 +492,48 @@ public static partial class Command /// The file to sign /// An awaitable task public Task AuthenticodeSign(string file) - => UseAuthenticodeSigning - ? ProcessRunner.OsslCodeSign( - Program.Configuration.Commands.OsslSignCode!, - Program.Configuration.ConfigFiles.AuthenticodePfxFile, + { + if (!UseAuthenticodeSigning) + return Task.CompletedTask; + + if (UseJsingToolForAuthenticode) + return ProcessRunner.JsignCodeSign(Configuration.Commands.JSign!, Input.SignkeyPin, file); + else + return ProcessRunner.OsslCodeSign( + Configuration.Commands.SignCode!, + Configuration.ConfigFiles.AuthenticodePfxFile, PfxPassword, - file) - : Task.CompletedTask; + file + ); + } /// /// Performs codesign on the given file /// /// The file to sign + /// A value indicating if deep signing should be used /// The entitlements to apply /// An awaitable task - public Task Codesign(string file, string entitlements) + public Task Codesign(string file, bool deep, string entitlements) => UseCodeSignSigning ? ProcessRunner.MacOSCodeSign( - Program.Configuration.Commands.Codesign!, - Program.Configuration.ConfigFiles.CodesignIdentity, + Configuration.Commands.Codesign!, + Configuration.ConfigFiles.CodesignIdentity, entitlements, + file, + deep + ) + : Task.CompletedTask; + + /// + /// Verifies the codesign of the given file or app bundle + /// + /// The file to verify + /// An awaitable task + public Task VerifyCodeSign(string file) + => UseCodeSignSigning + ? ProcessRunner.MacOSVerifyCodeSign( + Configuration.Commands.Codesign!, file ) : Task.CompletedTask; @@ -471,8 +546,8 @@ public static partial class Command public Task Productsign(string file) => UseCodeSignSigning ? ProcessRunner.MacOSProductSign( - Program.Configuration.Commands.Productsign!, - Program.Configuration.ConfigFiles.CodesignIdentity, + Configuration.Commands.Productsign!, + Configuration.ConfigFiles.CodesignIdentity, file ) : Task.CompletedTask; diff --git a/ReleaseBuilder/Build/Command.Upload.cs b/ReleaseBuilder/Build/Command.Upload.cs index b6fdef146..c16a3d9b7 100644 --- a/ReleaseBuilder/Build/Command.Upload.cs +++ b/ReleaseBuilder/Build/Command.Upload.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Net.Http.Json; using Amazon; using Amazon.S3; @@ -86,17 +106,18 @@ public static partial class Command /// /// The files to upload /// The runtime configuration + /// The release channels to propagate to /// An awaitable task - public static async Task UploadToS3(IEnumerable files, RuntimeConfig rtcfg) + public static async Task UploadToS3(IEnumerable files, RuntimeConfig rtcfg, IEnumerable propagateTo) { var totalSize = files.Sum(f => new FileInfo(f.Path).Length); Console.WriteLine($"Uploading {files.Count()} files ({Duplicati.Library.Utility.Utility.FormatSizeString(totalSize)}) to S3..."); var chain = new Amazon.Runtime.CredentialManagement.CredentialProfileStoreChain(); - if (!chain.TryGetAWSCredentials(Program.Configuration.ConfigFiles.AwsUploadProfile, out var awsCredentials)) - throw new Exception($"The aws-cli profile '{Program.Configuration.ConfigFiles.AwsUploadProfile}' could not be found."); + if (!chain.TryGetAWSCredentials(rtcfg.Configuration.ConfigFiles.AwsUploadProfile, out var awsCredentials)) + throw new Exception($"The aws-cli profile '{rtcfg.Configuration.ConfigFiles.AwsUploadProfile}' could not be found."); - chain.TryGetProfile(Program.Configuration.ConfigFiles.AwsUploadProfile, out var awsProfile); + chain.TryGetProfile(rtcfg.Configuration.ConfigFiles.AwsUploadProfile, out var awsProfile); var config = new AmazonS3Config { RegionEndpoint = awsProfile?.Region ?? RegionEndpoint.USEast1, @@ -110,16 +131,33 @@ public static partial class Command foreach (var file in files) { - await fileTransferUtility.UploadAsync( - file.Path, - Program.Configuration.ConfigFiles.AwsUploadBucket, - $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/{file.Name}" - ); + await Duplicati.Library.Utility.RetryHelper.Retry(() => + fileTransferUtility.UploadAsync( + file.Path, + rtcfg.Configuration.ConfigFiles.AwsUploadBucket, + $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/{file.Name}" + ), 3, TimeSpan.FromSeconds(1), CancellationToken.None); var filesize = new FileInfo(file.Path).Length; size += filesize; Console.WriteLine($"{size / (double)totalSize * 100:F1}% - Uploaded {file.Name} ({Duplicati.Library.Utility.Utility.FormatSizeString(filesize)})"); } + + foreach (var channel in propagateTo) + { + if (channel == rtcfg.ReleaseInfo.Channel) + continue; + + var target = $"{channel.ToString().ToLowerInvariant()}/latest-v2.manifest"; + var source = $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/latest-v2.manifest"; + await Duplicati.Library.Utility.RetryHelper.Retry(() => + client.CopyObjectAsync( + rtcfg.Configuration.ConfigFiles.AwsUploadBucket, + source, + rtcfg.Configuration.ConfigFiles.AwsUploadBucket, + target + ), 3, TimeSpan.FromSeconds(1), CancellationToken.None); + } } /// @@ -130,11 +168,14 @@ public static partial class Command /// An awaitable task public static async Task UploadToGithub(IEnumerable files, RuntimeConfig rtcfg) { + var commithash = (await ProcessHelper.ExecuteWithOutput(["git", "rev-parse", "HEAD"]))?.Trim() + ?? throw new Exception("Failed to get the current commit hash"); + using var httpClient = new HttpClient(); var totalSize = files.Sum(f => new FileInfo(f.Path).Length); Console.WriteLine($"Uploading {files.Count()} files ({Duplicati.Library.Utility.Utility.FormatSizeString(totalSize)}) to Github..."); - var ghtoken = File.ReadAllText(Program.Configuration.ConfigFiles.GithubTokenFile).Trim(); + var ghtoken = File.ReadAllText(rtcfg.Configuration.ConfigFiles.GithubTokenFile).Trim(); var owner = "duplicati"; var repo = "duplicati"; @@ -145,7 +186,7 @@ public static partial class Command request.Headers.Add("User-Agent", "Duplicati Release Builder v1"); request.Content = JsonContent.Create(new GhReleaseInfo( tag_name: $"v{rtcfg.ReleaseInfo.ReleaseName}", - target_commitish: "master", + target_commitish: commithash, name: $"v{rtcfg.ReleaseInfo.ReleaseName}", body: rtcfg.ChangelogNews, draft: false, @@ -163,18 +204,21 @@ public static partial class Command // Upload the files to the same tag foreach (var file in files) { - request = new HttpRequestMessage(HttpMethod.Post, $"https://uploads.github.com/repos/{owner}/{repo}/releases/{releasedata.id}/assets?name={System.Net.WebUtility.UrlEncode(Path.GetFileName(file.Name))}"); - request.Headers.Add("Accept", "application/vnd.github+json"); - request.Headers.Add("Authorization", $"Bearer {ghtoken}"); - request.Headers.Add("X-GitHub-Api-Version", "2022-11-28"); - request.Headers.Add("User-Agent", "Duplicati Release Builder v1"); + await Duplicati.Library.Utility.RetryHelper.Retry(async () => + { + request = new HttpRequestMessage(HttpMethod.Post, $"https://uploads.github.com/repos/{owner}/{repo}/releases/{releasedata.id}/assets?name={System.Net.WebUtility.UrlEncode(Path.GetFileName(file.Name))}"); + request.Headers.Add("Accept", "application/vnd.github+json"); + request.Headers.Add("Authorization", $"Bearer {ghtoken}"); + request.Headers.Add("X-GitHub-Api-Version", "2022-11-28"); + request.Headers.Add("User-Agent", "Duplicati Release Builder v1"); - using var fileStream = File.OpenRead(file.Path); - request.Content = new StreamContent(fileStream); - request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); + using var fileStream = File.OpenRead(file.Path); + request.Content = new StreamContent(fileStream); + request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/octet-stream"); - response = await httpClient.SendAsync(request); - response.EnsureSuccessStatusCode(); + response = await httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + }, 3, TimeSpan.FromSeconds(1), CancellationToken.None); var filesize = new FileInfo(file.Path).Length; size += filesize; @@ -187,18 +231,18 @@ public static partial class Command /// /// The runtime configuration /// An awaitable task - public static async Task ReloadUpdateServer(RuntimeConfig rtcfg) + public static async Task ReloadUpdateServer(RuntimeConfig rtcfg, IEnumerable propagateTo) { using var client = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Post, "https://updates.duplicati.com/reload"); - var reloadToken = Program.Configuration.ConfigFiles.ReloadUpdatesApiKey; + var reloadToken = rtcfg.Configuration.ConfigFiles.ReloadUpdatesApiKey; req.Headers.Add("X-API-KEY", reloadToken); req.Content = JsonContent.Create(new[] { $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/latest-v2.json", $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/latest-v2.js", $"{rtcfg.ReleaseInfo.Channel.ToString().ToLowerInvariant()}/latest-v2.manifest", - }); + }.Concat(propagateTo.Select(c => $"{c.ToString().ToLowerInvariant()}/latest-v2.manifest"))); var response = await client.SendAsync(req); response.EnsureSuccessStatusCode(); @@ -214,7 +258,7 @@ public static partial class Command using var client = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Post, "https://forum.duplicati.com/posts"); - var discourseToken = File.ReadAllText(Program.Configuration.ConfigFiles.DiscourseTokenFile).Trim().Split(":", 2); + var discourseToken = File.ReadAllText(rtcfg.Configuration.ConfigFiles.DiscourseTokenFile).Trim().Split(":", 2); req.Headers.Add("Api-Username", discourseToken[0]); req.Headers.Add("Api-Key", discourseToken[1]); req.Headers.Add("Accept", "application/json"); diff --git a/ReleaseBuilder/Build/Command.cs b/ReleaseBuilder/Build/Command.cs index 1aa319604..6f2579fb5 100644 --- a/ReleaseBuilder/Build/Command.cs +++ b/ReleaseBuilder/Build/Command.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.CommandLine; using System.CommandLine.NamingConventionBinder; using System.Security.Cryptography; @@ -65,6 +85,9 @@ public static partial class Command { "Duplicati.CommandLine.SharpAESCrypt", "duplicati-aescrypt" }, { "Duplicati.CommandLine.Snapshots", "duplicati-snapshots" }, { "Duplicati.CommandLine.ServerUtil", "duplicati-server-util" }, + { "Duplicati.CommandLine.SecretTool", "duplicati-secret-tool" }, + { "Duplicati.CommandLine.SyncTool", "duplicati-sync-tool" }, + { "Duplicati.CommandLine.SourceTool", "duplicati-source-tool" }, { "Duplicati.Service", "duplicati-service" }, { "Duplicati.Agent", "duplicati-agent" }, { "Duplicati.CommandLine", "duplicati-cli" }, @@ -200,6 +223,12 @@ public static partial class Command var passwordOption = SharedOptions.passwordOption; + var signkeyPinOption = new Option( + name: "--signkey-pin", + description: "The pin to use for the signing key", + getDefaultValue: () => string.Empty + ); + var disableDockerPushOption = new Option( name: "--disable-docker-push", description: "Disables pushing the docker image to the repository", @@ -219,7 +248,7 @@ public static partial class Command ); var changelogFileOption = new Option( - name: "--changelog-file", + name: "--changelog-news-file", description: "The path to the changelog news file. Contents from this file are prepended to the changelog.", getDefaultValue: () => new FileInfo(Path.GetFullPath("changelog-news.txt")) ); @@ -272,6 +301,18 @@ public static partial class Command getDefaultValue: () => false ); + var resumeFromUploadOption = new Option( + name: "--resume-from-upload", + description: "Resumes the build process from the upload step", + getDefaultValue: () => false + ); + + var propagateReleaseOption = new Option( + name: "--propagate-release", + description: "Propagate the release to the next channel", + getDefaultValue: () => false + ); + var command = new System.CommandLine.Command("build", "Builds the packages for a release") { gitStashPushOption, releaseChannelArgument, @@ -283,6 +324,7 @@ public static partial class Command disableAuthenticodeOption, disableCodeSignOption, passwordOption, + signkeyPinOption, macOsAppNameOption, disableDockerPushOption, dockerRepoOption, @@ -294,7 +336,9 @@ public static partial class Command disableGithubUploadOption, disableUpdateServerReloadOption, disableDiscordAnnounceOption, - useHostedBuildsOption + useHostedBuildsOption, + resumeFromUploadOption, + propagateReleaseOption }; command.Handler = CommandHandler.Create(DoBuild); @@ -315,10 +359,11 @@ public static partial class Command /// If authenticode signing should be disabled /// If signcode should be disabled /// The password to use for the keyfile + /// The pin to use for the signing key /// If the docker push should be disabled /// The name of the MacOS app bundle /// The docker repository to push to - /// The path to the changelog file + /// The path to the changelog news file /// If notarize signing should be disabled /// If GPG signing should be disabled /// If S3 upload should be disabled @@ -326,6 +371,8 @@ public static partial class Command /// If the update server should not be reloaded /// If forum posting should be disabled /// If hosted builds should be used + /// If the process should resume from the upload step + /// If the release should be propagated to the next channel record CommandInput( PackageTarget[] Targets, DirectoryInfo BuildPath, @@ -338,22 +385,26 @@ public static partial class Command bool DisableAuthenticode, bool DisableSignCode, string Password, + string SignkeyPin, bool DisableDockerPush, string MacOSAppName, string DockerRepo, - FileInfo ChangelogFile, + FileInfo ChangelogNewsFile, bool DisableNotarizeSigning, bool DisableGpgSigning, bool DisableS3Upload, bool DisableGithubUpload, bool DisableUpdateServerReload, bool DisableDiscordAnnounce, - bool UseHostedBuilds + bool UseHostedBuilds, + bool ResumeFromUpload, + bool PropagateRelease ); static async Task DoBuild(CommandInput input) { Console.WriteLine($"Building {input.Channel} release ..."); + var configuration = Configuration.Create(input.Channel); var buildTargets = input.Targets; @@ -364,15 +415,15 @@ public static partial class Command throw new FileNotFoundException($"Solution file not found: {input.SolutionFile.FullName}"); // This could be fixed, so we will throw an exception if the build is not possible - if (buildTargets.Any(x => x.Package == PackageType.MSI) && !Program.Configuration.IsMSIBuildPossible()) + if (buildTargets.Any(x => x.Package == PackageType.MSI) && !configuration.IsMSIBuildPossible()) throw new Exception("WiX toolset not configured, cannot build MSI files"); // This will be fixed in the future, but requires a new http-interface for Synology DSM - if (buildTargets.Any(x => x.Package == PackageType.SynologySpk) && !Program.Configuration.IsSynologyPkgPossible()) + if (buildTargets.Any(x => x.Package == PackageType.SynologySpk) && !configuration.IsSynologyPkgPossible()) throw new Exception("Synology SPK files are currently not supported"); // This will not work, so to make it easier for non-MacOS developers, we will remove the MacOS packages - if (buildTargets.Any(x => x.Package == PackageType.MacPkg || x.Package == PackageType.DMG) && !Program.Configuration.IsMacPkgBuildPossible()) + if (buildTargets.Any(x => x.Package == PackageType.MacPkg || x.Package == PackageType.DMG) && !configuration.IsMacPkgBuildPossible()) { Console.WriteLine("MacOS packages requested but not running on MacOS, removing from build targets"); buildTargets = buildTargets.Where(x => x.Package != PackageType.MacPkg && x.Package != PackageType.DMG).ToArray(); @@ -407,28 +458,28 @@ public static partial class Command if (!File.Exists(primaryCLI)) throw new Exception($"Failed to locate project file: {primaryCLI}"); - if (!input.ChangelogFile.Exists) + if (!input.ChangelogNewsFile.Exists) { - Console.WriteLine($"Changelog news file not found: {input.ChangelogFile.FullName}"); + Console.WriteLine($"Changelog news file not found: {input.ChangelogNewsFile.FullName}"); Console.WriteLine($"Create an empty file if you want a release without changes"); if (OperatingSystem.IsWindows()) - Console.WriteLine($"> type nul > {input.ChangelogFile.FullName}"); + Console.WriteLine($"> type nul > {input.ChangelogNewsFile.FullName}"); else - Console.WriteLine($"> touch {input.ChangelogFile.FullName}"); + Console.WriteLine($"> touch {input.ChangelogNewsFile.FullName}"); Program.ReturnCode = 1; return; } - if (input.ChangelogFile.LastAccessTimeUtc < DateTime.UtcNow.AddDays(-1)) + if (input.ChangelogNewsFile.LastAccessTimeUtc < DateTime.UtcNow.AddDays(-1)) { - Console.WriteLine($"Changelog news file was last modified {input.ChangelogFile.LastAccessTimeUtc:yyyy-MM-dd}, indicating a stale file"); + Console.WriteLine($"Changelog news file was last modified {input.ChangelogNewsFile.LastAccessTimeUtc:yyyy-MM-dd}, indicating a stale file"); Console.WriteLine($"Please update the file with the changelog news for the release"); Program.ReturnCode = 1; return; } - var changelogNews = File.ReadAllText(input.ChangelogFile.FullName); + var changelogNews = File.ReadAllText(input.ChangelogNewsFile.FullName); var releaseInfo = string.IsNullOrWhiteSpace(input.Version) ? ReleaseInfo.Create(input.Channel, Version.Parse(File.ReadAllText(versionFilePath)), 1) @@ -441,13 +492,14 @@ public static partial class Command if (string.IsNullOrWhiteSpace(keyfilePassword)) keyfilePassword = ConsoleHelper.ReadPassword("Enter keyfile password"); - var primarySignKey = LoadKeyFile(Program.Configuration.ConfigFiles.UpdaterKeyfile.FirstOrDefault(), keyfilePassword, false); - var additionalKeys = Program.Configuration.ConfigFiles.UpdaterKeyfile + var primarySignKey = LoadKeyFile(configuration.ConfigFiles.UpdaterKeyfile.FirstOrDefault(), keyfilePassword, false); + var additionalKeys = configuration.ConfigFiles.UpdaterKeyfile .Skip(1) .Select(x => LoadKeyFile(x, keyfilePassword, true)); // Configure runtime environment var rtcfg = new RuntimeConfig( + configuration, releaseInfo, additionalKeys.Prepend(primarySignKey).ToList(), keyfilePassword, @@ -471,6 +523,15 @@ public static partial class Command throw new Exception($"The following packages cannot be built without Docker: {string.Join(", ", unsupportedBuilds.Select(x => x.PackageTargetString))}"); } + // Prevent the system from going to sleep during the build + using var _ = new KeepAliveAssertion(); + + if (rtcfg.UseGithubUpload && !input.GitStashPush) + { + Console.WriteLine("Github upload requested, but pushing the tag is disabled"); + Program.ReturnCode = 1; + } + if (!input.KeepBuilds && Directory.Exists(input.BuildPath.FullName)) { Console.WriteLine($"Deleting build folder: {input.BuildPath.FullName}"); @@ -482,7 +543,7 @@ public static partial class Command // Generally, the builds should happen with a clean source tree, // but this can be disabled for debugging - if (input.GitStashPush) + if (input.GitStashPush && !input.ResumeFromUpload) await ProcessHelper.Execute(["git", "stash", "save", $"auto-build-{releaseInfo.Timestamp:yyyy-MM-dd}"], workingDirectory: baseDir); // Inject various files that will be embedded into the build artifacts @@ -541,11 +602,11 @@ public static partial class Command }), Path.Combine(input.BuildPath.FullName, "packages"), version: releaseInfo.Version.ToString(), - incompatibleUpdateUrl: ReplaceVersionPlaceholders(Program.Configuration.ExtraSettings.UpdateFromIncompatibleVersionUrl, releaseInfo), - genericUpdatePageUrl: ReplaceVersionPlaceholders(Program.Configuration.ExtraSettings.GenericUpdatePageUrl, releaseInfo), + incompatibleUpdateUrl: ReplaceVersionPlaceholders(configuration.ExtraSettings.UpdateFromIncompatibleVersionUrl, releaseInfo), + genericUpdatePageUrl: ReplaceVersionPlaceholders(configuration.ExtraSettings.GenericUpdatePageUrl, releaseInfo), releaseType: releaseInfo.Channel.ToString().ToLowerInvariant(), packages: builtPackages.Select(x => new PackageEntry( - RemoteUrls: Program.Configuration.ExtraSettings.PackageUrls + RemoteUrls: configuration.ExtraSettings.PackageUrls .Select(u => ReplaceVersionPlaceholders(u, releaseInfo) .Replace("${FILENAME}", HttpUtility.UrlEncode(Path.GetFileName(x.CreatedFile))) @@ -575,6 +636,18 @@ public static partial class Command files.Add(sigfile); } + // Ensure the tag is pushed before uploading, so the uploaded files + // are associated with the release tag + if (input.GitStashPush && !input.ResumeFromUpload) + await GitPush.TagAndPush(baseDir, releaseInfo); + + // Propagate the release to the next channel, if selected + var nextChannels = new[] { ReleaseChannel.Experimental, ReleaseChannel.Beta, ReleaseChannel.Stable } + .Where(x => x > releaseInfo.Channel) + .Where(x => rtcfg.ReleaseInfo.Channel != ReleaseChannel.Canary) + .Where(x => input.PropagateRelease) + .ToArray(); + if (rtcfg.UseS3Upload || rtcfg.UseGithubUpload) { Console.WriteLine("Build completed, uploading packages ..."); @@ -583,33 +656,37 @@ public static partial class Command var manifestNames = new[] { $"duplicati-{releaseInfo.ReleaseName}.manifest", "latest-v2.manifest" }; var packageJson = Path.Combine(input.BuildPath.FullName, "packages", "latest-v2.json"); - var packageJs = Path.Combine(input.BuildPath.FullName, "packages", "latest-v2.js"); var content = Upload.CreatePackageJson(builtPackages, rtcfg); File.WriteAllText(packageJson, content); - File.WriteAllText(packageJs, $"duplicati_installers = {content};"); var uploads = files.Select(x => new Upload.UploadFile(x, Path.GetFileName(x))) .Concat(manifestNames.Select(x => new Upload.UploadFile(manifestfile, x))) .Append(new Upload.UploadFile(packageJson, Path.GetFileName(packageJson))) - .Append(new Upload.UploadFile(packageJs, Path.GetFileName(packageJs))) - .Append(new Upload.UploadFile(packageJson, $"latest-v2-{releaseInfo.Version}.json")) - .Append(new Upload.UploadFile(packageJs, $"latest-v2-{releaseInfo.Version}.js")); + .Append(new Upload.UploadFile(packageJson, $"latest-v2-{releaseInfo.Version}.json")); - await Upload.UploadToS3(uploads, rtcfg); + await Upload.UploadToS3(uploads, rtcfg, nextChannels); } if (rtcfg.UseGithubUpload) + { + if (!rtcfg.UseS3Upload && input.GitStashPush && !input.ResumeFromUpload) + { + Console.WriteLine("Waiting for Github to create the release tag ..."); + await Task.Delay(TimeSpan.FromSeconds(30)); + } + await Upload.UploadToGithub( files.Select(x => new Upload.UploadFile(x, Path.GetFileName(x))), rtcfg ); + } } if (rtcfg.UseUpdateServerReload) { Console.WriteLine("Release completed, reloading update server ..."); - await Upload.ReloadUpdateServer(rtcfg); + await Upload.ReloadUpdateServer(rtcfg, nextChannels); } if (rtcfg.UseForumPosting) @@ -620,10 +697,8 @@ public static partial class Command if (input.GitStashPush) { - await GitPush.TagAndPush(baseDir, releaseInfo); - // Contents are added to changelog, so we can remove the file - input.ChangelogFile.Delete(); + input.ChangelogNewsFile.Delete(); } // Clean up the source tree @@ -631,10 +706,18 @@ public static partial class Command if (Directory.Exists(folder)) Directory.Delete(folder, true); - await ProcessHelper.Execute(new[] { + // This often fails and should be fixed + try + { + await ProcessHelper.Execute(new[] { "git", "checkout", }.Concat(revertableFiles.Select(x => Path.GetRelativePath(baseDir, x))) - , workingDirectory: baseDir); + , workingDirectory: baseDir); + } + catch (Exception ex) + { + Console.WriteLine($"Warning: Failed to clean source directory: {ex.Message}"); + } Console.WriteLine("All done!"); } @@ -675,17 +758,29 @@ public static partial class Command regex.Replace(File.ReadAllText(file), $"?v={releaseInfo.Version}") ); - var wixFile = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "Windows", "UpgradeData.wxi"); + var wixFileGUI = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "Windows", "TrayIcon", "UpgradeData.wxi"); File.WriteAllText( - wixFile, + wixFileGUI, Regex.Replace( - File.ReadAllText(wixFile), + File.ReadAllText(wixFileGUI), @"\<\?define ProductVersion\=\""" + versionre + @"\"" \?\>", $"" ) ); - targetfiles.Add(wixFile); + targetfiles.Add(wixFileGUI); + + var wixFileAgent = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "Windows", "Agent", "UpgradeData.wxi"); + File.WriteAllText( + wixFileAgent, + Regex.Replace( + File.ReadAllText(wixFileAgent), + @"\<\?define ProductVersion\=\""" + versionre + @"\"" \?\>", + $"" + ) + ); + + targetfiles.Add(wixFileAgent); return targetfiles; } @@ -701,7 +796,7 @@ public static partial class Command /// Modified files static Task> PrepareSourceDirectory(string baseDir, ReleaseInfo releaseInfo, RuntimeConfig rtcfg) { - var urlstring = string.Join(";", Program.Configuration.ExtraSettings.UpdaterUrls.Select(x => + var urlstring = string.Join(";", rtcfg.Configuration.ExtraSettings.UpdaterUrls.Select(x => ReplaceVersionPlaceholders(x, releaseInfo) .Replace("${FILENAME}", HttpUtility.UrlEncode("latest-v2.manifest")) )); @@ -749,8 +844,8 @@ public static partial class Command JsonSerializer.Serialize(new { Displayname = $"Duplicati v{releaseInfo.Version} - {releaseInfo.Channel}" }), Path.Combine(baseDir, "Duplicati", "Library", "AutoUpdater"), version: releaseInfo.Version.ToString(), - incompatibleUpdateUrl: ReplaceVersionPlaceholders(Program.Configuration.ExtraSettings.UpdateFromIncompatibleVersionUrl, releaseInfo), - genericUpdatePageUrl: ReplaceVersionPlaceholders(Program.Configuration.ExtraSettings.GenericUpdatePageUrl, releaseInfo), + incompatibleUpdateUrl: ReplaceVersionPlaceholders(rtcfg.Configuration.ExtraSettings.UpdateFromIncompatibleVersionUrl, releaseInfo), + genericUpdatePageUrl: ReplaceVersionPlaceholders(rtcfg.Configuration.ExtraSettings.GenericUpdatePageUrl, releaseInfo), releaseType: releaseInfo.Channel.ToString().ToLowerInvariant() ); @@ -785,7 +880,7 @@ public static partial class Command foreach (var target in targets) { - if (string.IsNullOrWhiteSpace(Program.Configuration.Commands.Npm)) + if (string.IsNullOrWhiteSpace(rtcfg.Configuration.Commands.Npm)) throw new Exception("NPM command not found, but required for building"); // Remove existing node_modules folder @@ -803,7 +898,7 @@ public static partial class Command EnvHelper.CopyDirectory(target, tmp, true); // Run npm install in the temporary folder - await ProcessHelper.Execute(new[] { Program.Configuration.Commands.Npm, "ci" }, workingDirectory: tmp); + await ProcessHelper.Execute([rtcfg.Configuration.Commands.Npm, "ci"], workingDirectory: tmp); var basefolder = Directory.EnumerateDirectories(Path.Combine(tmp, "node_modules"), "*", SearchOption.AllDirectories) .Where(x => File.Exists(Path.Combine(x, "index.html"))) .OrderBy(x => x.Length) diff --git a/ReleaseBuilder/Configuration.cs b/ReleaseBuilder/Configuration.cs index 000c0d2b1..b20b5934a 100644 --- a/ReleaseBuilder/Configuration.cs +++ b/ReleaseBuilder/Configuration.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder; using static EnvHelper; @@ -48,21 +68,35 @@ public record Configuration( /// /// Creates a new /// + /// The release channel /// The new configuration - public static Configuration Create() + public static Configuration Create(ReleaseChannel channel) => new( - ConfigFiles.Create(), + ConfigFiles.Create(channel), Commands.Create(), ExtraSettings.Create() ); + /// - /// Checks if signing with authenticode is possible given the current configuration + /// Checks if signing with authenticode using jsign is possible given the current configuration /// /// A boolean indicating if signing is possible - public bool IsAuthenticodePossible() + public bool IsAuthenticodePossibleWithJsignTool() { - if (string.IsNullOrWhiteSpace(ConfigFiles.AuthenticodePasswordFile) || string.IsNullOrWhiteSpace(ConfigFiles.AuthenticodePfxFile) || string.IsNullOrWhiteSpace(Commands.OsslSignCode)) + if (string.IsNullOrWhiteSpace(Commands.JSign)) + return false; + + return true; + } + + /// + /// Checks if signing with authenticode is possible with signtool given the current configuration + /// + /// A boolean indicating if signing is possible + public bool IsAuthenticodePossibleWithSignTool() + { + if (string.IsNullOrWhiteSpace(ConfigFiles.AuthenticodePasswordFile) || string.IsNullOrWhiteSpace(ConfigFiles.AuthenticodePfxFile) || string.IsNullOrWhiteSpace(Commands.SignCode)) return false; if (!File.Exists(ConfigFiles.AuthenticodePasswordFile) || !File.Exists(ConfigFiles.AuthenticodePfxFile)) @@ -263,12 +297,28 @@ public record ConfigFiles( /// /// Generates a new config files instance /// + /// The release channel /// The config files instance - public static ConfigFiles Create() + public static ConfigFiles Create(ReleaseChannel channel) { - ParseEnvironmentFile(ExpandEnv("BUILD_SETTINGS_FILE", "${HOME}/.config/build-settings")); - ParseEnvironmentFile(ExpandEnv("GATEKEEPER_SETTINGS_FILE", "${HOME}/.config/signkeys/Duplicati/macos-gatekeeper")); + // Grab the shared configuration + ParseEnvironmentFile(ExpandEnv("BUILD_SETTINGS_FILE", "${HOME}/.config/duplicati-build-settings")); + + // Override the configuration for the release type, if any + ParseEnvironmentFile(ExpandEnv(channel switch + { + ReleaseChannel.Stable or + ReleaseChannel.Beta or + ReleaseChannel.Experimental or + ReleaseChannel.Canary => "${HOME}/.config/signkeys/Duplicati/release-build-settings", + ReleaseChannel.Nightly => "${HOME}/.config/signkeys/Duplicati/nightly-build-settings-nightly", + ReleaseChannel.Debug => "${HOME}/.config/signkeys/Duplicati/debug-build-settings", + _ => throw new ArgumentOutOfRangeException(nameof(channel)) + })); + + // Override the configuration for the release channel, if any + ParseEnvironmentFile(ExpandEnv($"${{HOME}}/.config/signkeys/Duplicati/${channel.ToString().ToLowerInvariant()}-build-settings")); return new( ExpandEnv("UPDATER_KEYFILE", "${HOME}/.config/signkeys/Duplicati/updater-release.key").Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), @@ -292,7 +342,8 @@ public record ConfigFiles( /// The "build" command /// The "gpg" command /// The "aws" command -/// The "osslsigncode" command +/// The "osslsigncode" command +/// The "jsign" command /// The "codesign" command /// The "productsign" command /// The "wix" command @@ -301,7 +352,8 @@ public record ConfigFiles( public record Commands( string Dotnet, string? Gpg, - string? OsslSignCode, + string? SignCode, + string? JSign, string? Codesign, string? Productsign, string? Wix, @@ -318,6 +370,7 @@ public record Commands( FindCommand("dotnet", "DOTNET") ?? throw new Exception("Failed to find the \"dotnet\" command"), FindCommand("gpg2", "GPG", FindCommand("gpg", "GPG")), FindCommand(OperatingSystem.IsWindows() ? "signtool.exe" : "osslsigncode", "SIGNTOOL"), + FindCommand("jsign", "JSIGNTOOL"), OperatingSystem.IsMacOS() ? FindCommand("codesign", "CODESIGN") : null, OperatingSystem.IsMacOS() ? FindCommand("productsign", "PRODUCTSIGN") : null, FindCommand(OperatingSystem.IsWindows() ? "wix" : "wixl", "WIX"), diff --git a/ReleaseBuilder/ConsoleHelper.cs b/ReleaseBuilder/ConsoleHelper.cs index deb36b705..a71f191ff 100644 --- a/ReleaseBuilder/ConsoleHelper.cs +++ b/ReleaseBuilder/ConsoleHelper.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder; public static class ConsoleHelper diff --git a/ReleaseBuilder/CreateKey/Command.cs b/ReleaseBuilder/CreateKey/Command.cs index c2f3e3e85..8443896d7 100644 --- a/ReleaseBuilder/CreateKey/Command.cs +++ b/ReleaseBuilder/CreateKey/Command.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.CommandLine; using System.Security.Cryptography; @@ -12,7 +32,7 @@ public static class Command var keyfileArgument = new Argument( name: "keyfile", description: "Path to keyfile to use for signing release manifests", - getDefaultValue: () => new FileInfo(Program.Configuration.ConfigFiles.UpdaterKeyfile.FirstOrDefault() ?? "./signkey.key") + getDefaultValue: () => new FileInfo(Configuration.Create(ReleaseChannel.Debug).ConfigFiles.UpdaterKeyfile.FirstOrDefault() ?? "./signkey.key") ); var command = new System.CommandLine.Command("create-key", "Creates a new key for signing releases") diff --git a/ReleaseBuilder/EncryptionHelper.cs b/ReleaseBuilder/EncryptionHelper.cs index 6672ca705..8d332e5c9 100644 --- a/ReleaseBuilder/EncryptionHelper.cs +++ b/ReleaseBuilder/EncryptionHelper.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text; namespace ReleaseBuilder; diff --git a/ReleaseBuilder/EnvHelper.cs b/ReleaseBuilder/EnvHelper.cs index 7af2c912e..faadddbba 100644 --- a/ReleaseBuilder/EnvHelper.cs +++ b/ReleaseBuilder/EnvHelper.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Runtime.Versioning; using System.Text.RegularExpressions; @@ -21,11 +41,20 @@ public static class EnvHelper if (string.IsNullOrWhiteSpace(value)) value = defaultValue ?? string.Empty; + return ExpandEnv(value); + } + + /// + /// Reads the environment key, and expands environment variables inside. + /// If no key is found, the default value is returned + /// + /// The key to use + /// The expanded string + public static string ExpandEnv(string value) // Bash-style env expansion "${name}", done after normal env expansion - return Regex.Replace(Environment.ExpandEnvironmentVariables(value), "\\${(?[^}]+)}", m => + => Regex.Replace(Environment.ExpandEnvironmentVariables(value), "\\${(?[^}]+)}", m => Environment.GetEnvironmentVariable(m.Groups["name"].Value) ?? string.Empty ); - } /// /// Reads the environment key, and expands environment variables inside. @@ -43,17 +72,22 @@ public static class EnvHelper return value; } + /// + /// Extensions that are considered Windows executables + /// + private static readonly string[] WindowsExecutables = new[] { ".exe", ".cmd", ".ps1", ".bat" }; + /// /// Returns an executable path /// /// The path to expand /// The executable path - public static string GetExecutablePath(string path) + public static string[] GetExecutablePaths(string path) => string.IsNullOrWhiteSpace(path) - ? path + ? [] : OperatingSystem.IsWindows() - ? Path.ChangeExtension(path, ".exe") - : path; + ? WindowsExecutables.Select(x => Path.ChangeExtension(path, x)).ToArray() + : [path]; /// /// Returns a value if the path is executable @@ -66,7 +100,7 @@ public static class EnvHelper return false; if (OperatingSystem.IsWindows()) - return path.EndsWith(".exe"); + return WindowsExecutables.Any(x => path.EndsWith(x, StringComparison.OrdinalIgnoreCase)); return File.GetUnixFileMode(path).HasFlag(UnixFileMode.OtherExecute); } @@ -82,23 +116,26 @@ public static class EnvHelper { if (!string.IsNullOrWhiteSpace(envkey)) { - var target = GetExecutablePath(ExpandEnv(envkey, "")); + var targets = GetExecutablePaths(ExpandEnv(envkey, "")); - if (!string.IsNullOrWhiteSpace(target)) + foreach (var target in targets) { - if (!File.Exists(target)) - throw new Exception($"Executable specified for {envkey} but not found: {target}"); - if (!IsExecutable(target)) - throw new Exception($"File specified for {envkey} found but is not executable: {target}"); + if (!string.IsNullOrWhiteSpace(target)) + { + if (!File.Exists(target)) + throw new Exception($"Executable specified for {envkey} but not found: {target}"); + if (!IsExecutable(target)) + throw new Exception($"File specified for {envkey} found but is not executable: {target}"); - return target; + return target; + } } } var folders = (Environment.GetEnvironmentVariable("PATH") ?? string.Empty).Split(Path.PathSeparator); return folders .Where(x => !string.IsNullOrWhiteSpace(x)) - .Select(x => GetExecutablePath(Path.Combine(x, command))) + .SelectMany(x => GetExecutablePaths(Path.Combine(x, command))) .FirstOrDefault(IsExecutable) ?? defaultValue; } @@ -169,7 +206,7 @@ public static class EnvHelper var targetEntry = Path.GetFileName(path); // Use docker to set the ownership - await ProcessHelper.Execute(new[] { "docker", "run", "--mount", $"type=bind,source={baseFolder},target=/opt/mount", "alpine:latest", "chown", recursive ? "-R" : "", $"{uid}:{gid}", Path.Combine("/opt/mount", targetEntry) }); + await ProcessHelper.Execute(["docker", "run", "--mount", $"type=bind,source={baseFolder},target=/opt/mount", "alpine:latest", "chown", recursive ? "-R" : "", $"{uid}:{gid}", Path.Combine("/opt/mount", targetEntry)]); } /// diff --git a/ReleaseBuilder/KeepAliveAssertion.cs b/ReleaseBuilder/KeepAliveAssertion.cs new file mode 100644 index 000000000..493db70b2 --- /dev/null +++ b/ReleaseBuilder/KeepAliveAssertion.cs @@ -0,0 +1,98 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace ReleaseBuilder; + +/// +/// A class that prevents the system from sleeping during build +/// +public class KeepAliveAssertion : IDisposable +{ + /// + /// The previous state of the system, on Windows + /// + private NativeMethods.EXECUTION_STATE _previousState; + /// + /// The process that keeps the system awake, on Linux and MacOS + /// + private Process? _keepAliveProcess; + + /// + /// Creates a new instance of the class + /// + public KeepAliveAssertion() + { + if (OperatingSystem.IsWindows()) + { + _previousState = NativeMethods.SetThreadExecutionState(NativeMethods.EXECUTION_STATE.ES_CONTINUOUS | NativeMethods.EXECUTION_STATE.ES_SYSTEM_REQUIRED | NativeMethods.EXECUTION_STATE.ES_AWAYMODE_REQUIRED); + } + else if (OperatingSystem.IsLinux()) + { + _keepAliveProcess = Process.Start("xset", "s off -dpms"); + } + else if (OperatingSystem.IsMacOS()) + { + // Keep the display awake until this process exits + var selfProcessId = Process.GetCurrentProcess().Id; + _keepAliveProcess = Process.Start("caffeinate", ["-d", "-w", selfProcessId.ToString()]); + } + } + + /// + /// A class that contains Windows native methods + /// + [SupportedOSPlatform("windows")] + private class NativeMethods + { + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); // returns the previous state + + [Flags] + public enum EXECUTION_STATE : uint + { + ES_AWAYMODE_REQUIRED = 0x00000040, + ES_CONTINUOUS = 0x80000000, + ES_SYSTEM_REQUIRED = 0x00000001 + } + } + + /// + public void Dispose() + { + if (OperatingSystem.IsWindows()) + { + NativeMethods.SetThreadExecutionState(_previousState); + } + else if (OperatingSystem.IsLinux()) + { + _keepAliveProcess?.Kill(); + _keepAliveProcess?.Dispose(); + } + else if (OperatingSystem.IsMacOS()) + { + _keepAliveProcess?.Kill(); + _keepAliveProcess?.Dispose(); + } + } +} diff --git a/ReleaseBuilder/PackageTarget.cs b/ReleaseBuilder/PackageTarget.cs index 8b66bc484..e4d053f97 100644 --- a/ReleaseBuilder/PackageTarget.cs +++ b/ReleaseBuilder/PackageTarget.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Text.RegularExpressions; namespace ReleaseBuilder; diff --git a/ReleaseBuilder/ProcessHelper.cs b/ReleaseBuilder/ProcessHelper.cs index 76cccc1c5..8486fd2d9 100644 --- a/ReleaseBuilder/ProcessHelper.cs +++ b/ReleaseBuilder/ProcessHelper.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.Diagnostics; namespace ReleaseBuilder; @@ -18,10 +38,14 @@ public static class ProcessHelper /// If true, stderr is not forwarded to the console /// Function to write to stdin /// An awaitable task - public static async Task Execute(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) + public static async Task Execute(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) { if (!command.Any()) throw new ArgumentException("Needs at least one command", nameof(command)); + var executable = command.First(); + if (string.IsNullOrWhiteSpace(executable)) + throw new ArgumentException("Executable name cannot be empty", nameof(command)); + workingDirectory ??= Environment.CurrentDirectory; if (!Directory.Exists(workingDirectory)) @@ -29,7 +53,7 @@ public static class ProcessHelper codeIsError ??= (x) => x != 0; - var p = Process.Start(new ProcessStartInfo(command.First(), command.Skip(1)) + var p = Process.Start(new ProcessStartInfo(executable, command.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => x!)) { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = workingDirectory, @@ -37,7 +61,7 @@ public static class ProcessHelper RedirectStandardOutput = false, RedirectStandardInput = writeStdIn != null, UseShellExecute = false, - }) ?? throw new Exception($"Failed to launch process {command.First()}, null returned"); + }) ?? throw new Exception($"Failed to launch process {executable}, null returned"); // Forward error messages to stderr var t = suppressStdErr @@ -49,7 +73,7 @@ public static class ProcessHelper await p.WaitForExitAsync(cancellationToken).ConfigureAwait(false); if (codeIsError(p.ExitCode)) - throw new Exception($"Execution of {command.First()} gave error code {p.ExitCode}"); + throw new Exception($"Execution of {executable} gave error code {p.ExitCode}"); await t.ConfigureAwait(false); } @@ -64,7 +88,7 @@ public static class ProcessHelper /// Default value is null which will treat anything non-zero as an error /// If true, stderr is not forwarded to the console /// An awaitable task - public static async Task ExecuteAll(IEnumerable> commands, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false) + public static async Task ExecuteAll(IEnumerable> commands, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false) { foreach (var c in commands) await Execute(c, workingDirectory, cancellationToken, codeIsError, suppressStdErr).ConfigureAwait(false); @@ -81,10 +105,13 @@ public static class ProcessHelper /// If true, stderr is not forwarded to the console /// Function to write to stdin /// The output from stdout - public static async Task ExecuteWithOutput(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) + public static async Task ExecuteWithOutput(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) { if (!command.Any()) throw new ArgumentException("Needs at least one command", nameof(command)); + var executable = command.First(); + if (string.IsNullOrWhiteSpace(executable)) + throw new ArgumentException("Executable name cannot be empty", nameof(command)); workingDirectory ??= Environment.CurrentDirectory; if (!Directory.Exists(workingDirectory)) @@ -92,7 +119,7 @@ public static class ProcessHelper codeIsError ??= (x) => x != 0; - var p = Process.Start(new ProcessStartInfo(command.First(), command.Skip(1)) + var p = Process.Start(new ProcessStartInfo(executable, command.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => x!)) { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = workingDirectory, @@ -100,7 +127,7 @@ public static class ProcessHelper RedirectStandardOutput = true, RedirectStandardInput = writeStdIn != null, UseShellExecute = false, - }) ?? throw new Exception($"Failed to launch process {command.First()}, null returned"); + }) ?? throw new Exception($"Failed to launch process {executable}, null returned"); var tstdout = p.StandardOutput.ReadToEndAsync(cancellationToken); var tstderr = suppressStdErr @@ -112,7 +139,7 @@ public static class ProcessHelper await p.WaitForExitAsync(cancellationToken).ConfigureAwait(false); if (codeIsError(p.ExitCode)) - throw new Exception($"Execution of {command.First()} gave error code {p.ExitCode}"); + throw new Exception($"Execution of {executable} gave error code {p.ExitCode}"); await tstderr.ConfigureAwait(false); return await tstdout.ConfigureAwait(false); @@ -131,10 +158,13 @@ public static class ProcessHelper /// If true, stderr is not forwarded to the console /// Function to write to stdin /// The output from stdout - public static async Task ExecuteWithOutput(IEnumerable command, Stream stdout, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) + public static async Task ExecuteWithOutput(IEnumerable command, Stream stdout, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, bool suppressStdErr = false, Func? writeStdIn = null) { if (!command.Any()) throw new ArgumentException("Needs at least one command", nameof(command)); + var executable = command.First(); + if (string.IsNullOrWhiteSpace(executable)) + throw new ArgumentException("Executable name cannot be empty", nameof(command)); workingDirectory ??= Environment.CurrentDirectory; if (!Directory.Exists(workingDirectory)) @@ -142,7 +172,7 @@ public static class ProcessHelper codeIsError ??= (x) => x != 0; - var p = Process.Start(new ProcessStartInfo(command.First(), command.Skip(1)) + var p = Process.Start(new ProcessStartInfo(executable, command.Skip(1).Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x!)) { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = workingDirectory, @@ -150,7 +180,7 @@ public static class ProcessHelper RedirectStandardOutput = true, RedirectStandardInput = writeStdIn != null, UseShellExecute = false, - }) ?? throw new Exception($"Failed to launch process {command.First()}, null returned"); + }) ?? throw new Exception($"Failed to launch process {executable}, null returned"); var tstdout = p.StandardOutput.BaseStream.CopyToAsync(stdout, cancellationToken); var tstderr = suppressStdErr @@ -162,7 +192,7 @@ public static class ProcessHelper await p.WaitForExitAsync(cancellationToken).ConfigureAwait(false); if (codeIsError(p.ExitCode)) - throw new Exception($"Execution of {command.First()} gave error code {p.ExitCode}"); + throw new Exception($"Execution of {executable} gave error code {p.ExitCode}"); await tstderr.ConfigureAwait(false); await tstdout.ConfigureAwait(false); @@ -180,10 +210,13 @@ public static class ProcessHelper /// Default value is null which will treat anything non-zero as an error /// Function to write to stdin /// The output from stdout - public static async Task ExecuteWithLog(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, string? logFolder = null, Func? logFilename = null, Func? writeStdIn = null) + public static async Task ExecuteWithLog(IEnumerable command, string? workingDirectory = null, CancellationToken cancellationToken = default, Func? codeIsError = null, string? logFolder = null, Func? logFilename = null, Func? writeStdIn = null) { if (!command.Any()) throw new ArgumentException("Needs at least one command", nameof(command)); + var executable = command.First(); + if (string.IsNullOrWhiteSpace(executable)) + throw new ArgumentException("Executable name cannot be empty", nameof(command)); workingDirectory ??= Environment.CurrentDirectory; if (!Directory.Exists(workingDirectory)) @@ -193,7 +226,7 @@ public static class ProcessHelper codeIsError ??= (x) => x != 0; - var p = Process.Start(new ProcessStartInfo(command.First(), command.Skip(1)) + var p = Process.Start(new ProcessStartInfo(executable, command.Skip(1).Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x!)) { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = workingDirectory, @@ -201,9 +234,9 @@ public static class ProcessHelper RedirectStandardOutput = true, RedirectStandardInput = writeStdIn != null, UseShellExecute = false, - }) ?? throw new Exception($"Failed to launch process {command.First()}, null returned"); + }) ?? throw new Exception($"Failed to launch process {executable}, null returned"); - logFilename ??= (pid, isStdOut) => $"{command.First()}-{p.Id}.{(isStdOut ? "stdout" : "stderr")}.log"; + logFilename ??= (pid, isStdOut) => $"{executable}-{p.Id}.{(isStdOut ? "stdout" : "stderr")}.log"; using var logstdout = File.Create(Path.Combine(logFolder, logFilename(p.Id, true))); using var logstderr = File.Create(Path.Combine(logFolder, logFilename(p.Id, false))); @@ -216,7 +249,7 @@ public static class ProcessHelper await p.WaitForExitAsync(cancellationToken).ConfigureAwait(false); if (codeIsError(p.ExitCode)) - throw new Exception($"Execution of {command.First()} gave error code {p.ExitCode}"); + throw new Exception($"Execution of {executable} gave error code {p.ExitCode}, see log file {Path.Combine(logFolder, logFilename(p.Id, true))}"); await t1.ConfigureAwait(false); await t2.ConfigureAwait(false); diff --git a/ReleaseBuilder/ProcessRunner.cs b/ReleaseBuilder/ProcessRunner.cs index 93a05179a..060013644 100644 --- a/ReleaseBuilder/ProcessRunner.cs +++ b/ReleaseBuilder/ProcessRunner.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. namespace ReleaseBuilder; public static class ProcessRunner @@ -5,17 +25,48 @@ public static class ProcessRunner /// /// The hash algorithms used for signing with Authenticode /// - private static readonly IReadOnlyList OSSLHashAlgs = new[] { "sha1", "sha256" }; + private static readonly IReadOnlyList OSSLHashAlgs = ["sha256"]; /// /// The company name to encode in the Authenticode certificate /// - private const string OSSLOrganization = "Duplicati"; + private const string OSSLOrganization = "Duplicati Inc"; /// /// The url to encode in the Authenticode certificate /// private const string OSSLUrl = "https://duplicati.com"; + /// + /// Performs code signing of the + /// + /// The path to the jsign binary + /// The password to decrypt the PFX file + /// The executable to sign, in-place + /// An awaitable task + public static async Task JsignCodeSign(string jsign, string keypin, string executable) + { + var first = true; + foreach (var hashalg in OSSLHashAlgs) + { + await ProcessHelper.Execute([ + jsign, "sign", + "--storetype", "PIV", + "--storepass", keypin, + "--alias", "AUTHENTICATION", + "--name", OSSLOrganization, + "--url", OSSLUrl, + "--alg", hashalg, + first ? "--replace" : null, + "--tsaurl", "http://ts.ssl.com", + "--tsmode", "RFC3161", + executable + ]); + + + first = false; + } + } + /// /// Performs code signing of the /// @@ -32,20 +83,29 @@ public static class ProcessRunner var tmp = Path.GetTempFileName(); File.Delete(tmp); - var args = new[] { + // It is also possible to use a PKCS#11 module for the key, like this: + // osslsigncode sign -h sha256 + // -pkcs11module /opt/homebrew/lib/libykcs11.dylib + // -certs public-key.pem + // -key 'pkcs11:pin-value={pin}' + // -ts http://ts.ssl.com + // -in {executable} + // -out {tmp} + + + await ProcessHelper.Execute([ osslsigncode, "sign", "-pkcs12", pfxfile, "-pass", pfxpassword, "-n", OSSLOrganization, "-i", OSSLUrl, "-h", hashalg, - first ? "" : "-nest", + first ? null : "-nest", "-t", $"http://timestamp.digicert.com?alg={hashalg}", "-in", executable, "-out", tmp - }; + ]); - await ProcessHelper.Execute(args.Where(x => !string.IsNullOrWhiteSpace(x))); File.Move(tmp, executable, true); first = false; @@ -59,10 +119,12 @@ public static class ProcessRunner /// The identity used for codesign /// The entitlements to activate for the file /// The file to sign + /// Whether to sign deeply /// An awaitable task - public static Task MacOSCodeSign(string codesign, string codesignIdentity, string entitlementFile, string file) + public static Task MacOSCodeSign(string codesign, string codesignIdentity, string entitlementFile, string file, bool deep) => ProcessHelper.Execute([ codesign, + (deep ? "--deep" : null), "--force", "--timestamp", "--options=runtime", @@ -71,6 +133,21 @@ public static class ProcessRunner file ]); + /// + /// Verifies the MacOS codesign of a file or app bundle + /// + /// The path to the codesign binary + /// The file to verify + /// An awaitable task + public static Task MacOSVerifyCodeSign(string codesign, string file) + => ProcessHelper.Execute([ + codesign, + "--verify", + "--deep", + "--strict", + file + ]); + /// /// Runs MacOS codesign on a single file /// diff --git a/ReleaseBuilder/Program.cs b/ReleaseBuilder/Program.cs index 7925bfbc3..290476c2f 100644 --- a/ReleaseBuilder/Program.cs +++ b/ReleaseBuilder/Program.cs @@ -1,4 +1,24 @@ - +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + using System.CommandLine; namespace ReleaseBuilder; @@ -18,8 +38,8 @@ class Program "win-x86-gui.msi", "win-arm64-gui.zip", "win-arm64-gui.msi", - // "win-x64-agent.msi", // Need to add support for building without trayicon + window service support - // "win-arm64-agent.msi", // Need to add support for building without trayicon + window service support + "win-x64-agent.msi", // Missing window service support + "win-arm64-agent.msi", // Missing window service support "win-x64-agent.zip", "win-arm64-agent.zip", @@ -41,6 +61,8 @@ class Program "linux-arm7-cli.zip", "linux-arm7-cli.deb", "linux-arm7-cli.docker", + "linux-arm7-agent.zip", + "linux-arm7-agent.deb", "linux-arm64-gui.zip", "linux-arm64-gui.deb", @@ -57,20 +79,17 @@ class Program "osx-x64-gui.dmg", "osx-x64-gui.pkg", - // "osx-x64-agent.pkg", // Need to support building DMG without trayicon + "osx-x64-agent.pkg", + "osx-x64-cli.pkg", "osx-arm64-gui.dmg", "osx-arm64-gui.pkg", - // "osx-arm64-agent.pkg", // Need to support building DMG without trayicon + "osx-arm64-agent.pkg", + "osx-arm64-cli.pkg", } .Select(x => PackageTarget.ParsePackageId(x)) .Distinct() .ToList(); - /// - /// The environment shared configuration - /// - public static readonly Configuration Configuration = Configuration.Create(); - /// /// The return code of the application; shared state /// diff --git a/ReleaseBuilder/ReleaseBuilder.csproj b/ReleaseBuilder/ReleaseBuilder.csproj index dda3fa0e2..ae14fc7c7 100644 --- a/ReleaseBuilder/ReleaseBuilder.csproj +++ b/ReleaseBuilder/ReleaseBuilder.csproj @@ -5,7 +5,7 @@ net8.0 enable enable - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/ReleaseBuilder/Resources/MacOS/Agent/Distribution.xml b/ReleaseBuilder/Resources/MacOS/Agent/Distribution.xml new file mode 100644 index 000000000..d59f639c9 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/Distribution.xml @@ -0,0 +1,27 @@ + + + Duplicati Agent + com.duplicati + + + + + + + + + + + + + + + + + + + + + DuplicatiAgent.pkg + DuplicatiAgentDaemon.pkg + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/Entitlements.plist b/ReleaseBuilder/Resources/MacOS/Agent/Entitlements.plist similarity index 100% rename from ReleaseBuilder/Resources/MacOS/Entitlements.plist rename to ReleaseBuilder/Resources/MacOS/Agent/Entitlements.plist diff --git a/ReleaseBuilder/Resources/MacOS/LICENSE.html b/ReleaseBuilder/Resources/MacOS/Agent/LICENSE.html similarity index 100% rename from ReleaseBuilder/Resources/MacOS/LICENSE.html rename to ReleaseBuilder/Resources/MacOS/Agent/LICENSE.html diff --git a/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/postinstall new file mode 100755 index 000000000..92b94c40c --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/postinstall @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/preinstall new file mode 100755 index 000000000..ae777c150 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/app-scripts/preinstall @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e +if pgrep -x "duplicati-agent"; then + RES=`osascript \ + -e "set question to display dialog \"Duplicati Agent is currently running, you should quit before installing a new version\" with title \"Duplicati Agent is already running\" buttons {\"Stop installation\", \"Continue\"} default button 1" \ + -e "if button returned of question is equal to \"Stop installation\" then return \"STOP\""` + if [ "$RES" == "STOP" ]; + then + exit 1 + fi +fi \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/postinstall new file mode 100755 index 000000000..fe2c133f7 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/postinstall @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +# Load the LaunchAgent for the user +launchctl bootstrap gui/$(id -u "$USER") "/Library/LaunchAgents/com.duplicati.agent.launchagent.plist" +launchctl enable gui/$(id -u "$USER")/com.duplicati.agent.launchagent +exit 0 \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/preinstall new file mode 100755 index 000000000..4262d3740 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/daemon-scripts/preinstall @@ -0,0 +1,9 @@ +#!/bin/bash + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +if [ -f "/Library/LaunchAgents/com.duplicati.agent.launchagent.plist" ]; then + /bin/launchctl bootout gui/$(id -u "$USER")/com.duplicati.agent.launchagent + /bin/launchctl unload "/Library/LaunchAgents/com.duplicati.agent.launchagent.plist" +fi \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/Agent/daemon/com.duplicati.agent.launchagent.plist b/ReleaseBuilder/Resources/MacOS/Agent/daemon/com.duplicati.agent.launchagent.plist new file mode 100644 index 000000000..83804628b --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/daemon/com.duplicati.agent.launchagent.plist @@ -0,0 +1,15 @@ + + + + + Label + com.duplicati.agent.launchagent + ProgramArguments + + /usr/local/duplicati-agent/duplicati-agent + run + + RunAtLoad + + + diff --git a/ReleaseBuilder/Resources/MacOS/Agent/uninstall.sh b/ReleaseBuilder/Resources/MacOS/Agent/uninstall.sh new file mode 100755 index 000000000..1e66a12fa --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/Agent/uninstall.sh @@ -0,0 +1,44 @@ +#!/bin/bash +LAUNCHAGENT_LABEL="com.duplicati.agent.launchagent" +LAUNCHAGENT_PLIST="/Library/LaunchAgents/$LAUNCHAGENT_LABEL.plist" + +ANY_ULOAD_FAILURES="" + +if [ -f "$LAUNCHAGENT_PLIST" ]; then + + # Loop through all logged-in users + for USER_ID in $(ps aux | awk '/loginwindow/ && !/awk/ {print $1}' | uniq | xargs id -u); do + + # Check if the LaunchAgent is loaded for the user + launchctl print gui/$USER_ID/$LAUNCHAGENT_LABEL > /dev/null 2>&1 + if [ $? -eq 0 ]; then + # Unload the LaunchAgent for the user + launchctl bootout gui/$USER_ID "$LAUNCHAGENT_PLIST" + if [ $? -eq 0 ]; then + echo "Successfully unloaded LaunchAgent for user with UID: $USER_ID." + else + echo "Failed to unload LaunchAgent for user with UID: $USER_ID." + ANY_ULOAD_FAILURES="1" + fi + fi + done + + # Legacy unload, don't care about the result + /bin/launchctl unload "$LAUNCHAGENT_PLIST" > /dev/null 2>&1 + + rm "$LAUNCHAGENT_PLIST" + + if ! [ -z "$ANY_UNLOAD_FAILURES" ]; then + echo "Failed to unload LaunchAgent for one or more users. A machine restart may be required to fully remove the LaunchAgent." + fi +fi + +if [ -d /usr/local/duplicati-agent ]; then + rm -rf /usr/local/duplicati-agent + echo "Removed /usr/local/duplicati-agent" +fi + +pkgutil --forget "com.duplicati.agent" +pkgutil --forget "com.duplicati.agent.daemon" + + diff --git a/ReleaseBuilder/Resources/MacOS/Distribution.xml b/ReleaseBuilder/Resources/MacOS/AppBundle/Distribution.xml similarity index 93% rename from ReleaseBuilder/Resources/MacOS/Distribution.xml rename to ReleaseBuilder/Resources/MacOS/AppBundle/Distribution.xml index 45747b648..23edadb20 100644 --- a/ReleaseBuilder/Resources/MacOS/Distribution.xml +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/Distribution.xml @@ -8,7 +8,7 @@ - + diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/Entitlements.plist b/ReleaseBuilder/Resources/MacOS/AppBundle/Entitlements.plist new file mode 100644 index 000000000..384b033ce --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/Entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.automation.apple-events + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/LICENSE.html b/ReleaseBuilder/Resources/MacOS/AppBundle/LICENSE.html new file mode 100644 index 000000000..cc0f89548 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/LICENSE.html @@ -0,0 +1,27 @@ + + + + +

    +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +

    + + + diff --git a/ReleaseBuilder/Resources/MacOS/app-resources/Duplicati.icns b/ReleaseBuilder/Resources/MacOS/AppBundle/app-resources/Duplicati.icns similarity index 100% rename from ReleaseBuilder/Resources/MacOS/app-resources/Duplicati.icns rename to ReleaseBuilder/Resources/MacOS/AppBundle/app-resources/Duplicati.icns diff --git a/ReleaseBuilder/Resources/MacOS/app-resources/Info.plist b/ReleaseBuilder/Resources/MacOS/AppBundle/app-resources/Info.plist similarity index 100% rename from ReleaseBuilder/Resources/MacOS/app-resources/Info.plist rename to ReleaseBuilder/Resources/MacOS/AppBundle/app-resources/Info.plist diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/app-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/AppBundle/app-scripts/postinstall new file mode 100755 index 000000000..92b94c40c --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/app-scripts/postinstall @@ -0,0 +1,3 @@ +#!/bin/sh + +exit 0 \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/app-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/AppBundle/app-scripts/preinstall similarity index 100% rename from ReleaseBuilder/Resources/MacOS/app-scripts/preinstall rename to ReleaseBuilder/Resources/MacOS/AppBundle/app-scripts/preinstall diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/postinstall new file mode 100755 index 000000000..f2dd18bf2 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/postinstall @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +# Load the LaunchAgent for the user +launchctl bootstrap gui/$(id -u "$USER") "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" +launchctl enable gui/$(id -u "$USER")/com.duplicati.app.launchagent +exit 0 diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/preinstall new file mode 100755 index 000000000..97858788d --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon-scripts/preinstall @@ -0,0 +1,12 @@ +#!/bin/bash + +set +e + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +if [ -f "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" ]; then + /bin/launchctl bootout gui/$(id -u "$USER")/com.duplicati.app.launchagent + /bin/launchctl unload "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" +fi + diff --git a/ReleaseBuilder/Resources/MacOS/daemon/com.duplicati.app.launchagent.plist b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon/com.duplicati.app.launchagent.plist similarity index 80% rename from ReleaseBuilder/Resources/MacOS/daemon/com.duplicati.app.launchagent.plist rename to ReleaseBuilder/Resources/MacOS/AppBundle/daemon/com.duplicati.app.launchagent.plist index 40569a1ee..efd03e17b 100644 --- a/ReleaseBuilder/Resources/MacOS/daemon/com.duplicati.app.launchagent.plist +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/daemon/com.duplicati.app.launchagent.plist @@ -6,8 +6,7 @@ com.duplicati.app.launchagent ProgramArguments - /usr/bin/open - /Applications/Duplicati.app + /Applications/Duplicati.app/Contents/MacOS/duplicati RunAtLoad diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/template.dmg.bz2 b/ReleaseBuilder/Resources/MacOS/AppBundle/template.dmg.bz2 new file mode 100644 index 000000000..2844c909d Binary files /dev/null and b/ReleaseBuilder/Resources/MacOS/AppBundle/template.dmg.bz2 differ diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/background.png b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/background.png new file mode 100644 index 000000000..d0ad8734a Binary files /dev/null and b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/background.png differ diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/create.sh b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/create.sh new file mode 100755 index 000000000..37109de95 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/create.sh @@ -0,0 +1,33 @@ +# Create file to prevent file RO image +rm rw.*.template.dmg +touch template.dmg + +mkdir -p ./scaffold/Duplicati.app +touch ./scaffold/Duplicati.app/unused.txt + +create-dmg \ + --volname Duplicati \ + --volicon volicon.icns \ + --background background.png \ + --text-size 16 \ + --icon-size 120 \ + --icon Duplicati.app 150 120 \ + --app-drop-link 360 120 \ + --disk-image-size 200 \ + template.dmg \ + ./scaffold + +# Remove dummy and capture RW image +rm template.dmg +rm -rf scaffold + +NAME=(rw.*.template.dmg) +if [[ "$NAME" == "rw.*.template.dmg" ]]; then + echo "No matching file found" +elif [[ "$NAME" == *' '* ]]; then + echo "Error: More than one file matched" +else + mv "$NAME" template.dmg + bzip2 -z -9 template.dmg + mv template.dmg.bz2 ../template.dmg.bz2 +fi diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/volicon.icns b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/volicon.icns new file mode 100644 index 000000000..b69d6b25e Binary files /dev/null and b/ReleaseBuilder/Resources/MacOS/AppBundle/templatebuilder/volicon.icns differ diff --git a/ReleaseBuilder/Resources/MacOS/AppBundle/uninstall.sh b/ReleaseBuilder/Resources/MacOS/AppBundle/uninstall.sh new file mode 100755 index 000000000..e8a5ea1fb --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/AppBundle/uninstall.sh @@ -0,0 +1,44 @@ +#!/bin/bash +LAUNCHAGENT_LABEL="com.duplicati.app.launchagent" +LAUNCHAGENT_PLIST="/Library/LaunchAgents/$LAUNCHAGENT_LABEL.plist" + +ANY_ULOAD_FAILURES="" + +if [ -f "$LAUNCHAGENT_PLIST" ]; then + + # Loop through all logged-in users + for USER_ID in $(ps aux | awk '/loginwindow/ && !/awk/ {print $1}' | uniq | xargs id -u); do + + # Check if the LaunchAgent is loaded for the user + launchctl print gui/$USER_ID/$LAUNCHAGENT_LABEL > /dev/null 2>&1 + if [ $? -eq 0 ]; then + # Unload the LaunchAgent for the user + launchctl bootout gui/$USER_ID "$LAUNCHAGENT_PLIST" + if [ $? -eq 0 ]; then + echo "Successfully unloaded LaunchAgent for user with UID: $USER_ID." + else + echo "Failed to unload LaunchAgent for user with UID: $USER_ID." + ANY_ULOAD_FAILURES="1" + fi + fi + done + + # Legacy unload, don't care about the result + /bin/launchctl unload "$LAUNCHAGENT_PLIST" > /dev/null 2>&1 + + rm "$LAUNCHAGENT_PLIST" + + if ! [ -z "$ANY_UNLOAD_FAILURES" ]; then + echo "Failed to unload LaunchAgent for one or more users. A machine restart may be required to fully remove the LaunchAgent." + fi +fi + +if [ -d /Applications/Duplicati.app ]; then + rm -rf /Applications/Duplicati.app + echo "Removed /Applications/Duplicati.app" +fi + +pkgutil --forget "com.duplicati.app" +pkgutil --forget "com.duplicati.app.daemon" + + diff --git a/ReleaseBuilder/Resources/MacOS/CLI/Distribution.xml b/ReleaseBuilder/Resources/MacOS/CLI/Distribution.xml new file mode 100644 index 000000000..ef96c70f5 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/Distribution.xml @@ -0,0 +1,27 @@ + + + Duplicati Server + com.duplicati + + + + + + + + + + + + + + + + + + + + + DuplicatiCLI.pkg + DuplicatiServerDaemon.pkg + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/CLI/Entitlements.plist b/ReleaseBuilder/Resources/MacOS/CLI/Entitlements.plist new file mode 100644 index 000000000..384b033ce --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/Entitlements.plist @@ -0,0 +1,10 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.automation.apple-events + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/CLI/LICENSE.html b/ReleaseBuilder/Resources/MacOS/CLI/LICENSE.html new file mode 100644 index 000000000..cc0f89548 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/LICENSE.html @@ -0,0 +1,27 @@ + + + + +

    +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +

    + + + diff --git a/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/postinstall new file mode 100755 index 000000000..8479895a4 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/postinstall @@ -0,0 +1,3 @@ +#!/bin/sh + +mkdir -p /usr/local/bin diff --git a/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/preinstall new file mode 100755 index 000000000..366b0c013 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/app-scripts/preinstall @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e +if pgrep -x "duplicati-server"; then + RES=`osascript \ + -e "set question to display dialog \"Duplicati server is currently running, you should quit before installing a new version\" with title \"Duplicati Server is already running\" buttons {\"Stop installation\", \"Continue\"} default button 1" \ + -e "if button returned of question is equal to \"Stop installation\" then return \"STOP\""` + if [ "$RES" == "STOP" ]; + then + exit 1 + fi +fi \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/postinstall new file mode 100755 index 000000000..16e8767ae --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/postinstall @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +# Package is CLI, so we do not install the server launch agent automatically +# launchctl bootstrap gui/$(id -u "$USER") "/Library/LaunchAgents/com.duplicati.server.launchagent.plist" +# launchctl enable gui/$(id -u "$USER")/com.duplicati.server.launchagent +exit 0 diff --git a/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/preinstall new file mode 100755 index 000000000..9c00fa4f7 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/daemon-scripts/preinstall @@ -0,0 +1,9 @@ +#!/bin/bash + +# Identify the user installing the package +USER=$(stat -f%Su /dev/console) + +if [ -f "/Library/LaunchAgents/com.duplicati.server.launchagent.plist" ]; then + /bin/launchctl bootout gui/$(id -u "$USER")/com.duplicati.server.launchagent + /bin/launchctl unload "/Library/LaunchAgents/com.duplicati.server.launchagent.plist" +fi \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/CLI/daemon/com.duplicati.server.launchagent.plist b/ReleaseBuilder/Resources/MacOS/CLI/daemon/com.duplicati.server.launchagent.plist new file mode 100644 index 000000000..4aa2fe059 --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/daemon/com.duplicati.server.launchagent.plist @@ -0,0 +1,14 @@ + + + + + Label + com.duplicati.server.launchagent + ProgramArguments + + /usr/local/duplicati/duplicati-server + + + + diff --git a/ReleaseBuilder/Resources/MacOS/CLI/uninstall.sh b/ReleaseBuilder/Resources/MacOS/CLI/uninstall.sh new file mode 100755 index 000000000..2a492af8a --- /dev/null +++ b/ReleaseBuilder/Resources/MacOS/CLI/uninstall.sh @@ -0,0 +1,44 @@ +#!/bin/bash +LAUNCHAGENT_LABEL="com.duplicati.server.launchagent" +LAUNCHAGENT_PLIST="/Library/LaunchAgents/$LAUNCHAGENT_LABEL.plist" + +ANY_ULOAD_FAILURES="" + +if [ -f "$LAUNCHAGENT_PLIST" ]; then + + # Loop through all logged-in users + for USER_ID in $(ps aux | awk '/loginwindow/ && !/awk/ {print $1}' | uniq | xargs id -u); do + + # Check if the LaunchAgent is loaded for the user + launchctl print gui/$USER_ID/$LAUNCHAGENT_LABEL > /dev/null 2>&1 + if [ $? -eq 0 ]; then + # Unload the LaunchAgent for the user + launchctl bootout gui/$USER_ID "$LAUNCHAGENT_PLIST" + if [ $? -eq 0 ]; then + echo "Successfully unloaded LaunchAgent for user with UID: $USER_ID." + else + echo "Failed to unload LaunchAgent for user with UID: $USER_ID." + ANY_ULOAD_FAILURES="1" + fi + fi + done + + # Legacy unload, don't care about the result + /bin/launchctl unload "$LAUNCHAGENT_PLIST" > /dev/null 2>&1 + + rm "$LAUNCHAGENT_PLIST" + + if ! [ -z "$ANY_UNLOAD_FAILURES" ]; then + echo "Failed to unload LaunchAgent for one or more users. A machine restart may be required to fully remove the LaunchAgent." + fi +fi + +if [ -d /usr/local/duplicati ]; then + rm -rf /usr/local/duplicati + echo "Removed /usr/local/duplicati" +fi + +pkgutil --forget "com.duplicati.cli" +pkgutil --forget "com.duplicati.server.daemon" + + diff --git a/ReleaseBuilder/Resources/MacOS/app-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/app-scripts/postinstall deleted file mode 100755 index 13d191177..000000000 --- a/ReleaseBuilder/Resources/MacOS/app-scripts/postinstall +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -/usr/bin/open /Applications/Duplicati.app \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/daemon-scripts/postinstall b/ReleaseBuilder/Resources/MacOS/daemon-scripts/postinstall deleted file mode 100755 index 4409e99b3..000000000 --- a/ReleaseBuilder/Resources/MacOS/daemon-scripts/postinstall +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -set -e -/bin/launchctl load "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/daemon-scripts/preinstall b/ReleaseBuilder/Resources/MacOS/daemon-scripts/preinstall deleted file mode 100755 index 8b1dffc28..000000000 --- a/ReleaseBuilder/Resources/MacOS/daemon-scripts/preinstall +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -set -e -if /bin/launchctl list "com.duplicati.app.launchagent" &> /dev/null; then - /bin/launchctl unload "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" -fi \ No newline at end of file diff --git a/ReleaseBuilder/Resources/MacOS/template.dmg.bz2 b/ReleaseBuilder/Resources/MacOS/template.dmg.bz2 deleted file mode 100644 index 2d66f9ade..000000000 Binary files a/ReleaseBuilder/Resources/MacOS/template.dmg.bz2 and /dev/null differ diff --git a/ReleaseBuilder/Resources/MacOS/uninstall.sh b/ReleaseBuilder/Resources/MacOS/uninstall.sh deleted file mode 100755 index f4fe7254a..000000000 --- a/ReleaseBuilder/Resources/MacOS/uninstall.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -if [ -d /Applications/Duplicati.app ]; then - rm -rf /Applications/Duplicati.app -fi - -if /bin/launchctl list "com.duplicati.app.launchagent" &> /dev/null; then - /bin/launchctl unload "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" -fi - -if [ -f "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" ]; then - rm "/Library/LaunchAgents/com.duplicati.app.launchagent.plist" -fi - -pkgutil --forget "com.duplicati.app" -pkgutil --forget "com.duplicati.app.daemon" - - diff --git a/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs b/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs new file mode 100644 index 000000000..ffca583d8 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/Agent/Duplicati.wxs @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FORSERVICE = "true" + + + + + + + + + + + + + + + + + + + + + + SERVICE_WAS_STOPPED=1 + + + + + + + + + + + + + + + + + + diff --git a/ReleaseBuilder/Resources/Windows/License.rtf b/ReleaseBuilder/Resources/Windows/Agent/License.rtf similarity index 96% rename from ReleaseBuilder/Resources/Windows/License.rtf rename to ReleaseBuilder/Resources/Windows/Agent/License.rtf index 9b2b7cff7..7198f91ff 100644 --- a/ReleaseBuilder/Resources/Windows/License.rtf +++ b/ReleaseBuilder/Resources/Windows/Agent/License.rtf @@ -6,7 +6,7 @@ \deftab720 \pard\pardeftab720\ri0\sl380\sa240\partightenfactor0 -\f0\fs25 \cf0 Copyright (c) 2024 Team Duplicati, https://duplicati.com, hello@duplicati.com +\f0\fs25 \cf0 Copyright (c) 2025 Team Duplicati, https://duplicati.com, hello@duplicati.com \f1\fs24 \ \pard\pardeftab720\ri0\sl380\sb240\sa240\partightenfactor0 diff --git a/ReleaseBuilder/Resources/Windows/Resources/InstallerLarge.bmp b/ReleaseBuilder/Resources/Windows/Agent/Resources/InstallerLarge.bmp similarity index 100% rename from ReleaseBuilder/Resources/Windows/Resources/InstallerLarge.bmp rename to ReleaseBuilder/Resources/Windows/Agent/Resources/InstallerLarge.bmp diff --git a/ReleaseBuilder/Resources/Windows/Resources/InstallerSmall.bmp b/ReleaseBuilder/Resources/Windows/Agent/Resources/InstallerSmall.bmp similarity index 100% rename from ReleaseBuilder/Resources/Windows/Resources/InstallerSmall.bmp rename to ReleaseBuilder/Resources/Windows/Agent/Resources/InstallerSmall.bmp diff --git a/ReleaseBuilder/Resources/Windows/Agent/Shortcuts.wxs b/ReleaseBuilder/Resources/Windows/Agent/Shortcuts.wxs new file mode 100644 index 000000000..8204cb98a --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/Agent/Shortcuts.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/Agent/StartService.js b/ReleaseBuilder/Resources/Windows/Agent/StartService.js new file mode 100644 index 000000000..05136d497 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/Agent/StartService.js @@ -0,0 +1,47 @@ +function LogMessage(message) { + var record = Session.Installer.CreateRecord(1); + record.StringData(1) = message; + var INSTALLMESSAGE_INFO = 0x04000000; + // MessageType 1 = Shown as warning in the UI + Session.Message(INSTALLMESSAGE_INFO, record); // MessageType 1 = informational log +} + +// Function to start a service +function StartService(serviceName) { + var service; + + try { + // Connect to the WMI service + var locator = new ActiveXObject("WbemScripting.SWbemLocator"); + var root = locator.ConnectServer(".", "root\\CIMV2"); + + // Query the service + var query = "SELECT * FROM Win32_Service WHERE Name = '" + serviceName + "'"; + var services = root.ExecQuery(query); + + // Check if the service exists and start it if not already running + var enumerator = new Enumerator(services); + if (!enumerator.atEnd()) { + service = enumerator.item(); + if (service.State != "Running") { + service.StartService(); + LogMessage("Service '" + serviceName + "' started successfully."); + return 0; // Success + } else { + LogMessage("Service '" + serviceName + "' is already running."); + return 0; // Success + } + } else { + LogMessage("Service '" + serviceName + "' not found."); + return 1; // Failure + } + } catch (e) { + LogMessage("Error starting service '" + serviceName + "': " + e.message); + return 1; // Failure + } +} + +// Entry point for the custom action +function CustomAction() { + return StartService("Duplicati.Agent"); +} \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/Agent/StopService.js b/ReleaseBuilder/Resources/Windows/Agent/StopService.js new file mode 100644 index 000000000..33a64a1b1 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/Agent/StopService.js @@ -0,0 +1,78 @@ +function LogMessage(message) { + var record = Session.Installer.CreateRecord(1); + record.StringData(1) = message; + var INSTALLMESSAGE_INFO = 0x04000000; + // MessageType 1 = Shown as warning in the UI + Session.Message(INSTALLMESSAGE_INFO, record); +} + +// Function to check the status of a Windows service +function CheckServiceStatus(serviceName) { + var locator; + var root; + var query; + var services; + var enumerator; + var service; + + try { + // Connect to WMI (Windows Management Instrumentation) + locator = new ActiveXObject("WbemScripting.SWbemLocator"); + root = locator.ConnectServer(".", "root\\CIMV2"); + + // Query for the service by name + query = "SELECT * FROM Win32_Service WHERE Name = '" + serviceName + "'"; + services = root.ExecQuery(query); + + // Enumerate the results + enumerator = new Enumerator(services); + if (!enumerator.atEnd()) { + service = enumerator.item(); + + // Check if the service is running + if (service.State == "Running") { + LogMessage("Service '" + serviceName + "' is running. Attempting to stop it."); + + // Attempt to stop the service + var result = service.StopService(); + if (result === 0) { + LogMessage("Service '" + serviceName + "' stopped successfully."); + } else { + LogMessage("Failed to stop service '" + serviceName + "'. Error code: " + result); + } + + // Service was running and stopped + Session.Property("SERVICE_WAS_STOPPED") = "1"; + return true; + } else { + LogMessage("Service '" + serviceName + "' is not running."); + return false; // Service is not running + } + } else { + LogMessage("Service '" + serviceName + "' not found."); + return null; // Service not found + } + } catch (e) { + LogMessage("Error checking service status: " + e.message); + return null; // Error occurred + } +} + +// Entry point for the custom action +function CustomAction() { + // Check the service status + var isRunning = CheckServiceStatus("Duplicati.Agent"); + + // Set a property in the installer to track the service status + if (isRunning === true) { + Session.Property("SERVICE_WAS_STOPPED") = "1"; // Service was running + } else if (isRunning === false) { + Session.Property("SERVICE_WAS_STOPPED") = "0"; // Service was not running + } else { + // Service not found or error occurred + LogMessage("Failed to determine service status."); + Session.Property("SERVICE_WAS_STOPPED") = "0"; // Service was not running + } + + return 0; // Success +} \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/UpgradeData.wxi b/ReleaseBuilder/Resources/Windows/Agent/UpgradeData.wxi similarity index 72% rename from ReleaseBuilder/Resources/Windows/UpgradeData.wxi rename to ReleaseBuilder/Resources/Windows/Agent/UpgradeData.wxi index cdcc78720..f1813da74 100644 --- a/ReleaseBuilder/Resources/Windows/UpgradeData.wxi +++ b/ReleaseBuilder/Resources/Windows/Agent/UpgradeData.wxi @@ -1,9 +1,9 @@ - + - + diff --git a/ReleaseBuilder/Resources/Windows/Duplicati.wxs b/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs similarity index 80% rename from ReleaseBuilder/Resources/Windows/Duplicati.wxs rename to ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs index 321113e1f..928fe26dd 100644 --- a/ReleaseBuilder/Resources/Windows/Duplicati.wxs +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/Duplicati.wxs @@ -70,7 +70,11 @@ - + + + + + SERVICE_WAS_STOPPED=1 @@ -79,6 +83,22 @@ + + + + + + + + diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/License.rtf b/ReleaseBuilder/Resources/Windows/TrayIcon/License.rtf new file mode 100644 index 000000000..7198f91ff --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/License.rtf @@ -0,0 +1,21 @@ +{\rtf1\ansi\ansicpg1252\cocoartf2761 +\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\froman\fcharset0 TimesNewRomanPSMT;} +{\colortbl;\red255\green255\blue255;} +{\*\expandedcolortbl;;} +\paperw12240\paperh15840\margl1440\margr1440\margb1417\margt1417\vieww13140\viewh16360\viewkind1 +\deftab720 +\pard\pardeftab720\ri0\sl380\sa240\partightenfactor0 + +\f0\fs25 \cf0 Copyright (c) 2025 Team Duplicati, https://duplicati.com, hello@duplicati.com +\f1\fs24 \ +\pard\pardeftab720\ri0\sl380\sb240\sa240\partightenfactor0 + +\f0\fs25 \cf0 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +\f1\fs24 \ + +\f0\fs25 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +\f1\fs24 \ + +\f0\fs25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +\f1\fs24 \ +} \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerLarge.bmp b/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerLarge.bmp new file mode 100644 index 000000000..e4833d4e0 Binary files /dev/null and b/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerLarge.bmp differ diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerSmall.bmp b/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerSmall.bmp new file mode 100644 index 000000000..7ab19a757 Binary files /dev/null and b/ReleaseBuilder/Resources/Windows/TrayIcon/Resources/InstallerSmall.bmp differ diff --git a/ReleaseBuilder/Resources/Windows/Shortcuts.wxs b/ReleaseBuilder/Resources/Windows/TrayIcon/Shortcuts.wxs similarity index 100% rename from ReleaseBuilder/Resources/Windows/Shortcuts.wxs rename to ReleaseBuilder/Resources/Windows/TrayIcon/Shortcuts.wxs diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/StartService.js b/ReleaseBuilder/Resources/Windows/TrayIcon/StartService.js new file mode 100644 index 000000000..65e51a02c --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/StartService.js @@ -0,0 +1,47 @@ +function LogMessage(message) { + var record = Session.Installer.CreateRecord(1); + record.StringData(1) = message; + var INSTALLMESSAGE_INFO = 0x04000000; + // MessageType 1 = Shown as warning in the UI + Session.Message(INSTALLMESSAGE_INFO, record); // MessageType 1 = informational log +} + +// Function to start a service +function StartService(serviceName) { + var service; + + try { + // Connect to the WMI service + var locator = new ActiveXObject("WbemScripting.SWbemLocator"); + var root = locator.ConnectServer(".", "root\\CIMV2"); + + // Query the service + var query = "SELECT * FROM Win32_Service WHERE Name = '" + serviceName + "'"; + var services = root.ExecQuery(query); + + // Check if the service exists and start it if not already running + var enumerator = new Enumerator(services); + if (!enumerator.atEnd()) { + service = enumerator.item(); + if (service.State != "Running") { + service.StartService(); + LogMessage("Service '" + serviceName + "' started successfully."); + return 0; // Success + } else { + LogMessage("Service '" + serviceName + "' is already running."); + return 0; // Success + } + } else { + LogMessage("Service '" + serviceName + "' not found."); + return 1; // Failure + } + } catch (e) { + LogMessage("Error starting service '" + serviceName + "': " + e.message); + return 1; // Failure + } +} + +// Entry point for the custom action +function CustomAction() { + return StartService("Duplicati"); +} \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/StopService.js b/ReleaseBuilder/Resources/Windows/TrayIcon/StopService.js new file mode 100644 index 000000000..aacebcda2 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/StopService.js @@ -0,0 +1,78 @@ +function LogMessage(message) { + var record = Session.Installer.CreateRecord(1); + record.StringData(1) = message; + var INSTALLMESSAGE_INFO = 0x04000000; + // MessageType 1 = Shown as warning in the UI + Session.Message(INSTALLMESSAGE_INFO, record); +} + +// Function to check the status of a Windows service +function CheckServiceStatus(serviceName) { + var locator; + var root; + var query; + var services; + var enumerator; + var service; + + try { + // Connect to WMI (Windows Management Instrumentation) + locator = new ActiveXObject("WbemScripting.SWbemLocator"); + root = locator.ConnectServer(".", "root\\CIMV2"); + + // Query for the service by name + query = "SELECT * FROM Win32_Service WHERE Name = '" + serviceName + "'"; + services = root.ExecQuery(query); + + // Enumerate the results + enumerator = new Enumerator(services); + if (!enumerator.atEnd()) { + service = enumerator.item(); + + // Check if the service is running + if (service.State == "Running") { + LogMessage("Service '" + serviceName + "' is running. Attempting to stop it."); + + // Attempt to stop the service + var result = service.StopService(); + if (result === 0) { + LogMessage("Service '" + serviceName + "' stopped successfully."); + } else { + LogMessage("Failed to stop service '" + serviceName + "'. Error code: " + result); + } + + // Service was running and stopped + Session.Property("SERVICE_WAS_STOPPED") = "1"; + return true; + } else { + LogMessage("Service '" + serviceName + "' is not running."); + return false; // Service is not running + } + } else { + LogMessage("Service '" + serviceName + "' not found."); + return null; // Service not found + } + } catch (e) { + LogMessage("Error checking service status: " + e.message); + return null; // Error occurred + } +} + +// Entry point for the custom action +function CustomAction() { + // Check the service status + var isRunning = CheckServiceStatus("Duplicati"); + + // Set a property in the installer to track the service status + if (isRunning === true) { + Session.Property("SERVICE_WAS_STOPPED") = "1"; // Service was running + } else if (isRunning === false) { + Session.Property("SERVICE_WAS_STOPPED") = "0"; // Service was not running + } else { + // Service not found or error occurred + LogMessage("Failed to determine service status."); + Session.Property("SERVICE_WAS_STOPPED") = "0"; // Service was not running + } + + return 0; // Success +} \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/TrayIcon/UpgradeData.wxi b/ReleaseBuilder/Resources/Windows/TrayIcon/UpgradeData.wxi new file mode 100644 index 000000000..a8449f755 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/TrayIcon/UpgradeData.wxi @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InstallDirDlg.wxs b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InstallDirDlg.wxs new file mode 100644 index 000000000..2bc1bc9a9 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InstallDirDlg.wxs @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InvalidDirDlg.wxs b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InvalidDirDlg.wxs new file mode 100644 index 000000000..81c87beb9 --- /dev/null +++ b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/InvalidDirDlg.wxs @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/README.md b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/README.md index f1ffed102..723a09848 100644 --- a/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/README.md +++ b/ReleaseBuilder/Resources/Windows/WixUIExtension/ui/README.md @@ -1,7 +1,7 @@ # WIX UI custom files The base contents of this folder are copied from the [Wixl UI extensions](https://gitlab.gnome.org/GNOME/msitools/-/tree/master/data/ext/ui). -The [`Common.wxs`](./ui/Common.wxs) file has been modified to use the custom banners. +The [`Common.wxs`](./Common.wxs) file has been modified to use the custom banners. This was needed as it is not possible to pass outside strings/resources into the project as it is. Some extra files are included from [UI.wixext](https://github.com/wixtoolset/UI.wixext/tree/master/src/wixlib). @@ -11,10 +11,13 @@ Also, the dialogs were modified to place the buttons as the first items in the d The extra files are: -- [`BrowseDlg.wxs`](./ui/BrowseDlg.wxs) -- [`CustomizeDlg.wxs`](./ui/CustomizeDlg.wxs) -- [`DiskCostDlg.wxs`](./ui/DiskCostDlg.wxs) -- [`LicenseAgreementDlg.wxs`](./ui/LicenseAgreementDlg.wxs) -- [`WixUI_FeatureTree.wxs`](./ui/WixUI_FeatureTree.wxs) +- [`BrowseDlg.wxs`](./BrowseDlg.wxs) +- [`CustomizeDlg.wxs`](./CustomizeDlg.wxs) +- [`DiskCostDlg.wxs`](./DiskCostDlg.wxs) +- [`LicenseAgreementDlg.wxs`](./LicenseAgreementDlg.wxs) +- [`WixUI_FeatureTree.wxs`](./WixUI_FeatureTree.wxs) +- [`WixUI_InstallDir.wxs`](./WixUI_InstallDir.wxs) +- [`InstallDirDlg.wxs`](./InstallDirDlg.wxs) +- [`InvalidDirDlg.wxs`](./InvalidDirDlg.wxs) The files are all licensed under the [MS-RL](https://opensource.org/licenses/ms-rl) license. diff --git a/ReleaseBuilder/Resources/debian/control.template.txt b/ReleaseBuilder/Resources/debian/control.template.txt index 8d4f21ec5..5f98b38e3 100644 --- a/ReleaseBuilder/Resources/debian/control.template.txt +++ b/ReleaseBuilder/Resources/debian/control.template.txt @@ -3,7 +3,7 @@ Section: utils Priority: extra Maintainer: Kenneth Skovhede Homepage: https://duplicati.com -Package: duplicati +Package: duplicati%PACKAGE_TYPE% Architecture: %ARCH% Depends: %DEPENDS% Version: %VERSION% diff --git a/ReleaseBuilder/Resources/debian/postinst-agent b/ReleaseBuilder/Resources/debian/postinst-agent new file mode 100644 index 000000000..080afc588 --- /dev/null +++ b/ReleaseBuilder/Resources/debian/postinst-agent @@ -0,0 +1,3 @@ +#!/bin/sh +systemctl enable duplicati-agent +systemctl start duplicati-agent \ No newline at end of file diff --git a/ReleaseBuilder/Resources/debian/prerm-agent b/ReleaseBuilder/Resources/debian/prerm-agent new file mode 100644 index 000000000..8cb452bc7 --- /dev/null +++ b/ReleaseBuilder/Resources/debian/prerm-agent @@ -0,0 +1,3 @@ +#!/bin/sh +systemctl stop duplicati-agent +exit 0 \ No newline at end of file diff --git a/ReleaseBuilder/Resources/debian/systemd/duplicati-agent.service b/ReleaseBuilder/Resources/debian/systemd/duplicati-agent.service index ba53854ec..314e97228 100644 --- a/ReleaseBuilder/Resources/debian/systemd/duplicati-agent.service +++ b/ReleaseBuilder/Resources/debian/systemd/duplicati-agent.service @@ -7,7 +7,7 @@ Nice=19 IOSchedulingClass=idle IOSchedulingPriority=7 EnvironmentFile=-/etc/default/duplicati-agent -ExecStart=/usr/bin/duplicati-agent $DAEMON_OPTS +ExecStart=/usr/bin/duplicati-agent run $DAEMON_OPTS Restart=always [Install] diff --git a/ReleaseBuilder/Resources/fedora/Dockerfile.build b/ReleaseBuilder/Resources/fedora/Dockerfile.build index 661caf081..c334d56ef 100644 --- a/ReleaseBuilder/Resources/fedora/Dockerfile.build +++ b/ReleaseBuilder/Resources/fedora/Dockerfile.build @@ -7,7 +7,7 @@ ENV DOCKER_BUILD_PROXY=$DOCKER_BUILD_PROXY # Install common build tools RUN dnf -y install deltarpm RUN dnf -y upgrade -RUN dnf -y --allowerasing install @"Minimal Install" @buildsys-build yum-utils rpm-sign gnupg rpmdevtools +RUN dnf -y install --allowerasing @buildsys-build yum-utils rpm-sign gnupg rpmdevtools RUN dnf -y update diff --git a/ReleaseBuilder/Resources/fedora/duplicati.spec.template.txt b/ReleaseBuilder/Resources/fedora/duplicati.spec.template.txt index f75c364c9..b1a247a12 100644 --- a/ReleaseBuilder/Resources/fedora/duplicati.spec.template.txt +++ b/ReleaseBuilder/Resources/fedora/duplicati.spec.template.txt @@ -31,6 +31,7 @@ Source3: %{namer}%SERVICENAME%.service Source4: %{namer}%SERVICENAME%.default #GUI_ONLY#Source5: %{namer}.png #GUI_ONLY#Source6: %{namer}.desktop +#AGENT_ONLY#Source5: %{namer}%SERVICENAME%.preset #GUI_ONLY#BuildRequires: desktop-file-utils BuildRequires: systemd @@ -95,6 +96,7 @@ install -p -D -m 644 %{_topdir}/SOURCES/%{namer}%SERVICENAME%.default %{buildroo #GUI_ONLY#%{_bindir}/gtk-update-icon-cache \ #GUI_ONLY# --quiet %{_datadir}/icons/hicolor 2> /dev/null|| : %systemd_post %{namer}%SERVICENAME%.service +#AGENT_ONLY#%{__install} -Dm644 %{namer}%SERVICENAME%.preset %{buildroot}%{_presetdir}/50-%{namer}%SERVICENAME%.preset %preun %systemd_preun %{namer}%SERVICENAME%.service diff --git a/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.preset b/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.preset new file mode 100644 index 000000000..c0fd0bd91 --- /dev/null +++ b/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.preset @@ -0,0 +1 @@ +enable duplicati-agent.service \ No newline at end of file diff --git a/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.service b/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.service index 41184e9c2..5767bc0fc 100644 --- a/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.service +++ b/ReleaseBuilder/Resources/fedora/systemd/duplicati-agent.service @@ -7,7 +7,7 @@ Nice=19 IOSchedulingClass=idle IOSchedulingPriority=7 EnvironmentFile=-/etc/sysconfig/duplicati-agent -ExecStart=/usr/bin/duplicati-agent $DAEMON_OPTS +ExecStart=/usr/bin/duplicati-agent run $DAEMON_OPTS Restart=on-failure [Install] diff --git a/ReleaseBuilder/SharedOptions.cs b/ReleaseBuilder/SharedOptions.cs index 66f64004b..8d8843a02 100644 --- a/ReleaseBuilder/SharedOptions.cs +++ b/ReleaseBuilder/SharedOptions.cs @@ -1,3 +1,23 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. using System.CommandLine; namespace ReleaseBuilder; diff --git a/ReleaseBuilder/WixHeatBuilder.cs b/ReleaseBuilder/WixHeatBuilder.cs index b5404ffc3..79fdcb1cf 100644 --- a/ReleaseBuilder/WixHeatBuilder.cs +++ b/ReleaseBuilder/WixHeatBuilder.cs @@ -1,3 +1,26 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; using System.Xml; namespace ReleaseBuilder; @@ -7,6 +30,34 @@ namespace ReleaseBuilder; ///
    public static class WixHeatBuilder { + + /// + /// Converts a string to a valid Wix identifier. + /// + /// The input string. + /// The WiX identifier. + public static string ConvertToIdentifier(string input) + { + if (string.IsNullOrEmpty(input)) + throw new ArgumentException("Input string cannot be null or empty."); + + // Remove invalid characters (only allow A-Z, a-z, 0-9, _, .) + var cleanedInput = Regex.Replace(input, @"[^A-Za-z0-9_.]", "_"); + + if (cleanedInput.Length > 72) + { + var hash = BitConverter.ToString(SHA256.HashData(Encoding.UTF8.GetBytes(cleanedInput))) + .Replace("-", string.Empty)[..12]; + cleanedInput = cleanedInput[..(72 - hash.Length)] + hash; + } + + // Ensure the identifier starts with a letter or underscore + if (!char.IsLetter(cleanedInput[0]) && cleanedInput[0] != '_') + cleanedInput = "_" + cleanedInput; + + return cleanedInput; + } + /// /// Creates a Wix filelist from a directory /// @@ -15,14 +66,14 @@ public static class WixHeatBuilder /// The name of the component group /// A function to generate file IDs. /// The wix file xml contents - public static string CreateWixFilelist(string sourceFolder, string version, string folderPrefix = "$(var.HarvestPath)", string directoryRefName = "INSTALLLOCATION", string componentGroupId = "DUPLICATIBIN", Func? fileIdGenerator = null) + public static string CreateWixFilelist(string sourceFolder, string version, string folderPrefix = "$(var.HarvestPath)", string directoryRefName = "INSTALLLOCATION", string componentGroupId = "DUPLICATIBIN", string wixNs = "http://schemas.microsoft.com/wix/2006/wi", Func? fileIdGenerator = null) { var itemIds = new Dictionary(); - fileIdGenerator ??= (x) => Path.GetRelativePath(sourceFolder, x).Replace("\\", "_").Replace("/", "_").Replace(":", "_").Replace(" ", "_"); + fileIdGenerator ??= (x) => ConvertToIdentifier(Path.GetRelativePath(sourceFolder, x)); Func pathTransformer = (x) => $"{folderPrefix}{Path.GetRelativePath(sourceFolder, x)}"; var doc = new XmlDocument(); - doc.LoadXml(""); + doc.LoadXml($""); var root = doc.DocumentElement!; var fragment = doc.CreateElement("Fragment"); root.AppendChild(fragment); diff --git a/ReleaseBuilder/build_version.txt b/ReleaseBuilder/build_version.txt index 62a0ba5fc..6c3169b3e 100644 --- a/ReleaseBuilder/build_version.txt +++ b/ReleaseBuilder/build_version.txt @@ -1 +1 @@ -2.0.9.108 \ No newline at end of file +2.1.0.112 \ No newline at end of file diff --git a/Tools/RemoteSynchronization/Program.cs b/Tools/RemoteSynchronization/Program.cs new file mode 100644 index 000000000..91c8ae793 --- /dev/null +++ b/Tools/RemoteSynchronization/Program.cs @@ -0,0 +1,830 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + + +namespace RemoteSynchronization +{ + /// + /// Remote synchronization tool. + /// + public class Program + { + /// + /// Global configuration for the tool. Should be set after parsing the commandline arguments. + /// + private sealed record Config + ( + // Arguments + string Src, + string Dst, + + // Options + bool Confirm, + bool DryRun, + List DstOptions, + bool Force, + List GlobalOptions, + string LogFile, + string LogLevel, + bool ParseArgumentsOnly, + bool Progress, + bool Retention, + int Retry, + List SrcOptions, + bool VerifyContents, + bool VerifyGetAfterPut + ); + + /// + /// The log tag for this tool. + /// + private static readonly string LOGTAG = Duplicati.Library.Logging.Log.LogTagFromType(); + + /// + /// Main entry point for the tool. + /// + /// The commandline arguments + /// 0 on success, -1 on abort, and the number of errors encountered otherwise. + public static async Task Main(string[] args) + { + var arg_src = new Argument(name: "backend_src", description: "The source backend string"); + var arg_dst = new Argument(name: "backend_dst", description: "The destination backend string"); + + var root_cmd = new RootCommand(@"Remote Synchronization Tool + +This tool synchronizes two remote backends. The tool assumes that the intent is +to have the destination match the source. + +If the destination has files that are not in the source, they will be deleted +(or renamed if the retention option is set). + +If the destination has files that are also present in the source, but the files +differ in size, or if the source files have a newer (more recent) timestamp, +the destination files will be overwritten by the source files. Given that some +backends do not allow for metadata or timestamp modification, and that the tool +is run after backup, the destination files should always have a timestamp that +is newer (or the same if run promptly) compared to the source files. + +If the force option is set, the destination will be overwritten by the source, +regardless of the state of the files. It will also skip the initial comparison, +and delete (or rename) all files in the destination. + +If the verify option is set, the files will be downloaded and compared after +uploading to ensure that the files are correct. Files that already exist in the +destination will be verified before being overwritten (if they seemingly match). + ") + { + arg_src, + arg_dst, + + new Option(aliases: ["--confirm", "--yes", "-y"], description: "Automatically confirm the operation", getDefaultValue: () => false), + new Option(aliases: ["--dry-run", "-d"], description: "Do not actually write or delete files. If not set here, the global options will be checked", getDefaultValue: () => false), + OptionWithMultipleTokens(aliases: ["--dst-options"], description: "Options for the destination backend. Each option is a key-value pair separated by an equals sign, e.g. --dst-options key1=value1 key2=value2 [default: empty]", getDefaultValue: () => []), + new Option(aliases: ["--force", "-f"], description: "Force the synchronization", getDefaultValue: () => false), + OptionWithMultipleTokens(aliases: ["--global-options"], description: "Global options all backends. May be overridden by backend specific options (src-options, dst-options). Each option is a key-value pair separated by an equals sign, e.g. --global-options key1=value1 key2=value2 [default: empty]", getDefaultValue: () => []), + new Option(aliases: ["--log-file"], description: "The log file to write to. If not set here, global options will be checked [default: \"\"]", getDefaultValue: () => "") { Arity = ArgumentArity.ExactlyOne }, + new Option(aliases: ["--log-level"], description: "The log level to use. If not set here, global options will be checked", getDefaultValue: () => "Information") { Arity = ArgumentArity.ExactlyOne }, + new Option(aliases: ["--parse-arguments-only"], description: "Only parse the arguments and then exit", getDefaultValue: () => false), + new Option(aliases: ["--progress"], description: "Print progress to STDOUT", getDefaultValue: () => false), + new Option(aliases: ["--retention"], description: "Toggles whether to keep old files. Any deletes will be renames instead", getDefaultValue: () => false), + new Option(aliases: ["--retry"], description: "Number of times to retry on errors", getDefaultValue: () => 3) { Arity = ArgumentArity.ExactlyOne }, + OptionWithMultipleTokens(aliases: ["--src-options"], description: "Options for the source backend. Each option is a key-value pair separated by an equals sign, e.g. --src-options key1=value1 key2=value2 [default: empty]", getDefaultValue: () => []), + new Option(aliases: ["--verify-contents"], description: "Verify the contents of the files to decide whether the pre-existing destination files should be overwritten", getDefaultValue: () => false), + new Option(aliases: ["--verify-get-after-put"], description: "Verify the files after uploading them to ensure that they were uploaded correctly", getDefaultValue: () => false), + }; + + root_cmd.Handler = CommandHandler.Create((string backend_src, string backend_dst, Config config) => + { + var config_with_args = config with { Dst = backend_dst, Src = backend_src }; + + return Run(config_with_args); + }); + + return await root_cmd.InvokeAsync(args); + } + + /// + /// The main logic of the tool. + /// + /// The connection string for the source backend. + /// The connection string for the destination backend. + /// Various options for the tool + /// The return code for the main entry; 0 on success. + private static async Task Run(Config config) + { + // Unpack and parse the multi token options + Dictionary global_options = config.GlobalOptions + .Select(x => x.Split("=")) + .ToDictionary(x => x[0], x => x[1]); + + // Parse the log level + var log_level_parsed = Enum.TryParse(config.LogLevel, true, out var log_level_enum); + log_level_enum = log_level_parsed ? log_level_enum : Duplicati.Library.Logging.LogMessageType.Information; + + using var console_sink = new Duplicati.CommandLine.ConsoleOutput(Console.Out, global_options); + using var multi_sink = new Duplicati.Library.Main.ControllerMultiLogTarget(console_sink, log_level_enum, null); + + // Parse the log file + // The log file sink doesn't have to be disposed, as the multi_sink will take care of it + Duplicati.Library.Logging.StreamLogDestination? log_file_sink = null; + if (!string.IsNullOrEmpty(config.LogFile)) + { + string log_file_dir = SystemIO.IO_OS.PathGetDirectoryName(config.LogFile); + if (!string.IsNullOrEmpty(log_file_dir) && !SystemIO.IO_OS.DirectoryExists(log_file_dir)) + SystemIO.IO_OS.DirectoryCreate(log_file_dir); + log_file_sink = new Duplicati.Library.Logging.StreamLogDestination(config.LogFile); + } + multi_sink.AddTarget(log_file_sink, log_level_enum, null); + + // Start the logging scope + using var _ = Duplicati.Library.Logging.Log.StartScope(multi_sink, log_level_enum); + + Dictionary src_opts = config.SrcOptions + .Select(x => x.Split("=")) + .ToDictionary(x => x[0], x => x[1]); + + Dictionary dst_opts = config.DstOptions + .Select(x => x.Split("=")) + .ToDictionary(x => x[0], x => x[1]); + + // Merge the global options into the source and destination options. The global options will be overridden by the source and destination options. + foreach (var x in global_options) + { + if (!src_opts.ContainsKey(x.Key)) + src_opts[x.Key] = x.Value; + if (!dst_opts.ContainsKey(x.Key)) + dst_opts[x.Key] = x.Value; + } + + // Check if we only had to parse the arguments + if (config.ParseArgumentsOnly) + { + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Arguments parsed successfully; exiting."); + return 0; + } + + // Load the backends + static Exception throw_message(string target) + { + var message = $"The {target} backend does not support streaming operations."; + var ex = new Exception(message); + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", ex, message); + return ex; + } + + using var b1 = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(config.Src, src_opts); + var b1s = b1 as IStreamingBackend ?? throw throw_message("source"); + + using var b2 = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(config.Dst, dst_opts); + var b2s = b2 as IStreamingBackend ?? throw throw_message("destination"); + + // Prepare the operations + var (to_copy, to_delete, to_verify) = await PrepareFileLists(b1s, b2s, config, CancellationToken.None); + + // Verify the files if requested. If the files are not verified, they will be deleted and copied again. + long verified = 0, failed_verify = 0; + if (config.VerifyContents) + { + // As this is a potentially slow operation, ask for confirmation of the verification) + if (!config.Confirm) + { + Console.WriteLine($"This will verify {to_verify.Count()} files before copying them. Do you want to continue? [y/N]"); + var response = Console.ReadLine(); + if (!response?.Equals("y", StringComparison.CurrentCultureIgnoreCase) ?? true) + { + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Aborted"); + return -1; + } + } + + var not_verified = await VerifyAsync(b1s, b2s, to_verify, config); + failed_verify = not_verified.Count(); + verified = to_verify.Count() - failed_verify; + + if (not_verified.Any()) + { + Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "rsync", null, + "{0} files failed verification. They will be deleted and copied again.", + not_verified.Count()); + to_delete = to_delete.Concat(not_verified); + to_copy = to_copy.Concat(not_verified); + } + } + + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "The remote synchronization plan is to {0} {1} files from {2}, then copy {3} files from {4} to {2}.", + config.Retention ? "rename" : "delete", + to_delete.Count(), b2s.DisplayName, to_copy.Count(), b1s.DisplayName); + + // As this is a potentially destructive operation, ask for confirmation + if (!config.Confirm) + { + var delete_rename = config.Retention ? "Rename" : "Delete"; + Console.WriteLine($"This will perform the following actions (in order):"); + Console.WriteLine($" - {delete_rename} {to_delete.Count()} files from {config.Dst}"); + Console.WriteLine($" - Copy {to_copy.Count()} files from {config.Src} to {config.Dst}"); + Console.WriteLine(); + Console.WriteLine("Do you want to continue? [y/N]"); + + var response = Console.ReadLine(); + if (!response?.Equals("y", StringComparison.CurrentCultureIgnoreCase) ?? true) + { + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", "Aborted"); + return -1; + } + } + + // Delete or rename the files that are not needed + long renamed = 0, deleted = 0; + if (config.Retention) + { + renamed = await RenameAsync(b2, to_delete, config); + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Renamed {0} files in {1}", renamed, b2s.DisplayName); + } + else + { + deleted = await DeleteAsync(b2s, to_delete, config); + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Deleted {0} files from {1}", deleted, b2s.DisplayName); + } + + // Copy the files + var (copied, copy_errors) = await CopyAsync(b1s, b2s, to_copy, config); + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Copied {0} files from {1} to {2}", copied, b1s, b2s); + + // If there are still errors, retry a few times + if (copy_errors.Any()) + { + Duplicati.Library.Logging.Log.WriteWarningMessage(LOGTAG, "rsync", null, + "Could not copy {0} files.", copy_errors.Count()); + if (config.Retry > 0) + { + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Retrying {0} more times to copy the {1} files that failed", + config.Retry, copy_errors.Count()); + for (int i = 0; i < config.Retry; i++) + { + await Task.Delay(5000); // Wait 5 seconds before retrying + (copied, copy_errors) = await CopyAsync(b1s, b2s, copy_errors, config); + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Copied {0} files from {1} to {2}", copied, b1s, b2s); + if (!copy_errors.Any()) + break; + } + } + + if (copy_errors.Any()) + { + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", null, + "Could not copy {0} files. Not retrying any more.", copy_errors.Count()); + return copy_errors.Count(); + } + } + + // Results reporting + if (verified > 0) + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Verified {0} files in {1} that didn't need to be copied", + verified, b2s.DisplayName); + if (failed_verify > 0) + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Failed to verify {0} files in {1}, which were then attempted to be copied", + failed_verify, b2s.DisplayName); + if (copied > 0) + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Copied {0} files from {1} to {2}", copied, b1s.DisplayName, b2s.DisplayName); + if (deleted > 0) + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Deleted {0} files from {1}", deleted, b2s.DisplayName); + if (renamed > 0) + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Renamed {0} files in {1}", renamed, b2s.DisplayName); + + Duplicati.Library.Logging.Log.WriteInformationMessage(LOGTAG, "rsync", + "Remote synchronization completed successfully"); + + return 0; + } + + // TODO have concurrency parameters: uploaders, downloaders + // TODO low memory mode, where things aren't kept in memory. Maybe utilize SQLite? + // TODO For convenience, have the option to launch a "duplicati test" on the destination backend after the synchronization + // TODO Save hash to minimize redownload + // TODO Duplicati Results + + /// + /// Copies the files from one backend to another. + /// The files are copied one by one, and each file is verified after uploading if the verify flag is set. + /// + /// The source backend. + /// The destination backend. + /// The files that will be copied. + /// The parsed configuration for the tool. + /// A tuple holding the number of succesful copies and a List of the files that failed. + private static async Task<(long, IEnumerable)> CopyAsync(IStreamingBackend b_src, IStreamingBackend b_dst, IEnumerable files, Config config) + { + long successful_copies = 0; + List errors = []; + using var s_src = new MemoryStream(); + using var s_dst = new MemoryStream(); + long i = 0, n = files.Count(); + var sw_get_src = new System.Diagnostics.Stopwatch(); + var sw_put_dst = new System.Diagnostics.Stopwatch(); + var sw_get_dst = new System.Diagnostics.Stopwatch(); + var sw_get_cmp = new System.Diagnostics.Stopwatch(); + + foreach (var f in files) + { + if (config.Progress) + Console.Write($"\rCopying: {i}/{n}"); + + Duplicati.Library.Logging.Log.WriteVerboseMessage(LOGTAG, "rsync", + "Copying {0} from {1} to {2}", f.Name, b_src.DisplayName, b_dst.DisplayName); + + try + { + sw_get_src.Start(); + await b_src.GetAsync(f.Name, s_src, CancellationToken.None); + sw_get_src.Stop(); + if (config.DryRun) + { + Duplicati.Library.Logging.Log.WriteDryrunMessage(LOGTAG, "rsync", + "Would write {0} bytes of {1} to {2}", + Duplicati.Library.Utility.Utility.FormatSizeString(s_src.Length), + f.Name, b_dst.DisplayName); + } + else + { + sw_put_dst.Start(); + await b_dst.PutAsync(f.Name, s_src, CancellationToken.None); + sw_put_dst.Stop(); + if (config.VerifyGetAfterPut) + { + sw_get_dst.Start(); + await b_dst.GetAsync(f.Name, s_dst, CancellationToken.None); + sw_get_dst.Stop(); + + sw_get_cmp.Start(); + string? err_string = null; + if (s_src.Length != s_dst.Length) + { + err_string = $"The sizes of the files do not match: {s_src.Length} != {s_dst.Length}."; + } + + if (!s_src.ToArray().SequenceEqual(s_dst.ToArray())) + { + err_string = (err_string is null ? "" : err_string + " ") + "The contents of the files do not match."; + } + sw_get_cmp.Stop(); + + if (err_string is not null) + { + throw new Exception(err_string); + } + } + } + + successful_copies++; + } + catch (Exception e) + { + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", e, + "Error copying {0}: {1}", f.Name, e.Message); + errors.Add(f); + } + finally + { + s_src.SetLength(0); + s_dst.SetLength(0); + i++; + + // Stop any running timers + sw_get_src.Stop(); + sw_put_dst.Stop(); + sw_get_dst.Stop(); + sw_get_cmp.Stop(); + } + } + + if (config.Progress) + Console.WriteLine($"\rCopying: {n}/{n}"); + + Duplicati.Library.Logging.Log.WriteProfilingMessage(LOGTAG, "rsync", + "Copy | Get source: {0} ms, Put destination: {1} ms, Get destination: {2} ms, Get compare: {3} ms", + TimeSpan.FromMilliseconds(sw_get_src.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_put_dst.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_get_dst.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_get_cmp.ElapsedMilliseconds)); + + return (successful_copies, errors); + } + + /// + /// Deletes the files from a backend. + /// + /// The backend to delete the files from. + /// The files to delete. + /// The parsed configuration for the tool. + /// The number of successful deletions. + private static async Task DeleteAsync(IStreamingBackend b, IEnumerable files, Config config) + { + long successful_deletes = 0; + long i = 0, n = files.Count(); + using var timer = new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Delete operation"); + + foreach (var f in files) + { + if (n > 1 && config.Progress) + { + Console.Write($"\rDeleting: {i}/{n}"); + } + + Duplicati.Library.Logging.Log.WriteVerboseMessage(LOGTAG, "rsync", + "Deleting {0} from {1}", f.Name, b.DisplayName); + + try + { + if (config.DryRun) + { + Duplicati.Library.Logging.Log.WriteDryrunMessage(LOGTAG, "rsync", + "Would delete {0} from {1}", f.Name, b.DisplayName); + } + else + { + await b.DeleteAsync(f.Name, CancellationToken.None); + } + successful_deletes++; + } + catch (Exception e) + { + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", e, + "Error deleting {0}: {1}", f.Name, e.Message); + } + + i++; + } + + if (config.Progress) + Console.WriteLine($"\rDeleting: {n}/{n}"); + return successful_deletes; + } + + /// + /// Creates an option that allows multiple tokens and multiple arguments per token. + /// + /// The aliases for the option. + /// The description for the option. + /// The created option. + private static Option> OptionWithMultipleTokens(string[] aliases, string description, Func> getDefaultValue) + { + return new Option>(aliases: aliases, description: description, getDefaultValue: getDefaultValue) + { + Arity = ArgumentArity.OneOrMore, + AllowMultipleArgumentsPerToken = true + }; + } + + /// + /// Prepares the lists of files to copy, delete and verify. + /// The files to copy are the files that are not in the destination, have a different size or have a more recent modification date. + /// The files to delete are the files that are found in the destination but not found in the source. + /// The files to verify are the files that are found in both the source and the destination, and that have the same size and modification date. + /// + /// The source backend. + /// The destination backend. + /// The parsed configuration for the tool. + /// A tuple of Lists each holding the files to copy, delete and verify. + private static async Task<(IEnumerable, IEnumerable, IEnumerable)> PrepareFileLists(IStreamingBackend b_src, IStreamingBackend b_dst, Config config, CancellationToken cancelToken) + { + IEnumerable files_src, files_dst; + + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | List source")) + files_src = await b_src.ListAsync(cancelToken).ToListAsync(cancelToken).ConfigureAwait(false); + + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | List destination")) + files_dst = await b_dst.ListAsync(cancelToken).ToListAsync(cancelToken).ConfigureAwait(false); + + // Shortcut for force + if (config.Force) + { + return (files_src, files_dst, []); + } + + // Shortcut for empty destination + if (!files_dst.Any()) + { + return (files_src, [], []); + } + + Dictionary lookup_src, lookup_dst; + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | Build lookup for source and destination")) + { + lookup_src = files_src.ToDictionary(x => x.Name); + lookup_dst = files_dst.ToDictionary(x => x.Name); + } + + var to_copy = new List(); + var to_delete = new HashSet(); + var to_verify = new List(); + + // Find all of the files in src that are not in dst, where the dst has a different size than src or src a more recent modification date than dst + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | Check the files that are present in source against destination")) + foreach (var f_src in files_src) + { + if (lookup_dst.TryGetValue(f_src.Name, out var f_dst)) + { + if (f_src.Size != f_dst.Size || f_src.LastModification > f_dst.LastModification) + { + // The file is different, so we need to copy it + to_copy.Add(f_src); + to_delete.Add(f_dst.Name); + } + else + { + // The file seems to be the same, so we need to verify it if the user wants to + to_verify.Add(f_src); + } + } + else + { + // The file is not in the destination, so we need to copy it + to_copy.Add(f_src); + } + } + + // Find all of the files in dst that are not in src + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | Check the files that are present in destination against source")) + foreach (var f_dst in files_dst) + { + if (to_delete.Contains(f_dst.Name)) + continue; + + if (!lookup_src.ContainsKey(f_dst.Name)) + { + to_delete.Add(f_dst.Name); + } + } + + List to_delete_lookedup; + using (new Duplicati.Library.Logging.Timer(LOGTAG, "rsync", "Prepare | Lookup the files to delete")) + to_delete_lookedup = [.. to_delete.Select(x => lookup_dst[x])]; + + return (to_copy, to_delete_lookedup, to_verify); + } + + /// + /// Renames the files in a backend. + /// The renaming is done by deleting the file and re-uploading it with a new name. + /// + /// The backend to rename the files in. + /// The files to rename. + /// The parsed configuration for the tool. + /// The number of successful renames. + private static async Task RenameAsync(IBackend b, IEnumerable files, Config config) + { + long successful_renames = 0; + string prefix = $"{System.DateTime.UtcNow:yyyyMMddHHmmss}.old"; + using var downloaded = new MemoryStream(); + long i = 0, n = files.Count(); + + switch (b) + { + case IStreamingBackend sb: + { + var sw_get_src = new System.Diagnostics.Stopwatch(); + var sw_put_dst = new System.Diagnostics.Stopwatch(); + var sw_del_src = new System.Diagnostics.Stopwatch(); + + foreach (var f in files) + { + if (config.Progress) + Console.Write($"\rRenaming: {i}/{n}"); + + Duplicati.Library.Logging.Log.WriteVerboseMessage(LOGTAG, "rsync", + "Renaming {0} to {1}.{0} by deleting and re-uploading {2} bytes to {3}", + f.Name, prefix, + Duplicati.Library.Utility.Utility.FormatSizeString(downloaded.Length), + sb.DisplayName); + + try + { + sw_get_src.Start(); + await sb.GetAsync(f.Name, downloaded, CancellationToken.None); + sw_get_src.Stop(); + + if (config.DryRun) + { + Duplicati.Library.Logging.Log.WriteDryrunMessage(LOGTAG, "rsync", + "Would rename {0} to {1}.{0} by deleting and re-uploading {2} bytes to {3}", + f.Name, prefix, + Duplicati.Library.Utility.Utility.FormatSizeString(downloaded.Length), + sb.DisplayName); + } + else + { + sw_put_dst.Start(); + await sb.PutAsync($"{prefix}.{f.Name}", downloaded, CancellationToken.None); + sw_put_dst.Stop(); + sw_del_src.Start(); + await sb.DeleteAsync(f.Name, CancellationToken.None); + sw_del_src.Stop(); + } + successful_renames++; + } + catch (Exception e) + { + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", e, + "Error renaming {0}: {1}", f.Name, e.Message); + } + finally + { + // Reset the stream + downloaded.SetLength(0); + + // Stop any running timers + sw_get_src.Stop(); + sw_put_dst.Stop(); + sw_del_src.Stop(); + } + + i++; + } + + Duplicati.Library.Logging.Log.WriteProfilingMessage(LOGTAG, "rsync", + "Rename | Get source: {0} ms, Put destination: {1} ms, Delete source: {2} ms", + TimeSpan.FromMilliseconds(sw_get_src.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_put_dst.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_del_src.ElapsedMilliseconds)); + + if (config.Progress) + Console.WriteLine($"\rRenaming: {n}/{n}"); + + return successful_renames; + } + case IRenameEnabledBackend rb: + { + var sw = new System.Diagnostics.Stopwatch(); + + foreach (var f in files) + { + if (config.Progress) + Console.Write($"\rRenaming: {i}/{n}"); + + Duplicati.Library.Logging.Log.WriteVerboseMessage(LOGTAG, "rsync", + "Renaming {0} to {1}.{0} by calling Rename on {2}", + f.Name, prefix, rb.DisplayName); + + try + { + if (config.DryRun) + { + Duplicati.Library.Logging.Log.WriteDryrunMessage(LOGTAG, "rsync", + "Would rename {0} to {1}.{0} by calling Rename on {2}", + f.Name, prefix, rb.DisplayName); + } + else + { + sw.Start(); + await rb.RenameAsync(f.Name, $"{prefix}.{f.Name}", CancellationToken.None); + sw.Stop(); + } + successful_renames++; + } + catch (Exception e) + { + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", e, + "Error renaming {0}: {1}", f.Name, e.Message); + } + finally + { + // Ensure the timer is stopped + sw.Stop(); + } + + i++; + } + + Duplicati.Library.Logging.Log.WriteProfilingMessage(LOGTAG, "rsync", + "Rename: {0} ms", + TimeSpan.FromMilliseconds(sw.ElapsedMilliseconds)); + + if (config.Progress) + Console.WriteLine($"\rRenaming: {n}/{n}"); + + return successful_renames; + } + default: + throw new NotSupportedException("The backend does not support renaming"); + } + } + + /// + /// Verifies the files in the destination backend. + /// The verification is done by downloading the files from the destination backend and comparing them to the source files. + /// + /// The source backend. + /// The destination backend. + /// The files to verify. + /// The parsed configuration for the tool. + /// A list of the files that failed verification. + private static async Task> VerifyAsync(IStreamingBackend b_src, IStreamingBackend b_dst, IEnumerable files, Config config) + { + var errors = new List(); + using var s_src = new MemoryStream(); + using var s_dst = new MemoryStream(); + long i = 0, n = files.Count(); + var sw_get = new System.Diagnostics.Stopwatch(); + var sw_cmp = new System.Diagnostics.Stopwatch(); + + foreach (var f in files) + { + if (config.Progress) + Console.Write($"\rVerifying: {i}/{n}"); + + Duplicati.Library.Logging.Log.WriteVerboseMessage(LOGTAG, "rsync", + "Verifying {0} by downloading and comparing {1} bytes from {2} and {3}", + f.Name, + Duplicati.Library.Utility.Utility.FormatSizeString(s_src.Length), + b_dst.DisplayName, b_src.DisplayName); + + try + { + // Get both files + sw_get.Start(); + var fs = b_src.GetAsync(f.Name, s_src, CancellationToken.None); + var ds = b_dst.GetAsync(f.Name, s_dst, CancellationToken.None); + await Task.WhenAll(fs, ds); + sw_get.Stop(); + + // Compare the contents + sw_cmp.Start(); + if (s_src.Length != s_dst.Length || !s_src.ToArray().SequenceEqual(s_dst.ToArray())) + { + errors.Add(f); + } + sw_cmp.Stop(); + } + catch (Exception e) + { + errors.Add(f); + Duplicati.Library.Logging.Log.WriteErrorMessage(LOGTAG, "rsync", e, + "Error during verification of {0}: {1}", f.Name, e.Message); + } + finally + { + // Reset the streams + s_src.SetLength(0); + s_dst.SetLength(0); + + // Stop any running timers + sw_get.Stop(); + sw_cmp.Stop(); + } + + i++; + } + + Duplicati.Library.Logging.Log.WriteProfilingMessage(LOGTAG, "rsync", + "Verify | Get: {0} ms, Compare: {1} ms", + TimeSpan.FromMilliseconds(sw_get.ElapsedMilliseconds), + TimeSpan.FromMilliseconds(sw_cmp.ElapsedMilliseconds)); + + if (config.Progress) + Console.WriteLine($"\rVerifying: {n}/{n}"); + + return errors; + } + + } +} diff --git a/Tools/RemoteSynchronization/RemoteSynchronization.csproj b/Tools/RemoteSynchronization/RemoteSynchronization.csproj new file mode 100644 index 000000000..9c800af07 --- /dev/null +++ b/Tools/RemoteSynchronization/RemoteSynchronization.csproj @@ -0,0 +1,16 @@ + + + + Library + net8.0 + disable + enable + + + + + + + + + diff --git a/Tools/SQLiteTool/Main.cs b/Tools/SQLiteTool/Main.cs index 592de0710..6e87d8de8 100644 --- a/Tools/SQLiteTool/Main.cs +++ b/Tools/SQLiteTool/Main.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Tools/SQLiteTool/SQLiteTool.csproj b/Tools/SQLiteTool/SQLiteTool.csproj index 6a7afdafc..0ef948873 100644 --- a/Tools/SQLiteTool/SQLiteTool.csproj +++ b/Tools/SQLiteTool/SQLiteTool.csproj @@ -3,7 +3,7 @@ net8.0 Exe - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/Tools/TestDataGenerator/.gitignore b/Tools/TestDataGenerator/.gitignore new file mode 100644 index 000000000..1269488f7 --- /dev/null +++ b/Tools/TestDataGenerator/.gitignore @@ -0,0 +1 @@ +data diff --git a/Tools/TestDataGenerator/.vscode/launch.json b/Tools/TestDataGenerator/.vscode/launch.json new file mode 100644 index 000000000..b265cd745 --- /dev/null +++ b/Tools/TestDataGenerator/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Generate Test Data", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug/net8.0/TestDataGenerator.dll", + "args": ["create", "./data", "--file-count=1000", "--max-folder-count=100", "--max-fan-out=3"], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": "Update Test Data", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/bin/Debug/net8.0/TestDataGenerator.dll", + "args": ["update", "./data", "--new-files=10", "--updated-files=10", "--deleted-files=10"], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopAtEntry": false + } + ] +} \ No newline at end of file diff --git a/Tools/TestDataGenerator/.vscode/tasks.json b/Tools/TestDataGenerator/.vscode/tasks.json new file mode 100644 index 000000000..f2835a246 --- /dev/null +++ b/Tools/TestDataGenerator/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/TestDataGenerator.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Tools/TestDataGenerator/Commands/Create.cs b/Tools/TestDataGenerator/Commands/Create.cs new file mode 100644 index 000000000..967e6f7c3 --- /dev/null +++ b/Tools/TestDataGenerator/Commands/Create.cs @@ -0,0 +1,274 @@ +using System.Collections.Concurrent; +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using static TestDataGenerator.Commands.Shared; + +namespace TestDataGenerator.Commands; + +/// +/// Create test data in a folder +/// +public static class Create +{ + /// + /// The input parameters for the command + /// + /// The folder to create files in + /// The number of files to create in the target folder + /// The maximum total size of the files to create in the target folder + /// The maximum size of each file to create in the target folder + /// The minimum size of each file to create in the target folder + /// The percentage of data that should be zeroed out in each file + /// The maximum number of subfolders to create in each folder + /// The maximum depth of subfolders to create in the target folder + /// The maximum number of folders to create in the target folder + /// The maximum length of each path segment in the target folder + /// The random seed to use for generating the test data + /// The number of parallel tasks to use for creating the test data + record CommandInput( + DirectoryInfo TargetFolder, + int FileCount, + long MaxTotalSize, + long MaxFileSize, + long MinFileSize, + int SparseFactor, + int MaxFanOut, + int MaxDepth, + int MaxFolderCount, + int MaxPathSegmentLength, + string RandomSeed, + int Parallel); + + /// + /// Creates the command + /// + /// The command + public static Command CreateCommand() + { + var command = new Command("create", "Create test data in a folder"); + + var targetFolderOption = new Argument("target-folder", "The folder to create files in"); + targetFolderOption.SetDefaultValue(new DirectoryInfo(Directory.GetCurrentDirectory())); + command.AddArgument(targetFolderOption); + + var fileCountOption = new Option("--file-count", "The number of files to create in the target folder"); + fileCountOption.SetDefaultValue(200000); + command.AddOption(fileCountOption); + + var maxTotalSizeOption = new Option("--max-total-size", "The maximum total size of the files to create in the target folder"); + maxTotalSizeOption.SetDefaultValue(1024 * 1024 * 1024L); + command.AddOption(maxTotalSizeOption); + + var maxFileSizeOption = new Option("--max-file-size", "The maximum size of each file to create in the target folder"); + maxFileSizeOption.SetDefaultValue(1024 * 1024L); + command.AddOption(maxFileSizeOption); + + var minFileSizeOption = new Option("--min-file-size", "The minimum size of each file to create in the target folder"); + minFileSizeOption.SetDefaultValue(0L); + command.AddOption(minFileSizeOption); + + var sparseFactorOption = new Option("--sparse-factor", "The percentage of data that should be zeroed out in each file"); + sparseFactorOption.SetDefaultValue(10); + command.AddOption(sparseFactorOption); + + var maxFanOutOption = new Option("--max-fan-out", "The maximum number of subfolders to create in each folder"); + maxFanOutOption.SetDefaultValue(10); + command.AddOption(maxFanOutOption); + + var maxDepthOption = new Option("--max-depth", "The maximum depth of subfolders to create in the target folder"); + maxDepthOption.SetDefaultValue(5); + command.AddOption(maxDepthOption); + + var maxFolderCountOption = new Option("--max-folder-count", "The maximum number of folders to create in the target folder"); + maxFolderCountOption.SetDefaultValue(10000); + command.AddOption(maxFolderCountOption); + + var maxPathSegmentLengthOption = new Option("--max-path-segment-length", "The maximum length of each path segment in the target folder"); + maxPathSegmentLengthOption.SetDefaultValue(15); + command.AddOption(maxPathSegmentLengthOption); + + var randomSeedOption = new Option("--random-seed", "The random seed to use for generating the test data"); + randomSeedOption.SetDefaultValue("Duplicati"); + command.AddOption(randomSeedOption); + + var parallelOption = new Option("--parallel", "The number of parallel tasks to use for creating the test data"); + parallelOption.SetDefaultValue(Environment.ProcessorCount); + command.AddOption(parallelOption); + + command.Handler = CommandHandler.Create(Execute); + + return command; + } + + /// + /// Executes the command + /// + /// The input parameters + private static void Execute(CommandInput input) + { + if (input.TargetFolder.Exists && input.TargetFolder.GetFileSystemInfos().Length > 0) + throw new Exception($"The target folder {input.TargetFolder.FullName} already exists"); + + if (input.MaxFileSize == 0) + throw new Exception("The maximum file size must be greater than zero"); + + if (input.MaxTotalSize == 0) + throw new Exception("The maximum total size must be greater than zero"); + + if (input.MinFileSize > input.MaxFileSize) + throw new Exception("The minimum file size must be less than or equal to the maximum file size"); + + if (input.FileCount == 0) + throw new Exception("The file count must be greater than zero"); + + if (input.MaxFolderCount == 0) + throw new Exception("The maximum folder count must be greater than zero"); + + if (input.MaxFanOut == 0) + throw new Exception("The maximum fan-out must be greater than zero"); + + if (input.MaxDepth == 0) + throw new Exception("The maximum depth must be greater than zero"); + + if (input.MaxPathSegmentLength == 0) + throw new Exception("The maximum path segment length must be greater than zero"); + + Console.WriteLine($"Creating test data in {input.TargetFolder.FullName}"); + var rnd = new Random(input.RandomSeed.GetHashCode()); + var folders = GeneratePathStructure(rnd, input.TargetFolder.FullName, input.MaxDepth, input.MaxFanOut, input.MaxPathSegmentLength); + while (folders.Count < input.MaxFolderCount) + { + folders.AddRange(GeneratePathStructure(rnd, input.TargetFolder.FullName, input.MaxDepth, input.MaxFanOut, input.MaxPathSegmentLength)); + folders = folders.Distinct().ToList(); + } + + while (folders.Count > input.MaxFolderCount) + folders.RemoveAt(folders.Count - 1); + + folders.Add(input.TargetFolder.FullName); + + var files = DistributeFiles(rnd, folders, GenerateFileNames(rnd, input.FileCount, input.MaxPathSegmentLength)); + + var filesWithSizes = files.Select(x => (x, rnd.NextInt64(input.MinFileSize, input.MaxFileSize))).ToList(); + var totalSize = filesWithSizes.Sum(x => x.Item2); + + while (totalSize > input.MaxTotalSize) + { + var index = rnd.Next(filesWithSizes.Count); + var newSize = rnd.NextInt64(input.MinFileSize, filesWithSizes[index].Item2); + totalSize -= filesWithSizes[index].Item2 - newSize; + filesWithSizes[index] = (filesWithSizes[index].Item1, newSize); + } + + Console.WriteLine($"Creating {folders.Count - 1} folders and {files.Count} files ({SizeToHumanReadable(totalSize)})"); + if (!input.TargetFolder.Exists) + input.TargetFolder.Create(); + + var lastUpdate = DateTime.UtcNow; + var foldersCreated = 0L; + + var updateInterval = TimeSpan.FromSeconds(10); + + foreach (var folder in folders) + { + Directory.CreateDirectory(folder); + foldersCreated++; + if ((DateTime.UtcNow - lastUpdate) > updateInterval) + { + Console.WriteLine($"Created {foldersCreated} of {folders.Count} folders"); + lastUpdate = DateTime.UtcNow; + } + } + + object stats_lock = new(); + var filesCreated = 0L; + var fileSizeCreated = 0L; + var generateStart = DateTime.UtcNow; + + var cancelToken = new CancellationTokenSource(); + var status_updater = Task.Run(async () => + { + while (!cancelToken.Token.IsCancellationRequested) + { + await Task.Delay((int)updateInterval.TotalMilliseconds, cancelToken.Token); + long local_filesCreated, local_fileSizeCreated; + lock (stats_lock) + { + local_filesCreated = filesCreated; + local_fileSizeCreated = fileSizeCreated; + } + + var generateNow = DateTime.UtcNow; + long throughput = (long)Math.Max(Math.Floor(local_fileSizeCreated / (generateNow - generateStart).TotalSeconds), 1); + var timeLeft = TimeSpan.FromSeconds((input.MaxTotalSize - local_fileSizeCreated) / throughput); + string timeLeftString = timeLeft.ToString(@"hh\:mm\:ss"); + Console.WriteLine($"Created {local_filesCreated} of {files.Count} files ({SizeToHumanReadable(local_fileSizeCreated)} of {SizeToHumanReadable(totalSize)}) ({SizeToHumanReadable(throughput)}/s) - ETA: {timeLeftString}"); + } + }, cancelToken.Token); + + Parallel.ForEach(Partitioner.Create(0, filesWithSizes.Count), new ParallelOptions { MaxDegreeOfParallelism = input.Parallel }, range => + { + var local_rnd = new Random(input.RandomSeed.GetHashCode() + range.Item1); + for (int i = range.Item1; i < range.Item2; i++) + { + var (file, size) = filesWithSizes[i]; + var filename = file; + try + { + // Sometimes with a large number of files, the file name clashes with a folder name. In that case, generate a new file name + for (int j = 0; j < 10 && Path.Exists(filename); j++) + filename = Path.Combine(Path.GetDirectoryName(filename) ?? "", "_" + Path.GetFileName(filename)); + + using var fs = new FileStream(filename, FileMode.Create, FileAccess.Write); + if (size > 0) + { + fs.SetLength(size); + fs.Position = 0; + + WriteRandomData(local_rnd, fs, size, input.SparseFactor); + } + + lock (stats_lock) + { + filesCreated++; + fileSizeCreated += size; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error creating file {filename}: {ex.Message}"); + } + } + }); + + cancelToken.Cancel(); + var time_string = (DateTime.UtcNow - generateStart).ToString(@"hh\:mm\:ss"); + Console.WriteLine($"Created {filesCreated} of {files.Count} files ({SizeToHumanReadable(fileSizeCreated)} of {SizeToHumanReadable(totalSize)} at {SizeToHumanReadable((long)(fileSizeCreated / (DateTime.UtcNow - generateStart).TotalSeconds))}/s) in {time_string}"); + } + + /// + /// Generates a list of random folder paths + /// + /// The random number generator to use + /// The prefix to use for the folder paths + /// The maximum depth of the folder structure + /// The maximum number of subfolders to create in each folder + /// The maximum length of each path segment + private static List GeneratePathStructure(Random rnd, string prefix, int maxDepth, int maxFanOut, int maxSegmentLength) + { + var folders = new List(); + var folderCount = rnd.Next(1, maxFanOut + 1); + + if (maxDepth == 0) + return GeneratePathSegments(rnd, folderCount, maxSegmentLength).Select(x => Path.Combine(prefix, x)).ToList(); + + for (var i = 0; i < folderCount; i++) + { + var folderName = GetPathSegment(rnd, maxSegmentLength); + folders.Add(Path.Combine(prefix, folderName)); + folders.AddRange(GeneratePathStructure(rnd, Path.Combine(prefix, folderName), maxDepth - 1, maxFanOut, maxSegmentLength)); + } + + return folders; + } +} diff --git a/Tools/TestDataGenerator/Commands/Shared.cs b/Tools/TestDataGenerator/Commands/Shared.cs new file mode 100644 index 000000000..caf3e614b --- /dev/null +++ b/Tools/TestDataGenerator/Commands/Shared.cs @@ -0,0 +1,142 @@ +using System.Text; + +namespace TestDataGenerator.Commands; + +/// +/// Shared utility methods for the test data generator +/// +public static class Shared +{ + /// + /// Distributes the files in the list to the folders in the list + /// + /// The random number generator to use + /// The list of folders to distribute the files to + /// The list of files to distribute + /// The list of files distributed to the folders + public static List DistributeFiles(Random rnd, List folders, List list) + { + var expanded = new List(); + foreach (var file in list) + expanded.Add(Path.Combine(folders[rnd.Next(folders.Count)], file)); + + return expanded; + } + + /// + /// Converts a size in bytes to a human-readable string + /// + /// The size in bytes + /// The human-readable string + public static string SizeToHumanReadable(long size) + { + if (size < 1024) + return $"{size} B"; + if (size < 1024 * 1000) + return $"{size / 1024m:F2} KiB"; + if (size < 1024 * 1024 * 1000) + return $"{size / 1024 / 1024m:F2} MiB"; + if (size < 1024L * 1024 * 1024 * 1000) + return $"{size / 1024 / 1024 / 1024m:F2} GiB"; + return $"{size / 1024 / 1024 / 1024 / 1024m:F2} TiB"; + } + + /// + /// The list of file extensions to use for random file names + /// + private static readonly IReadOnlyList FileExtensions = new List([".bin", ".dat", ".binary", ".data", ".test", ""]); + + /// + /// The characters to use for random path segments + /// + private const string PathSegmentChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + /// + /// Generates a random path segment + /// + /// The random number generator to use + /// The maximum length of the path segment + /// The random path segment + public static string GetPathSegment(Random rnd, int maxLength) + { + var length = rnd.Next(1, maxLength + 1); + var sb = new StringBuilder(length); + for (int j = 0; j < length; j++) + sb.Append(PathSegmentChars[rnd.Next(PathSegmentChars.Length)]); + return sb.ToString(); + } + + /// + /// Generates a list of random path segments + /// + /// The random number generator to use + /// The number of path segments to generate + /// The maximum length of each path segment + /// The list of path segments + public static List GeneratePathSegments(Random rnd, int count, int maxLength) + { + var names = new List(count); + for (int i = 0; i < count; i++) + names.Add(GetPathSegment(rnd, maxLength)); + return names; + } + + /// + /// Generates a random file name + /// + /// The random number generator to use + /// The maximum length of the file name + public static string GenerateFileName(Random rnd, int maxLength) + { + return GetPathSegment(rnd, maxLength) + FileExtensions[rnd.Next(FileExtensions.Count)]; + } + + /// + /// Generates a list of random file names + /// + /// The random number generator to use + /// The number of file names to generate + /// The maximum length of each file name + /// The list of file names + public static List GenerateFileNames(Random rnd, int count, int maxLength) + { + var names = new List(count); + for (int i = 0; i < count; i++) + names.Add(GenerateFileName(rnd, maxLength)); + return names; + } + + /// + /// Writes random data to a stream + /// + /// The random number generator to use + /// The stream to write to + /// The size of the data to write + /// The factor to use for sparse data + /// + /// The sparse factor is a percentage value that determines how many bytes are skipped in the stream. + /// For example, a sparse factor of 10 means that 10% of the data will be not be overwritten. + /// + public static void WriteRandomData(Random rnd, Stream fs, long size, int sparseFactor) + { + var buffer = new byte[4096]; + while (size > 0) + { + var chunkSize = rnd.Next(1, (int)Math.Min(size, buffer.Length)); + var sp = buffer.AsSpan(chunkSize); + if (rnd.Next(100) < sparseFactor) + { + // Skip the bytes + fs.Position += sp.Length; + } + else + { + rnd.NextBytes(sp); + fs.Write(sp); + } + + size -= sp.Length; + } + } + +} \ No newline at end of file diff --git a/Tools/TestDataGenerator/Commands/Update.cs b/Tools/TestDataGenerator/Commands/Update.cs new file mode 100644 index 000000000..5066ac50e --- /dev/null +++ b/Tools/TestDataGenerator/Commands/Update.cs @@ -0,0 +1,195 @@ +using System.CommandLine; +using System.CommandLine.NamingConventionBinder; +using static TestDataGenerator.Commands.Shared; + +namespace TestDataGenerator.Commands; + +/// +/// Update test data in a folder +/// +public static class Update +{ + /// + /// The input parameters for the command + /// + /// The folder to update files in + /// The number of new files to create in the target folder + /// The number of files to update in the target folder + /// The number of files to delete in the target folder + /// The number of files to rename in the target folder + /// The maximum size of each file to create in the target folder + /// The minimum size of each file to create in the target folder + /// The percentage of data that should be zeroed out in each file + /// The percentage of files to update in the target folder + /// The maximum length of each path segment in the target folder + record CommandInput( + DirectoryInfo TargetFolder, + int NewFiles, + int UpdatedFiles, + int DeletedFiles, + int RenameFiles, + long MaxFileSize, + long MinFileSize, + int SparseFactor, + int UpdateFactor, + int MaxPathSegmentLength); + + /// + /// Creates the command + /// + /// The command + public static Command CreateCommand() + { + var command = new Command("update", "Update test data in a folder"); + + var targetFolderOption = new Argument("target-folder", "The folder to update files in"); + targetFolderOption.SetDefaultValue(new DirectoryInfo(Directory.GetCurrentDirectory())); + command.AddArgument(targetFolderOption); + + var newFilesOption = new Option("--new-files", "The number of new files to create in the target folder"); + newFilesOption.SetDefaultValue(1000); + command.AddOption(newFilesOption); + + var updatedFilesOption = new Option("--updated-files", "The number of files to update in the target folder"); + updatedFilesOption.SetDefaultValue(1000); + command.AddOption(updatedFilesOption); + + var deletedFilesOption = new Option("--deleted-files", "The number of files to delete in the target folder"); + deletedFilesOption.SetDefaultValue(1000); + command.AddOption(deletedFilesOption); + + var renameFilesOption = new Option("--rename-files", "The number of files to rename in the target folder"); + renameFilesOption.SetDefaultValue(1000); + command.AddOption(renameFilesOption); + + var maxFileSizeOption = new Option("--max-file-size", "The maximum size of each file to create in the target folder"); + maxFileSizeOption.SetDefaultValue(1024 * 1024L); + command.AddOption(maxFileSizeOption); + + var minFileSizeOption = new Option("--min-file-size", "The minimum size of each file to create in the target folder"); + minFileSizeOption.SetDefaultValue(1024L); + command.AddOption(minFileSizeOption); + + var sparseFactorOption = new Option("--sparse-factor", "The percentage of data that should be zeroed out in each file"); + sparseFactorOption.SetDefaultValue(10); + command.AddOption(sparseFactorOption); + + var updateFactorOption = new Option("--update-factor", "The percentage of files to update in the target folder"); + updateFactorOption.SetDefaultValue(10); + command.AddOption(updateFactorOption); + + var maxPathSegmentLengthOption = new Option("--max-path-segment-length", "The maximum length of each path segment in the target folder"); + maxPathSegmentLengthOption.SetDefaultValue(15); + command.AddOption(maxPathSegmentLengthOption); + + command.Handler = CommandHandler.Create(Execute); + + return command; + } + + /// + /// Executes the command + /// + /// The input parameters + private static void Execute(CommandInput input) + { + if (!input.TargetFolder.Exists) + throw new DirectoryNotFoundException($"The target folder {input.TargetFolder.FullName} does not exist"); + + if (input.MaxFileSize == 0) + throw new Exception("The maximum file size must be greater than zero"); + + Console.WriteLine($"Updating test data in {input.TargetFolder.FullName}"); + + var folders = new List(); + var files = new List(); + + foreach (var folder in input.TargetFolder.EnumerateDirectories("*", SearchOption.AllDirectories)) + { + folders.Add(folder.FullName); + foreach (var file in folder.EnumerateFiles()) + files.Add(file.FullName); + } + + Console.WriteLine($"Found {folders.Count} folders and {files.Count} files"); + + folders.Add(input.TargetFolder.FullName); + + var rnd = new Random(); + var newFileNames = DistributeFiles(rnd, folders, GenerateFileNames(rnd, input.NewFiles, input.MaxPathSegmentLength)); + + foreach (var file in newFileNames) + { + Console.WriteLine($"Creating new file {file}"); + + var size = rnd.NextInt64((int)input.MinFileSize, (int)input.MaxFileSize); + using var fs = new FileStream(file, FileMode.Create, FileAccess.Write); + fs.SetLength(size); + if (size > 0) + { + fs.Position = 0; + WriteRandomData(rnd, fs, size, input.SparseFactor); + } + } + + var updatedFileNames = new HashSet(); + while (updatedFileNames.Count < input.UpdatedFiles) + { + var file = files[rnd.Next(files.Count)]; + if (!updatedFileNames.Contains(file)) + updatedFileNames.Add(file); + + if (updatedFileNames.Count == files.Count) + break; + } + + foreach (var file in updatedFileNames) + { + Console.WriteLine($"Updating file {file}"); + + var size = rnd.NextInt64(input.MinFileSize, input.MaxFileSize); + using var fs = new FileStream(file, FileMode.Open, FileAccess.Write); + fs.SetLength(size); + if (size > 0) + { + fs.Position = 0; + WriteRandomData(rnd, fs, size, 100 - input.UpdateFactor); + } + } + + var deletedFileNames = new HashSet(); + while (deletedFileNames.Count < input.DeletedFiles) + { + var file = files[rnd.Next(files.Count)]; + if (!deletedFileNames.Contains(file)) + deletedFileNames.Add(file); + + if (deletedFileNames.Count == files.Count) + break; + } + + foreach (var file in deletedFileNames) + { + Console.WriteLine($"Deleting file {file}"); + File.Delete(file); + } + + var renamedFileNames = new HashSet(); + while (renamedFileNames.Count < input.RenameFiles) + { + var file = files[rnd.Next(files.Count)]; + if (!renamedFileNames.Contains(file)) + renamedFileNames.Add(file); + + if (renamedFileNames.Count == files.Count) + break; + } + + foreach (var file in renamedFileNames) + { + var newName = Path.Combine(folders[rnd.Next(folders.Count)], GenerateFileNames(rnd, 1, input.MaxPathSegmentLength).First()); + Console.WriteLine($"Renaming file {file} to {newName}"); + File.Move(file, newName); + } + } +} diff --git a/Tools/TestDataGenerator/Program.cs b/Tools/TestDataGenerator/Program.cs new file mode 100644 index 000000000..6f43b554d --- /dev/null +++ b/Tools/TestDataGenerator/Program.cs @@ -0,0 +1,10 @@ +using System.CommandLine; + +var rootCommand = new RootCommand("Test Data Generator") +{ + TestDataGenerator.Commands.Create.CreateCommand(), + TestDataGenerator.Commands.Update.CreateCommand() +}; + +return rootCommand.Invoke(args); + diff --git a/Tools/TestDataGenerator/README.md b/Tools/TestDataGenerator/README.md new file mode 100644 index 000000000..f01ba7e88 --- /dev/null +++ b/Tools/TestDataGenerator/README.md @@ -0,0 +1,7 @@ +# Test data generator + +This tool is intended for use by developer who need to generate a data set for testing Duplicati. + +The tool can create a set of random data at a target folder using the `create` command, and make alterations using the `update` command. + +Use the `help` command to examine the supported properties and adjust to make the desired test dataset. diff --git a/Tools/TestDataGenerator/TestDataGenerator.csproj b/Tools/TestDataGenerator/TestDataGenerator.csproj new file mode 100644 index 000000000..04c2e9be8 --- /dev/null +++ b/Tools/TestDataGenerator/TestDataGenerator.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/Tools/TestDataGenerator/TestDataGenerator.sln b/Tools/TestDataGenerator/TestDataGenerator.sln new file mode 100644 index 000000000..6cbfc34b3 --- /dev/null +++ b/Tools/TestDataGenerator/TestDataGenerator.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDataGenerator", "TestDataGenerator.csproj", "{547C0549-040B-4F4F-A410-351FC0176F28}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {547C0549-040B-4F4F-A410-351FC0176F28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {547C0549-040B-4F4F-A410-351FC0176F28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {547C0549-040B-4F4F-A410-351FC0176F28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {547C0549-040B-4F4F-A410-351FC0176F28}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F3FB6242-FFDF-42A7-A78F-BB1FE67C12D8} + EndGlobalSection +EndGlobal diff --git a/Tools/ZipFileDebugger/Program.cs b/Tools/ZipFileDebugger/Program.cs index e6116dc94..69eb9907f 100644 --- a/Tools/ZipFileDebugger/Program.cs +++ b/Tools/ZipFileDebugger/Program.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2024, The Duplicati Team +// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a diff --git a/Tools/ZipFileDebugger/ZipFileDebugger.csproj b/Tools/ZipFileDebugger/ZipFileDebugger.csproj index cc4c40db7..c2f65546b 100644 --- a/Tools/ZipFileDebugger/ZipFileDebugger.csproj +++ b/Tools/ZipFileDebugger/ZipFileDebugger.csproj @@ -3,7 +3,7 @@ net8.0 Exe - Copyright © 2024 Team Duplicati, MIT license + Copyright © 2025 Team Duplicati, MIT license diff --git a/changelog.txt b/changelog.txt index 5f7815149..14f784298 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,551 @@ +2025-03-26 - 2.1.0.112_canary_2025-03-26 +========== +This release is a canary release intended to be used for testing. + +# Major changes in this version + +- Updated backends +The major change in this version is the addition of timeouts to every backend. +This has the potential to affect each backend individually. + +- Improved validation +Further increased the checks made to validate that the database is consistent, and added new features to the repair operation to assist in recovering. +Certain problems are now checked before and after a backup, in an attempt to better close in on the conditions that create these situations. + +- Updates to ngclient +Updates to reconnect logic and locale support. + +# Potentially breaking changes (from 2.1.0.109) + - Location of data folder has changed in some cases, refer to previous release for details + - Permissions are now applied to the data folder, refer to previous release for details + - New user interface, `ngclient` is the default; there is a button to switch between the two + +## Detailed list of changes: +- Added retries for sending HTTP-based reports +- Added warning icon back to TrayIcon +- Fixed a check that prevented repairs in timezones that are have a negative UTC offset +- Rewrote a number of SQL queries to avoid localization of SQL strings +- Added guards against faulty startup delays +- Updated all backends to use same parsing logic for authentication options +- Updated all backends to support timeouts +- Fixed a bug where databases would be created with strict permissions, even if opting out +- Updated SQLite loading to always set custom pragmas +- Updated SQLite loading to support custom `SQLITE_TMPDIR` environment variable +- Removed unwanted data in report JSON output +- Added support for deleting empty index files on repair +- Improved handling of stop "errors" +- Updated `box.com` backend to use HttpClient +- Added option to inject custom parameters into reports +- Improved error message on manifest version mismatches +- Removed a warning when attempting to pick a restore date with a negative prefix +- Support for large form-encoded HTTP reports +- Fixed an issue where the URL parser would return different results for similar inputs (for the `file://` input) +- Handling backspace on console password input +- Added guard against fileset errors in the database +- Added support for rebuilding filesets from remote data on repair +- TrayIcon can now start in a disconnected state, and keep probing for a connection +- Added current update channel to reports +- Added automatic zip64 usage for large bugreports +- Added support for showing the crashlog in the UI +- Fixed an issue with some dates causing problems in the timezone aware calculations +- Added support for sorting the list of backups in the UI +- Fixed an issue with restoring timestamps and metadata on folders + +2025-03-15 - 2.1.0.111_canary_2025-03-15 +========== +This release is a canary release intended to be used for testing. + +# Major changes in this version + +- Restore improvements +This version has a few tweaks to the new restore flow that makes it more robust. + +- Improved validation +To safeguard against internal errors, there is now a more strict validation before performing cleanup. +The new logic checks if the previous operation failed and will only automatically repair the remote if this is the case. +The repair command bypasses this check and can repair the remote regardless. + +If the previous run was fully completed, any leftover remote files are now treated as an error. +Additionally, operations that does not change remote state (list, test, restore, etc) will not repair the remote state, but just verify that it is working for the requested operation. + +- Updates to ngclient +Multiple updates to ngclient: improved auth flow, searchable menus, Windows filesystem support, restore from configuration, and more. + +# Potentially breaking changes (from 2.1.0.109) + - Location of data folder has changed in some cases, refer to previous release for details + - Permissions are now applied to the data folder, refer to previous release for details + - New user interface, `ngclient` is the default; there is a button to switch between the two + +## Detailed list of changes: +- Reduced number of samples taken for testing +- Improved restore flow +- Improved error handling for Azure Blob storage +- Improved help text for remote synchronization tool +- Implemented more rigorous validation and safeguards for failed backups +- Better support for IPv6 and unmapped localhost scenarios +- Added check to avoid block sizes that are the same or less than volume size +- Introduced more async calls for better thread usage in handlers +- Added forced culture for SQL strings and simpler transaction logic +- Added Arm v7 Agent builds +- Improved certificate validation to check expiration and support root or intermediate certificate hashes + +2025-02-28 - 2.1.0.110_canary_2025-02-28 +========== +This release is a canary release intended to be used for testing. + +# Major changes in this version + +- Restore improvements +Building further on the new restore engine, this version has significant performance speedups during restore. + +- New signing keys +The builds are now signed by "Duplicati Inc" for MacOS and Windows. +The Windows signatures are now done with an extended validation (EV) certificate and should not trigger Windows Defender warnings. + +- New enumeration logic +The enumeration of files and folders has been rewritten to better support some edge cases. + +- Improved pagination support +All backends are updated to support the new list methods that supports pagination and cancellation. +The test method will now only load a single page of results, instead of listing all items. + +- Support for backend-as-source +This version has support for backups of both remote and local data. +In this version, S3, IDrive, SSH and CIFS sources are supported. +The UI does not yet support this, but it is possible to read data from a remote location by "mounting" it into the local filesystem. +For the commandline (and manual text entry in the UI) enter sources such as: +``` +// Linux/MacOS +@/mnt/s3-data|s3://example?auth-username=... + +// Windows +@X:\server1|cifs://server/share?auth-username=... +``` + +# Potentially breaking changes (from 2.1.0.109) + - Location of data folder has changed in some cases, refer to previous release for details + - Permissions are now applied to the data folder, refer to previous release for details + - New user interface, `ngclient` is the default; there is a button to switch between the two + +## Detailed list of changes: +- Improved script examples +- Fixed double-click TrayIcon on Windows, thanks @Alex-Heard +- Fixed an issue with CORS headers +- Parallel downloads for restore +- Report interface and port in Webserver startup message +- Fixed missing example script on Windows +- Fixed a case where a leading slash could be stripped from the path or a URL +- Added additional Infomaniak endpoints, thanks @ochaze +- Backend listing is now async for better pagination and cancellation support +- Updated signing support for the release builds +- Added a link from `ngax` to `ngclient` (the other way already exists) +- Added alternate source providers and new enumeration process +- Performance optimizations for restore process + +2025-02-11 - 2.1.0.109_canary_2025-02-11 +========== +This release is a canary release intended to be used for testing. + +# Important change for Windows Service +This build changes the folder locations for the `Duplicati-server.sqlite` file on Windows and Linux when running as a service. +There is support for backwards compatibility, so existing installations should not be affected. + +The detailed changes are: +Windows: Avoid storing data in `C:\Windows\System32\config\systemprofile\AppData\Local\Duplicati` and prefer `{CSIDL_COMMON_APPDATA}\Duplicati` +Linux: Avoid storing data in `/Duplicati` and prefer `/var/lib/Duplicati`. + +If you are not running Duplicati as a service, this has no effect. +If you are using `--server-datafolder` or `DUPLICATI_HOME`, this has no effect on the database, but may cause your machineid and installid to change. + +The `machineid.txt` and `installid.txt` would previously be stored in the local app data folder, even when using portable mode or choosing a specific data folder. +This has been fixed, so the files will now follow the database. +If you are using the Duplicati console or otherwise depend on these values, you need to move them into the folder where the database is stored. + +## Important for users running 2.1.0.108 +For **2.1.0.108 version only**, the service folder was `{SYSTEMDRIVE}\Users\LocalService\Duplicati` which was dropped. +If you have only installed 2.1.0.108 and ended up using the previous folder, you must manually move it to `{CSIDL_COMMON_APPDATA}\Duplicati`. + +# Permissions on database and data folder +This update sets permissions on the data folder and the databases to prevent unauthorized access from local accounts. +To opt out of setting permissions on each startup, place a file named `insecure-permissions.txt` inside the data folder. + +# New user interface +This version uses the `ngclient` as the default interface. If some feature is not working as expected, it is possible to switch back to the `ngax` client. +To switch client, replace `ngclient` with `ngax` in the url, or use the `theme.html` page to choose the UI (example: `http://localhost:8200/theme.html`). +Please report any issues with the new UI. + +# Removed timeout guard +Due to many reports of failures with the timeout mechanism this has been removed in the current version. It will be added back into each of the backends later. + +# Added a new tool for offsite backup +This release features a new tool: `Duplicati.CommandLine.SyncTool.exe` / `duplicati-sync-tool` which can be used to create copies of backup files in multiple locations. The tool is currently documented with the built-in help, and more documentation is on the way. The tool is intended to be more integrated into Duplicati later and enables a straightforward way to achieve 3-2-1 backups, including copies in immutable storage. + +## Detailed list of changes: +- Support topics on Telegram reports +- Added remote syncronization tool +- Removed high-level backend timeout +- Added guard to prevent dataloss on shared destinations +- Added option to disable automatic update checks +- Fixed a bug with the new restore that could cause incorrect files to be restored if files alredy exists +- Fixed a case where updated timestamps were discarded if no data was changed +- Added permission updates to data folder +- Fixed automatic fallback on unsupported Zip files +- Fixed multiple issues with TrayIcon state +- Mark Mega.nz backend as deprecated +- Switched to ngclient as the default UI +- Added support for 429 responses on B2 + +2025-01-31 - 2.1.0.108_canary_2025-01-31 +========== +This release is a canary release intended to be used for testing. + +# Important change +This build changes the folder locations for the `Duplicati-server.sqlite` file on Windows and Linux when running as a service. +There is support for backwards compatibility, so existing installations should not be affected. + +The detailed changes are: +Windows: Avoid storing data in `C:\Windows\System32\config\systemprofile\AppData\Local\Duplicati` and prefer `{SystemRoot}\Users\LocalService\Duplicati` +Linux: Avoid storing data in `/Duplicati` and prefer `/var/lib/Duplicati`. + +If you are not running Duplicati as a service, this has no effect. +If you are using `--server-datafolder` or `DUPLICATI_HOME`, this has no effect on the database, but may cause your machineid and installid to change. + +The `machineid.txt` and `installid.txt` would previously be stored in the local app data folder, even when using portable mode or choosing a specific data folder. +This has been fixed, so the files will now follow the database. +If you are using the Duplicati console or otherwise depend on these values, you need to move them into the folder where the database is stored. + +# New UI +This release contains an updated version of the new UI. +In the url, change `ngax` to `ngclient` to try the new UI. + +## Detailed list of changes: +- Fixed encoding of exported files +- Fixed issue with restore trying to read files opened with write-only +- Improved backend tester +- Missing file detection during recreate is now a warning +- Fixed Minio backend not throwing exceptions +- Fixed hidden files display in source picker +- Added additional source folders for MacOS +- Removed SHA1 signatures on Windows signing +- Rewrote the backend manager to better control backend (re-)use +- Updated reporting to support sending OS type and backend type +- Tagging Docker images with channel, thanks @stavros-k +- Added support for CORS headers in the Webserver +- Unified help arguments across all executables +- Fixed parsing flags as multiple values for FTP SSL type +- Added guard against storing files in Windows folder +- Fixed crash in AutoUpdater + +2025-01-17 - 2.1.0.107_canary_2025-01-17 +========== +This release is a canary release intended to be used for testing. + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +This release contains a substantially updated version of the new UI. +In the url, change `ngax` to `ngclient` to try the new UI. + +## Detailed list of changes: +- Avoid optimize after vacuum +- Prevent inifinite shutdown loop +- Fixed a recreate database issue +- Added a warning in the UI if the volume size is excessive +- Fixed an issue with AWS S3 permission check +- Prevent a termination error caused by unused transactions +- Updated B2 backend to use HttpClient +- Changed timeouts for WebDAV +- Updated new UI (ngclient) + +2025-01-11 - 2.1.0.106_canary_2025-01-11 +========== +This release is a canary release intended to be used for testing. + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +## Detailed list of changes: +- Added server status and wait to ServerUtil +- Fixed an issue where index files would contain replicated blocklists +- Fixed an issue with duplicate blocks preventing repair +- Size strings are now formatted as kikibytes +- Fixed some issues with CIFS/SMB backend + +2025-01-07 - 2.1.0.105_canary_2025-01-07 +========== +This release is a canary release intended to be used for testing. + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +## Detailed list of changes: +- Fix issue with FTP not connecting correctly +- Improved large restore speed (+5TiB), thanks @snamds +- Corrected reported options for Azure, Aliyun, OpenStack and Storj +- OpenStack detects protocol version from URL +- Sia redundancy option is now a decimal (culture invariant parsing) +- Added help links to connection dialog and login pages +- Fixed issue with new restore not auto-creating missing folder structure +- Fixed issue with new restore without overwrite +- Fixed a place not honoring disabled quota +- Added CIFS / SMB backend +- Updated MacOS DMG image + +2024-12-31 - 2.1.0.104_canary_2024-12-31 +========== +This release is a canary release intended to be used for testing. + +**Note that this build is using .NET8, unlike the 2.0.8.1 build** + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +## Detailed list of changes: +- Fixed issue with launchAgent for CLI server not taking an argument +- Added simultaneous file processors, thanks @gpatel-fr +- Updated translations, thansk to many contributors! +- Added NOOP Daemon for FTP backend +- Excluded Google Drive and GCS from timeout streams +- Fixed an issue with the TrayIcon being disconnected after 15 minutes +- Removed the captcha fro full backup removal +- Reduced number of backend instances created +- Removed Windows-only options on the File backend on non-Windows +- Added option to log FTP messages to regular log + +2024-12-21 - 2.1.0.103_canary_2024-12-21 +========== +This release is a canary release intended to be used for testing. + +**Note that this build is using .NET8, unlike the 2.0.8.1 build** + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +## Detailed list of changes: +- Updated MacOS LaunchAgents to run as current user +- Updated preloader logic to search additional paths +- Fixed a few potential issues in JS +- Disabled openid probing on the server +- Fixed problem with FTP backend not unescaping special characters +- Reduced amount of logging output from Server +- Fixed bugreport download +- Fixed pause/stop/resume/abort and throttle control +- Fixed issues with timeouts in Microsoft Graph-based backends +- Fixed a potential exit crash +- Renamed some strings from "OneDrive v2" to "OneDrive" +- Removed `--use-http-client` from Microsoft Graph-based backends +- Logging on Windows moved to separate Windows Log + +2024-12-12 - 2.1.0.102_canary_2024-12-12 +========== +This release is a canary release intended to be used for testing. + +**Note that this build is using .NET8, unlike the 2.0.8.1 build** + +**Note that this build has a redesigned restore flow** +Should issues arise, the previous restore engine can be enabled with `--restore-legacy=true`. + +## Detailed list of changes: +- Upgraded FluentFTP to fix a crash on socket timeout +- Removed named channels to fix a rare exception +- Rewrote the restore process to be multithreaded and target optimal disk I/O write performance +- Added missing documentation to the iDrive e2 backend +- Added activity timeout on backend transfers +- Added support for generating a "forever token" + +2024-12-06 - 2.1.0.101_canary_2024-12-06 +========== +This release is a canary release intended to be used for testing. + +**Note that this build is using .NET8, unlike the 2.0.8.1 build** + +## Breaking change from previous canary +The pCloud backend is updated to support subfolders. +Paths with `/` will now be treated as a folder structure on pCloud. +Previous version would treat `folder/subfolder` as a single foldername, +this version treats it as `folder` / `subfolder`. + +## Detailed list of changes: +- Fixed a case where the TrayIcon would hang in detached mode +- Improved OpenAPI description for filter groups +- Fixed an issue with websocket requests through a proxy +- Updated icons for Linux and dark-mode MacOS +- Added logging of unobserved exceptions +- Fixed a compatibility issue with FTP not correctly detecting explicit TLS +- Fixed an issue where the quota was reported incorrectly +- Removed the unused `--use-block-cache` option +- Agent now has a dedicated `register` command +- Sizes can now be supplied as kikibytes, eg. `20kib` +- Fixed supporting subfolders in pCloud +- Added option to send http requests with relaxed TLS security +- Agent can now be asked to retain the previous UI password +- Updated SSH.NET to v2024.2.0 + +2024-11-25 - 2.1.0.100_canary_2024-11-25 +========== +This release is a canary release intended to be used for testing. + +**Note that this build is using .NET8, unlike the current beta** +See release notes from previous canary builds for details. + +## Detailed list of changes: +- Fixed startup delay caused by AWS upgrade +- Running WindowsService INSTALL will now automatically start the new service +- Removed log messages from GPG detection +- Fixed an issue with Agent not being able to see connection status in the UI +- Added Downloads folder to list of special folders +- Fixed an issue with the remote control heartbeat period +- Re-introduced the crashlog feature +- Updated FTP backend to be compatible with the previous FTP backend +- Fixed an issue with quiet MSI upgrades +- MSI will automatically attempt to stop and restart the service during install +- Added single-click handler to TrayIcon on Linux+Windows +- Added pCloud native backend +- Fixed issue with DST changes causing schedule time-of-day to change +- Fixed issue with lingering update notification +- Added option to ignore snapshot warnings from HyperV on client-Windows + +2024-11-14 - 2.0.9.111_canary_2024-11-14 +========== +This release is a canary release intended to be used for testing. +This release is expected to progress to a stable release if no major blockers are found. + +**Unlike regular canary builds, this one has a major change in the build system, so it now runs on .NET8.** +The builds are self-contained so Mono or .NET installations are not required to install. + +**Important changes from last Beta** +- Updated to .NET8 with OS specific builds +- Using Kestrel as the API/UI server +- Mandatory password and new authentication scheme for server +- Settings database version updated to v8 +- Backup database updated to v13 + +# New tool to manage a running server +Due to incompatibility with `duplicati_client` a new tool is included, named `Duplicati.CommandLine.ServerUtil.exe`/`duplicati-server-util`. + +# Upgrade from `v2.0.9.105` +If you are upgrading from 2.0.9.105 please see the release notes from 2.0.9.106 for how to prepare the database. +Upgrades from other versions do not need special steps. + +## Detailed list of changes: +- Added missing options to Agent commandline +- Fixed a warning when using the S3 backend +- Sending package type id to usage reporter +- Reduced log output and increased heartbeat period for Agent + +2024-11-09 - 2.0.9.110_canary_2024-11-09 +========== +This release is a canary release intended to be used for testing. +This release is expected to progress to a stable release if no major blockers are found. + +**Unlike regular canary builds, this one has a major change in the build system, so it now runs on .NET8.** +The builds are self-contained so Mono or .NET installations are not required to install. + +For users running on builds prior to 2.0.9.x, this build will appear later as an update that requires manual installation. + +**Important changes from last Beta** +- Updated to .NET8 with OS specific builds +- Using Kestrel as the API/UI server +- Mandatory password and new authentication scheme for server +- Settings database version updated to v8 +- Backup database updated to v13 + +Please see list of known issues related to .NET8/Kestrel upgrade: +https://github.com/orgs/duplicati/projects/2 + +# New tool to manage a running server +Due to incompatibility with `duplicati_client` a new tool is included, named `Duplicati.CommandLine.ServerUtil.exe`/`duplicati-server-util`. + +# Upgrade from `v2.0.9.105` +If you are upgrading from 2.0.9.105 please see the release notes from 2.0.9.106 for how to prepare the database. +Upgrades from other versions do not need special steps. + +## Downgrade backup database to v12 +If you need to go back to v12, run these two SQL statements: +``` +DROP INDEX "UniqueBlockVolumeDuplicateBlock"; +UPDATE "Version" SET "Version" = 12; +``` + +## Detailed list of changes: +- Minor updates to help text and option types +- Fixed an issue with file backend url on Windows, thanks @Jojo-1000 +- Fixed a condition that could hang the trayicon on a crash +- Updated AWSSDK to latest version +- Fixed an issue with the AWS Secrets Manader provider stalling the load of settings +- Fixed Telegram notifications +- Added Agent MSI installer +- Updated Avalonia to the latest version to fix crash on Debian +- Added Agent PKG for MacOS +- Added CLI PKG for MacOS +- Added autolaunch for Agent Deb package +- Added option to let scripts create log entries + +2024-11-06 - 2.0.9.109_canary_2024-11-06 +========== +This release is a canary release intended to be used for testing. +This release is expected to progress to a stable release if no major blockers are found. + +**Unlike regular canary builds, this one has a major change in the build system, so it now runs on .NET8.** +The builds are self-contained so Mono or .NET installations are not required to install. + +For users running on builds prior to 2.0.9.x, this build will appear later as an update that requires manual installation. + +**Important changes from last Beta** +- Updated to .NET8 with OS specific builds +- Using Kestrel as the API/UI server +- Mandatory password and new authentication scheme for server +- Settings database version updated to v8 +- Backup database updated to v13 + +Please see list of known issues related to .NET8/Kestrel upgrade: +https://github.com/orgs/duplicati/projects/2 + +# New tool to manage a running server +Due to incompatibility with `duplicati_client` a new tool is included, named `Duplicati.CommandLine.ServerUtil.exe`/`duplicati-server-util`. + +# Upgrade from `v2.0.9.105` +If you are upgrading from 2.0.9.105 please see the release notes from 2.0.9.106 for how to prepare the database. +Upgrades from other versions do not need special steps. + +## Downgrade backup database to v12 +If you need to go back to v12, run these two SQL statements: +``` +DROP INDEX "UniqueBlockVolumeDuplicateBlock"; +UPDATE "Version" SET "Version" = 12; +``` + +## Detailed list of changes: +- Updated list of S3 endpoints to include Segate Lyve +- Updated all backends to support asyncronous calls +- Bugfixes for the new API implementation +- Bugfix for recreate failing on some systems +- Added option to disregard advisory locking on Linux +- Fixed issues with WebDAV and self-signed certificates +- Removed the deprecated FTP backend and made FluentFTP handle both FTP and aFTP +- Fixed a performance issue with compacting +- Fixed issues with remote control registration +- Fixed a performance issue with SharpAESCrypt +- Fixed an incorrect error masking another error in backups +- Support for EC-based certificates, thanks @mnaiman +- Always excluding the SQLite journal file for the active database +- Added support for detecting self-signed certificates and securely accepting them +- Cleaned up CSS/Less files, thanks @luixxiul +- Improved IPv6 support for `loopback` and `any` +- Fixed Rclone backend not setting defaults for empty strings +- Fixed a bug in the recompress tool, if not using encryption +- Fixed an issue where SharePoint would not honor the server throttle requests +- Improved cleanup after removing volumes, thanks @Jojo-1000 +- Removed 7z compression +- Simplified showing the default value of boolean options +- Fixed an issue with leftover index files, thanks @Jojo-1000 and @ts678 +- Fixed an issue that would cause verification errors if the compact was interrupted, thanks @Jojo-1000 and @ts678 +- Fixed an issue where `dindex` files would be missing a blocklist, causing extra download on restores +- Fixed CSS/Less to rely on autoprefixer, thanks @luixxiul +- Added support for reducing the system load intensity +- Disabled Telegram notification destination +- Added support for external secret providers + 2024-10-03 - 2.0.9.108_canary_2024-10-03 ========== This release is a canary release intended to be used for testing in preparation of a later stable release. @@ -474,1547 +1022,3 @@ Points of attention: manually adjusting the version number in the database. The additions can be re-applied if the database is upgraded again later. - -2023-05-25 - 2.0.7.0_experimental_2023-05-25 -========== -After almost 2 years, a new Beta has taken its time, but it’s finally -there. It’s a roll-up of all the fixes since the last beta build and is -expected to cause very few issues when upgrading. This release is almost -identical to Canary 106. - -It involves a better UI for dark mode, better support for Dropbox, -Jottacloud, S3, Tencent backends, add IDrive backend, TLS modernization, -bug fixes, upgrades to libraries. - -Points of attention: - -- database upgrade from 11 to 12: Downgrade from this version requires -manually adjusting the version -number in the database. The additions can be re-applied if the database is -upgraded again later. - -2023-05-03 - 2.0.6.106_canary_2023-05-03 -========== -- Added endpoints for S3 compatible storage from Scaleway, thanks -@adithajay -- updates rumps to python3, add icon normal-warning, set rumps as default, -thanks @gpatel-fr - -2023-04-09 - 2.0.6.105_canary_2023-04-09 -========== -- Disable automatic use of v2 authid for Jottacloud, thanks @albertony -- Fix gui tests, thanks @gpatel-fr -- Make Xamarin-based CocoaRunner the default on Mac, thanks @dgileadi -- Fix #4716 by falling back to System hasher, thanks @vilaureu -- Allow install on Debian bookworm by using libayatana-appindicator1, thanks @gpatel-fr -- Build Debs with gzip compression for old debian releases, thanks @gpatel-fr -- Add Impossible Cloud provider for S3 backend, thanks @daniel0m0baker -- Add possibility to set custom SQlite pragmas, thanks @gpatel-fr -- Updates Newtonsoft, Mega and SSH.NET libraries, thanks @gpatel-fr -- Raise the file time shift to 2s for Mac unittests, thanks @gpatel-fr -- Remove obsolete Letsencrypt cert in Docker builds, thanks @Bubblesaway (forum) - -2022-06-15 - 2.0.6.104_canary_2022-06-15 -========== -* Updated compressed files exclusion list, thanks @martengooz -* Updated list of Infomaniak servers, thanks @linitio -* Fixed irregular parsing of `--patch-with-local-blocks`, thanks @warwickmm -* Added `IDrivee2` backend, thanks @MuhammadAbrar1984 -* Fixed `--use-move-for-put` which broken in previous build, thanks @kenkendk - -2022-06-12 - 2.0.6.103_canary_2022-06-12 -========== -* Avoid creating empty data folders, thanks @albertony -* Disable updates for debug builds, thanks @AxeOfMen -* Fixed issue with empty popups, thanks @AxeOfMen -* Improved Python verification script, thanks @mr-flibble -* Fixed issue where invalid timestamps would prevent files from being backed up, thanks @albertony -* Improved restore progress information, thanks @flawiddsouza -* Python verification script now has statistics, thanks @mr-flibble -* Added Swiss Backup to list of OpenStack and S3 providers, thanks @PAPAMICA -* Fixed errors on updated Hyper-V versions, thanks @awerghcpc -* Added check to detect partially written files on network drives, thanks @dkrahmer -* Added IDrive as a backup destination, thanks @dkrahmer -* Removed insecure TLS versions, thanks @mnaiman -* Changed Jottacloud backend to use OAuth, thanks @albertony - -2022-04-06 - 2.0.6.102_canary_2022-04-06 -========== -**NOTE**: This is a rebuild of 2.0.6.101, now signed. - -- Fixed duplicity documentation Url, thanks @beppo-dd -- Added option to set Tencent storage class, thanks @moesoha -- Added Poli Systems as S3 provider, thanks @IIPoliII -- Improved automated unittests, thanks @samuel-w -- Improved contrast ratios in themes, thanks @samuel-w -- Improved handling of relative paths and extended device paths, thanks @dferreyra -- UI performance improvements, thanks @ltGuillaume -- Added exponential backup for retries, thanks @vmsh0 -- Improved filter-text-box and made log-view resizable, thanks @idotj -- Improved error messages, thanks @warwickmm -- Added support for old and new Dropbox AuthIDs, thanks @kenkendk -- Improved database query performance, thanks @jedthe3rd - -2022-03-13 - 2.0.6.101_canary_2022-03-13 -========== -**NOTE**: This is a preliminary build that is not signed with AuthentiCode. - -It is primarily intended for users that need access to Dropbox which stopped working after an update. -A signed version will be published asap. - -- Fixed duplicity documentation Url, thanks @beppo-dd -- Added option to set Tencent storage class, thanks @moesoha -- Added Poli Systems as S3 provider, thanks @IIPoliII -- Improved automated unittests, thanks @samuel-w -- Improved contrast ratios in themes, thanks @samuel-w -- Improved handling of relative paths and extended device paths, thanks @dferreyra -- UI performance improvements, thanks @ltGuillaume -- Added exponential backup for retries, thanks @vmsh0 -- Improved filter-text-box and made log-view resizable, thanks @idotj -- Improved error messages, thanks @warwickmm -- Added support for old and new Dropbox AuthIDs, thanks @kenkendk -- Improved database query performance, thanks @jedthe3rd - -2021-08-11 - 2.0.6.100_canary_2021-08-11 -========== -* Switched to improved CI model, thanks @warwickmm -* Fixed issue with reporting wasted space, thanks @aureliandevel -* Fixed throttling requests to OneDrive and respecting the server `retry-after` headers, thnaks @tygill -* File backend now overwrites files, thanks @warwickmm -* Added `.dar` files to list of compressed file extensions, thanks @samuel-w -* Fixed typos in example scripts, thanks @warwickmm -* The `Makefile` now calls `msbuild`, thanks @warwickmm -* When testing for known compressed file extensions, the compare is now case-insensitive, thanks @samuel-w -* Improved code quality, thanks @marodev -* Fixed an issue with handling the deprecated Azure and S3 options, thanks @warwickmm -* Avoid using ECDSA algorithm for SFTP if the client does not support it, thanks @warwickmm -* Added SAS token support to Azure backend, thanks @sergethedev17 -* Improved test method for aFTP backend, thanks @sergethedev17 -* Added support for mega.nz 2fa, thanks @vfrz -* Changed from Tardigrade backend to Storj DCS backend, thanks @TopperDEL -* Removed BouncyCastle dependency and now using .Net built-in parsing of SSL certificates, thanks @mnaiman -* Regex filters now match newlines in paths with wildcards, thanks @warwickmm -* Reduced number of cases where database cleanup is triggered, thanks @warwickmm -* Fixed a dispose bug in most backends introduced with `PutAsync`, thanks @warwickmm -* Updated UI to more prominently display deprecation information, thanks @warwickmm -* Fixed handling of special characters in paths on the aFTP backend, thanks @warwickmm - -2021-06-17 - 2.0.6.3_beta_2021-06-17 -========== -This is a minor bugfix release with only two changes, which affects a small number of users: - -* Disabled SSH backend ECDSA keys on Mono as it does not work (see issue #4506) -* Fixed priority of options from AWS and Azure backend if both underscore and dashed version were used (see issue #4513) - -2021-05-29 - 2.0.6.2_experimental_2021-05-29 -========== -This is a minor bugfix release with only two changes: - -* Disabled SSH backend ECDSA keys on Mono as it does not work (see issue #4506) -* Fixed priority of options from AWS and Azure backend if both underscore and dashed version were used (see issue #4513) - -2021-05-03 - 2.0.6.1_beta_2021-05-03 -========== -## First beta build in over a year! - -We have been hard at work adding new features and fixing issues. This beta release is a roll-up of all the fixes since the last beta build and is expected to cause very few issues when upgrading. This release is based on 2.0.6.0_experimental. - -A big thanks to all Duplicati contributors, supporters, fans and users for keeping the project evolving and improving. - -### New in this version - -- Added Tardigrade backend -- Added Tencent Cloud Object Storage (COS) -- More Docker image versions -- Improved long-path support on Windows - -We changed some larger things that you should be aware of when upgrading. - -### The SSH backend has been updated -This update increases the security by prefering stronger encryption algorithms. -However, this _can_ cause failing backups as the host key is reported as "changed". -If this happens, edit the backup and update the host key. - -### Database version update from v10 to v11! -Downgrade from this version requires manually adjusting the version number in the database. The additions can be re-applied if the database is upgraded again later. - -### Other changes that needs attention - -- Duplicati now requires Mono 5.10 or greater -- S3 and Sharepoint option names on commandline now uses `-` for consistency (previously used `_`) - -2021-03-10 - 2.0.5.114_canary_2021-03-10 -========== -## This build intends to remove some erronous warnings from the S3 backend introduced in the previous canary. - -## Detailed changes - -* Fixed the cURL error message url, thanks @warwickmm -* Fixed the S3 warning messages, thanks @warwickmm - -2021-03-07 - 2.0.5.113_canary_2021-03-07 -========== -## This build has two potentially breaking changes: - -### The Telegram backend is no longer included -If you rely on this backend, do not upgrade. - -### The SSH backend has been updated -This update increases the security by prefering stronger encryption algorithms. -However, this _can_ cause failing backups as the host key is reported as "changed". -If this happens, edit the backup and update the host key. - - -## Detailed list of changes - -* Fixed case where the stop button in the UI would be hidden behind the progress bar, thanks @jfparis -* Fixed tools to be Python3 compatible, thanks @jfparis -* Removed the Telegram backend, due to poor user feedback, thanks @warwickmm -* Reduced build sized by clearing unused Tardigrade library, thanks @warwickmm -* Fixed issue where license files were not correctly included in build, thanks @warwickmm -* Fixed cases where the `run-script` would get incorrect result values, thanks @warwickmm -* Updated SSH.Net to 2020.0.1, thanks @warwickmm -* Fixed an error when repairing a database, thanks @warwickmm -* Changed options to use `-` instead of `_` for consistency, thanks @julianohubel -* Fixed help output to hide deprecated options in some cases, thanks @kenkendk -* Updated login page to redirect better when a proxy is in use, thanks @scambra -* Fixed layout issues with scrollbars, thanks @warwickmm -* Updated to using specific Mono versions when building images and testing, thanks @warwickmm -* Improved unittesting, thanks @warwickmm -* Exclude `$XDG_CACHE_HOME` as part of the normal filters, thanks @warwickmm - -2021-01-20 - 2.0.5.112_canary_2021-01-20 -========== -Hightlights for this update: -* Stability improvements -* Error/help message improvemens -* New Telegram backend - -Detailed list: -* Improved testing, thanks @warwickmm -* Disabled legacy path handling, thanks @dferreyra -* Bugfix for rclone backend (wrong paths), thanks @warwickmm -* Bugfix for Sia backend, thanks @warwickmm -* Fixed check for admin privileges on Synology, thanks @warwickmm -* Defer uploading fileset to guard against missing blocks error, thanks @seantempleton -* Fixed setting background-io-priority, thanks @achims311 -* Added Telegram backend, thanks @martikyan -* Improved error logging on restore, thanks @barfoo4711 -* Improved commandline documentation, thanks @apreiml -* Bugfix for run-script-before ignoring remoteurl, thanks @warwickmm -* Better error messages, thanks @warwickmm -* Code quality improvements, thanks @warwickmm -* Fixed error that occured when using the dry-run option of purge-broken-files, thanks @warwickmm -* Fixed logging errors when reading symlink targers, thanks @warwickmm -* Fixed error in OpenStack configuration UI, thanks @warwickmm -* Better error messages if webrequests return null, thanks @Gurthurb -* Fixed password manager autofill, thanks @drwtsn32x -* Updated CI GUI tests to work again and use Python3, thanks @warwickmm -* Fixed an issue that caused restore errors when running in Docker, thanks @warwickmm - -2020-09-26 - 2.0.5.111_canary_2020-09-26 -========== -* Support for Windows paths starting with "\\?\", thanks @dferreyra -* Improved performance of USN, thanks @dgehri -* Improved performance for restoring, thanks @pcmind -* Added support for filters with literals that would normally be treated as wildcards, thanks @dferreyra -* Improved build and tests, thanks @warwickmm and @dferreyra -* Switched to the new built-in .Net support for long paths, thanks @dferreyra -* Fixed issues with the Docker images by including mono-complete, thanks @warwickmm -* Fixed an issue with exporting passwords with url-encoding characters, thanks @warwickmm -* Fixed an issue with displaying icons in the restore tree, thanks @warwickmm -* Fixed a type that prevented a warning box from showing, thanks @warwickmm -* Added Storadera as an S3 destination, thanks @warwickmm - -2020-08-10 - 2.0.5.110_canary_2020-08-10 -========== -* Fixed an issue with snapshot paths, thanks @dferreyra - -2020-08-07 - 2.0.5.109_canary_2020-08-07 -========== -* Fixed hiding compression errors, thanks @jpschewe -* Fixed Tardigrade backend issues, thanks @TopperDEL -* Fixed a case where the recovery tool would fail to recover files witth dashes in the name, thanks @dferreyra -* Improved handling of "problematic" filenames on Windows and increased test coverage, thanks @dferreyra -* Improved test coverage for invalid paths and removed unused code, thanks @warwickmm -* Improved code to remove remote files to better support remote folders that are not exclusively used by Duplicati, thanks @warwickmm -* Updated to use and require .Net 4.7.1, thanks @warwickmm -* Updated USN support to work with the newer Windows features for moving instead of deleting files, thanks @dgehri - -2020-07-09 - 2.0.5.108_canary_2020-07-09 -========== -* Removed unsupported compiler directives, thanks @kenkendk -* Removed incorrect missing SSL certificate warnings, thanks @kenkendk -* Upograded to retina icons, thanks @baumerdev -* Updates to Tardigrade support, thanks @TopperDEL -* Added Tencent Cloud Object Storage (COS) backend, thanks @trueai-org -* Improved error reporting for S3 backend, thanks @jpschewe -* Added the Milan EU region to AWS S3 regions, thanks @warwickmm -* Fixed incorrect documentation on OpenStack help, thanks @warwickmm -* Fixed an incorrect endpoint for AWS S3 Bahrain, thanks @warwickmm -* Code cleanup, thanks @warwickmm -* Fixed a case where restoring files could fail if the containing folder was not restored, thanks @warwickmm -* Improved unittests, thanks @warwickmm - -2020-05-26 - 2.0.5.107_canary_2020-05-26 -========== -* Added a backend for Tardigrade.io, thanks @TopperDEL -* Updated translations, thanks to all translators! - -2020-05-11 - 2.0.5.106_canary_2020-05-11 -========== -NOTE: this version updates the database version from v10 to v11! - -Downgrade from this version requires manually adjusting the version number in the database, -The additions can be re-applied if the database is upgraded again later. - -* Added certificate dependency to debian packages, thanks @borfast -* Sorting files and folders in the WEB UI, thanks @drwtsn32x -* Code cleanup, unused code removal and more, thanks @warwickmm -* Updated exclusion folder list, thanks @drwtsn32x -* Fixed spelling issues for CLI help, thanks @comradekingu -* Fixed the hostname check to be case-insensitive, thanks @warwickmm -* No longer removes AlphaFS and AlphaVSS files from non-Windows packages, thanks @warwickmm -* Fixed a case where backups could run immediately and ignore the scheduled time, thanks @warwickmm -* Added a database index that signficantly improves database rebuilds, thanks @warwickmm -* Added MyCloudPlace as an S3 provider, thanks @cedbossneo -* Improved errors from Json source, thanks @kenkendk -* Changed mentions of "Amazon S3" to "S3 compatible", thanks @kenkendk - -2020-03-25 - 2.0.5.104_canary_2020-03-25 -========== -* Improved log messags when using retetion rules, thanks @warwickmm -* Added logging for USN soft-failures, thannks @drwtsn32x -* Improved regexp magic for parsing Windows, thanks @drwtsn32x -* Fixed storing `dlist` file after interrupted backup, thanks @seantempleton -* Improved logic around throttle values, thanks @seantempleton -* Fixed issues with Windows metadata and inheritance, thanks @drwtsn32x -* Major code cleanup, removing unused members and other, thanks @warwickmm -* Corrected retry attempts log message, thanks @warwickmm -* Fixed device-type reports on Mono, thanks @warwickmm -* Improved error messages during HTTP timeouts in some cases, thanks @warwickmm -* Reworked the MS-Graph backend to use an alternate HTTP implementation, thanks @tygill -* Fixed some cases where an interrupted backup could cause database corruption, thanks @seantempleton - -2020-02-18 - 2.0.5.103_canary_2020-02-18 -========== - -This is a rebuild of 2.0.5.102 with a fix for MacOS packages - -* Improved performance of the recovery-tool, thanks @tfriedel and @warwickmm -* Debian and RPM packages now require Mono 5.0, thanks @drwtsn32x -* Fixed issue with USN returning too many results, thanks @drwtsn32x -* Code quality improvements, thanks @warwickmm -* Added `.odg` and `.otg` as compressed file types, thanks @warwickmm -* Improved USN changes by looking at size and attributes, thanks @drwtsn32x -* Code quality improvements, thanks @drwtsn32x -* Unittest improvements, thanks @warwickmm -* The OVH backend now points to Keystone v3, thanks @warwickmm -* Fixed allowing minutes in retention rules, thanks @Pectojin -* Fixed loading SQLite on Windows, thanks @warwickmm - -2020-02-18 - 2.0.5.102_canary_2020-02-18 -========== -* Improved performance of the recovery-tool, thanks @tfriedel and @warwickmm -* Debian and RPM packages now require Mono 5.0, thanks @drwtsn32x -* Fixed issue with USN returning too many results, thanks @drwtsn32x -* Code quality improvements, thanks @warwickmm -* Added `.odg` and `.otg` as compressed file types, thanks @warwickmm -* Improved USN changes by looking at size and attributes, thanks @drwtsn32x -* Code quality improvements, thanks @drwtsn32x -* Unittest improvements, thanks @warwickmm -* The OVH backend now points to Keystone v3, thanks @warwickmm -* Fixed allowing minutes in retention rules, thanks @Pectojin -* Fixed loading SQLite on Windows, thanks @warwickmm - -2020-01-23 - 2.0.5.101_canary_2020-01-23 -========== -* Improved tests, thanks @drwtsn32x -* Fixed a case where repair fails due to missing `dindex` files, thanks @warwickmm -* Fixed an issue where compacted files would miss a blocklist, thanks @seantempleton -* Now producing Docker multi-arch images, thanks @dnrce -* Improved code quality and testing, thanks @warwickmm - -2020-01-18 - 2.0.5.100_canary_2020-01-18 -========== -* Fixed issue with restoring permissions with inheritance, thanks @drwtsn32x -* Improved tests, thanks @warwickmm -* Fixed an issue that could be triggered by force stopping a backup, thanks @warwickmm - -2020-01-18 - 2.0.5.1_beta_2020-01-18 -========== -This beta release is a collection of additions and improvements from many contributors. -Since it is released more than a year after the last beta, there is a large number of changes. - -Important notes: -* On Linux, macOS, and other systems that require Mono, this version requires Mono v5 or later. - -* On macOS please remove the current beta and install this version, instead of using the updater. Doing so, will make Duplicati compatible with Gatekeeper and allow you to grant permissions. - -A big thanks to all Duplicati contributors, supporters, fans and users for keeping the project evolving and improving. - -Note that the beta `v2.0.4.23` has database version `8` and this release has database version `10`. This means that your local database will be upgraded once you run this version, and you cannot roll back without some manual intervention. - -Here is a short list of some of the more notable changes: - -* Numerous bug fixes and performance enhancements. -* Updated translations, new languages added. -* Improved support for macOS Catalina -* Add support for minio backend. -* Improve tray icon to reflect warning state. -* Fix long path issue when path has 259 or 260 characters. -* Remove support for Amazon Cloud Drive. -* Fix input issues with dark theme. -* Fix bug in handling filter expressions. -* Fix purge operation with simple filters and case-sensitive filesystems. -* Fix ignoring of --tempdir option when using the command-line. -* Improved list of S3 compatible options and providers. -* Fix ability to stop backup after current file has finished processing. -* Updates to support libraries -* Improve logging during database recreation. -* Add option to skip vacuum operations within specified duration, `--auto-vacuum-interval`. -* Add option to skip compact operations within specified duration, `--auto-compact-interval`. -* Fix backup corruption from upload throttling. #3787 -* Fix for “Unexpected difference in fileset”. #3800 -* Fix for “CheckingErrorsForIssue1400” and “FoundIssue1400Error”. #3868 -* Fix for “FTP (Alternative)” upload verification. -* Server database version is still 6. Backup database rose to 10. This may impede downgrades. - -2020-01-03 - 2.0.5.0_experimental_2020-01-03 -========== -This is once again a cumulative release for more than 6 months of hard work. -This release has a large number of stability improvements and speed optimizations. - -If you are using macOS, please re-install from the .dmg or .pkg, as the launcher has been updated to work with GateKeeper. -Using the built-in update mechanism will not update the launcer and will not work well with GateKeeper on macOS Catalina. - -A big thanks to the community members who work hard with fixing issues and responding on the forum: -https://forum.duplicati.com - -2019-12-29 - 2.0.4.38_canary_2019-12-29 -========== -* Fixed AutoUpdater environment setting to be parsed as a boolean, thanks @drwtsn32x -* Code cleanup for the SSH backend, thanks @verhoek -* Fixed an issue with restoring metadata on Windows, thanks @drwtsn32x -* Improved AppVeyor test builds, thanks @warwickmm -* Updated translations, thanks @kenkendk - -2019-12-12 - 2.0.4.37_canary_2019-12-12 -========== -* Updated and improved TrayIcon's for warning state, thanks @drwtsn32x -* Fixed a selection issue for file restores, thanks @drwtsn32x -* Updated build system and added tests, thanks @verhoek -* Added support for Minio as an S3 backend option, thanks @verhoek - -2019-12-08 - 2.0.4.36_canary_2019-12-08 -========== -* Removed Amazon Cloud Drive support, thanks @warwickmm -* Fixed an edge case where paths longer than 258 characters and short than 260 characters produced errors, thanks @drwtsn32x -* Fixed a number of edge cases where partial backups could create defect backups when used with retention rules, thanks @warwickmm -* Fixed a problem with SharePoint reporting conflicts after v2.0.4.35, thanks @warwickmm - -2019-11-29 - 2.0.4.35_canary_2019-11-29 -========== -* Made password input in dark-theme better, thanks @mithileshz -* Fixed resource leaks and shutdown code for OneDrive, thanks @tygill -* Report a warning if the backup was cancelled, thanks @warwickmm -* Do not warn if deletion fails but actually removes the remote file, thanks @drwtsn32x -* Prevent storing journal data on incomplete backups, thanks @drwtsn32x -* Disabled file attribute masking on Windows 10, thanks @drwtsn32x - -2019-11-05 - 2.0.4.34_canary_2019-11-05 -========== -* Stapling macOS notarization to bundles, thanks @kenkendk -* Removed option to store fileset in memory, thanks @tygill -* Fixed failure on password-protected servers introduced in 2.0.4.32, thanks @warwickmm -* Improved progress reporting, thanks @ltfish - -2019-11-01 - 2.0.4.33_canary_2019-11-01 -========== -* Re-added support for buffering requests via the http module, thanks @tygill -* Fixed an issue with invalid DateTimeOffset, thanks @warwickmm -* Added new macOS launchers that play nice with Gatekeeper in macOS Catalina, thanks @kenkendk -* Fixed an issue where `Exclude files whose names contains` would create invalid filters, thanks @kenkendk - -2019-10-24 - 2.0.4.32_canary_2019-10-24 -========== -* Code quality improvements, thanks @warwickmm -* Fixes to the `Alternative FTP` backend, thanks @kenkendk - -2019-10-19 - 2.0.4.31_canary_2019-10-19 -========== -* Updated translations, thanks all translators! -* Improved computing EPOCH style timestamps, thanks @kylestanfield -* Cleanup for removing deleted volumes with incorrect timestamps, thanks @BlueBlock -* Improved progress when deleting volumes, thanks @drwtsn32x -* Fixed an issue with ignoring the `--tempdir` option, thanks @drwtsn32x -* Fixed an issue restoring from a single-file backup, thanks @drwtsn32x -* Fixed filename color in dark theme, thanks @mithileshz -* Improved documentation and help texts, thanks @warwickmm -* Fixed parsing `%u`-style encoded unicode in URLs, thanks @warwickmm -* Fixed cases where a filelists could be omitted from `dindex` files, thanks @warwickmm -* Added a new tray-icon for the warning state, thanks @drwtsn32x -* Fixed a retry error where uploaded `dindex`-files would reference non-existing `dblock` files, thanks @warwickmm -* Code quality improvements, thanks @warwickmm -* Fixed a case where purging a single file would purge all *other* files, if the filesystem is case-sensitive, thanks @warwickmm - -2019-09-20 - 2.0.4.30_canary_2019-09-20 -========== -* Updated builds scripts to automatically notarize all packages for MacOS -* Updated all string using ... to us the ellipsis character, thanks @leela52452 -* Code quality improvements and more tests, thanks @warwickmm -* Removed escaping of pipe characters on Linux, thanks @kenkendk -* Updated CoCoL to avoid excessive CPU usage, thanks @kenkendk -* Updated translations, thanks to all translators! -* Fixed a crash when connecting to a password protected server instance from the trayicon, thanks @kenkendk - -2019-09-17 - 2.0.4.29_canary_2019-09-17 -========== -* Added workaround for bug in Mono 6.0, thanks @warwickmm -* Extra logging during database recreates, thanks @drwtsn32x -* Added options to MSI installer for parameterized installs, thanks @BlueBlock -* Improved debug building, thanks @BlueBlock -* Updated packages: FluentFTP, CoCoL, Microsoft.Azure, AWSSDK, MailKit, MimeKit, thanks @BlueBlock -* Improved progress bar status, thanks @drwtsn32x -* Fixed a parsing issue when reading the server path in the UI, thanks @FlyingFox333 -* Code quality improvements, thanks @warwickmm -* Added code to actually remove purged volumes, thanks @BlueBlock -* Updated bundled GPG and checking for user-installed GPG on Windows, thanks @BlueBlock -* Improved handling of the "Stop after current file" method, thnaks @BlueBlock -* Updated list of S3 locations and storage classes, thanks @kenkendk - -2019-09-05 - 2.0.4.28_canary_2019-09-05 -========== -* Updated CoCoL and WiX to fix rollback and upgrade errors, thanks @BlueBlock - -2019-09-03 - 2.0.4.27_canary_2019-09-03 -========== -* Fixed issues with the MSI packages not upgrading correctly, thanks @BlueBlock -* Fixed some assembly redirects and package references, thanks @BlueBlock -* Code cleanup for EPOCH times, thanks @warwickmm - -2019-09-02 - 2.0.4.26_canary_2019-09-02 -========== -== This is a rebuild of 2.0.4.24 with MSI packages and **new** assembly redirect fixes == - -Change list from 2.0.4.24: -* Code cleanup, thanks @warwickmm -* Removed extra console output, thanks @wjansenw -* Fixed disposing backends after job completion, thanks @warwickmm -* Fixed an issue with throttled write, thanks @warwickmm -* Spelling fix, thanks @BlueBlock -* Fixed documetation in `run-script` examples, thanks @beppo-dd -* Improved unittests, thanks @BlueBlock -* Improved auto-compact and auto-vacuum, thanks @drwtsn32x -* Fixed problem with exceeeding the number of parameters supported by SQLite, thanks @drwtsn32x -* Improved status display, thanks @drwtsn32x -* Improved bugreport database scrubbing, thanks @drwtsn32x -* Removed GoogleDrive trash items in listing, thanks @BlueBlock -* Fixed the rename method in the GoogleDrive backend, thanks @BlueBlock -* Improved GCS storage class text, thanks @warwickmm -* Improved unittests for easier local testing, thanks @warwickmm -* Added workaround for AFTP upload delay, thanks @BlueBlock -* Fixed issue with reading the largest path prefix, thanks @BlueBlock -* Added conditional download of unittest data zip files, thanks @BlueBlock -* CI testing improvements, thanks @BlueBlock -* Fixed sporadic issue with backups of files being written, thanks @BlueBlock -* Updated to .Net Framework 4.6.2 -* Fixed issue with temporary files not being deleted as expected, thanks @BlueBlock -* Support for forcing calendar dates in time pickers, thanks @LacunaSoftware - -2019-09-02 - 2.0.4.25_canary_2019-09-02 -========== -== This is a rebuild of 2.0.4.24 with MSI packages and assembly redirect fixes == - -Change list from 2.0.4.24: -* Code cleanup, thanks @warwickmm -* Removed extra console output, thanks @wjansenw -* Fixed disposing backends after job completion, thanks @warwickmm -* Fixed an issue with throttled write, thanks @warwickmm -* Spelling fix, thanks @BlueBlock -* Fixed documetation in `run-script` examples, thanks @beppo-dd -* Improved unittests, thanks @BlueBlock -* Improved auto-compact and auto-vacuum, thanks @drwtsn32x -* Fixed problem with exceeeding the number of parameters supported by SQLite, thanks @drwtsn32x -* Improved status display, thanks @drwtsn32x -* Improved bugreport database scrubbing, thanks @drwtsn32x -* Removed GoogleDrive trash items in listing, thanks @BlueBlock -* Fixed the rename method in the GoogleDrive backend, thanks @BlueBlock -* Improved GCS storage class text, thanks @warwickmm -* Improved unittests for easier local testing, thanks @warwickmm -* Added workaround for AFTP upload delay, thanks @BlueBlock -* Fixed issue with reading the largest path prefix, thanks @BlueBlock -* Added conditional download of unittest data zip files, thanks @BlueBlock -* CI testing improvements, thanks @BlueBlock -* Fixed sporadic issue with backups of files being written, thanks @BlueBlock -* Updated to .Net Framework 4.6.2 -* Fixed issue with temporary files not being deleted as expected, thanks @BlueBlock -* Support for forcing calendar dates in time pickers, thanks @LacunaSoftware - -2019-09-02 - 2.0.4.24_canary_2019-09-02 -========== -* Code cleanup, thanks @warwickmm -* Removed extra console output, thanks @wjansenw -* Fixed disposing backends after job completion, thanks @warwickmm -* Fixed an issue with throttled write, thanks @warwickmm -* Spelling fix, thanks @BlueBlock -* Fixed documetation in `run-script` examples, thanks @beppo-dd -* Improved unittests, thanks @BlueBlock -* Improved auto-compact and auto-vacuum, thanks @drwtsn32x -* Fixed problem with exceeeding the number of parameters supported by SQLite, thanks @drwtsn32x -* Improved status display, thanks @drwtsn32x -* Improved bugreport database scrubbing, thanks @drwtsn32x -* Removed GoogleDrive trash items in listing, thanks @BlueBlock -* Fixed the rename method in the GoogleDrive backend, thanks @BlueBlock -* Improved GCS storage class text, thanks @warwickmm -* Improved unittests for easier local testing, thanks @warwickmm -* Added workaround for AFTP upload delay, thanks @BlueBlock -* Fixed issue with reading the largest path prefix, thanks @BlueBlock -* Added conditional download of unittest data zip files, thanks @BlueBlock -* CI testing improvements, thanks @BlueBlock -* Fixed sporadic issue with backups of files being written, thanks @BlueBlock -* Updated to .Net Framework 4.6.2 -* Fixed issue with temporary files not being deleted as expected, thanks @BlueBlock -* Support for forcing calendar dates in time pickers, thanks @LacunaSoftware - -2019-07-14 - 2.0.4.23_beta_2019-07-14 -========== -This update only contains warnings to inform users on Amazon Cloud Drive that the service is discontinued. -The update will warn users running or configuring a backup stored on Amazon Cloud Drive. - -This does **NOT** affect other Amazon services such as S3. -If you do not use Amazon Cloud Drive, you can skip this update. - -For more information on the changes, please see: -https://forum.duplicati.com/t/amazon-cloud-drive-discontinued/7391 - -2019-06-30 - 2.0.4.22_canary_2019-06-30 -========== -* Fixed link to usage reporter, thanks @warwickmm -* Fixed using the B2 prefix, thanks @drwtsn32x -* Fixed data corruption caused by upload throttling, thanks @ts678 -* Fixed data corruption caused by compacting, thanks @ts678 and @warwickmm - -2019-06-28 - 2.0.4.21_experimental_2019-06-28 -========== -This is a cumulative release for more than 6 months of hard work. -Most of these are speedup improvements, error handling and general quality fixes. - -This release will give a warning for all users of the Amazon Cloud Drive backen, as it is being discontinued by Amazon: -https://forum.duplicati.com/t/amazon-cloud-drive-discontinued/7391 - -A big thanks to the community members who work hard with fixing issues and responding on the forum: -https://forum.duplicati.com - -2019-06-25 - 2.0.4.20_canary_2019-06-25 -========== -* Added warnings about AmzCD closing - -2019-06-17 - 2.0.4.19_canary_2019-06-17 -========== -* Show vacuum operation in status, thanks @drwtsn32x -* Remove deprectated options and code from backends, thanks @warwickmm -* Improved timeout handling for OAuth login, thanks @warwickmm -* Improved names of backup databases, thanks @drwtsn32x -* Removed the Google Plus icon, thanks @locked-fg - -2019-05-12 - 2.0.4.18_canary_2019-05-12 -========== -* Improved UI progress to show the VACUUM step, thanks @drwtsn32x -* Fixed GTK tray icons, thanks @warwickmm -* Improved error reporting from the webserver, thanks @warwickmm -* Code cleanup and quality improvements, thanks @warwickmm -* Ignoring empty remote files on restore, which speeds up recovery, thanks @pectojin -* Fixed subject line in email status messages, thanks @pectojin -* Fixed issues with Box and Amazon introduced in the latest canary, thanks @warwickmm -* Fixed issues with readling parameter file, thanks @warwickmm -* Improved filter handling for empty filters, thanks @dgehri -* Added API method to allow calling the VACUUM command from the browser, hanks @drwtsn32x - -2019-04-11 - 2.0.4.17_canary_2019-04-11 -========== -* Improved code quality, thanks @warwickmm -* Fixed an issue where index files were not generated, thanks @seantempleton -* Masked password fields in UI, thanks @drwtsn32x -* Added `fr_CA` translation and updated `fr` translation, thanks @flafleur -* Improved default exclude filters to catch Chrome file on Mac/Linux, thanks @flafleur -* Added support for Google Team Drive, thanks @mukut1994 -* Fixed random database names being numeric -* Corrected description for approving self-signed certificates, thanks @flafleur -* Click on balloon tip on Windows now opens the browser, thanks @flafleur -* Fixed an error that prevented bug report databases from being created, thanls @pectojin -* Fixed documentation for buffer sizes, thanks @jonmikeiv -* Fixed an issue where an upload error would not be reported, thanks @warwickmm -* Improved documentation, thanks @space-alien -* Fixed captcha generating out-of-bounds characters, thanks @oslerw - -2019-03-28 - 2.0.4.16_canary_2019-03-28 -========== -* Fixed an issue with Sia authentication, thanks @Ajedi32 -* Improved documentation, thanks @kesava-wow -* Improved code quality, thanks @warwickmm -* Added custom B2 download url option, thanks @aahung -* Updated list of Wasabi and S3 storage destinations, thanks @FroggieFrog -* Fixed a case where temporary files were not removed, thanks @warwickmm -* Code refactor and quality improvement, thanks @verhoek -* Added option for parallel uploads, thanks @seantempleton -* Improved exception messages, thanks @warwickmm -* Implemented Jottacloud multithreading uploads, thanks @nescafe2002 - -2019-02-06 - 2.0.4.15_canary_2019-02-06 -========== -* Fixed an issue with backup of a Windows drive (without a path) -* Clarified the B2 user interface, thanks @Pectojin - -2019-01-30 - 2.0.4.14_canary_2019-01-30 -========== -* Fixed an issue with upgrading the database from a previous version -* If you used v2.0.4.13, your database is likely broken, see the forum for details: https://forum.duplicati.com/t/release-2-0-4-13-canary-2019-01-29/ - -2019-01-29 - 2.0.4.13_canary_2019-01-29 -========== -* Added tests and improved code quality, thanks @warwickmm -* Changed the internal storage of paths to use a prefix method. This should reduce the size of the database significantly and enable much faster database queries later on -* Increased timeouts for reading the output from the commandline process to allow long running background jobs - -2019-01-16 - 2.0.4.12_canary_2019-01-16 -========== -* Many source code quality improvements, thanks @warwickmm -* Added test percentage option, thanks @warwickmm -* Fixed an issue with the log page not loading, thanks @LacunaSoftware -* Updated the Mega API, thanks @Pectojin -* Added tool to import a CLI backup into the server, thanks @warwickmm -* Improved build tools to separate and simplify the GPG signing step, thanks @v$ -* Fixed a case where listing files backed up from a UNC path are not shown, tha$ -* Removed the unused file lookup table, thanks @Pectojin -* Fixed an issue with the FTP backend where it would sporadically create files $ -* Fixed quota reporting from GoogleDrive, thanks @warwickmm -* Improved code quality, thanks @verhoek -* (rebuild of 2.0.4.11) - -2019-01-16 - 2.0.4.11_canary_2019-01-16 -========== -* Many source code quality improvements, thanks @warwickmm -* Added test percentage option, thanks @warwickmm -* Fixed an issue with the log page not loading, thanks @LacunaSoftware -* Updated the Mega API, thanks @Pectojin -* Added tool to import a CLI backup into the server, thanks @warwickmm -* Improved build tools to separate and simplify the GPG signing step, thanks @verhoek -* Fixed a case where listing files backed up from a UNC path are not shown, thanks @mnaimann -* Removed the unused file lookup table, thanks @Pectojin -* Fixed an issue with the FTP backend where it would sporadically create files in the root folder of the server, thanks @balchen -* Fixed quota reporting from GoogleDrive, thanks @warwickmm -* Improved code quality, thanks @verhoek - -2018-12-29 - 2.0.4.10_canary_2018-12-29 -========== -* Improved passphrase export, thanks @Pectojin -* Fixed an issue with restores that have no password, thanks @warwickmm -* Fixed a process shutdown/restart issue on Synology, thanks @drwtsn32x -* Improved build and tests, thanks @verhoek -* Fixed issues with displaying logs from the UI, thanks @Pectojin -* Improved performance of filters by around 10x, thanks @warwickmm - -2018-12-22 - 2.0.4.9_canary_2018-12-22 -========== -* Improved code quality, thanks @warwickmm -* Fixed typo, thanks @JonMikeIV -* Improved speed from default filter groups, thanks @warwickmm -* Fixed CPU/memory issue on MacOS, thanks @warwickmm - -2018-12-13 - 2.0.4.8_canary_2018-12-13 -========== -* Fixed an issue that broke IE, thanks @mikaelmello - -2018-12-13 - 2.0.4.7_canary_2018-12-13 -========== -* Fixed an issue that broke IE, thanks @mikaelmello - -2018-12-11 - 2.0.4.6_canary_2018-12-11 -========== -* Improved code quality and documentation, thanks @warwickmm -* Don't ask for password for an unencrypted backup, thanks @verhoek -* SFTP can now create missing folder tree, thanks @warwickmm -* Removed duplicati http options, thanks @warwickmm -* Fixed an issue with exporting passwords, thanks @Pectojin -* Added notificaiton button for warnings, thanks @LacunaSoftware -* Added cache-buster for all local static files, thanks @LacunaSoftware -* Improved code quality, thanks @verhoek -* Fixed an issue where the GUI would not delete the local database, even on request, thanks @warwickmm -* Fixed a misleading path error message, thanks @mikaelmello -* Fixed scheduler checking allowed days in local timezone, thanks @LacunaSoftware -* Reworked the log UI, thanks @LacunaSoftware -* Fixed an issue where local times were reported as clashing if times overlapped due to DST - -2018-11-28 - 2.0.4.5_beta_2018-11-28 -========== -The major changes in this version are: - -* New multi-threaded processing engine -* New logging system with filter options -* Better external reporting, with JSON support -* Filter groups to exclude common unwanted files -* Ignore filenames and empty folders added -* USN support on Windows -* Improved repair and validation -* Fixed reading password from console -* UID and GID now correctly restored on Linux/BSD/MacOS -* Added a number of new languages to the user interface - -And of course many, many, other updates and fixes contributed by the Duplicati community. - -A big thanks to all who contributes fixes, reportes issues, and participate on the forum! - -2018-11-28 - 2.0.4.3_beta_2018-11-28 -========== -The major changes in this version are: - -* New multi-threaded processing engine -* New logging system with filter options -* Better external reporting, with JSON support -* Filter groups to exclude common unwanted files -* Ignore filenames and empty folders added -* USN support on Windows -* Improved repair and validation -* Fixed reading password from console -* UID and GID now correctly restored on Linux/BSD/MacOS -* Added a number of new languages to the user interface - -And of course many, many, other updates and fixes contributed by the Duplicati community. - -A big thanks to all who contributes fixes, reportes issues, and participate on the forum! - -2018-11-12 - 2.0.4.2_experimental_2018-11-12 -========== -* Rebuild of 2.0.4.1 due to errors in the Docker images - -2018-11-08 - 2.0.4.1_experimental_2018-11-08 -========== -This is a collection release that is based on the 2.0.3.14 canary build. - -The major changes in this version are: - -* New multi-threaded processing engine -* New logging system with filter options -* Better external reporting, with JSON support -* Filter groups to exclude common unwanted files -* Ignore filenames and empty folders added -* USN support on Windows -* Improved repair and validation -* Fixed reading password from console -* UID and GID now correctly restored on Linux/BSD/MacOS -* Added a number of new languages to the user interface - -And of course many, many, other updates and fixes contributed by the Duplicati community. - -A big thanks to all who contributes fixes, reportes issues, and participate on the forum! - -2018-11-14 - 2.0.4.4_canary_2018-11-14 - ========== -* Added password confirmation on change, thanks @LacunaSoftware -* Fixed a crash with VSS, thanks @verhoek - -2018-11-14 - 2.0.4.4_canary_2018-11-14 - ========== -* Added password confirmation on change, thanks @LacunaSoftware -* Fixed a crash with VSS, thanks @verhoek - - -2018-11-08 - 2.0.3.14_canary_2018-11-08 -========== -* Removed unused icons, thanks @Pectojin -* Updated AWS libraries, thanks @Pectojin -* Removed the WindowsService restart fix as it did not work as expected - -2018-10-31 - 2.0.3.13_canary_2018-10-31 -========== -* Added more tests, thanks @warwickmm -* Fixed duration display in restore, thanks @verhoek -* Refactored Dropbox backend, thanks @verhoek -* Fixed a typo in the filter for excluding Firefox data, thanks @warwickmm -* Fixed problem with OneDrive "eating" leading slashes, thanks @warwickmm -* Improved Windows version detection for HyperV, thanks @mnaiman -* Added code to allow the Windows Service to correctly autoupdate - -2018-10-23 - 2.0.3.12_canary_2018-10-23 -========== -* Fixed translations not working for sub-cultures, thanks @LacunaSoftware -* Improved error detection and reporting -* Fixed progress bar not updating, thanks @LacunaSoftware -* Improved LVM handling, thanks @jkellerer -* Fixed issues with long paths and USN, thanks @dgehri -* Numerous code quality improvements, thanks @warwickmm -* Removal of unused code, thanks @Pectojin -* Added backup description field to UI, thanks @sffetlio -* Updated MegaApiClient to 1.6.3, thanks @Pectojin -* Normalize paths by forcing Windows drive letters to upper case, thanks @mnaiman -* Fixed progress stats gammar and consistency, thanks @lucascosti -* Fixed server errors with empty form changes, thanks @LacunaSoftware -* Improved error messages, thanks @warwickmm -* Added US-West to Wasabi, thanks @gzzengwei -* Added app manifest files for Windows, thanks @LacunaSoftware - -2018-09-05 - 2.0.3.11_canary_2018-09-05 -========== -* Cleanup of async methods and naming, thanks @warwickmm -* Added more non-compressible file extensions, thanks @ravisorg -* Login password field is now auto-focused, thanks @ltGuillaume -* Added more pause time options, thanks @bmendonca -* Fixed filename comparer to support correct filename encoding, thanks @warwickmm -* Changed the default filename query to fix reported slowdowns -* Added additional experimental queries to possible speed things up even more with - -2018-08-30 - 2.0.3.10_canary_2018-08-30 -========== -* Updated the help text for no certificates found, thanks @jonmikeiv -* Fixed logging details, thanks @mnaiman -* Fixed error messages on repair, thanks @mnaiman -* Refactored the FTP backend, thanks @verhoek -* Rewrote the lock on database queries to be simpler and less CPU intensive -* Removed some logging details in core performance areas (can be re-enabled with `--profile-all-database-queries`) -* Removed automatic attempts to rebuild `dblock` files as it is slow and rarely finds all the missing pieces (can be enabled with `--rebuild-missing-dblock-files`). -* Fixed the version number on MacOS builds -* Updated the signing certificate for executables and Windows installers -* Added a hostname check for the webserver -* Fixed an issue where the number of remaining files would be negative -* Updated localization files -* Now emits a warning if the option is missing a suffix on sizes (b, kb, mb, gb, tb) -* Added partially translated Romanian, Swedish, Thai, Hungarian, Slovakian, Catalan, Japanese, Bengali, and Korean to langauges -* Fixed a number of issues with `--check-filetime-only` -* Removed the `--store-metadata` option -* Rewrote the query that fetches the previous information for a file or folder. Set the environment variable `TEST_QUERY_VERSION=1` to revert to the old version for speed comparison, or `TEST_QUERY_VERSION=2` for an alternate version. -* Improved UI status messages, thanks @lucascosti -* Failing to add a file will now give a warning instead of stopping the backup -* Removed a hot-item cache for VSS -* Added option `--disable-filelist-consistency-checks` to allow speeding up large backups -* Now ignoring `ENODATA` error message when reading metadata on Linux/BSD -* Added additional support for exit codes in `--run-script-before` to allow stopping the backup or emitting a warning -* Fixed an issue with Google Cloud Storage, thanks @warwickmm -* Improved the B2 username field description, thanks @xfakt-pj -* Removed some unused code, thanks @warwickmm -* Improved source code documentation, thanks @mikaelmello - -2018-06-30 - 2.0.3.9_canary_2018-06-30 -========== -* Fixed an issue with dectection HyperV, thanks @mnaiman -* Default to exclude the System State VSS writers, thanks @mnaiman -* Fixed an issue where restores from the GUI would not autodetect blocksize and other parameters -* Fixed an issue with VSS failing to map the paths - -2018-06-28 - 2.0.3.8_canary_2018-06-28 -========== -* Fixed MSI version number -* Un-hid the Google GCS backend -* Fixed file sizes reported as zero -* Fixed a wrong display of sizes less than 1kb, thanks @fyndecano -* Improvements to the build process, thanks @verhoek -* Fixed a problem with the Amazon Cloud Drive delay, thanks @snamds -* Fixed a potential deadlock/performance issue, thanks @warwickmm -* Improved metadata reporting and UI, thanks @verhoek -* Improved Hyper-V detection, thanks @mnaiman -* Improved ways to handle the temporary folder, thanks @verhoek -* Added logic to remove privileges from the database files, thanks @verhoek -* Fixed a problem with USN support, thanks @dgehri -* Fixed temporary files not being removed -* Fixed no output from commandline on Windows -* Enabled password input from console again - -2018-06-17 - 2.0.3.7_canary_2018-06-17 -========== -* Added option to exclude empty folders -* Added option to exclude folders based on the existence of a marker file -* Changed the generation of index files to avoid pulling them from a database query -* Fixed an issue that would create large index files -* Improved the recreate logic to work if some files are missing, but the data exists in other files -* Fixed spacing in the status bar UI, thanks @SanduRajapakse -* Optimized unittests for serialization, thanks @warwickmm -* Improved commandline parsing, thanks @verhoek -* Added support for using the USN numbers for faster backups on NTFS drives, thanks @dgehri -* Added Duplicati program version in status results, thanks @drwtsn32x -* Added thread safe collections to the Synology auth handler, thanks @warwickmm -* Improved code readability, thanks @warwickmm -* Removed all calls to `Console.WriteLine` as it could lock up the console on Windows -* Added Memset Cloud Storage to OpenStack providers, thanks @tnwhitwell -* Refactored GoogleDrive backend, thanks @verhoek -* Improved unittest scripts, thanks @verhoek -* Optimized some string operations, thanks @warwickmm -* Fixed a few issues with the commandline interface, thanks @verhoek -* Updated built-in help for the new `--log-level` options, thanks @tygill -* Code cleanup and standardization, thanks @warwickmm -* Fixed issue on OneDrive (MSGraph) that would fail when trying to delete a missing file, thanks @tygill -* Added additional help text for the dblock size picker UI -* Added UI validation for B2 paths -* No longer restoring metadata on symlinks by default, as that updated the targets -* Fixed an issue with OAuth timeouts, thanks @tygill -* Fixed an issue with overriding the temporary folder, thanks @verhoek -* Fixed an issue with clearing callcontext settings, thanks @verhoek -* Fixed an issue with shared buffers causing validation errors when running multiple index file generators -* Improved the repair and broken file detection, thanks @tygill -* Refactored the Google Cloud Storage backend, thanks @verhoek -* Improved error message and handling for source folders with permission issues, thanks @verhoek -* Fixed a problem with recreating symlinks on Windows, thanks @snamds -* Improved handling of the console input/output, thanks @verhoek -* Fixed a race in the webserver when decoding locale, thanks @verhoek -* Added an option to disable automatic login from the tray icon -* Fixed some incorrect status reports being generated during backup -* Fixed an null-error in the box.com backend, thanks @warwickmm - -2018-04-23 - 2.0.3.6_canary_2018-04-23 -========== -This update adds concurrent processing for the backup. With this update, the backup will now use multiple cores to perform checking and compressing. -Use the advanced option `--concurrency-max-threads` to toggle how many threads to use. -The options `--concurrency-block-hashers` and `--concurrency-compressors` can be used to adjust the number of hashers and compressors to use. - -Beware that this update contains a lot of new code, and should only be used in test environments. - -Other fixes in this build: -* Fixes for filter groups, thanks @tygill -* Fixed a backup import issue with empty metadata -* Added upper bound to password checker, thanks @pectojin - -2018-04-13 - 2.0.3.5_canary_2018-04-13 -========== -* Improved progress bar messsages and layout, thanks @SanduRajapakse -* Improved icon status images, thanks @dbddhkpde -* Code and test cleanups, thanks @warwickmm -* Fixed an issue with rclone backend ignoring options, thanks @Bruceforce -* Added a fix for browsers with scripting disabled, thanks @Pectojin -* Added a button to dismiss all messages, thanks @Pectojin -* Added KeyStone v3 support to OpenStack backend, thanks @epol -* Updated translations, thanks to all translators! -* Fixed an issue that caused large log data to accumulate and break sending report/email status -* Added support for fine-grained control over how log data is reported via email, http and Jabber/XMPP -* Added support for sending JSON formatted data with the http report module -* Fixed an issue with MS Graph authentication, thanks @tygill -* Fixed a performance issue during file scanning, thanks @ltfish -* Added support for serializing results into json for all report modules and the run-script module as well, thanks @StephenGregory -* Added filter groups and a UI for it, thanks @tygill -* Fixed an issue where some paths were not reported via test-filters -* Fixed some issues with handling internal server settings, thanks @warwickmm - -2018-04-02 - 2.0.3.4_canary_2018-04-02 -========== -* Added support for setting low IO priority during backups -* Changed order of throttling arguments to fix incorrect throtling -* Added an option to disable backups when running on battery, thanks @warwickmm -* Fixed a crash on the CLI help page, thanks @tygill -* Added new Graph-API based backends for SharePoint and Office 365, thanks @tygill -* Added new UI status badges, thanks @dbddhkpde - -2018-04-02 - 2.0.3.3_beta_2018-04-02 -========== -This beta release contains many changes, a quick summary of changes: - -* Fixed not running the slow VACUUM command after each operation -* Fixed a problem with SSL certificates periodically failing -* Added Sia and rclone backends -* Updated the hashing library to use faster native libraries where available -* Added a new retention policy and UI which allows backup versions to decrease over time -* Updated the auto-updater, such that new installs will spawn an extra process instead of using the AppDomain method as previously done. -* Faster browsing of files to restore -* Removed automatic tilde expansions as it created numerous problems -* Multiple new languages added to the UI -* Fixed a number of browser cache issues, which should fix the XSRF errors -* Fixed not attempting to read non-symlink reparse points on Windows -* Fixed some crashes that were caused by the usage reporter filling up reports - -Thanks to the many contributors who worked hard to get this release ready! - -2018-03-16 - 2.0.3.2_canary_2018-03-16 -========== -* Added a new logging system with filters -* Source code documentation fixes and code quality fixes, thanks @warwickmm -* Improved the speed of the re-encrypt python script, thanks @piegamesde -* Added username and started-by information in the WebUI, thanks @Pectojin - -2018-03-16 - 2.0.3.1_experimental_2018-03-16 -========== -This experimental release is mostly the same as the canary build v2.0.2.20. -Some of the major changes from the experimental build 2.0.2.15 are: - -* A new UI for setting the retention policy -* Added an rclone backend -* External links in the UI are now marked -* Fixed a number of browser cache issues, which should fix the XSRF errors -* Optional warnings when reaching the quota limit -* Fixed not attempting to read non-symlink reparse points on Windows -* Fixed a problem with the box.com backend -* Fixed some crashes that were caused by the usage reporter filling up reports -* Option to disable the file-size read-ahead scanner - -2018-03-06 - 2.0.2.21_canary_2018-03-06 -========== -* Rebuilt the style.css file -* Added support in all backends for reloading DNS names when the error indicates a DNS error -* Removed some default filters (still disabled by default), thanks @tygill -* Fixed a parsing case for the 'U' parameter, thanks @TekkiWuff -* Fixed a problem with IE not loading the page correctly, thanks @TekkiWuff -* Fixed a problem with setting the initial password on the server and some change-password issues, thanks @mnaiman - -2018-02-27 - 2.0.2.20_canary_2018-02-27 -========== -* Added Docker images, thanks @fkrull -* Fixed log entries being cut in UI, thanks @Pectojin -* Improved the webserver status codes, thanks @Pectojin -* Fixed the Mono download link for the OSX package, thanks @aranaur -* Improved the log UI to not load excessive results, thanks @Pectojin -* Fixed an issue where retention options would not clear in the UI, thanks @TekkiWuff -* Not applying default filters, unless explicitly enabled by the user -* Automatically reloading DNS entries for WebDAV if there is a name lookup error, thanks @Pectojin -* Improved rentention policy syntax and functionality, thanks @TekkiWuff -* Added script to re-encrypt remote files, thanks @wjansenw -* Fixed a number of incorrect HTTP status codes, thanks @Pectojin - -2018-02-12 - 2.0.2.19_canary_2018-02-12 -========== -* Updated translations -* Rebuilt stylesheet - -2018-02-11 - 2.0.2.18_canary_2018-02-11 -========== -* Fixes to the "advanced options" list in the GUI, thanks @pectojin -* Fixed OpenStack URI selector in the GUI, thanks @pectojin -* Added workaround to broken OneDrive file listing, thanks @osmogar -* Improvements to the log page, thanks @pectojin -* Fixed a scheduler issue with clock drift, thanks @pectojin -* Added option to ignore metadata when importing a backup, thanks @pectojin -* Improvements to backup retention, thanks @TekkiWuff -* Added `--parameters-file` to the server, thanks @pectojin -* Fixed incorrect size requirements for Synology package -* Fixes to the DuplicatiVerify.ps script, thanks @mnaiman -* Fixed showing backup durations longer than 24 hours, thanks @pectojin -* Fixed leaving orphan data in the settings database after deleting a backup job, thanks @pectojin -* Fixed many caching issues, which should also fix the XSRF error message -* Fixed sending "run now" commands in front of the backup queue, thanks @pectojin -* Added warnings when the backup is nearing the quota limit, thanks @tygill -* Fixed all reparse points were treated as symlinks, which fixes backup of OneDrive folders, thanks @tygill -* Fixed a crash on very fast copy operations, thanks @TekkiWuff -* Added .m2ts (BlueRay container) to the list of non-compressible extensions, thanks @pectojin -* Fixed a bug in the box.com backend that prevented backups -* Fixed a weird "0 warnings" message -* Made the GUI menu stay in place instead of scroll away, thanks @pectojin -* Fixed a problem where default filters were not applied when running from the server, thanks @tygill -* Added limitations to the usage reporter, so it does not attempt to submit a large backlog of data -* Fixed two cases where commandline arguments were not correctly formatted when sent to new processes -* Added support for disabling the file-size read-ahead scanner - -2018-01-23 - 2.0.2.17_canary_2018-01-23 -========== -- Fixed a bug with the update packages having an invalid hash -- Updated external links in the UI to be clearly marked -- Experimental support for Google Team Drives -- Fixed WEBDAV Test function, thanks @inat3k -- Added an rclone backend, thanks @wjansenw -- Fixed some broken error messages, thanks @JonMikeIV -- Added a simple UI for exposing the `--retention-policy` option - -2018-01-18 - 2.0.2.16_canary_2018-01-18 -========== -* Reworked some internal compression code, thanks @vnau -* Updated commandline documentation, thanks @mr-flibble -* Improved 64bit OS detection, thanks @warwickmm -* Fixed a problem with the progress bar hiding under the top bar, thanks @pectojin -* Fixed a problem displaying more than 10 advanced options, thanks @pectojin -* Improved detection of 32bit MSSQL on 64bit Windows, thanks @inat3k -* Changed load order of modules to allow `run-script` to change setup, thanks @BrianLakstins -* Added a `UserAgent` header to OD4B requests - -2018-01-03 - 2.0.2.15_experimental_2018-01-03 -========== -This is the same as build 2.0.2.14 canary - -Overview of major and breaking changes since 2.0.1.73 experimental: -* Fixed not running the slow VACUUM command after each operation -* Improved S3 to work better with non AWS hosts -* Updated backend support libraries to the latest versions: Azure, AWS, SSH, SharpCompress, Mega, AlphaFS -* Fixed a problem with SSL certificates periodically failing -* Added a Sia backend -* Added a number of default excludes -* Updated the hashing library to use faster native libraries where available -* Added a new retention policy which allows backup versions to decrease over time -* Updated the auto-updater, such that new installs will spawn an extra process instead of using the AppDomain method as previously done. -* Faster browsing of files to restore -* Removed automatic tilde expansions as it created numerous problems -* Multiple new languages added to the UI - -2017-12-22 - 2.0.2.14_canary_2017-12-22 -========== -* Multiple code quality fixes, thansk @warwickmm -* Fixed a case where commandline arguments ending with a backslash were not correctly escaped on Windows - -2017-11-22 - 2.0.2.13_canary_2017-11-22 -========== -* Fixed an issue with AutoUpdater semi-ignoring webroot -* Fixed an issue where source folders would be omitted from the backup if a file with the same leading name was also added -* Switched to default "keep forever", thanks @ItEndsWithTens -* Fixed a startup crash caused by invalid throttle values -* Added a number of new languages, thanks to all translators! -* Timestamps in the log are now reported in local time and include a UTC timestamp for easy script parsing -* Added Windows registry exclusion list to the default exclude filters -* Added .dv files to compression exclusion list, thanks @jesjimher -* Fixed some spelling in the UI, thanks @davegold24 and @JonMikeIV -* Fixed logic of the `affected` command to not return empty results if the local database was not found -* Fixed a case where the "Resume" button did not appear clickable, thanks @JonMikeIV -* Improved query time for browsing folders during restore, thanks @RobSauro -* Commands are now sorted alphabetically, thanks @JonMikeIV -* Improved an error message when the SSH key failed to parse -* Removed tilde expansion to fix multiple issues caused by unexpected expansio, thanks @warwickmm -* Fixed .rbf files to only be excluded if they are part of the `config.msi` folder -* Improved the commandline export to show commandlines that are correctly escaped for the shell -* Fixed a case where the AutoUpdater would fail to send the correct commandline to the updated process - -2017-10-20 - 2.0.2.12_canary_2017-10-20 -========== -* Fixed not reporting "Finished" until the backup is actually finished, thanks @warwickmm -* Fixed stack overflow in FTP backend, thanks @tygill -* Fixed a number of incorrect exception messages, thanks @warwickmm -* Removed assembly signing for now, thanks @warwickmm - -2017-10-20 - 2.0.2.11_canary_2017-10-20 -========== -* Changed the way the auto-updater works. It now spawns a new version of the executable instead of attempting to run it in an AppDomain - It is possible to revert to the old version by setting the environment variable `AUTOUPDATER_USE_APPDOMAIN=true` -* Updated Synology script to be better at locating Mono -* Service description now requests auto-restart -* Added the Czech (cs) translations -* Updated FasterHashing, so it automatically picks up openssl 1.0 and 1.1 on all platforms - -2017-10-11 - 2.0.2.10_canary_2017-10-11 -========== -* Fixed an issue with the compare command not working with filters -* Fixed showing the full version number on the app on MacOS -* Code quality cleanup, thanks @warwickmm -* Added code to detect the power supply source, thanks @warwickmm -* Fixed an issuw with quota detection for non-drive roots on Windows, thanks @t$ -* Implemented retention policy as a commandline switch, thanks @tekkiwulf -* Improved default filters, thanks @tygill -* Updated FasterHashing to fix problems on MacOS and with OpenSSL 1.1 -* Fixed the remote-file delete function to only delete Duplicati-related files -* Added option to run the tray-icon detached from the console - -2017-10-01 - 2.0.2.9_canary_2017-10-01 -========== -* Updated libraries: NewtonSoft.Json, AzureBlob, AWS, SSH, SharpCompress, Meg, NGetText, MimeKit, MailKit, AlphaFS, CoCoL -* Fixed the default-filters warning, thanks @tygill -* Added quota reporting to OneDrive, thanks @tygill -* Added a number of performance improvementes, thanks @warwickmm -* Switched hashing to use FasterHashing which loads OpenSSL or Apple CC libraries for much faster hashing performance -* Changed the `--zip-compression-method` option to report only the supported methods -* Fixed error on Compact that was caused by backend re-use - -2017-09-20 - 2.0.2.8_canary_2017-09-20 -========== -* Fixed passwordless SSH setup, thanks @Pectojin -* More fixes to the SSL certificate override issue -* Changed hash provider to use the native CNG version if available - -2017-09-19 - 2.0.2.7_canary_2017-09-19 -========== -* Fixed an issue with S3 IAM not working if the destination had subfolders -* Fixed an issue where the backup would error out reporting an extra file that was just deleted -* Fixed an issue with AmzCD not honoring the consistency delay in all cases -* Added support for GUID based source folders on Windows, thanks @tygill -* Fixed not being able to set "no delay" for startup settings, thanks @Pectojin -* Added nice-level to Duplicati-server on Linux-based installs, thanks @z0rc -* Re-introduced the machine name in the WebUI page title, thanks @Pectojin -* Reduced size of PNG images, thanks @PeterDaveHello -* Added default filters to exclude temp folders and similar, thanks @tygill -* Fixed all string compares to use ordinal compare, thanks @tygill -* Fixed a case where temporary files were not removed after verification - -2017-09-16 - 2.0.2.6_canary_2017-09-16 -========== -* Added an experiemental --use-block-cache flag to test performance potential - -2017-09-14 - 2.0.2.5_canary_2017-09-14 -========== -* Added support for wildcards in source paths on Windows, thanks @tygill -* Improved default values for Sia backend, thanks @fuffenz - -2017-09-09 - 2.0.2.4_canary_2017-09-09 -========== -* Fixed an issue that cause Duplicati GUI to fail on startup - -2017-09-08 - 2.0.2.3_canary_2017-09-08 -========== -* Added Italian and Chinese (Taiwan) to languages -* Fixed periodically loosing http certificate overrides -* Added status variables to mail/jabber/script/http reporters -* Added a Sia backend (thanks @fuffenz) -* Fixed issue with using Azure backend - -2017-08-30 - 2.0.2.2_canary_2017-08-30 -========== -* Fixed not performing vacuum all the time, thanks @danstahr -* Added Selectel as an OpenStack prodvider, thanks @febalist -* Added a fix for hanging "Connecting ..." dialogs on some machines -* Fixed an issue with the HTTP report module not using a custom message -* Updated translations, added Danish to the list, thanks to all translators! -* Improved error messages on passphrase change -* Fixed a crash that happened in the scheduler if the clock was changed with more than 3 months -* Fixed TrayIcon connecting over HTTPS if possible, thanks @mnaiman -* Added options to SSH library that allows configuring the timeout values -* Updated the Mega and SSH libraries -* Added support for detecting MacPorts versions of Mono -* Added a tooltip to the Windows trayicon -* Changed the logic in S3 to auto-set `--s3-ext-forcepathstyle` if the destination is not a known Amazon server -* Improved error message for WebDAV if the server returns 405 -* Added a startup dialog that asks users to password protect the UI -* Added Wasabi as an S3 provider - -2017-08-01 - 2.0.2.1_beta_2017-08-01 -========== -This is the first Beta release of Duplicati 2.0. - -It is a rebuild of version 2.0.1.73, intended for users who prefer a stable version instead of "bleeding edge" updates. - -Thanks to everyone who has used Duplicati, reported bugs and contributed fixes! - -2017-07-15 - 2.0.1.73_experimental_2017-07-15 -========== -This version is the same as 2.0.1.72_canary. -Som of the changes from 2.0.1.61_experimental: - -* Added a "dark" theme -* Updated thirdparty libraries -* Added JottaCloud backend -* Improved database rebuild speed -* Added throttle controls to the WebUI -* Switched to %LOCALAPPDATA% for storing data on Windows -* And more ... - -2017-07-05 - 2.0.1.72_canary_2017-07-05 -========== -* Fix duplicate `zip-compression-level` warning - -2017-07-04 - 2.0.1.71_canary_2017-07-04 -========== -* Fixed a problem with the Synology package allowing access without being authenticated with DSM - -2017-07-04 - 2.0.1.70_canary_2017-07-04 -========== -* Added support for enabling zip64 -* Rewrote the Synology package to use nginx proxy features instead of CGI -* Added code signature to Synology package - -2017-06-29 - 2.0.1.69_canary_2017-06-29 -========== -* Added support for attaching the console to the service on Windows, thanks @mnaiman -* Fixed a crash when starting the trayicon with a password set, thanks @mnaiman - -2017-06-28 - 2.0.1.68_canary_2017-06-28 -========== -* Fixed an issue with password managers setting the password on configuration export -* Updated SQLite to 1.0.105.2 (3.19.3) on Windows -* Fixed an issue that prevented the TrayIcon "Quit" menu item from working -* Now using %LOCALAPPDATA% for storing local files on Windows, unless %APPDATA% already has data -* Updated AWS S3 libraries -* Switched to new Authenticode signing key - -2017-06-21 - 2.0.1.67_canary_2017-06-21 -========== -* Fixed the main menu "floating" up on small screens -* Fixed some issues with stopping a backup -* Fixed the help dialog for HubiC suggesting the default/ prefix -* Changed some texts in the UI -* Added live throttle control options -* Added option for TrayIcon to connect to the server without the password, if it can read the local database -* Added systed service file for RPM installs - -2017-06-15 - 2.0.1.66_canary_2017-06-15 -========== -* Removed the warning text on the settings page -* Added internal system for better database upgrades, thanks @Danstahr -* Included Jottacloud backend in the build output, thanks @Danstahr -* Fixed an arugment-out-of-range error in the "compare" method, thanks @Danstahr -* Added support for reading and writing filters in external scripts, thanks @IGTHORN -* Added an environment variable to override the default file-system case-sensitivity -* Improved recreate database speeds, thanks @zed9h - -2017-06-09 - 2.0.1.65_canary_2017-06-09 -========== -* Updated the Azure libraries -* Cleaned the deployed builds from XML files -* Fixed an out-of-bounds error, thanks @okoetter -* Updated localizations -* Fixed a bug where the SSL certificate was reported to the client - -2017-06-02 - 2.0.1.64_canary_2017-06-02 -========== -* Compile all executables without `Prefer32Bit` -* Fixed not showing the commandline arguments if the input is incorrect - -2017-06-01 - 2.0.1.63_canary_2017-06-01 -========== -* Rebuild, gpg was removed from the build maching causing 2.0.1.62 to be a partially unsigned build - -2017-06-01 - 2.0.1.62_canary_2017-06-01 -========== -* Caching is now enabled for static content in the WebUI, thanks @mknjc -* Fixed an issue with `--deleted-files`, thanks @downpoured -* Added permissions testing for the "Test connection" on aFTP, thanks @vnau -* Changed some `GET` calls to `POST` for the login sequence -* Fixed not being able to change the usagereporter level -* Added support for setting a theme -* Added the "dark" theme, thanks Michal -* Updated SharpCompress to the latest version - -2017-05-30 - 2.0.1.61_experimental_2017-05-30 -========== -This experimental release contains all the changes from canary v2.0.1.60, -including: - -* Performance updates, faster backups, less memory -* Fixed an issue with zip files larger than 4GiB -* Improved import/export functions - -If you are using VSS snapshots on Windows, -make sure you install VC++ 2015 redist: -https://www.microsoft.com/en-us/download/details.aspx?id=48145 - -2017-05-23 - 2.0.1.60_canary_2017-05-23 -========== -* Re-enabled the GTK trayicon that was missing in the previous two versions - -2017-05-22 - 2.0.1.59_canary_2017-05-22 -========== -* Fixed an issue with NewtonSoft.Json not loading for some backends -* Note that Windows VSS now requires VC++ Redist 2015 installed - -2017-05-19 - 2.0.1.58_canary_2017-05-19 -========== -* Fixed an issue with the FTP backend in 2.0.1.57 -* Improved the query used to get the timestamp if `--check-filetime-only` is enabled - -2017-05-17 - 2.0.1.57_canary_2017-05-17 -========== -* Fixed an issue with not being performed correctly with AmzCD -* Fixed the Windows verifyscript, thanks @mnaiman -* Updated fonts to work better with Russian language display, thanks @vnau -* Removed Tlsv1 option from server, thanks @mnaiman -* Updated NewtonSoft.Json, MailKit, MimeKit and AlphaVSS -* Fixed the way AllowWriteBuffering is used, which should fix delete errors on AmzCD -* Fixed an error in searching for files with regular expressions - -2017-04-21 - 2.0.1.56_canary_2017-04-21 -========== -* Fixed an issue with duplicate strings in S3 providers -* Fixed an issue where a file being expanded during backup could cause the file to not be backed up. -* Changed HTTP requests to not buffer the output to fix out-of-memory errors -* Added a Python script to do restores completely without using Duplicati, thanks @downpoured ! -* Improved Amazon Cloud Drive by not waiting while performing batched requests - -2017-04-06 - 2.0.1.55_canary_2017-04-06 -========== -* Updated SharpCompress to support creating zip files larger than 4 GiB -* Added a commandline-like interface to the WebUI -* Updated the backendtool to support full paths -* Updated the list of S3 regions and servers -* Change the UI to have a fixed top-bar with status and progress -* Fixed a crash that occurs if the web-browser does not send `Accept-Language` -* Fixed an issue with filtering the output of the LIST command - -2017-03-26 - 2.0.1.54_canary_2017-03-26 -========== -* Fixed an unwanted empty folder creation, thanks @albertony -* Fixed logic issues with volumesize minimum bounds, thanks @albertony -* Fixed importing config files with AllowedDays set -* Improved database performance on backups, thanks @danstahr - -2017-03-13 - 2.0.1.53_experimental_2017-03-13 -========== -* This update contains the same fixes as canary 2.0.1.51 - -2017-03-13 - 2.0.1.52_canary_2017-03-13 -========== -* Added fail-over to read zip files with invalid central directory -* Improved the login page for compatibility with password managers, thanks @johenning -* Improved the URL parser for better commandline import -* Improved the URL parsing when importing a saved configuration -* Added exit/status codes to the verification command -* Added IBM Bluemix COS to list of S3 providers, thanks @greyhoundforty -* Improved startup time for commandline, thanks @frelis -* Improved non-hosted mode for TrayIcon, thanks @mnaiman -* Added a Jottacloud backend, thanks @albertony - -2017-03-03 - 2.0.1.51_canary_2017-03-03 -========== -* Updated the bundled version of SQLite for Windows to 1.0.104.0 -* Added deep archive verification when invoking verify -* Changed the pagination method for GoogleDrive - -2017-02-28 - 2.0.1.50_canary_2017-02-28 -========== -* Added error message if `--prefix` contains hyphens -* Fixed an issue with editing the allowed days on the schedule page -* Changed "import" so it just loads the configuration into the editor, allowing the user to edit it before saving -* Added function to restore that loads setup from a configuration file -* Updated SharpCompress and S3 libraries -* Improved crash messages for Windows TrayIcon - -2017-02-23 - 2.0.1.49_experimental_2017-02-23 -========== -* This build is the same as the canary - -2017-02-23 - 2.0.1.48_canary_2017-02-23 -========== -* Updated translations, thanks @agrajaghh -* Improved verification script, thanks @mnaiman -* Support for SSL certificates in server when running with Mono, thanks @mnaiman -* Improved progress bar updates when processing large files, thanks @asselin - -2017-02-14 - 2.0.1.47_canary_2017-02-14 -========== -* Updated some localizations -* Updated the user interface -* Fixed an issue where the Force Stop action did not work immediately - -2017-02-03 - 2.0.1.46_canary_2017-02-03 -========== -* Fixed icon sizes -* Removed the fake control.cgi entry -* Added experimental support for setting the SQLite tempdir -* Added an optimization that makes bug reports faster to create -* Added a new HTTP reporter module, thanks @vatara - -2017-02-03 - 2.0.1.45_experimental_2017-02-03 -========== -* This experimental release contains the fixes in the latest canary and some updated translations, and a fix that makes creating a bug-report faster - -2017-01-24 - 2.0.1.44_canary_2017-01-24 -========== -* Added SSL test tools for Mono debugging -* Fixed an issue with not reporting the correct error message when listing files -* Updated logs UI to show the backup name -* Added extra menu items to the start page -* Fixed a number of typos -* Added a more thorough consistency check -* Added an extra result level "fatal" to reporting -* Fixed reporting warning status if there were warnings during the backup - -2017-01-19 - 2.0.1.43_canary_2017-01-19 -========== -* Improved queries that are used to recreate the database -* Fixed duplicate logging entries when a log file was used - -2017-01-16 - 2.0.1.41_canary_2017-01-16 -========== -* Added a debugging feature to assist with #1699, #2048, #2140, #2178 -* Fixed a problem with listing changes from the commandline - -2017-01-12 - 2.0.1.40_canary_2017-01-12 -========== -* Added extra functionality to the `--allow-full-removal` option -* Various updates to send-mail -* Reworked logging support to allow modules to funnel logs back into the current operation -* Reverted an optimization that cause backup scanning to be really slow - -2017-01-08 - 2.0.1.39_canary_2017-01-08 -========== -* Fixed some typos, thanks @sfahrenholz -* Added webp to compressed file extension list, thanks @dessant -* Changed aftp backend to use CWD, thanks @seaeagle1 -* Added "purge" command to purge files from the database -* Added "list-broken-files" and "purge-broken-files" to aid in recovering from remote data loss -* Added additional trace log messages to debug some consistency issues -* Fixed support for operations across different operating systems diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 1b587ea8a..000000000 --- a/nuget.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bb16c7206..cecab55e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,8 +5,11 @@ "packages": { "": { "devDependencies": { + "autoprefixer": "^10.4.20", "less": "^4.2.0", "less-plugin-clean-css": "^1.6.0", + "postcss": "^8.4.47", + "postcss-cli": "^11.0.0", "stylelint": "^16.9.0", "stylelint-config-standard-less": "^3.0.1" } @@ -190,6 +193,19 @@ "node": ">= 8" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -233,6 +249,20 @@ "node": ">=4" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -260,6 +290,44 @@ "node": ">=8" } }, + "node_modules/autoprefixer": { + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", @@ -267,6 +335,19 @@ "dev": true, "license": "MIT" }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -280,6 +361,39 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -290,6 +404,27 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001677", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz", + "integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -305,6 +440,31 @@ "node": ">=4" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -318,6 +478,44 @@ "node": ">= 10.0" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -437,6 +635,16 @@ } } }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -450,6 +658,13 @@ "node": ">=8" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.51", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.51.tgz", + "integrity": "sha512-kKeWV57KSS8jH4alKt/jKnvHPmJgBxXzGUSbMd4eQF+iOsVPl7bz2KUmu6eo80eMP8wVioTfTyTzdMgM15WXNg==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -491,6 +706,16 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -599,6 +824,73 @@ "dev": true, "license": "ISC" }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -673,8 +965,7 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "ISC", - "optional": true + "license": "ISC" }, "node_modules/has-flag": { "version": "3.0.0", @@ -788,6 +1079,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -896,6 +1200,19 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -963,6 +1280,19 @@ "node": ">=0.10" } }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -1105,6 +1435,13 @@ "node": ">= 4.4.x" } }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1115,6 +1452,16 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -1168,9 +1515,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, @@ -1199,9 +1546,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -1220,13 +1567,90 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-cli": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.0.tgz", + "integrity": "sha512-xMITAI7M0u1yolVcXJ9XTZiO9aO49mcoKQy6pCDFdMh9kGqhzLVpWxeD/32M/QBmkhcGypZFFOLNLmIW4Pg4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^11.0.0", + "get-stdin": "^9.0.0", + "globby": "^14.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^5.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-cli/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-cli/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-cli/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/postcss-less": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-6.0.0.tgz", @@ -1240,6 +1664,73 @@ "postcss": "^8.3.5" } }, + "node_modules/postcss-load-config": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", + "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1", + "yaml": "^2.4.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + } + } + }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/postcss-resolve-nested-selector": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", @@ -1295,6 +1786,16 @@ "dev": true, "license": "MIT" }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -1324,6 +1825,49 @@ ], "license": "MIT" }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -1494,9 +2038,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1815,6 +2359,13 @@ "node": ">=8" } }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1835,6 +2386,60 @@ "dev": true, "license": "0BSD" }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -1855,6 +2460,83 @@ "which": "bin/which" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", @@ -1868,6 +2550,58 @@ "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } } } } diff --git a/package.json b/package.json index dbfce6044..0261e4a9a 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { "devDependencies": { + "autoprefixer": "^10.4.20", "less": "^4.2.0", "less-plugin-clean-css": "^1.6.0", + "postcss": "^8.4.47", + "postcss-cli": "^11.0.0", "stylelint": "^16.9.0", "stylelint-config-standard-less": "^3.0.1" }, "scripts": { - "build:style": "npm run lint:style-fix; npx lessc Duplicati/Server/webroot/ngax/less/dark.less Duplicati/Server/webroot/ngax/styles/dark.css --clean-css -m=always && npx lessc Duplicati/Server/webroot/ngax/less/default.less Duplicati/Server/webroot/ngax/styles/default.css --clean-css -m=always", + "build:style": "npm run lint:style-fix; npx lessc Duplicati/Server/webroot/ngax/less/dark.less Duplicati/Server/webroot/ngax/styles/dark.css --clean-css -m=always && npx lessc Duplicati/Server/webroot/ngax/less/default.less Duplicati/Server/webroot/ngax/styles/default.css --clean-css -m=always && npx postcss Duplicati/Server/webroot/ngax/styles/dark.css Duplicati/Server/webroot/ngax/styles/default.css --no-map --use autoprefixer --replace", "lint:style": "npx stylelint \"Duplicati/Server/**/less/*.less\"", "lint:style-fix": "npx stylelint \"Duplicati/Server/**/less/*.less\" --fix" } diff --git a/thirdparty/ManagedLZMA/README.txt b/thirdparty/ManagedLZMA/README.txt deleted file mode 100644 index a740ba758..000000000 --- a/thirdparty/ManagedLZMA/README.txt +++ /dev/null @@ -1,18 +0,0 @@ -The binaries here are obtained by checking out the source for e12204c6: -https://github.com/weltkante/managed-lzma/commit/3e12204c60ada8164561a841bb8dcb4c96078c99 - -The upgrading the project "master" to .Net8 with the `upgrade-assistant` tool, and finally building in release mode with "dotnet build master.2012.csproj /p:Configuration=Release" - -This is a workaround from an earlier version of the project, and the support should either use the published `0.2.0-alpha-7` version from NuGet, or perhaps be deprecated. - -Full instructions for building: -``` -git clone https://github.com/weltkante/managed-lzma -cd managed-lzma -git checkout 3e12204c60ada8164561a841bb8dcb4c96078c99 -upgrade-assistant upgrade master/master.2012.csproj --operation Inplace --targetFramework net8.0 -cd master -dotnet build /p:Configuration=Release master.2012.csproj -``` - -Result file is in `bin/Release/net8.0/managed-lzma.dll` \ No newline at end of file diff --git a/thirdparty/ManagedLZMA/download.txt b/thirdparty/ManagedLZMA/download.txt deleted file mode 100644 index 59b1afc9e..000000000 --- a/thirdparty/ManagedLZMA/download.txt +++ /dev/null @@ -1 +0,0 @@ -https://github.com/weltkante/managed-lzma \ No newline at end of file diff --git a/thirdparty/ManagedLZMA/license.txt b/thirdparty/ManagedLZMA/license.txt deleted file mode 100644 index 57a710260..000000000 --- a/thirdparty/ManagedLZMA/license.txt +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2011-2012 Tobias Käs - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/ManagedLZMA/licensedata.json b/thirdparty/ManagedLZMA/licensedata.json deleted file mode 100644 index ec84e8663..000000000 --- a/thirdparty/ManagedLZMA/licensedata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "ManagedLZMA", - "description": "An LZMA/7z compression library by Tobias Käs.", - "link": "https://github.com/weltkante/managed-lzma", - "license": "MIT", - "notes": "" -} \ No newline at end of file diff --git a/thirdparty/ManagedLZMA/managed-lzma.dll b/thirdparty/ManagedLZMA/managed-lzma.dll deleted file mode 100755 index de900da99..000000000 Binary files a/thirdparty/ManagedLZMA/managed-lzma.dll and /dev/null differ diff --git a/thirdparty/Meziantou/Homepage.txt b/thirdparty/Meziantou/Homepage.txt new file mode 100644 index 000000000..60b9543b1 --- /dev/null +++ b/thirdparty/Meziantou/Homepage.txt @@ -0,0 +1 @@ +https://github.com/meziantou/Meziantou.Framework diff --git a/thirdparty/Meziantou/License.txt b/thirdparty/Meziantou/License.txt new file mode 100644 index 000000000..84236f279 --- /dev/null +++ b/thirdparty/Meziantou/License.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Gérald Barré + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/thirdparty/Meziantou/licensedata.json b/thirdparty/Meziantou/licensedata.json new file mode 100644 index 000000000..d175b57fa --- /dev/null +++ b/thirdparty/Meziantou/licensedata.json @@ -0,0 +1,7 @@ +{ + "name": "Meziantou.Framework", + "description": "Meziantou.Framework.Win32.CredentialManager.", + "link": "https://github.com/meziantou/Meziantou.Framework", + "license": "MIT", + "notes": "" +} diff --git a/thirdparty/SharpAESCrypt/License.txt b/thirdparty/SharpAESCrypt/License.txt index 09905eb1a..4ac9a5e9c 100755 --- a/thirdparty/SharpAESCrypt/License.txt +++ b/thirdparty/SharpAESCrypt/License.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Duplicati +Copyright (c) 2025 Duplicati Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/thirdparty/VaultSharp/Homepage.txt b/thirdparty/VaultSharp/Homepage.txt new file mode 100644 index 000000000..af9196d1c --- /dev/null +++ b/thirdparty/VaultSharp/Homepage.txt @@ -0,0 +1 @@ +https://github.com/rajanadar/VaultSharp \ No newline at end of file diff --git a/thirdparty/VaultSharp/License.txt b/thirdparty/VaultSharp/License.txt new file mode 100644 index 000000000..a765bc16b --- /dev/null +++ b/thirdparty/VaultSharp/License.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/thirdparty/VaultSharp/licensedata.json b/thirdparty/VaultSharp/licensedata.json new file mode 100644 index 000000000..7ebfb514f --- /dev/null +++ b/thirdparty/VaultSharp/licensedata.json @@ -0,0 +1,7 @@ +{ + "name": "HashiCorp.Vault", + "description": "The most comprehensive cross-platform .NET Library for HashiCorp's Vault - A Secret Management System.", + "link": "https://github.com/rajanadar/VaultSharp", + "license": "Apache-2.0", + "notes": "" +} diff --git a/thirdparty/azure-sdk-for-net/Homepage.txt b/thirdparty/azure-sdk-for-net/Homepage.txt new file mode 100644 index 000000000..52190f38b --- /dev/null +++ b/thirdparty/azure-sdk-for-net/Homepage.txt @@ -0,0 +1 @@ +https://github.com/Azure/azure-sdk-for-net diff --git a/thirdparty/azure-sdk-for-net/License.txt b/thirdparty/azure-sdk-for-net/License.txt new file mode 100644 index 000000000..e94f95848 --- /dev/null +++ b/thirdparty/azure-sdk-for-net/License.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/thirdparty/azure-sdk-for-net/licensedata.json b/thirdparty/azure-sdk-for-net/licensedata.json new file mode 100644 index 000000000..b09939698 --- /dev/null +++ b/thirdparty/azure-sdk-for-net/licensedata.json @@ -0,0 +1,7 @@ +{ + "name": "azure-sdk-for-net", + "description": "Azure SDK for .NET", + "link": "https://github.com/Azure/azure-sdk-for-net", + "license": "MIT", + "notes": "" +} diff --git a/thirdparty/google-cloud-dotnet/Homepage.txt b/thirdparty/google-cloud-dotnet/Homepage.txt new file mode 100644 index 000000000..e2eda5656 --- /dev/null +++ b/thirdparty/google-cloud-dotnet/Homepage.txt @@ -0,0 +1 @@ +https://github.com/googleapis/google-cloud-dotnet diff --git a/thirdparty/google-cloud-dotnet/License.txt b/thirdparty/google-cloud-dotnet/License.txt new file mode 100644 index 000000000..c0ee81299 --- /dev/null +++ b/thirdparty/google-cloud-dotnet/License.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/thirdparty/google-cloud-dotnet/licensedata.json b/thirdparty/google-cloud-dotnet/licensedata.json new file mode 100644 index 000000000..86f5f5dcd --- /dev/null +++ b/thirdparty/google-cloud-dotnet/licensedata.json @@ -0,0 +1,7 @@ +{ + "name": "google-cloud-dotnet", + "description": "Google Cloud Libraries for .NET", + "link": "https://github.com/googleapis/google-cloud-dotnet", + "license": "Apache-2.0", + "notes": "" +}